Using Delphi 10.2.3, I'm implementing a service that, among other things, will need to shut down a user app before a database restore and then restart the app after. Shutting down the app is no problem, but starting the app back up is, for the obvious session-0 reason. I found the following code online to do this, and it works fine, with one exception.
function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll';
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll';
function SvcLaunchAppInCurrUserSession(const AppToLaunch: String;
const Params: String = '';
WaitForIt: Boolean = False;
HideIt: Boolean = False): Cardinal;
var
PI: PROCESS_INFORMATION;
SI: STARTUPINFO;
bResult: Boolean;
dwSessionId: DWORD;
hUserTokenDup, hPToken: THANDLE;
dwCreationFlags: DWORD;
CommandLine: string;
Directory: string;
tp: TOKEN_PRIVILEGES;
pEnv: Pointer;
begin
Result := S_OK;
try
try
pEnv := nil;
dwCreationFlags := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
CommandLine := Trim('"'+Trim(AppToLaunch)+'" '+Params);
Directory := ExtractFileDir(AppToLaunch);
// get the current active session and the token
dwSessionId := WtsGetActiveConsoleSessionID;
// initialize startup info
ZeroMemory(#SI, SizeOf(SI));
SI.cb := SizeOf(STARTUPINFO);
SI.lpDesktop := nil; //PChar('winsta0\Default');
SI.dwFlags := STARTF_USESHOWWINDOW;
if HideIt then
SI.wShowWindow := SW_HIDE
else
SI.wShowWindow := SW_SHOWNORMAL;
if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or
TOKEN_QUERY or
TOKEN_DUPLICATE or
TOKEN_ASSIGN_PRIMARY or
TOKEN_ADJUST_SESSIONID or
TOKEN_READ or
TOKEN_WRITE,
hPToken) then begin
tp.PrivilegeCount := 1;
tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, hUserTokenDup);
SetTokenInformation(hUserTokenDup, TokenSessionId, #dwSessionId, SizeOf(DWORD));
if CreateEnvironmentBlock(pEnv, hUserTokenDup, True) then
dwCreationFlags := dwCreationFlags or CREATE_UNICODE_ENVIRONMENT
else
pEnv := nil;
// Launch the process in the client's logon session.
bResult := CreateProcessAsUser(hUserTokenDup, // client's access token
nil, // file to execute
PChar(CommandLine), // command line
nil, // pointer to process SECURITY_ATTRIBUTES
nil, // pointer to thread SECURITY_ATTRIBUTES
False, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
PChar(Directory), // name of current directory
si, // pointer to STARTUPINFO structure
pi); // receives information about new process
if not bResult then begin
Result := GetLastError;
Exit;
end;
end
else begin
Result := GetLastError;
Exit;
end;
end
else begin
Result := GetLastError;
Exit;
end;
if WaitForIt then begin
WaitForSingleObject(PI.hProcess, INFINITE);
GetExitCodeProcess(PI.hProcess, Result);
end;
finally
// close all handles
if Assigned(pEnv) then
DestroyEnvironmentBlock(pEnv);
CloseHandle(hUserTokenDup);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
CloseHandle(hPToken);
end;
except
on E:Exception do begin
DbgLogFmt('SvcLaunchApp %s: %s', [E.ClassName, E.Message]);
raise;
end;
end;
end;
The problem with this is that it launches the app with the service's permissions (SYSTEM), which is a huge security hole. I want it to launch the app with the current user's permissions, either user or admin, but not system. I know zilch about the ins and outs of Windows security, but I'm sure there's a way to do this - I just don't know what parts of the above need to be tweaked so the right permissions are used. Or if there's a better way to do this, I'm open to it. Suggestions?
Thanks.
You are using the user token that your service is running as (SYSTEM). Use WTSQueryUserToken() to get the user token of the target session instead.
You must first impersonate the user and then run the application.
I wrote a blog article using impersonation that will probably help you. You need user credentials for that purpose.
Impersonation will make your program (Here a service) act as another user. The blog article use that feature to hide data from current user. In your case, your service will first impersonate as the target user and then launch the application which in turn will run in the context of the impersonated user.
I published the source code on github. This code is the result of a StackOverflow discussion.
Related
I have successfully wrote a script to get memory consumption (TProcessMemoryCounters) of any process. However I need to run my program elevated (run as admin) in order to get these information for some system or elevated process (otherwise OpenProcess returns ERROR_ACCESS_DENIED). Programs like Process Explorer didn't need elevated mode.
My script to get memory information of a process:
function GetProcessMemory(ProcID: Cardinal): Cardinal;
var
MemCounters: TProcessMemoryCounters;
HProc: THandle;
begin
Result:= 0;
HProc:= OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION //or SYNCHRONIZE
, False, ProcID);
//HProc: 5 = ERROR_ACCESS_DENIED
if GetProcessMemoryInfo(HProc, #MemCounters, SizeOf(TProcessMemoryCounters)) then
Result :=
MemCounters.WorkingSetSize;
end;
With the following script I got memory information for all process, but still my program needs to be elevated:
function EnableDebugPriv(): Boolean;
const
SE_DEBUG_NAME = 'SeDebugPrivilege';
var
HToken: NativeUInt;
LUID: Int64;
Tkp: TTokenPrivileges;
AA: Cardinal;
begin
OpenProcessToken(GetCurrentProcess,
TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,
HToken
);
LookupPrivilegeValue(nil, SE_DEBUG_NAME, LUID);
Tkp.PrivilegeCount:= 1;
Tkp.Privileges[0].Luid:= LUID;
Tkp.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(HToken, False, Tkp, SizeOf(Tkp), nil, AA);
CloseHandle(HToken);
end;
Q: How to make my script get memory information of all process (include systems) without elevated mode?
To use GetProcessMemoryInfo(), you must specify the PROCESS_VM_READ access right as well, as stated by the documentation:
Process
A handle to the process. The handle must have the PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access right and the PROCESS_VM_READ access right. For more information, see Process Security and Access Rights.
Windows Server 2003 and Windows XP: The handle must have the PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access rights.
You are also leaking the handle if it were able to be opened successfully.
Try this instead:
function GetProcessMemory(ProcID: DWORD): DWORD;
var
MemCounters: TProcessMemoryCounters;
HProc: THandle;
begin
Result := 0;
HProc := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION or PROCESS_VM_READ, False, ProcID);
if HProc <> 0 then
begin
if GetProcessMemoryInfo(HProc, #MemCounters, SizeOf(TProcessMemoryCounters)) then
Result := MemCounters.WorkingSetSize;
CloseHandle(HProc);
end;
end;
I am developing using Delphi 6 on a WinXP system.
I have been using the following function to run a program with elevated rights.
function LB_RunAsAdminWait(hWnd: HWND;
filename: string;
Parameters: string): Boolean;
var sei: TShellExecuteInfo; // shell execute info
begin
Result := False; // default to failure
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := hWnd;
sei.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS;
sei.lpVerb := 'runas';
sei.lpFile := PChar(filename);
sei.lpParameters := PChar(Parameters);
sei.nShow := SW_SHOWNORMAL;
if ShellExecuteEx(#sei) then // if success
Result := True; // return sucess
if sei.hProcess <> 0 then begin // wait for process to finish
while WaitForSingleObject(sei.hProcess, 50) = WAIT_TIMEOUT do
Application.ProcessMessages;
CloseHandle(sei.hProcess);
end;
end; // of function LB_RunAsAdminWait
How I call it:
if (LB_RunAsAdminWait(FPGM.Handle,'RegSvr32',FName+' /s') = False) then
begin
ShowMessage('WARNING: unable to register OCX');
exit;
end;
where FPGM.handle is the handle to my application
and Fname is the OCX i want to register.
When I run it on a WIN7 machine it returns true(successful) but the OCX is not registered.
Any help would be appreciated.
Most likely explanation is that this is a 32 bit vs 64 bit issue. The DLL is 64 bit, and you are running the 32 bit regsvr32. Or vice versa. Or the file system redirector is confounding you. You put the DLL in system32, but the redirector turns that into SysWow64.
The obvious way to debug it is to remove the silent switch and let regsvr32 tell you what went wrong.
As an aside, as you have discovered, you cannot use the return value of ShellExecuteEx to determine whether or not the server registration succeeded. The return value of ShellExecuteEx merely tells you whether or not the process started.
Some years ago, we encountered problems where an administrator would install our application on a user's machine Our app would create registry entries at the time of install, but would then fail due to registry registry rights when a different user ran our application.
The code below was written by a predecessor. It seems to fixed the above problem. But recently, the code below itself began to fail gracefully, reporting a Windows error: "Failed Key" on the SetSecurityDescriptorDacl call.
This seems to happen on networked machines which have a corporate group policy in place. We suspect that the call to SetSecurityDescriptorDacl with nil may not legal on these machines, even if the user has admin rights.
This is not an area that we have any expertise in, so I'm hoping someone will have some ideas or an alternate code snippet that lets us circumvent this problem...
class function TQPWindowsRegistry.GiveEveryoneFullAccessToRegistryKey( const RootKey: HKey;
const RegPath : string;
out ErrorMsg : string): boolean;
var
Access : LongWord;
WinResult : LongWord;
SD : PSecurity_Descriptor;
LastError : DWORD;
Reg : TRegistry;
begin
Result := TRUE;
ErrorMsg := '';
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
begin
if TOOLS.UserHasAdminToken then
Access := KEY_ALL_ACCESS
else
Access := KEY_READ OR KEY_WRITE;
Reg := TRegistry.Create(Access);
try
Reg.RootKey := RootKey;
if NOT Reg.OpenKey(RegPath, TRUE) then
Exit;
GetMem(SD, SECURITY_DESCRIPTOR_MIN_LENGTH);
try
Result := InitializeSecurityDescriptor(SD, SECURITY_DESCRIPTOR_REVISION);
if Result then
Result := SetSecurityDescriptorDacl(SD, TRUE, NIL, FALSE); // Fails here!
if NOT Result then
begin
LastError := WINDOWS.GetLastError;
if NOT Result then
ErrorMsg := SYSUTILS.SysErrorMessage(LastError);
end
else
begin
WinResult := RegSetKeySecurity(Reg.CurrentKey, DACL_SECURITY_INFORMATION, SD);
Result := (WinResult = ERROR_SUCCESS);
ErrorMsg := SYSUTILS.SysErrorMessage(WinResult);
end;
finally
FreeMem(SD);
end;
Reg.CloseKey;
finally
FreeAndNIL(Reg);
end;
end;
end; {GiveEveryoneFullAccessToRegistryKey}
I'm trying to pass information between two of my applications in Delphi 2010.
I'm using a simplified version of code that I've used successfully in the past (simplified because I don't need the sender to know that the send has been successful) I've boiled down the send received to a pair of example applications, which in essence are as follows
Send
procedure TMF.SendString;
var
copyDataStruct: TCopyDataStruct;
s: AnsiString;
begin
s := ebFirm.Text;
copyDataStruct.cbData := 1 + length(s);
copyDataStruct.lpData := PAnsiChar(s);
SendData(copyDataStruct);
end;
procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
var
rh: THandle;
res: integer;
begin
rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
if rh = 0 then
begin
// Launch the target application
ShellExecute(Handle, 'open', GetPhone, nil, nil, SW_SHOWNORMAL);
// Give time for the application to launch
Sleep(3000);
SendData(copyDataStruct); // RECURSION!
end;
SendMessage(rh, WM_COPYDATA, Integer(Handle), Integer(#copyDataStruct));
end;
Receive Application
procedure TMF.WMCopyData(var Msg: TWMCopyData);
var
s : AnsiString;
begin
s := PAnsiChar(Msg.CopyDataStruct.lpData) ;
jobstatus.Panels[1].Text := s;
end;
The major difference between the working test applications and the application I am adding the code to is that there is a lot of extra activity going on in target application. Especially on startup.
Any suggestions on why the WMCopyData procedure seems not to be firing at all?
CHeers
Dan
There are a few problems with your code.
One, you are not assigning a unique ID to the message. The VCL, and various third-party components, also use WM_COPYDATA, so you have to make sure you are actually processing YOUR message and not SOMEONE ELSE'S message.
Two, you may not be waiting long enough for the second app to start. Instead of Sleep(), use ShellExecuteEx() with the SEE_MASK_WAITFORINPUTIDLE flag (or use CreateProcess() and WaitForInputIdle()).
Third, when starting the second app, your recursive logic is attempting to send the message a second time. If that happened to fail, you would launch a third app, and so on. You should take out the recursion altogether, you don't need it.
Try this:
var
GetPhoneMsg: DWORD = 0;
procedure TMF.SendString;
var
copyDataStruct: TCopyDataStruct;
s: AnsiString;
begin
if GetPhoneMsg = 0 then Exit;
s := ebFirm.Text;
copyDataStruct.dwData := GetPhoneMsg;
copyDataStruct.cbData := Length(s);
copyDataStruct.lpData := PAnsiChar(s);
SendData(copyDataStruct);
end;
procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
var
rh: HWND;
si: TShellExecuteInfo;
res: Integer;
begin
rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
if rh = 0 then
begin
// Launch the target application and give time to start
ZeroMemory(#si, SizeOf(si));
si.cbSize := SizeOf(si);
si.fMask := SEE_MASK_WAITFORINPUTIDLE;
si.hwnd := Handle;
si.lpVerb := 'open';
si.lpFile := GetPhone;
si.nShow := SW_SHOWNORMAL;
if not ShellExecuteEx(#si) then Exit;
rh := FindWindow(PChar('TMF'), PChar('Get Phone'));
if rh = 0 then Exit;
end;
SendMessage(rh, WM_COPYDATA, WParam(Handle), LParam(#copyDataStruct));
end;
initialization
GetPhoneMsg := RegisterWindowMessage('TMF_GetPhone');
Receive Application
var
GetPhoneMsg: DWORD = 0;
procedure TMF.WMCopyData(var Msg: TWMCopyData);
var
s : AnsiString;
begin
if (GetPhoneMsg <> 0) and (Msg.CopyDataStruct.dwData = GetPhoneMsg) then
begin
SetString(s, PAnsiChar(Msg.CopyDataStruct.lpData), Msg.CopyDataStruct.cbData);
jobstatus.Panels[1].Text := s;
end else
inherited;
end;
initialization
GetPhoneMsg := RegisterWindowMessage('TMF_GetPhone');
I think it is a good habit to add
copyDataStruct.dwData := Handle;
in procedure TMF.SendString; - if you don't have a custom identifier, putting the source HWND value will help debugging on the destination (you can check for this value in the other side, and therefore avoid misunderstand of broadcasted WMCOPY_DATA e.g. - yes, there should not be, but I've seen some!).
And
procedure WMCopyData(var Msg : TWMCopyData); message WM_COPYDATA;
in TMF client class definition, right?
There should be a missing exit or else after the nested SendData call:
procedure TMF.SendData(copyDataStruct: TCopyDataStruct);
(...)
Sleep(3000);
SendData(copyDataStruct);
end else
SendMessage(rh, WM_COPYDATA, NativeInt(Handle), NativeInt(#copyDataStruct));
end;
But this won't change much.
Check the rh := FindWindow() returned handle: is it the Handle of the TMF client form, or the Application.Handle?
It doesn't work anymore if you are using Windows 7.
If you are using it, check this page to see how to add an exception: http://msdn.microsoft.com/en-us/library/ms649011%28v=vs.85%29.aspx
I thought there was a problem with the (rh) handle being 0 when you call it, if the app needed to be started. But now I see that SendData calls itself recursively. I added a comment in the code for that, as it was non-obvious. But now there's another problem. The 2nd instance of SendData will have the right handle. But then you're going to pop out of that, back into the first instance where the handle is still 0, and then you WILL call SendMessage again, this time with a 0 handle. This probably is not the cause of your trouble, but it's unintended, unnecessary, and altogether bad. IMO, this is a case complicating things by trying to be too clever.
I have code which is used both in services and within VCL Form applications (win32 application). How can I determine if the underlying application is running as a NT Service or as an application?
Thanks.
BEGIN OF EDIT
Since this still seems to be getting some attention I decided to update the answer with missing info and newer windows patches. In any case you should not copy / paste the code. The code is just a showcase on how the things should be done.
END OF EDIT:
You can check if the parent process is SCM (service control manager). If you are running as service this is always the case and never the case if running as standard application. Also I think that SCM has always the same PID.
You can check it like this:
type
TAppType = (atUnknown, atDesktop, atService);
var
AppType: TAppType;
function InternalIsService: Boolean;
var
PL: TProcessList;
MyProcessId: DWORD;
MyProcess: PPROCESSENTRY32;
ParentProcess: PPROCESSENTRY32;
GrandParentProcess: PPROCESSENTRY32;
begin
Result := False;
PL := TProcessList.Create;
try
PL.CreateSnapshot;
MyProcessId := GetCurrentProcessId;
MyProcess := PL.FindProcess(MyProcessId);
if MyProcess <> nil then
begin
ParentProcess := PL.FindProcess(MyProcess^.th32ParentProcessID);
if ParentProcess <> nil then
begin
GrandParentProcess := PL.FindProcess(ParentProcess^.th32ParentProcessID);
if GrandParentProcess <> nil then
begin
Result := SameText(string(ParentProcess^.szExeFile), 'services.exe') and
(SameText(string(GrandParentProcess^.szExeFile), 'winlogon.exe') or
SameText(string(GrandParentProcess^.szExeFile), 'wininit.exe'));
end;
end;
end;
finally
PL.Free;
end;
end;
function IsService: Boolean;
begin
if AppType = atUnknown then
begin
try
if InternalIsService then
AppType := atService
else
AppType := atDesktop;
except
AppType := atService;
end;
end;
Result := AppType = atService;
end;
initialization
AppType := atUnknown;
The TProcessList is implemented like this (again THashTable is not included but any hash table should be fine):
type
TProcessEntryList = class(TList)
private
function Get(Index: Integer): PPROCESSENTRY32;
procedure Put(Index: Integer; const Value: PPROCESSENTRY32);
public
property Items[Index: Integer]: PPROCESSENTRY32 read Get write Put; default;
function Add(const Entry: TProcessEntry32): Integer; reintroduce;
procedure Clear; override;
end;
TProcessList = class
private
ProcessIdHashTable: THashTable;
ProcessEntryList: TProcessEntryList;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure CreateSnapshot;
function FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
end;
implementation
{ TProcessEntryList }
procedure TProcessEntryList.Clear;
var
i: Integer;
begin
i := 0;
while i < Count do
begin
FreeMem(Items[i]);
Inc(i);
end;
inherited;
end;
procedure TProcessEntryList.Put(Index: Integer; const Value: PPROCESSENTRY32);
var
Item: Pointer;
begin
Item := inherited Get(Index);
CopyMemory(Item, Value, SizeOf(tagPROCESSENTRY32));
end;
function TProcessEntryList.Get(Index: Integer): PPROCESSENTRY32;
begin
Result := PPROCESSENTRY32(inherited Get(Index));
end;
function TProcessEntryList.Add(const Entry: TProcessEntry32): Integer;
var
EntryCopy: PPROCESSENTRY32;
begin
GetMem(EntryCopy, SizeOf(tagPROCESSENTRY32));
CopyMemory(EntryCopy, #Entry, SizeOf(tagPROCESSENTRY32));
Result := inherited Add(EntryCopy);
end;
{ TProcessList }
constructor TProcessList.Create;
begin
inherited;
ProcessEntryList := TProcessEntryList.Create;
ProcessIdHashTable := THashTable.Create;
end;
destructor TProcessList.Destroy;
begin
FreeAndNil(ProcessIdHashTable);
FreeAndNil(ProcessEntryList);
inherited;
end;
function TProcessList.FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
var
ItemIndex: Integer;
begin
Result := nil;
if not ProcessIdHashTable.ContainsKey(IntToStr(ProcessId)) then
Exit;
ItemIndex := Integer(ProcessIdHashTable.Item[IntToStr(ProcessId)]);
Result := ProcessEntryList.Items[ItemIndex];
end;
procedure TProcessList.CreateSnapshot;
var
SnapShot: THandle;
ProcessEntry: TProcessEntry32;
ItemIndex: Integer;
begin
SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if SnapShot <> 0 then
try
ProcessEntry.dwSize := SizeOf(ProcessEntry);
if Process32First(SnapShot, ProcessEntry) then
repeat
ItemIndex := ProcessEntryList.Add(ProcessEntry);
ProcessIdHashTable.Add(IntToStr(ProcessEntry.th32ProcessID), TObject(ItemIndex));
until not Process32Next(SnapShot, ProcessEntry);
finally
CloseHandle(SnapShot);
end;
end;
The application object (Forms.application) mainform will be nil if it is not a forms based application.
uses
Forms, ... ;
function IsFormBased : boolean;
begin
Result := Assigned(Forms.Application.MainForm);
end;
How about matching GetCurrentProcessId against EnumServicesStatusEx?
The lpServices parameter points to a buffer that receives an array of ENUM_SERVICE_STATUS_PROCESS structures.
The match is done against the enumerated service process ID: ServiceStatusProcess.dwProcessId in that structure.
Another option is using WMI to query for Win32_Service instances where ProcessId=GetCurrentProcessId.
I doubt that
System.IsConsole
System.IsLibrary
will give you the expected results.
All I can think of is to pass an Application object as TObject to the method where you need to make that distinction and test for the passed object's classname being a
TServiceApplication
or
TApplication
That said, there shouldn't be a need for you to know if your code is running in a service or a GUI. You should probably rethink your design and make the caller to pass an object to handle messages you want (or don't want) to show. (I assume it is for showing messages/exceptions you'd like to know).
You can try something like this
Function IsMyformInsideaWindowsService(aForm:TObject) :Boolean;
Begin
Result:=aForm.ClassParent.ClassName='TService'; //When a form is running under a service the Class Parent is a TService
End;
A single project cannot (or I should say ideally is not) both a service and a forms application, at least not if you are able to distinguish between the Forms Application object and the SvcMgr Application object - you must presumably have separate projects for the forms code and the service code.
So perhaps the easiest solution is a project conditional define. i.e. in your project settings for the service project add "SERVICEAPP" to the Conditional Defines.
Then whenever you need to change behaviour simply:
{$ifdef SERVICEAPP}
{$else}
{$endif}
For belts and braces you might adopt one of the previously described tests within some startup code to ensure that your project has been compiled with the expected symbol defined.
program ... ;
:
begin
{$ifdef SERVICEAPP}
// test for service app - ASSERT if not
{$else}
// test for forms app - ASSERT if not
{$endif}
:
end.
It is possible that your Forms app is actually running as a service, using the crude technique that allows any application to be running as a service.
In that case of course your app will always be a Forms application and the easiest way to handle that situation is to have a command line switch that you specify only in the service definition for your executable so that your app can respond appropriate by testing for that command line switch.
This does allow you to more easily test your "service mode" behaviour of course, since you can run your app in "debug" mode with that switch defined from within the IDE, but it's not an ideal way to build a service application so I would not recommend it on the strength of that alone. It's a technique that is usually only used when you have an EXE that you wish to run as a service but have no way to modify the source code to turn it into a "proper" service.
The answer from "Runner" ( https://stackoverflow.com/a/1568462 ) looked very helpful, but I could not use it since neither TProcessList, nor CreateSnapshot is defined. Searching for "TProcessList CreateSnapshot" in Google will just find 7 pages, including this one and mirrors/quotes of this page. No code exists. Alas, my reputation is too low to send him a comment, asking where I can find the code of TProcessList.
Another problem: At my computer (Win7 x64), the "services.exe" is NOT inside "winlogon.exe". It is inside "wininit.exe". Since it seems to be an implementation detail of Windows, I would suggest not querying the grand parent. Also, services.exe does not need to be the direct parent, since processes could be forked.
So this is my version using TlHelp32 directly, solving all the problems:
uses
Classes, TlHelp32;
function IsRunningAsService: boolean;
function FindProcess(FSnapshotHandle: THandle; PID: DWORD; var lppe: TProcessEntry32): boolean;
var
ContinueLoop: BOOL;
begin
ContinueLoop := Process32First(FSnapshotHandle, lppe);
while Integer(ContinueLoop) <> 0 do
begin
if lppe.th32ProcessID = PID then
begin
result := true;
Exit;
end;
ContinueLoop := Process32Next(FSnapshotHandle, lppe);
end;
result := false;
end;
var
CurProcessId: DWORD;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
ExeName, PrevExeName: string;
DeadlockProtection: TList<Integer>;
begin
Result := false;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
DeadlockProtection := TList<Integer>.Create;
try
CurProcessId := GetCurrentProcessId;
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ExeName := '';
while FindProcess(FSnapshotHandle, CurProcessId, FProcessEntry32) do
begin
if DeadlockProtection.IndexOf(FProcessEntry32.th32ProcessID) > -1 then break;
DeadlockProtection.Add(FProcessEntry32.th32ProcessID);
PrevExeName := ExeName;
ExeName := FProcessEntry32.szExeFile;
(*
Result := SameText(PrevExeName, 'services.exe') and // Parent
SameText(ExeName, 'winlogon.exe'); // Grandparent
*)
Result := SameText(ExeName, 'services.exe'); // Parent
if Result then Exit;
CurProcessId := FProcessEntry32.th32ParentProcessID;
end;
finally
CloseHandle(FSnapshotHandle);
DeadlockProtection.Free;
end;
end;
This code works, also even in applications without MainForm (e.g. CLI apps).
you can use GetStdHandle method for get out console handle.when applications run as windows service has not output console.if GetStdHandle equals zero means your application run as windows service.
{$APPTYPE CONSOLE} // important
uses
uServerForm in 'uServerForm.pas' {ServerForm},
uWinService in 'uWinService.pas' {mofidWinServer: TService},
Windows,
System.SysUtils,
WinSvc,
SvcMgr,
Forms,etc;
function RunAsWinService: Boolean;
var
H: THandle;
begin
if FindCmdLineSwitch('install', ['-', '/'], True) then
Exit(True);
if FindCmdLineSwitch('uninstall', ['-', '/'], True) then
Exit(True);
H := GetStdHandle(STD_OUTPUT_HANDLE);
Result := H = 0;
end;
begin
if RunAsWinService then
begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TmofidWinServer, mofidWinServer);
SvcMgr.Application.Run;
end
else
begin
Forms.Application.Initialize;
Forms.Application.CreateForm(TServerForm, ServerForm);
Forms.Application.Run;
end;
end.
I didn't find the simple answer which can be used easily and does not require recompilation and allows using one exe as a service and an application. You can install your program as a service with the command line parameter like “…\myapp.exe –s” and then check it from the program:
if ParamStr(ParamCount) = '-s' then
You can base the check on checking the session ID of the current process. All services runs with session ID = 0.
function IsServiceProcess: Boolean;
var
LSessionID, LSize: Cardinal;
LToken: THandle;
begin
Result := False;
LSize := 0;
if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, LToken) then
Exit;
try
if not GetTokenInformation(LToken, TokenSessionId, #LSessionID, SizeOf(LSessionID), LSize) then
Exit;
if LSize = 0 then
Exit;
Result := LSessionID = 0;
finally
CloseHandle(LToken);
end;
end;
I actually ended up checking the application.showmainform variable.
The problem with skamradt's isFormBased is that some of this code is called before the main form is created.
I am using a software library called SvCom_NTService from aldyn-software. One of purposes is for errors; either to log them or show a message. I totally agree with #Rob; our code should be better maintained and handle this outside of the functions.
The other intention is for failed database connections and queries; I have different logic in my functions to open queries. If it is a service then it will return nil but continue the process. But if failed queries/connections occur in an application then I would like to display a messaage and halt the application.
Check if your Applicatoin is an instance of TServiceApplication:
IsServiceApp := Application is TServiceApplication;