123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- { ***************************************************************************
- Copyright (c) 2016-2017 Kike Pérez
- Unit : Quick.AppService
- Description : Allow run app as console or service
- Author : Kike Pérez
- Version : 1.0
- Created : 14/09/2017
- Modified : 01/12/2017
- This file is part of QuickLib: https://github.com/exilon/QuickLib
- ***************************************************************************
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- *************************************************************************** }
- unit Quick.AppService;
- {$i QuickLib.inc}
- interface
- {$IFNDEF FPC}
- {$IFDEF DELPHI2010_UP}
- {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
- {$WEAKLINKRTTI ON}
- {$ENDIF}
- {$ENDIF}
- uses
- {$IFDEF MSWINDOWS}
- Windows,
- {$ENDIF}
- SysUtils,
- {$IFNDEF FPC}
- WinSvc,
- {$ENDIF}
- Quick.Commons;
- const
- DEF_SERVICENAME = 'QuickAppService';
- DEF_DISPLAYNAME = 'QuickAppService';
- NUM_OF_SERVICES = 2;
- type
- TSvcStatus = (ssStopped = SERVICE_STOPPED,
- ssStopping = SERVICE_STOP_PENDING,
- ssStartPending = SERVICE_START_PENDING,
- ssRunning = SERVICE_RUNNING,
- ssPaused = SERVICE_PAUSED);
- TSvcStartType = (stAuto = SERVICE_AUTO_START,
- stManual = SERVICE_DEMAND_START,
- stDisabled = SERVICE_DISABLED);
- TSvcInitializeEvent = procedure of object;
- {$IFDEF FPC}
- TSvcAnonMethod = procedure of object;
- {$ELSE}
- TSvcAnonMethod = reference to procedure;
- {$ENDIF}
- TSvcRemoveEvent = procedure of object;
- TAppService = class
- private
- fSCMHandle : SC_HANDLE;
- fSvHandle : SC_HANDLE;
- fServiceName : string;
- fDisplayName : string;
- fLoadOrderGroup : string;
- fDependencies : string;
- fDesktopInteraction : Boolean;
- fUsername : string;
- fUserPass : string;
- fStartType : TSvcStartType;
- fFileName : string;
- fSilent : Boolean;
- fStatus : TSvcStatus;
- fCanInstallWithOtherName : Boolean;
- fOnInitialize : TSvcInitializeEvent;
- fOnStart : TSvcAnonMethod;
- fOnStop : TSvcAnonMethod;
- fOnExecute : TSvcAnonMethod;
- fAfterRemove : TSvcRemoveEvent;
- fServiceDescription : string;
- procedure ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
- procedure Execute;
- procedure Help;
- procedure DoStop;
- procedure SetServiceDescription;
- public
- constructor Create;
- destructor Destroy; override;
- property ServiceName : string read fServiceName write fServiceName;
- property DisplayName : string read fDisplayName write fDisplayName;
- property ServiceDescription : string read fServiceDescription write fServiceDescription;
- property LoadOrderGroup : string read fLoadOrderGroup write fLoadOrderGroup;
- property Dependencies : string read fDependencies write fDependencies;
- property DesktopInteraction : Boolean read fDesktopInteraction write fDesktopInteraction;
- property UserName : string read fUserName write fUserName;
- property UserPass : string read fUserPass write fUserPass;
- property StartType : TSvcStartType read fStartType write fStartType;
- property FileName : string read fFileName write fFileName;
- property Silent : Boolean read fSilent write fSilent;
- property CanInstallWithOtherName : Boolean read fCanInstallWithOtherName write fCanInstallWithOtherName;
- property Status : TSvcStatus read fStatus write fStatus;
- property OnStart : TSvcAnonMethod read fOnStart write fOnStart;
- property OnStop : TSvcAnonMethod read fOnStop write fOnStop;
- property OnExecute : TSvcAnonMethod read fOnExecute write fOnExecute;
- property OnInitialize : TSvcInitializeEvent read fOnInitialize write fOnInitialize;
- property AfterRemove : TSvcRemoveEvent read fAfterRemove write fAfterRemove;
- procedure Install;
- procedure Remove;
- procedure CheckParams;
- class function InstallParamsPresent : Boolean;
- class function ConsoleParamPresent : Boolean;
- class function IsRunningAsService : Boolean;
- class function IsRunningAsConsole : Boolean;
- end;
- var
- ServiceStatus : TServiceStatus;
- StatusHandle : SERVICE_STATUS_HANDLE;
- ServiceTable : array [0..NUM_OF_SERVICES] of TServiceTableEntry;
- ghSvcStopEvent: Cardinal;
- AppService : TAppService;
- implementation
- {$IFDEF MSWINDOWS}
- uses
- Registry;
- {$ENDIF}
- procedure ServiceCtrlHandler(Control: DWORD); stdcall;
- begin
- case Control of
- SERVICE_CONTROL_STOP:
- begin
- AppService.Status := TSvcStatus.ssStopping;
- SetEvent(ghSvcStopEvent);
- ServiceStatus.dwCurrentState := SERVICE_STOP_PENDING;
- SetServiceStatus(StatusHandle, ServiceStatus);
- end;
- SERVICE_CONTROL_PAUSE:
- begin
- AppService.Status := TSvcStatus.ssPaused;
- ServiceStatus.dwcurrentstate := SERVICE_PAUSED;
- SetServiceStatus(StatusHandle, ServiceStatus);
- end;
- SERVICE_CONTROL_CONTINUE:
- begin
- AppService.Status := TSvcStatus.ssRunning;
- ServiceStatus.dwCurrentState := SERVICE_RUNNING;
- SetServiceStatus(StatusHandle, ServiceStatus);
- end;
- SERVICE_CONTROL_INTERROGATE: SetServiceStatus(StatusHandle, ServiceStatus);
- SERVICE_CONTROL_SHUTDOWN:
- begin
- AppService.Status := TSvcStatus.ssStopped;
- AppService.DoStop;
- end;
- end;
- end;
- procedure RegisterService(dwArgc: DWORD; var lpszArgv: PChar); stdcall;
- begin
- ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
- ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
- ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_PAUSE_CONTINUE;
- ServiceStatus.dwServiceSpecificExitCode := 0;
- ServiceStatus.dwWin32ExitCode := 0;
- ServiceStatus.dwCheckPoint := 0;
- ServiceStatus.dwWaitHint := 0;
- StatusHandle := RegisterServiceCtrlHandler(PChar(AppService.ServiceName), @ServiceCtrlHandler);
- if StatusHandle <> 0 then
- begin
- AppService.ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
- try
- AppService.Status := TSvcStatus.ssRunning;
- AppService.Execute;
- finally
- AppService.ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
- end;
- end;
- end;
- constructor TAppService.Create;
- begin
- inherited;
- fServiceName := DEF_SERVICENAME;
- fDisplayName := DEF_DISPLAYNAME;
- fLoadOrderGroup := '';
- fDependencies := '';
- fDesktopInteraction := False;
- fUserName := '';
- fUserPass := '';
- fStartType := TSvcStartType.stAuto;
- fFileName := ParamStr(0);
- fSilent := True;
- fStatus := TSvcStatus.ssStopped;
- fCanInstallWithOtherName := False;
- fOnExecute := nil;
- IsQuickServiceApp := True;
- end;
- destructor TAppService.Destroy;
- begin
- fOnStart := nil;
- fOnStop := nil;
- fOnExecute := nil;
- if fSCMHandle <> 0 then CloseServiceHandle(fSCMHandle);
- if fSvHandle <> 0 then CloseServiceHandle(fSvHandle);
- inherited;
- end;
- procedure TAppService.ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
- begin
- //fill in the SERVICE_STATUS structure
- ServiceStatus.dwCurrentState := dwCurrentState;
- ServiceStatus.dwWin32ExitCode := dwWin32ExitCode;
- ServiceStatus.dwWaitHint := dwWaitHint;
- if dwCurrentState = SERVICE_START_PENDING then ServiceStatus.dwControlsAccepted := 0
- else ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP;
- case (dwCurrentState = SERVICE_RUNNING) or (dwCurrentState = SERVICE_STOPPED) of
- True: ServiceStatus.dwCheckPoint := 0;
- False: ServiceStatus.dwCheckPoint := 1;
- end;
- //report service status to SCM
- SetServiceStatus(StatusHandle,ServiceStatus);
- end;
- procedure TAppService.SetServiceDescription;
- {$IFDEF MSWINDOWS}
- var
- reg: TRegistry;
- {$ENDIF}
- begin
- {$IFDEF MSWINDOWS}
- reg := TRegistry.Create(KEY_READ or KEY_WRITE);
- try
- reg.RootKey := HKEY_LOCAL_MACHINE;
- if reg.OpenKey('\SYSTEM\CurrentControlSet\Services\' + fServiceName, False) then
- begin
- reg.WriteString('Description', fServiceDescription);
- reg.CloseKey;
- end;
- finally
- reg.Free;
- end;
- {$ENDIF}
- end;
- procedure TAppService.Execute;
- begin
- //we have to do something or service will stop
- ghSvcStopEvent := CreateEvent(nil,True,False,nil);
- if ghSvcStopEvent = 0 then
- begin
- ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
- Exit;
- end;
- if Assigned(fOnStart) then fOnStart;
- //report running status when initialization is complete
- ReportSvcStatus(SERVICE_RUNNING,NO_ERROR,0);
- //perform work until service stops
- while True do
- begin
- //external callback process
- if Assigned(fOnExecute) then fOnExecute;
- //check whether to stop the service.
- WaitForSingleObject(ghSvcStopEvent,INFINITE);
- ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
- Exit;
- end;
- end;
- procedure TAppService.DoStop;
- begin
- if Assigned(fOnStop) then fOnStop;
- end;
- procedure TAppService.Remove;
- const
- cRemoveMsg = 'Service "%s" removed successfully!';
- var
- SCManager: SC_HANDLE;
- Service: SC_HANDLE;
- begin
- SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
- if SCManager = 0 then
- Exit;
- try
- Service := OpenService(SCManager,PChar(fServiceName),SERVICE_ALL_ACCESS);
- ControlService(Service,SERVICE_CONTROL_STOP,ServiceStatus);
- DeleteService(Service);
- CloseServiceHandle(Service);
- if fSilent then Writeln(Format(cRemoveMsg,[fServiceName]))
- else MessageBox(0,cRemoveMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
- finally
- CloseServiceHandle(SCManager);
- if Assigned(fAfterRemove) then fAfterRemove;
- end;
- end;
- procedure TAppService.Install;
- const
- cInstallMsg = 'Service "%s" installed successfully!';
- cSCMError = 'Error trying to open SC Manager (you need admin permissions)';
- var
- servicetype : Cardinal;
- starttype : Cardinal;
- svcloadgroup : PChar;
- svcdependencies : PChar;
- svcusername : PChar;
- svcuserpass : PChar;
- begin
- fSCMHandle := OpenSCManager(nil,nil,SC_MANAGER_ALL_ACCESS);
- if fSCMHandle = 0 then
- begin
- if fSilent then Writeln(cSCMError)
- else MessageBox(0,cSCMError,PChar(fServiceName),MB_ICONERROR or MB_OK or MB_TASKMODAL or MB_TOPMOST);
- Exit;
- end;
- //service interacts with desktop
- if fDesktopInteraction then servicetype := SERVICE_WIN32_OWN_PROCESS and SERVICE_INTERACTIVE_PROCESS
- else servicetype := SERVICE_WIN32_OWN_PROCESS;
- //service load order
- if fLoadOrderGroup.IsEmpty then svcloadgroup := nil
- else svcloadgroup := PChar(fLoadOrderGroup);
- //service dependencies
- if fDependencies.IsEmpty then svcdependencies := nil
- else svcdependencies := PChar(fDependencies);
- //service user name
- if fUserName.IsEmpty then svcusername := nil
- else svcusername := PChar(fUserName);
- //service user password
- if fUserPass.IsEmpty then svcuserpass := nil
- else svcuserpass := PChar(fUserPass);
- fSvHandle := CreateService(fSCMHandle,
- PChar(fServiceName),
- PChar(fDisplayName),
- SERVICE_ALL_ACCESS,
- servicetype,
- Cardinal(fStartType),
- SERVICE_ERROR_NORMAL,
- PChar(fFileName),
- svcloadgroup,
- nil,
- svcdependencies,
- svcusername, //user
- svcuserpass); //password
- if Length(fServiceDescription) > 0 then
- SetServiceDescription;
- if fSvHandle <> 0 then
- begin
- if fSilent then Writeln(Format(cInstallMsg,[fServiceName]))
- else MessageBox(0,cInstallMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
- end;
- end;
- procedure TAppService.Help;
- begin
- Writeln('HELP:');
- if fCanInstallWithOtherName then
- begin
- Writeln(Format('%s [/instance:<Service name>] [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
- WriteLn(' [/instance:<service name>]'+#9+'Install service with a custom name');
- end
- else Writeln(Format('%s [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
- WriteLn(' [/console]'+#9#9#9+'Force run as a console application (when run from another service)');
- WriteLn(' [/install]'+#9#9#9+'Install as a service');
- WriteLn(' [/remove]'+#9#9#9+'Remove service');
- WriteLn(' [/h /help]'+#9#9#9+'This help');
- end;
- procedure TAppService.CheckParams;
- var
- svcname : string;
- begin
- if ParamCount > 0 then
- begin
- if (ParamFindSwitch('h')) or (ParamFindSwitch('help')) then Self.Help
- else if ParamFindSwitch('install') then
- begin
- if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
- begin
- fServiceName := svcname;
- fDisplayName := svcname;
- end;
- Self.Install;
- end
- else if ParamFindSwitch('remove') then
- begin
- if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
- begin
- fServiceName := svcname;
- fDisplayName := svcname;
- end;
- Self.Remove;
- end
- else if ParamFindSwitch('console') then
- begin
- Writeln('Forced console mode');
- end
- else Writeln('Unknow parameter specified!');
- end
- else
- begin
- //initialize as a service
- if Assigned(fOnInitialize) then fOnInitialize;
- ServiceTable[0].lpServiceName := PChar(fServiceName);
- ServiceTable[0].lpServiceProc := @RegisterService;
- ServiceTable[1].lpServiceName := nil;
- ServiceTable[1].lpServiceProc := nil;
- {$IFDEF FPC}
- StartServiceCtrlDispatcher(@ServiceTable[0]);
- {$ELSE}
- StartServiceCtrlDispatcher(ServiceTable[0]);
- {$ENDIF}
- end;
- end;
- class function TAppService.ConsoleParamPresent : Boolean;
- begin
- Result := ParamFindSwitch('console');
- end;
- class function TAppService.InstallParamsPresent : Boolean;
- begin
- Result := (ParamFindSwitch('install') or ParamFindSwitch('remove') or ParamFindSwitch('help') or ParamFindSwitch('h'));
- end;
- class function TAppService.IsRunningAsService : Boolean;
- begin
- Result := (IsService and not ConsoleParamPresent) or InstallParamsPresent;
- end;
- class function TAppService.IsRunningAsConsole : Boolean;
- begin
- Result := (not IsService) or (ConsoleParamPresent);
- end;
- initialization
- AppService := TAppService.Create;
- finalization
- AppService.Free;
- end.
|