1

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)
Scott Smith
  • 3,900
  • 2
  • 31
  • 63
  • Are you saying you have tried callign the WTS apis and they dont exist, or they dont work? – TheGeneral Aug 13 '20 at 02:25
  • @MichaelRandall - The API mentioned above is documented (updated w/link) **not to work** if Remote Desktop Services isn't running. Windows 10 Home editions reportedly don't include remote desktop at all. I haven't actually **tried** it, but that's why I believe it won't work... – Scott Smith Aug 13 '20 at 03:01
  • Yeah maybe so, anyway, where does it say this calls fails on windows home or when the service is not running? edit ahh i see now – TheGeneral Aug 13 '20 at 03:14
  • I am wondering if you can the user from the session id via WMI – TheGeneral Aug 13 '20 at 03:17
  • @ScottSmith Find a process running in the given session (maybe enumerate and match [`ProcessIdToSessionId`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-processidtosessionid)) then [lookup SID](https://stackoverflow.com/a/2686150). – dxiv Aug 13 '20 at 03:19
  • *I haven't actually tried it, but that's why I believe it won't work* - why not at begin try call `WTSQuerySessionInformationW` before ask ? – RbMm Aug 13 '20 at 05:32
  • 1
    if Remote Desktop Services isn't available or not running - simply will be no session change. as result no notifications, and you never call `WTSQuerySessionInformationW` in this case. from another side this api is exported always so you always can import it – RbMm Aug 13 '20 at 07:09
  • @RbMm - "why not at begin try call WTSQuerySessionInformationW before ask ?" Normally I would, but I'd have to write the code, spin up a WIndows Home instance, install the service and test it. Probably at least a day's work for something that's documented _not_ to work. – Scott Smith Aug 13 '20 at 16:25
  • @ScottSmith - read my next comment – RbMm Aug 13 '20 at 16:27
  • @RbMm - I've updated the question with some clarifications based on your feedback – Scott Smith Aug 13 '20 at 16:41
  • 1
    also i think documentation is very bad and incorrect. *If Remote Desktop Services is not running, calls to WTSQuerySessionInformation fail* - unclear this is only for *To retrieve the session ID* or for any case. but on my system Remote Desktop Services (*TermService*) is not running, but `WTSQuerySessionInformation` is **not fail** and return actual info – RbMm Aug 13 '20 at 17:05
  • 1
    `OnSessionChange` - how i understand you register exactly terminal session notifications. not logon sessions. here you get `wParam` parameter of the [`WM_WTSSESSION_CHANGE`](https://learn.microsoft.com/en-us/windows/win32/termserv/wm-wtssession-change) message and pointer to [`WTSSESSION_NOTIFICATION`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wtssession_notification). so you need call exactly `WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, dwSessionId, WTSSessionInfo, (PWSTR*)&pp, &dwSessionId)` and i sure this call not fail. – RbMm Aug 13 '20 at 18:20
  • 1
    i think you not understand different between terminal and logon sessions..inside single terminal session can be several different logon sessions. run for example [this](https://github.com/rbmm/partial/blob/master/X64/lgSessions.exe) an look output – RbMm Aug 13 '20 at 18:22
  • 1
    anyway service when registered for `SERVICE_ACCEPT_SESSIONCHANGE` it got [`SERVICE_CONTROL_SESSIONCHANGE`](https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nc-winsvc-lphandler_function_ex) notifications. it called when user logon, logoff, lock, unlock.. etc. local user too, not only for remote. in case local user `WinStationName` will be *Console* – RbMm Aug 13 '20 at 18:28

2 Answers2

1

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

Scott Smith
  • 3,900
  • 2
  • 31
  • 63
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • 1
    question about **logon** sessions (*LUID*) or **terminal** sessions (*ULONG*) ? this is absolute different things. you query information about logon session. for this exist [*LsaGetLogonSessionData*](https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsagetlogonsessiondata). use wmi always bad solution. this is only remote call to wmi service process. but what api then this process call for get info ? why not call this api direct ? – RbMm Aug 13 '20 at 05:56
  • @RbMm if LsaGetLogonSessionData does what the OP wants. Maybe you can answer this question :) my answer was only a guess, if its wrong, ill happily delte – TheGeneral Aug 13 '20 at 06:00
  • 1
    i not understand original question - about which session he ask. but i simply note - if information can get with wmi - this mean the same information we can get without wmi (this is only very slow remote call). in all sense better use `LsaGetLogonSessionData` which return [`SECURITY_LOGON_SESSION_DATA`](https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-security_logon_session_data) then wmi which internal anyway call `LsaGetLogonSessionData` – RbMm Aug 13 '20 at 06:07
  • @RbMm ahh ok, i understand what you are saying. Yes `WMI` is slow and should be discouraged. The OP has a session ID, he will have to try and get the `LogonId` from the `SessionId` to use this method (`LsaGetLogonSessionData`), i am not sure how this can be done. – TheGeneral Aug 13 '20 at 06:13
  • 1
    LogonId and SessionId absolute different. in same SessionId can be multiple different LogonId sessions. i think OP ask about terminal session id which he received inside [`HandlerEx`](https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nc-winsvc-lphandler_function_ex) - `SERVICE_CONTROL_SESSIONCHANGE` and [`WTSSESSION_NOTIFICATION`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wtssession_notification). where he got `DWORD dwSessionId`. you , i assume, confuse this with logon sessions – RbMm Aug 13 '20 at 06:20
  • @RbMm yeah, its true, and it makes sense now that you say it. Maybe you can write an answer explaining ? ill delete this answer – TheGeneral Aug 13 '20 at 06:22
  • 1
    sorry for delay, but i think [*this comment*](https://stackoverflow.com/questions/63387067/convert-sessionid-to-user-account-sid-without-wtsquerysessioninformation/63387677?noredirect=1#comment112089423_63387067) explain situation – RbMm Aug 13 '20 at 07:10
1

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
Carsten
  • 1,612
  • 14
  • 21