Unfortunately there aren't really any clean ways to do this, at least not that I could find, unless you are willing to use something like query user
with PsExec
to remotely execute it on each PC as a sub-process, and then parse the results. Even then you don't get a direct answer as to locked status, you would have to go by Idle time, as Status shows one of the users as Active when no one is using the computer.
Then there is the problem of multiple users being logged on a computer, using the Switch User functionality in Windows 7 or higher. In my environment, a PC might have 3 or 4 background users and one console user. In some cases, PCs are used by RDP users. And it turns out there is a special case when you RDP to a computer then later logon to the console or do the opposite, as LogonSession LogonType isn't updated in these cases. Unfortunately, it is also possible to catch a user just logging into a computer, in which case my function will incorrectly say the computer isn't in use.
On my PC and network, this function takes about 0.2 seconds to run, if the PC is on. On some PCs, it may take much longer (up to 20 seconds), as it loads a perfmon provider on the PC. If the PC is off, the timeout is quite long, and if that is a possibility, I would recommend doing a ping check first.
Basically the function uses WMI to get LogonSession and Interactive Desktops information, and Process
to get LogonUI and explorer processes. Since LogonSession returns old sessions that have logged out, and sessions for UAC Admin programs and other (Windows 10) background processes (DWM/UMFD), we only count LogonSessions that have an explorer.exe
process (desktop).
It then combines the information into different cases:
If the number of LogonUI processes is greater than or equal to the number of interactive desktops, the PC is either logged off or locked. If there are any LogonSessions (with explorer) on the PC, it is locked, otherwise it is logged off.
If the number of LogonUI processes is less than the number of interactive desktops, then the PC is in use.
Here is the code:
enum PCUserStatuses {
Locked, // all users are locked
LoggedOff, // No users are logged in
InUse, // A user is using this computer
Unknown // unable to connect to computer / other error
}
PCUserStatuses GetPCUserStatus(string machineName) {
try {
var scope = GetManagementScope(machineName);
scope.Connect();
var explorerProcesses = Process.GetProcessesByName("explorer", machineName)
.Select(p => p.Id.ToString())
.ToHashSet();
var REprocessid = new Regex(@"(?<=Handle="").*?(?="")", RegexOptions.Compiled);
var numberOfLogonSessionsWithExplorer = new ManagementObjectSearcher(scope, new SelectQuery("SELECT * FROM Win32_SessionProcess")).Get()
.Cast<ManagementObject>()
.Where(mo => explorerProcesses.Contains(REprocessid.Match(mo["Dependent"].ToString()).Value))
.Select(mo => mo["Antecedent"].ToString())
.Distinct()
.Count();
var numberOfUserDesktops = new ManagementObjectSearcher(scope, new SelectQuery("select * from win32_Perfrawdata_TermService_TerminalServicesSession")).Get().Count - 1; // don't count Service desktop
var numberOflogonUIProcesses = Process.GetProcessesByName("LogonUI", machineName).Length;
if (numberOflogonUIProcesses >= numberOfUserDesktops) {
if (numberOfLogonSessionsWithExplorer > 0)
return PCUserStatuses.Locked;
else
return PCUserStatuses.LoggedOff;
}
else
return PCUserStatuses.InUse;
}
catch {
return PCUserStatuses.Unknown;
}
}