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.
-
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