Convert SessionId to User Account SID _without_ WTSQuerySessionInformation?

1k Views Asked by At

I'm working on a service (C#) that receives session-change notifications (specifically SessionLogon). The only piece of information I get with that notification is SessionId.

My ultimate goal is to check the logon user's profile (local/roaming AppData/MyCorp/MyApp folder) for a particular setting, and perform a task if it's there.

I need to go from SessionId to something I can map to a local user profile, either directly to a User Account SID or to something that can be mapped to a SID, (e.g. "<domain>\<username>", etc).

The solutions I've found on SO depend upon Windows Terminal Services (WTS) APIs (e.g. WTSQuerySessionInformation), but Remote Desktop Services isn't available on Windows 10 Home edition, so that's a non-starter.

Does anyone know how to map from SessionId to a local user account that doesn't involve WTS APIs?

(EDIT #1) CLARIFICATION:

In .NET, the ServiceBase class has an OnSessionChange override that gets called for login/logout/unlock/lock events. I was originally thinking this was for all such events (from physical machine or Terminal Server).

It looks like this only applies to Terminal Server sessions(?) So, apparently, the sessionId that I get back is a TerminalServer-specific thing. As @RbMm points out below, this override probably wouldn't get called in the first place on Windows Home edition. It's a moot point, though, because it was the local (physical) logon events I was interested in, and that's completely different from Terminal Service sessions.

It seems odd to me that the service base class would have a useful event like this, but have it tied to Terminal Services, rather than work for all cases. Maybe someone has some insight into this?

(EDIT #2) REALIZATION:

@RbMm's comments have cleared up some misconceptions that I started with. Here's a update:

  • The OnSessionChange event is only for Terminal Services, and has nothing to do with local (physical) logon sessions (I was conflating the two).
  • I'm only interested in the local logon sessions, so I'll be looking for a way to get notified about them inside my service. If no such notification is available, I'll have to set up a timer and poll.
  • I'll need to derive a user account SID from whatever piece of information I receive along with such a notification (or periodic call to LsaGetLogonSessionData)
2

There are 2 best solutions below

7
TheGeneral On

I think you can retrieve this information via WMI

Win32_LogonSession

class Win32_LogonSession : Win32_Session
{
  string   Caption;
  string   Description;
  datetime InstallDate;
  string   Name;
  string   Status;
  datetime StartTime;
  string   AuthenticationPackage;
  string   LogonId;
  uint32   LogonType;
};

Furthermore:

LogonId

Data type: string

Access type: Read-only

Qualifiers: key

ID assigned to the logon session.

Example

var scope = new ManagementScope(ManagementPath.DefaultPath);
var query = new SelectQuery($"Select * from Win32_LogonSession where LogonId = {SessionId}");
var searcher = new ManagementObjectSearcher(scope, query);
var results = searcher.Get();
foreach (ManagementObject mo in results)
{
 
}

Note this is fully untested

0
Carsten On

You can resolve this info via processes instead of tokens. I dont have a c# sample here, but here is PS-snippet to demo the concept:

# https://learn.microsoft.com/en-us/windows/win32/devnotes/getting-the-active-console-session-id
$sessionId = [System.Runtime.InteropServices.Marshal]::ReadByte(0x7ffe02d8)
$pList = (Get-CimInstance Win32_Process -Filter "name = 'explorer.exe' and SessionId=$sessionId")
$winEx = $pList | where {$_.Path -eq 'C:\WINDOWS\explorer.exe'} | sort CreationDate | select -First 1
$sid = (Invoke-CimMethod -InputObject $winEx -MethodName GetOwnerSid).sid