Sunday, March 6, 2011

Detecting multiple logged on users via win32

Using the standard win32 api, what's the best way to detect more than one user is logged on? I have an upgrade to our software product that can't be run when more than one user is logged in. (I know this is something to be avoided because of its annoyance factor, but the product is very complicated. You'll have to trust me when I say there really is no other solution.) Thanks.

From stackoverflow
  • In order to have more than one user logged in at once, Terminal Services or Fast User Switching must be enabled. Since Fast User Switching is implemented using Terminal Services, you first need to find out if the OS has it enabled. You can use GetVersionEx with an OSVERSIONINFOEX. Check for the VER_SUITE_SINGLEUSERTS and VER_SUITE_TERMINAL flags.

    If TS is enabled, you can use WTSEnumerateSessions to find out how many users are logged on. This only works if the "Terminal Services" service is started.

    If the machine doesn't support Terminal Services (or if the service isn't started), then you can only have one user logged on.

    Paul Betts : This is incorrect, with fast-user switching, > 1 user could be running processes at the same time.
    Roger Lipscombe : @Paul Betts: If you're using Fast user switching, then WTSEnumerateSessions will return all currently logged-on sessions, whether they're active or not.
  • This might be a roundabout way, but run down the process list and see who the process owners are.

  • Here's a solution that works on XP, Server 2003, Vista, and Server 2008. Note, this won't work on Windows 2000, because "LsaEnumerateLogonSessions" is not available on Windows 2000. This code is modified from a Delphi-PRAXIS post.

    To compile this, create a new VCL application with a TButton and a TMemo on the form. Then copy and paste this code and it should compile. I tested on XP and Vista and it works well. It will return interactive and remote users.

    unit main;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;
    
    const
      WTS_CURRENT_SERVER_HANDLE = 0;
    
    type
      PTOKEN_USER = ^TOKEN_USER;
      _TOKEN_USER = record
        User: TSidAndAttributes;
      end;
      TOKEN_USER = _TOKEN_USER;
    
      USHORT = word;
    
      _LSA_UNICODE_STRING = record
        Length: USHORT;
        MaximumLength: USHORT;
        Buffer: LPWSTR;
      end;
      LSA_UNICODE_STRING = _LSA_UNICODE_STRING;
    
      PLuid = ^LUID;
      _LUID = record
        LowPart: DWORD;
        HighPart: LongInt;
      end;
      LUID = _LUID;
    
      _SECURITY_LOGON_TYPE = (
        seltFiller0, seltFiller1,
        Interactive,
        Network,
        Batch,
        Service,
        Proxy,
        Unlock,
        NetworkCleartext,
        NewCredentials,
        RemoteInteractive,
        CachedInteractive,
        CachedRemoteInteractive);
      SECURITY_LOGON_TYPE = _SECURITY_LOGON_TYPE;
    
      PSECURITY_LOGON_SESSION_DATA = ^SECURITY_LOGON_SESSION_DATA;
      _SECURITY_LOGON_SESSION_DATA = record
        Size: ULONG;
        LogonId: LUID;
        UserName: LSA_UNICODE_STRING;
        LogonDomain: LSA_UNICODE_STRING;
        AuthenticationPackage: LSA_UNICODE_STRING;
        LogonType: SECURITY_LOGON_TYPE;
        Session: ULONG;
        Sid: PSID;
        LogonTime: LARGE_INTEGER;
        LogonServer: LSA_UNICODE_STRING;
        DnsDomainName: LSA_UNICODE_STRING;
        Upn: LSA_UNICODE_STRING;
      end;
      SECURITY_LOGON_SESSION_DATA = _SECURITY_LOGON_SESSION_DATA;
    
      _WTS_INFO_CLASS = (
        WTSInitialProgram,
        WTSApplicationName,
        WTSWorkingDirectory,
        WTSOEMId,
        WTSSessionId,
        WTSUserName,
        WTSWinStationName,
        WTSDomainName,
        WTSConnectState,
        WTSClientBuildNumber,
        WTSClientName,
        WTSClientDirectory,
        WTSClientProductId,
        WTSClientHardwareId,
        WTSClientAddress,
        WTSClientDisplay,
        WTSClientProtocolType);
      WTS_INFO_CLASS = _WTS_INFO_CLASS;
    
      _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
      WTS_CONNECTSTATE_CLASS = _WTS_CONNECTSTATE_CLASS;
    
      function LsaFreeReturnBuffer(Buffer: pointer): Integer; stdcall;
    
      function WTSGetActiveConsoleSessionId: DWORD; external 'Kernel32.dll';
    
      function LsaGetLogonSessionData(LogonId: PLUID;
         var ppLogonSessionData: PSECURITY_LOGON_SESSION_DATA): LongInt; stdcall;
         external 'Secur32.dll';
    
      function LsaNtStatusToWinError(Status: cardinal): ULONG; stdcall;
         external 'Advapi32.dll';
    
      function LsaEnumerateLogonSessions(Count: PULONG; List: PLUID): LongInt;
         stdcall; external 'Secur32.dll';
    
      function WTSQuerySessionInformationA(hServer: THandle; SessionId: DWORD;
         WTSInfoClass: WTS_INFO_CLASS; var pBuffer: Pointer;
         var pBytesReturned: DWORD): BOOL; stdcall; external 'Wtsapi32.dll';
    
    type
      TForm1 = class(TForm)
        Button1: TButton;
        Memo1: TMemo;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    function LsaFreeReturnBuffer; external 'secur32.dll' name 'LsaFreeReturnBuffer';
    
    procedure GetActiveUserNames(var slUserList : TStringList);
    var
       Count: cardinal;
       List: PLUID;
       sessionData: PSECURITY_LOGON_SESSION_DATA;
       i1: integer;
       SizeNeeded, SizeNeeded2: DWORD;
       OwnerName, DomainName: PChar;
       OwnerType: SID_NAME_USE;
       pBuffer: Pointer;
       pBytesreturned: DWord;
       sUser : string;
    begin
       //result:= '';
       //Listing LogOnSessions
       i1:= lsaNtStatusToWinError(LsaEnumerateLogonSessions(@Count, @List));
       try
          if i1 = 0 then
          begin
              i1:= -1;
              if Count > 0 then
              begin
                  repeat
                    inc(i1);
                    LsaGetLogonSessionData(List, sessionData);
                    //Checks if it is an interactive session
                    sUser := sessionData.UserName.Buffer;
                    if (sessionData.LogonType = Interactive)
                      or (sessionData.LogonType = RemoteInteractive)
                      or (sessionData.LogonType = CachedInteractive)
                      or (sessionData.LogonType = CachedRemoteInteractive) then
                    begin
                        //
                        SizeNeeded := MAX_PATH;
                        SizeNeeded2:= MAX_PATH;
                        GetMem(OwnerName, MAX_PATH);
                        GetMem(DomainName, MAX_PATH);
                        try
                        if LookupAccountSID(nil, sessionData.SID, OwnerName,
                                           SizeNeeded, DomainName,SizeNeeded2,
                                           OwnerType) then
                        begin
                          if OwnerType = 1 then  //This is a USER account SID (SidTypeUser=1)
                          begin
                            sUser := AnsiUpperCase(sessionData.LogonDomain.Buffer);
                            sUser := sUser + '\';
                            sUser := sUser + AnsiUpperCase(sessionData.UserName.Buffer);
                            slUserList.Add(sUser);
    //                          if sessionData.Session = WTSGetActiveConsoleSessionId then
    //                          begin
    //                            //Wenn Benutzer aktiv
    //                            try
    //                                if WTSQuerySessionInformationA
    //                                   (WTS_CURRENT_SERVER_HANDLE,
    //                                    sessionData.Session, WTSConnectState,
    //                                    pBuffer,
    //                                    pBytesreturned) then
    //                                begin
    //                                    if WTS_CONNECTSTATE_CLASS(pBuffer^) = WTSActive then
    //                                    begin
    //                                      //result:= sessionData.UserName.Buffer;
    //                                      slUserList.Add(sessionData.UserName.Buffer);
    //                                    end;
    //                                end;
    //                            finally
    //                              LSAFreeReturnBuffer(pBuffer);
    //                            end;
                              //end;
                          end;
                        end;
                        finally
                        FreeMem(OwnerName);
                        FreeMem(DomainName);
                        end;
                    end;
                    inc(List);
                    try
                        LSAFreeReturnBuffer(sessionData);
                    except
                    end;
                until (i1 = Count-1);// or (result <> '');
              end;
          end;
       finally
          LSAFreeReturnBuffer(List);
       end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      slUsers : TStringList;
    begin
      slUsers := TStringList.Create;
      slUsers.Duplicates := dupIgnore;
      slUsers.Sorted := True;
    
      try
        GetActiveUserNames(slUsers);
        Memo1.Lines.AddStrings(slUsers);
      finally
        FreeAndNil(slUsers)
      end;
    end;
    
    end.
    
    Mick : I should add that you can turn this code into an MSI DLL and use it as a windows installer (MSI) custom action. I have an example of how to do this with Delphi here: http://stackoverflow.com/questions/367365/how-do-i-write-custom-action-dll-for-use-in-an-msi.
    Mick : If you don't have Delphi, all of this code will compile and work just fine using the free Turbo Delphi. You can download that here: http://www.codegear.com/downloads/free/turbo

0 comments:

Post a Comment