tags:

views:

1033

answers:

3

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.

+1  A: 

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.

Roger Lipscombe
This is incorrect, with fast-user switching, > 1 user could be running processes at the same time.
Paul Betts
@Paul Betts: If you're using Fast user switching, then WTSEnumerateSessions will return all currently logged-on sessions, whether they're active or not.
Roger Lipscombe
A: 

This might be a roundabout way, but run down the process list and see who the process owners are.

Paul Betts
+1  A: 

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
Mick