With a lot of help from users of this forum, I can now get a Windows service to detect user logons. However, for this feature to be any use I also need the user name. On investigation I have discovered that getting the user name from an environment variable doesn't work. Conventional methods will often give 'SYSTEM' or the host name followed by a $ because the service is not running as a user but under the system account. It seems a workaround is to get the owner of the process
explorer.exe (the Windows Explorer), as this will always be running. This also isn't easy because it involves low-level programming.
I found the following code online (thanks to Rodrigo on
https://theroadtodelphi.com/2011/11/06/wmi-tasks-using-delphi-%E2%80%93-processes/). This is unchanged apart from the count, which I added.
const
wbemFlagForwardOnly = $00000020;
var
Count: Integer;
ErrorMsg: String;
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
NameOfUser : OleVariant;
UserDomain : OleVariant;
begin
// quick check parameters
ErrorMsg:=CheckOptions('h', 'help');
if ErrorMsg<>'' then begin
ShowException(Exception.Create(ErrorMsg));
Terminate;
Exit;
end;
// parse parameters
if HasOption('h', 'help') then begin
WriteHelp;
Terminate;
Exit;
end;
begin;
Count:= 0;
FSWbemLocator:= CreateOleObject('WbemScripting.SWbemLocator');
FWMIService:= FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_Process', 'WQL', wbemFlagForwardOnly);
oEnum:= IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Inc(Count);
Write(IntToStr(Count) + ': ');
FWbemObject.GetOwner(NameOfUser, UserDomain);
Writeln(Format('Process %s is owned by %s\%s',[String(FWbemObject.Name),String(NameOfUser), String(UserDomain)]));
FWbemObject:= Unassigned;
end;
end;
// stop program loop
Terminate;
end;
It looks as if it should list all running processes and their owners. It's intended for Delphi but it compiled and ran in Lazarus as a console application for me but then failed immediately with the error
1: Exception at 00000001000399F8: EVariantTypeCastError:
Could not convert variant of type (Null) into type (String).
Of course, my understanding of objects is not good enough to fully get what this is doing (in particular, the types of the subvalues in the objects). I tried adding the condition
"If Assigned(FWbemObject.Name)" then and then
"If Assigned(FWbemObject)" then to the start of the
Writeln but then the application wouldn't compile.
I have two questions:
1) Can anyone tell me what condition I need to add to avoid the error? and
2) Will the adapted code also work in a service, or is it again accessing processes not available there?
Thank you.