I have a Windows service that needs to access registry hives under HKEY_USERS when users log on, either locally or via Terminal Server. I'm using a WMI query on win32_logonsession to receive events when users log on, and one of the properties I get from that query is a LogonId. To figure out which registry hive I need to access, now, I need the users's SID, which is used as a registry key name beneath HKEY_USERS.
In most cases, I can get this by doing a RelatedObjectQuery like so (in C#):
RelatedObjectQuery relatedQuery = new RelatedObjectQuery( "associators of {Win32_LogonSession.LogonId='" + logonID + "'} WHERE AssocClass=Win32_LoggedOnUser Role=Dependent" );
where "logonID" is the logon session ID from the session query. Running the RelatedObjectQuery will generally give me a SID property that contains exactly what I need.
There are two issues I have with this. First and most importantly, the RelatedObjectQuery will not return any results for a domain user that logs in with cached credentials, disconnected from the domain. Second, I'm not pleased with the performance of this RelatedObjectQuery --- it can take up to several seconds to execute.
Here's a quick and dirty command line program I threw together to experiment with the queries. Rather than setting up to receive events, this just enumerates the users on the local machine:
using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
namespace EnumUsersTest
{
class Program
{
static void Main( string[] args )
{
ManagementScope scope = new ManagementScope( "\\\\.\\root\\cimv2" );
string queryString = "select * from win32_logonsession"; // for all sessions
//string queryString = "select * from win32_logonsession where logontype = 2"; // for local interactive sessions only
ManagementObjectSearcher sessionQuery = new ManagementObjectSearcher( scope, new SelectQuery( queryString ) );
ManagementObjectCollection logonSessions = sessionQuery.Get();
foreach ( ManagementObject logonSession in logonSessions )
{
string logonID = logonSession["LogonId"].ToString();
Console.WriteLine( "=== {0}, type {1} ===", logonID, logonSession["LogonType"].ToString() );
RelatedObjectQuery relatedQuery = new RelatedObjectQuery( "associators of {Win32_LogonSession.LogonId='" + logonID + "'} WHERE AssocClass=Win32_LoggedOnUser Role=Dependent" );
ManagementObjectSearcher userQuery = new ManagementObjectSearcher( scope, relatedQuery );
ManagementObjectCollection users = userQuery.Get();
foreach ( ManagementObject user in users )
{
PrintProperties( user.Properties );
}
}
Console.WriteLine( "\nDone! Press a key to exit..." );
Console.ReadKey( true );
}
private static void PrintProperty( PropertyData pd )
{
string value = "null";
string valueType = "n/a";
if ( pd.Value != null )
{
value = pd.Value.ToString();
valueType = pd.Value.GetType().ToString();
}
Console.WriteLine( " \"{0}\" = ({1}) \"{2}\"", pd.Name, valueType, value );
}
private static void PrintProperties( PropertyDataCollection properties )
{
foreach ( PropertyData pd in properties )
{
PrintProperty( pd );
}
}
}
}
So... is there way to quickly and reliably obtain the user SID given the information I retrieve from WMI, or should I be looking at using something like SENS instead?
Another simple way: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \ProfileList
Another working answer (code in VB.Net)
Powershell is easier.
I asked a very similar question a while back and got this answer: how to get a SID from a windows username.
I was planning on using SystemEvents to detect when a user logs on to windows, then looping through the logged on users list at that point to detect all the logged on users. (Here's my question, about all this including references for detecting logons and current users.)
If you decide on an approach please post an update - I'd be interested to hear what you find works well.