I'm coding a windows service and in one point I need to know if there is an active interactive session.
I tried using OnSessionChange() and keep in a variable the last SessionChangeReason. When I reach to the mentioned point I compare if it's equal to SessionChangeReason.SessionLogOn. This works with the inconvenient that the service has a delayed start so if the user logs on before the service starts running this information is lost.
I have also seen the System.Environment.Interactive property but as I understand this refers to the process of the current service which is not interactive, so it wouldn't give me the information I need (I might misunderstood this, though).
Is there a way to get this info 'on demand' without having to keep register of the SessionChangeReason?
Edit: Maybe I wasn't clear about this point. Aside from knowing that there is an interactive session I also need to know that it isn't locked.
P/Invoke WTSEnumerateSessions to see if there are additional sessions and what their connected states are. You obviously have to ignore session 0 on Vista+.
You should only do this when your service is started, the session change notification should be used to detect further changes.
Finally I've resigned to knowing specifically that there is a session and isn't locked so we'll work with whether there is an active session or not.
If only knowing there is an active session works for you and you don't want to use pInvoke you can either:
a) Search for the explorer process
Process[] ps = Process.GetProcessesByName("explorer");
bool explorerActive = (ps.Length > 0);
b) Use the following WMI query to get the UserName of the active session:
using System.Management;
ConnectionOptions oConn = new ConnectionOptions();
ManagementScope oMs = new ManagementScope("\\\\localhost", oConn);
ObjectQuery oQuery = new ObjectQuery("select * from Win32_ComputerSystem");
ManagementObjectSearcher oSearcher = new ManagementObjectSearcher(oMs, oQuery);
ManagementObjectCollection oReturnCollection = oSearcher.Get();
foreach (ManagementObject oReturn in oReturnCollection)
{
if (oReturn["UserName"] == null)
{
// No active session
Console.Write("UserName: null");
}
else
{
// Active session
Console.Write("UserName: " + oReturn["UserName"].ToString());
}
}
If you want to use pInvoke see Anders' answer.
No amount of googling has gotten me anywhere with this.
I have the following code that checks if user(s) are logged on to a PC either via console or RDP.
It runs OK but the issue is that after the user logs off, WMI (or something somewhere) still thinks that they are logged on, and in this case, returns true, when it should return false.
If I reboot the PC it goes back to 0 until I login.
I am prepared to accept that this might be a bug within WMI, so if anyone has a better alternative to achieve the same thing I would massively appreciate the advice.
public bool PCInUse(string Hostname)
{
ConnectionOptions connection = new ConnectionOptions();
connection.Username = "username;
connection.Password = "password";
ManagementScope scope = new ManagementScope("\\\\" + Hostname + "\\root\\CIMV2", connection);
try
{
scope.Connect();
var Query = new SelectQuery("SELECT LogonId FROM Win32_LogonSession Where (LogonType= 10) or (LogonType= 2)");
var Searcher = new ManagementObjectSearcher(scope, Query);
if (Searcher.Get().Count > 0)
return true;
else
return false;
}
catch
{
return false;
}
}
Another approach (assuming your process is running as a service under local system or some privileged account) would be to get all running processes, make a list of distinct User Names for those processes (excluding all the system accounts like "SYSTEM", "NETWORK SERVICE", "LOCAL SERVICE") and any remaining unique userIds of the types you included in your "where" clause on the WMI query ought to give you some idea. Sure, it's possible someone might setup a service to run under a user account, and that might screw it up for you, and on servers running IIS or some other application that creates special user accounts, you might have to work around excluding those also.
You might have to include the inverse of your WMI query above to get the list of non-interactive sessions and the userIds associated with them to know what all to exclude. You might also just look for those processes that you know are only in use by an interactive session (like "dwm.exe" and "explorer.exe").
I have been working on a small WMI Info grabber for my job as I work with hundreds of servers and being able to grab certain information can be quite beneficial when having to get it on more than 5 servers.
List<string> output = new List<string>();
ObjectQuery query = new ObjectQuery("SELECT * FROM ComputerSystem");
ManagementObjectSearcher objectSearcher = new ManagementObjectSearcher(ServerConnect.HardwareScope, query);
ManagementObjectCollection retObject = objectSearcher.Get();
foreach (ManagementObject manObj in retObject)
{
string[] data = ((string)manObj["Description"]).Split('-');
string IPMI = data[1].Substring(7);
string firmware = data[2].Substring(5);
output.Add("IPMI Version: " + IPMI);
output.Add("BMC Firmware Version: " + firmware);
}
return output;
This is currently what I am using to grab some basic data from the ComputerSystem, this which works on some servers but crashes on others obtains the correct information that I am wanting; however, I need to prevent it from crashing with a COMException.
It says it breaks out with the System.Runtime.InteropServices.COMException at the following line of code:
foreach (ManagementObject manObj in retObject)
I have been trying to solve this issue as to why some servers get this exception returned rather than being able to grab the information and if it is possible to fix it.
I have been trying to fix this for the last couple weeks and have finally decided to ask for help about this specific problem that I am having.
UPDATE
I have ran the same name space and selection for the servers giving me issues in WMI Code Creator ( http://www.microsoft.com/en-us/download/confirmation.aspx?id=8572 ) and it is able to obtain the data I need however when ran through my program it gives the COMException. I have gone over the code that Code Creator makes, and my code and have modified some of it and it still gives me the error.
I have a Problem, which is... i start a programm with right click -> run as administrator.
Which means the programm is running in an administrative context.
WindowsIdentity.GetCurrent().Name;
if i try to get the user name that way i will get the user that started the programm as admin.. for example "administrator", but what i need is the name of the current logged in user which is for example: bob
Can anybody help me out? :)
You could try using WMI (System.Management.dll) to get the owner of the explorer.exe process.
string GetExplorerUser()
{
var query = new ObjectQuery(
"SELECT * FROM Win32_Process WHERE Name = 'explorer.exe'");
var explorerProcesses = new ManagementObjectSearcher(query).Get();
foreach (ManagementObject mo in explorerProcesses)
{
string[] ownerInfo = new string[2];
mo.InvokeMethod("GetOwner", (object[])ownerInfo);
return String.Concat(ownerInfo[1], #"\", ownerInfo[0]);
}
return string.Empty;
}
This relies on the fact that the explorer process is single instance an so you don't end up with the possibility of having several explorer processes running with different user credentials.
You will probably need to use win32 API for that. Read about Window Station and Desktop functions here: http://msdn.microsoft.com/en-us/library/ms687107%28v=vs.85%29.aspx
Also see this question:
Get the logged in Windows user name associated with a desktop
Maybe you could start as normal user, save user name, then programmatically request elevation :
Windows 7 and Vista UAC - Programmatically requesting elevation in C#
All .NET libraries will give you the user from the current context ('Administrator' in your case).
If you are trying to secure your code, you might consider reading about: Security in the .NET framework
1) Cassia should be able to give you a list of currently logged in users including RDC.
foreach (ITerminalServicesSession sess in new TerminalServicesManager().GetSessions())
{
// sess.SessionId
// sess.UserName
}
2) WMI (SO answer)
Select * from Win32_LogonSession
3) PInvoke to WTSEnumerateSessions
4) Enumerate all instances of "explorer.exe" and get the owner using PInvoke (OpenProcessHandle).
Process[] processes = Process.GetProcessesByName("explorer");
This is a bit hacky. WMI can also be used for this.
It might be a good idea to set winmgmt as a dependency for your service if you decided to go with solution that uses WMI.
I've been looking at the code in this tutorial, and I found that it uses My.Computer.Name to save settings that shouldn't roam between computers. It's entirely possible, however, for a user to have two identically named PCs. If they wanted to have the same username on each PC, for example, they may very well end up with two PCs named Username-PC.
What are some good methods of identifying different PCs? Do PCs have GUIDs associated with them, or should I look into pulling the serial number off of some hardware? I don't care if the identification persists through reinstallation of Windows.
(The tutorial I linked is in VB.Net, but I'm implementing it in C#)
Some good identifiers:
MAC Address: It's fairly easy to get at, and it's usually unique. However, it can be spoofed/changed rather easily, so it depends on how unique it needs to be.
CPU Serial Number: It's not available on lots of older systems, but it's there. Check out this MSDN page. It won't change, but it's bound to a computer.
HDD Serial Number: It's likely to not change, but can be a nuisance if the HD fails. Check out this MSDN page.
If you are on windows HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\ CurrentVersion\ProductId is unique per machine/per windows install. where as in some of the other answers like the MAC address, Proc SN, and HD SN will stay the same between windows reinstalls/dual boot situations.
The real answer to that question: There is no such thing.
There are several "close enough" solutions, but each one of those has it's own limitation.
All the hardware IDs - Hardware changes. And, in many cases you can change those identifiers (For example, MAC spoofing).
The SID, as I've already commented, Is not that good as well, because the SID won't change if the computer was installed from an image. The SID is generated by windows installation, if windows wasn't installed, but copied from an image, the SID won't change (although it is common to regenerate it because of a myth about "security risk" - you can't count on that).
Computer name - Well, as mentioned, They suppose to be unique, but it's not enforced in any way.
Another solution you can implement is to generate you own unique identifier and store it locally (assuming you can do such thing). Again, this solution won't work if your computer was imaged with your application.
The best solution for you really depends on what you are trying to accomplish.
I had the same problem with a quite large network, and the best solution in my case was the computer's name.
If you are absolutely sure that your process won't be imaged, I would generate a unique identifier using Guid because it will probably be the safest.
Here is a way to uniquely identify a computer.
Using System.Management to get Win32_BIOS, you can get unique values from your machine's BIOS.
See: Win32_BIOS class, http://msdn.microsoft.com/en-us/library/aa394077.aspx
using System.Management;
string UniqueMachineId()
{
StringBuilder builder = new StringBuilder();
String query = "SELECT * FROM Win32_BIOS";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
// This should only find one
foreach (ManagementObject item in searcher.Get())
{
Object obj = item["Manufacturer"];
builder.Append(Convert.ToString(obj));
builder.Append(':');
obj = item["SerialNumber"];
builder.Append(Convert.ToString(obj));
}
return builder.ToString();
}
With similar logic, you can also step through "Win32_DiskDrive";
http://msdn.microsoft.com/en-us/library/aa394132.aspx;
and get "SerialNumber" for each physical drive. In this case, the
foreach (ManagementObject item in searcher.Get())
should find multiple items
Take three identifiers that are semi-unique and semi-constant. Use the rule that 2 out of 3 is sufficient for a positive identification. Update the registered data for the 1 out of 3 that is occasionally wrong.
Use the network card's MAC address. It's supposed to be unique. It can be changed, though. It depends on how malicious you expect your users to be and how critical your application is.
Some sample code to do it:
public string GetMACAddress() {
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
string MACAddress = String.Empty;
foreach (ManagementObject mo in moc) {
if (MACAddress == String.Empty) { // only return MAC Address from first card
if ((bool)mo["IPEnabled"] == true) MACAddress = mo["MacAddress"].ToString();
}
mo.Dispose();
}
return MACAddress;
}
One thing you can use is the MAC of any Network interface. You can also combine several sources of information. Like HDD Serial number, mac, processor type to calculate a hash from it.
I don't think it's possible to have two PC's with the same name on the same domain. Have you tried capturing the domain name?
Take a look here: Getting Service Tag from Dell Machine using .net?
You could snatch some unique data from the registry.
Each computer has a SID that's unique under normal circumstances.
In a managed network environment, the best, most reliable identifier might be the one you create, but there are some downsides.
Some (many?) manufacturers provide a utility that allows you to set an asset tag that is stored in the firmware. This might be a bootable utility, or it might run within Windows, or it might even be built into the firmware setup. This "tag" is an arbitrary text string that you can set to whatever you want, and then read it back using WMI and the Win32_SystemEnclosure class...
string[] selectedProperties = new string[] { "SMBIOSAssetTag" };
ObjectQuery enclosureQuery = new SelectQuery("Win32_SystemEnclosure", null, selectedProperties);
using (ManagementObjectSearcher enclosureSearcher = new ManagementObjectSearcher(enclosureQuery))
using (ManagementObjectCollection enclosureCollection = enclosureSearcher.Get())
{
foreach (ManagementObject enclosure in enclosureCollection)
{
string assetTag = (string) enclosure.GetPropertyValue("SMBIOSAssetTag");
}
}
Pros:
You can use whatever scheme you want (e.g. incorporating date, department, incrementing integers, GUIDs, etc.).
You can use one scheme for all machines regardless of their manufacturer, instead of having to handle manufacturer-specific schemes.
By allocating and tracking the identifiers yourself, you can guarantee that they are unique. Not relying on an identifier set by the manufacturer means there is no risk of duplicates within a manufacturer or between manufacturers.
The identifier is stored in the firmware — not on the hard drive — so it will survive reformatting, upgrades, etc. but also not be duplicated by backups/imaging/cloning.
Cons:
You need to actually set the asset tag; they'll all be blank until you do so.
Setting a machine's asset tag may require physical access and a reboot.
Asset tags are not write-once and could, therefore, be changed or erased.
Password-protected firmware should require that password before changing the tag, but that's not guaranteed.
By allocating and tracking the identifiers yourself, there's not only the overhead of...allocating and tracking the identifiers, but also the possibility that you'll introduce duplicates if you're not careful.
Using asset tags for this purpose requires that all machines support setting an asset tag and properly report it to WMI.
We use a combination of the ProcessorID from Win32_processor and the UUID from Win32_ComputerSystemProduct:
ManagementObjectCollection mbsList = null;
ManagementObjectSearcher mos = new ManagementObjectSearcher("Select ProcessorID From Win32_processor");
mbsList = mos.Get();
string processorId = string.Empty;
foreach (ManagementBaseObject mo in mbsList)
{
processorId = mo["ProcessorID"] as string;
}
mos = new ManagementObjectSearcher("SELECT UUID FROM Win32_ComputerSystemProduct");
mbsList = mos.Get();
string systemId = string.Empty;
foreach (ManagementBaseObject mo in mbsList)
{
systemId = mo["UUID"] as string;
}
var compIdStr = $"{processorId}{systemId}";
Previously, we used a combination: processor ID ("Select ProcessorID From Win32_processor") and the motherboard serial number ("SELECT SerialNumber FROM Win32_BaseBoard"), but then we found out that the serial number of the motherboard may not be filled in, or it may be filled in with uniform values:
To be filled by O.E.M.
None
Default string
Therefore, it is worth considering this situation.
Also keep in mind that the ProcessorID number may be the same on different computers.
There is a sample code with complete notes in this link for getting CPU and HD Drive ID: http://www.vcskicks.com/hardware_id.php
add this dll to refrences
System.Management.dll
for CPU ID:
string cpuInfo = string.Empty;
ManagementClass mc = new ManagementClass("win32_processor");
ManagementObjectCollection moc = mc.GetInstances();
foreach (ManagementObject mo in moc)
{
if (cpuInfo == "")
{
//Get only the first CPU's ID
cpuInfo = mo.Properties["processorID"].Value.ToString();
break;
}
}
return cpuInfo;
and for Hard Drive ID (Volume Serial):
ManagementObject dsk = new ManagementObject(#"win32_logicaldisk.deviceid=""" + drive + #":""");
dsk.Get();
string volumeSerial = dsk["VolumeSerialNumber"].ToString();