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?