Search code examples
c#c++.netwinapiterminal-services

How to reproduce quser.exe using API calls?


Quser.exe allows a client to see user sessions on a remote RDP server. For example,

 C:\>quser /server:MyRDPserver

 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 userA                                     3  Disc      1+20:03  08/07/2014 12:36
 userB                                     4  Disc      1+22:28  08/07/2014 10:38

I would like to build this functionality into a C++ or C# program. Yes, I could just spawn quser.exe and parse the output, but is there an Win32 API or .Net framework class that can give me the same information? Specifically:

  • User Name
  • Connection State
  • Logon time

I've found that using WMI (Win32_LoggedOnUser) to find the same information is unreliable, as it often lists stale connections. I've also tried the psloggedon approach of enumerating subkeys of HKEY_USERS and looking for the Volatile Environment key, but this also suffers from the same problem.


Solution

  • I'm going to answer my own question.

    First of all, you need to make sure that permissions are set correctly on the target machine. This entails setting HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\AllowRemoteRPC to 1. A powershell script to do this is:

    # Get the service account credential
    $cred = Get-Credential my_admin_account
    $Target_Computers = @("Computer_1","Computer_2")
    
    # open the remote registry
    [long]$HIVE_HKLM = 2147483650
    foreach($c in $Target_Computers)
    {
      $StdRegProv = Get-WmiObject -List -Namespace root\default -ComputerName $c -Credential $cred | where { $_.Name -eq "StdRegProv" }
      $StdRegProv.SetDWORDValue($HIVE_HKLM, "SYSTEM\CurrentControlSet\Control\Terminal Server", "AllowRemoteRPC", 1)
    }
    

    As Xearinox said, for C++ you can use the WTSxxx functions in the Win32 API. Assuming your computers are not XP, here is some C++ code:

    #include <string>
    #include <iostream>
    #include <iomanip>
    #include <windows.h>
    #include <WtsApi32.h>
    
    using namespace std;
    
    const unsigned num_connection_states = 10;
    const wchar_t* connection_state_list[num_connection_states] = {
        L"Active",
        L"Connected",
        L"ConnectQuery",
        L"Shadow",
        L"Disc",
        L"Idle",
        L"Listen",
        L"Reset",
        L"Down",
        L"Init" };
    
    int print_error(DWORD err)
    {
      // format the message
      LPTSTR* ppBuffer = nullptr;
      DWORD retval = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, err, 0, reinterpret_cast<LPTSTR>(ppBuffer), 0, nullptr);
    
      // print out
      wcerr << "Error: *ppBuffer" << endl;
      return 1;
    }
    
    wstring format_time(const LARGE_INTEGER& time)
    {
      // convert to a local Win32 file time
      FILETIME ft = { time.LowPart, time.HighPart };
      FileTimeToLocalFileTime( &ft, &ft );
    
      // convert to a system time
      SYSTEMTIME st;
      FileTimeToSystemTime( &ft, &st );
    
      wchar_t local_date[255], local_time[255];
      GetDateFormat( LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, local_date, sizeof(local_date)/sizeof(wchar_t) );
      GetTimeFormat( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, local_time, sizeof(local_time)/sizeof(wchar_t) );
    
      wstring result = local_date;
      result.append(L" ");
      result.append(local_time);
    
      return result;
    }
    
    const _int64 SECOND = 10000000;
    const _int64 MINUTE = 60*SECOND;
    const _int64 HOUR = 60*MINUTE;
    const _int64 DAY = 24*HOUR;
    
    wstring format_timespan(const LARGE_INTEGER& timespan)
    {
      // convert to a local Win32 file time
      FILETIME ft = { timespan.LowPart, timespan.HighPart };
      FileTimeToLocalFileTime( &ft, &ft );
    
      // convert to a system time
      SYSTEMTIME st;
      FileTimeToSystemTime( &ft, &st );
    
      wchar_t local_time[255];
      int daydiff = floor(
      GetTimeFormat( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, local_time, sizeof(local_time)/sizeof(wchar_t) );
    
      wstring result = local_date;
      result.append(L" ");
      result.append(local_time);
    
      return result;
    }
    
    int wmain(int argc, wchar_t* argv[])
    {
      // check args
      if(argc > 2)
      {
        wcout << "Usage: " << argv[0] << " [server_name]\n";
        return 1;
      }
    
      // server name
      bool current_server = true;
      wstring server_name = L".";
      if(argc == 2)
      {
        server_name = argv[1];
        current_server = false;
      }
    
      // open the server
      HANDLE hServer;
      if(current_server)
        hServer = WTS_CURRENT_SERVER_HANDLE;
      else
        hServer = WTSOpenServer(const_cast<LPWSTR>(server_name.c_str()));  
    
      // enumerate through the sessions
      DWORD Count = 0;
      WTS_SESSION_INFO* pSessionInfo = nullptr;
      BOOL success = WTSEnumerateSessions(hServer, 0, 1, &pSessionInfo, &Count);
      if(success == 0)
        return false;
    
      // write the headers
      wcout << " " << left << setw(24) << "USERNAME";
      wcout << setw(19) << "SESSIONNAME";
      wcout << "ID  ";
      wcout << setw(9) << "STATE";
      wcout << "IDLE TIME  LOGON TIME";
    
      // loop through each session
      for(unsigned long s=0; s<Count; s++)
      {
        LPTSTR pBuffer = nullptr;
        DWORD BytesReturned = 0;
        wcout << "\n " << left;
    
        // try getting all info at once
        WTSINFO* info = nullptr; 
        success = WTSQuerySessionInformation(hServer, pSessionInfo[s].SessionId, WTSSessionInfo, reinterpret_cast<LPTSTR*>(&info), &BytesReturned);
        bool have_wtsinfo = true;
        if(!success)
        {
          // see why failed
          DWORD err = GetLastError();
          if(err == ERROR_NOT_SUPPORTED)
            have_wtsinfo = false;
          else
            return print_error(err);
        }
    
        // print user name 
        wstring user_name;
        if(have_wtsinfo)
          user_name = info->UserName;
        else
        {
          success = WTSQuerySessionInformation(hServer, pSessionInfo[s].SessionId, WTSUserName, &pBuffer, &BytesReturned);
          if(!success)
            continue;
          user_name = pBuffer;
          WTSFreeMemory(pBuffer);
        }
        wcout << setw(24) << user_name;
    
        // print session name 
        wstring session_name;
        if(have_wtsinfo)
          session_name = info->WinStationName;
        else
        {
          success = WTSQuerySessionInformation(hServer, pSessionInfo[s].SessionId, WTSWinStationName, &pBuffer, &BytesReturned);
          if(!success)
            continue;
          session_name = pBuffer;
          WTSFreeMemory(pBuffer);
        }
        wcout << setw(19) << session_name;
    
        // print session ID
        wcout << right << setw(2) << pSessionInfo[s].SessionId;
    
        // print connection state
        WTS_CONNECTSTATE_CLASS connect_state;
        if(have_wtsinfo)
          connect_state = info->State;
        else
        {
          success = WTSQuerySessionInformation(hServer, pSessionInfo[s].SessionId, WTSConnectState, &pBuffer, &BytesReturned);
          if(!success)
            continue;
          connect_state = *reinterpret_cast<WTS_CONNECTSTATE_CLASS*>(pBuffer);
          WTSFreeMemory(pBuffer);
        }
        if(connect_state>=num_connection_states)
          continue;
        wcout << "  " << left << setw(8) << connection_state_list[connect_state];
    
        // get idle time 
        LARGE_INTEGER idle = info->CurrentTime;
        idle.QuadPart -= info->LogonTime.QuadPart;
    
        // print logon time - not supported
        if(info->LogonTime.QuadPart!=0)
        {
          wcout << format_time(info->LogonTime);
        }
    
        // clean up
        WTSFreeMemory(info);
      }
    
      // clean up
      WTSFreeMemory(pSessionInfo);
      if(!current_server)
        WTSCloseServer(hServer);
    }
    

    For C#, the easiest way is to use the Cassia library, which is basically a C# wrapper around the same API functions.