3

Suppose there are multiples users currently logged on on Windows. Say, user1 logs on, then switch user and user2 logs on, (without making user1 log off). Suppose there is an app which runs when user logs on. There are two users user1 and user2 logged on, with user2 as the active user, and there are two apps.

My question is: How does the app know whether its corresponding user is active or not? I.e., app in user2 domain determines that its user is active, while app in user1 domain determines its user is currently inactive. Thanks!

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
Zhi Wang
  • 1,138
  • 4
  • 20
  • 34
  • Maybe next article helps: http://support.microsoft.com/kb/310153 – Arvo Jan 14 '13 at 08:26
  • Besides the API in Arvos link check out `WTSGetActiveConsoleSessionId` followed by a `WTSQueryUserToken` for the returned session id. Regardless of MSDN talking about RemoteDesktopServices it also is used for local sessions. – Zarat Jan 14 '13 at 08:52
  • Thanks, but how to get the user by user token, any api? – Zhi Wang Jan 14 '13 at 09:10
  • If you tant to get user info with the token you can use GetTokenInformation with TokenUser as param. Then use LookupAccountSid to get the username. – João Augusto Jan 14 '13 at 09:57
  • WTSQueryToken is an awkward interface to use, mainly because it can only be called when running as local system, and probably inappropriate in this case. – Harry Johnston Jan 14 '13 at 20:42

2 Answers2

4

You can call WTSGetActiveConsoleSessionId to get the terminal services (aka "fast user switching" aka "remote desktop") session ID that is currently active on the physical console.

You can call WTSQuerySessionInformation with WTS_CURRENT_SESSION for the session identifier and WTSSessionId for WTSInfoClass to get the terminal services session ID for the current process.

If the active session ID and the current process session ID are the same, the user corresponding to the current process has the active session on the physical console.

If what you want to know is whether the session that the current process is running in is active (but not necessarily on the physical console) you can instead use the WTSConnectState option to WTSQuerySessionInformation.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • Note this doesn't hold for processes run with e.g. `RunAs`. – alexchandel Jun 16 '20 at 01:48
  • @alexchandel, using RunAs shouldn't change anything in this answer; what did you have in mind? (Perhaps you are confusing logon session IDs with terminal services session IDs? RunAs generates a new logon session, but that doesn't affect the terminal services session ID.) – Harry Johnston Jun 16 '20 at 02:32
  • That's what I mean. A `RunAs` process will find the user whose permissions it operates under (its "corresponding user" perhaps, possibly with a different profile & environment loaded) isn't the user operating the current console session, who may still have spawned it. A corner case for programs that run in multiple sessions & under multiple users. – alexchandel Jun 17 '20 at 17:53
4

WTSGetActiveConsoleSessionId() may actually return session 0 when run from a windows service. If you need to do this from a Windows service you will need to enumerate all sessions and find the connected session then get the user from that.

The code below does much more than that, including impersonation of that user and running a process as that user all from a windows service, but if you are just interested in the user name please look for the second instance the WTSQuerySessionInformation() function is called.

//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
    DWORD session_id = -1;
    DWORD session_count = 0;

    WTS_SESSION_INFOA *pSession = NULL;


    if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
    {
        //log success
    }
    else
    {
        //log error
        return;
    }

    for (int i = 0; i < session_count; i++)
    {
        session_id = pSession[i].SessionId;

        WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
        WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;

        DWORD bytes_returned = 0;
        if (::WTSQuerySessionInformation(
            WTS_CURRENT_SERVER_HANDLE,
            session_id,
            WTSConnectState,
            reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
            &bytes_returned))
        {
            wts_connect_state = *ptr_wts_connect_state;
            ::WTSFreeMemory(ptr_wts_connect_state);
            if (wts_connect_state != WTSActive) continue;
        }
        else
        {
            //log error
            continue;
        }

        HANDLE hImpersonationToken;

        if (!WTSQueryUserToken(session_id, &hImpersonationToken))
        {
            //log error
            continue;
        }


        //Get real token from impersonation token
        DWORD neededSize1 = 0;
        HANDLE *realToken = new HANDLE;
        if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
        {
            CloseHandle(hImpersonationToken);
            hImpersonationToken = *realToken;
        }
        else
        {
            //log error
            continue;
        }


        HANDLE hUserToken;

        if (!DuplicateTokenEx(hImpersonationToken,
            //0,
            //MAXIMUM_ALLOWED,
            TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
            NULL,
            SecurityImpersonation,
            TokenPrimary,
            &hUserToken))
        {
            //log error
            continue;
        }

        // Get user name of this process
        //LPTSTR pUserName = NULL;
        WCHAR* pUserName;
        DWORD user_name_len = 0;

        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
        {
            //log username contained in pUserName WCHAR string
        }

        //Free memory                         
        if (pUserName) WTSFreeMemory(pUserName);

        ImpersonateLoggedOnUser(hUserToken);

        STARTUPINFOW StartupInfo;
        GetStartupInfoW(&StartupInfo);
        StartupInfo.cb = sizeof(STARTUPINFOW);
        //StartupInfo.lpDesktop = "winsta0\\default";

        PROCESS_INFORMATION processInfo;

        SECURITY_ATTRIBUTES Security1;
        Security1.nLength = sizeof SECURITY_ATTRIBUTES;

        SECURITY_ATTRIBUTES Security2;
        Security2.nLength = sizeof SECURITY_ATTRIBUTES;

        void* lpEnvironment = NULL;

        // Get all necessary environment variables of logged in user
        // to pass them to the new process
        BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
        if (!resultEnv)
        {
            //log error
            continue;
        }

        WCHAR PP[1024]; //path and parameters
        ZeroMemory(PP, 1024 * sizeof WCHAR);
        wcscpy(PP, path);
        wcscat(PP, L" ");
        wcscat(PP, args);

        // Start the process on behalf of the current user 
        BOOL result = CreateProcessAsUserW(hUserToken, 
            NULL,
            PP,
            //&Security1,
            //&Security2,
            NULL,
            NULL,
            FALSE, 
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
            //lpEnvironment,
            NULL,
            //"C:\\ProgramData\\some_dir",
            NULL,
            &StartupInfo,
            &processInfo);

        if (!result)
        {
            //log error
        }
        else
        {
            //log success
        }

        DestroyEnvironmentBlock(lpEnvironment);

        CloseHandle(hImpersonationToken);
        CloseHandle(hUserToken);
        CloseHandle(realToken);

        RevertToSelf();
    }

    WTSFreeMemory(pSession);
}
Fotios Basagiannis
  • 1,432
  • 13
  • 14
  • 1
    *WTSGetActiveConsoleSessionId() will actually return session 0 when run from a windows service* - that's not true; I've just checked. Keep in mind the function returns the session number that is currently connected to the *physical console*, so it operates independently of which session it is called from. (It should only return 0 during system startup.) – Harry Johnston Feb 11 '16 at 02:06
  • (On the other hand, re-reading the question, it isn't clear that the OP was interested in the physical console in particular; WTSConnectState might well be the right answer.) – Harry Johnston Feb 11 '16 at 02:08
  • Well it is returning session 0 for me for some reason. It's not supposed to but it does (Windows 10). My guess is that the behavior of this function may be unpredictable in some cases which is probably why the OP had the same problem. My experience is that enumerating sessions is more reliable. Will edit the post as my original statement is stronger than it should. Thanks! – Fotios Basagiannis Feb 11 '16 at 18:11
  • Hmmm. I'll see if I can try it on a Windows 10 machine here at some point and report back. It works for me on Windows 7. (Of course if an automatic-start service calls it immediately the result may well be 0, since at that stage of system startup session 0 *is* active on the console, IIRC.) The potential problem with using WTSConnectState, depending on what you're actually trying to do, is that AFAIK it doesn't let you distinguish between the physical console and remote sessions. – Harry Johnston Feb 11 '16 at 20:29
  • 1
    Ok, so, another scenario (not mine) that I am not sure how WTSGetActiveConsoleSessionId() will behave is services that are allowed to interact with the desktop. Also, it looks like the WTSIsRemoteSession value in the WTS_INFO_CLASS enumeration may be used to distinguish remote sessions. – Fotios Basagiannis Feb 12 '16 at 10:19
  • It *shouldn't* matter whether the service has the interactive flag set, but perhaps a bug or some kind of compatibility shim is interfering - of course, that flag should never be set anyway. :-) As for the link, I'm not entirely sure what that poster meant by "returned a session ID of Console" but it sounds as if they were on the logon screen at the time. In that scenario, the user's session is not connected to the console, so you wouldn't expect WTSGetActiveConsoleSessionId to point at it anyway. Yes, if you're on Windows 7 or later, WTSIsRemoteSession should be a satisfactory solution. – Harry Johnston Feb 12 '16 at 20:14