Search code examples
delphidelphi-6

Delphi6: Need to know is my application running in console session or remote desktop session


My application is accessed from Remote Desktop clients from time to time.

I want to know is it currently being used in console session or Remote Desktop session. If the latter is the case and the session is disconnected (user has disconnected but not logged off) it should redirect itself to console (like tscon.exe 0 /dest:console does on Windows XP).

I'm currently running a shell script to achieve that (using query.exe user and tscon.exe) but would like my Delhi6 app do that instead.


Solution

  • I use the following

    const
      SM_REMOTESESSION = $1000;
    if GetSystemMetrics(SM_REMOTESESSION) <> 0 then
    begin
      // you are in a remote session
    end
    

    Per the MSDN page for GetSystemMetrics:

    SM_REMOTESESSION = 0x1000
    This system metric is used in a Terminal Services environment. If the calling process is associated with a Terminal Services client session, the return value is nonzero. If the calling process is associated with the Terminal Services console session, the return value is 0. Windows Server 2003 and Windows XP: The console session is not necessarily the physical console. For more information, see WTSGetActiveConsoleSessionId.

    I am using this in Delphi 2007 and the function is defined in the Windows unit, but I did need to define the constant myself. I don't know if Delphi 6 has the function defined. The Minimum supported windows version was Windows 2000 so you should be able to use it unless you are going way back.

    --

    To find out the current state of the session you need the WTSQuerySessionInformation function. You can use this function to find out a lot of information on the current session including the current state.

    I found an entry in the Embarcadero Discussion Forums that gave me the starting code. That post was called remote desktop question.

    Here are some constants and function prototypes you will need:

    const
      WTS_CURRENT_SERVER_HANDLE: THandle = 0;
      WTS_CURRENT_SESSION: DWORD = DWORD(-1);
    
    
    type
      WTS_INFO_CLASS = (
        WTSInitialProgram,
        WTSApplicationName,
        WTSWorkingDirectory,
        WTSOEMId,
        WTSSessionId,
        WTSUserName,
        WTSWinStationName,
        WTSDomainName,
        WTSConnectState,
        WTSClientBuildNumber,
        WTSClientName,
        WTSClientDirectory,
        WTSClientProductId,
        WTSClientHardwareId,
        WTSClientAddress,
        WTSClientDisplay,
        WTSClientProtocolType,
        WTSIdleTime,
        WTSLogonTime,
        WTSIncomingBytes,
        WTSOutgoingBytes,
        WTSIncomingFrames,
        WTSOutgoingFrames,
        WTSClientInfo,
        WTSSessionInfo,
        WTSSessionInfoEx,
        WTSConfigInfo,
        WTSValidationInfo,
        WTSSessionAddressV4,
        WTSIsRemoteSession
      );
    
     WTS_CONNECTSTATE_CLASS = (
        WTSActive,              // User logged on to WinStation
        WTSConnected,           // WinStation connected to client
        WTSConnectQuery,        // In the process of connecting to client
        WTSShadow,              // Shadowing another WinStation
        WTSDisconnected,        // WinStation logged on without client
        WTSIdle,                // Waiting for client to connect
        WTSListen,              // WinStation is listening for connection
        WTSReset,               // WinStation is being reset
        WTSDown,                // WinStation is down due to error
        WTSInit);               // WinStation in initialization
    
      TWTSQuerySessionInformationFunction = function(hServer: THandle; SessionId:
                    DWORD; WTSInfoClass: WTS_INFO_CLASS; var ppBuffer: Pointer; var pBytesReturned: DWORD): BOOL; stdcall;
      TWTSFreeMemoryProcedure = procedure(pMemory: Pointer); stdcall;
    

    And here is the code in use. I put this in a timer and output the state to a list box. I could disconnect and then reconnect and see the state change in the list box.

    There are different ways to handle the load library and function mapping call. Probably should not load library on every call if you end up polling like this. I just used the example I found.

    function TForm3.GetTSClientState: WTS_CONNECTSTATE_CLASS;
    var
      LibHandle: HMODULE;
      WTSQuerySessionInformation: TWTSQuerySessionInformationFunction;
      WTSFreeMemory: TWTSFreeMemoryProcedure;
      ClientState: Pointer;
      cBytesReturned: DWORD;
    begin
    
      LibHandle := LoadLibrary('wtsapi32.dll');
      if LibHandle &lt;&gt; 0 then
      begin
        try
          @WTSQuerySessionInformation := GetProcAddress(LibHandle, 'WTSQuerySessionInformationA');
          @WTSFreeMemory := GetProcAddress(LibHandle, 'WTSFreeMemory');
          if Assigned(WTSQuerySessionInformation) and Assigned(WTSFreeMemory) 
          then
          begin
            if WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION,
                                          WTSConnectState, ClientState, cBytesReturned) then
            try
              result := WTS_CONNECTSTATE_CLASS(ClientState^);
            finally
              WTSFreeMemory(ClientState);
            end;
    
          end;
        finally
          FreeLibrary(LibHandle);
        end;
      end;
    end;
    
    procedure TForm3.Timer1Timer(Sender: TObject);
    var
      State: WTS_CONNECTSTATE_CLASS;
    begin
       ListBox1.AddItem(GetTSClientName, nil);
    
       State := GetTSClientState;
    
       case State of
         WTSActive: ListBox1.AddItem('WTSActive', nil);
         WTSConnected: ListBox1.AddItem('WTSConnected', nil);
         WTSConnectQuery: ListBox1.AddItem('WTSConnectQuery', nil);
         WTSShadow: ListBox1.AddItem('WTSShadow', nil);
         WTSDisconnected: ListBox1.AddItem('WTSDisconnected', nil);
         WTSIdle: ListBox1.AddItem('WTSIdle', nil);
         WTSListen: ListBox1.AddItem('WTSListen', nil);
         WTSReset: ListBox1.AddItem('WTSReset', nil);
         WTSDown: ListBox1.AddItem('WTSDown', nil);
         WTSInit: ListBox1.AddItem('WTSInit', nil);
       end;
    end;