Quick.AppService.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. { ***************************************************************************
  2. Copyright (c) 2016-2017 Kike Pérez
  3. Unit : Quick.AppService
  4. Description : Allow run app as console or service
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 14/09/2017
  8. Modified : 05/10/2017
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.AppService;
  22. interface
  23. {$IF CompilerVersion > 20}
  24. {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
  25. {$WEAKLINKRTTI ON}
  26. {$ENDIF}
  27. uses
  28. Windows,
  29. System.SysUtils,
  30. WinSvc,
  31. Quick.Commons;
  32. const
  33. DEF_SERVICENAME = 'QuickAppService';
  34. DEF_DISPLAYNAME = 'QuickAppService';
  35. NUM_OF_SERVICES = 2;
  36. type
  37. TSvcStatus = (ssStopped = SERVICE_STOPPED,
  38. ssStopping = SERVICE_STOP_PENDING,
  39. ssStartPending = SERVICE_START_PENDING,
  40. ssRunning = SERVICE_RUNNING,
  41. ssPaused = SERVICE_PAUSED);
  42. TSvcInitializeEvent = procedure of object;
  43. TSvcAnonMethod = reference to procedure;
  44. TSvcRemoveEvent = procedure of object;
  45. TAppService = class
  46. private
  47. fSCMHandle : SC_HANDLE;
  48. fSvHandle : SC_HANDLE;
  49. fServiceName : string;
  50. fDisplayName : string;
  51. fFileName : string;
  52. fSilent : Boolean;
  53. fStatus : TSvcStatus;
  54. fCanInstallWithOtherName : Boolean;
  55. fOnInitialize : TSvcInitializeEvent;
  56. fOnStart : TSvcAnonMethod;
  57. fOnStop : TSvcAnonMethod;
  58. fOnExecute : TSvcAnonMethod;
  59. fAfterRemove : TSvcRemoveEvent;
  60. procedure ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
  61. procedure Execute;
  62. procedure Help;
  63. procedure DoStop;
  64. public
  65. constructor Create;
  66. destructor Destroy; override;
  67. property ServiceName : string read fServiceName write fServiceName;
  68. property DisplayName : string read fDisplayName write fDisplayName;
  69. property FileName : string read fFileName write fFileName;
  70. property Silent : Boolean read fSilent write fSilent;
  71. property CanInstallWithOtherName : Boolean read fCanInstallWithOtherName write fCanInstallWithOtherName;
  72. property Status : TSvcStatus read fStatus write fStatus;
  73. property OnStart : TSvcAnonMethod read fOnStart write fOnStart;
  74. property OnStop : TSvcAnonMethod read fOnStop write fOnStop;
  75. property OnExecute : TSvcAnonMethod read fOnExecute write fOnExecute;
  76. property OnInitialize : TSvcInitializeEvent read fOnInitialize write fOnInitialize;
  77. property AfterRemove : TSvcRemoveEvent read fAfterRemove write fAfterRemove;
  78. procedure Install;
  79. procedure Remove;
  80. procedure CheckParams;
  81. class function InstallParamsPresent : Boolean;
  82. class function ConsoleParamPresent : Boolean;
  83. class function IsRunningAsService : Boolean;
  84. class function IsRunningAsConsole : Boolean;
  85. end;
  86. var
  87. ServiceStatus : TServiceStatus;
  88. StatusHandle : SERVICE_STATUS_HANDLE;
  89. ServiceTable : array [0..NUM_OF_SERVICES] of TServiceTableEntry;
  90. ghSvcStopEvent: Cardinal;
  91. AppService : TAppService;
  92. implementation
  93. procedure ServiceCtrlHandler(Control: DWORD); stdcall;
  94. begin
  95. case Control of
  96. SERVICE_CONTROL_STOP:
  97. begin
  98. AppService.Status := TSvcStatus.ssStopping;
  99. SetEvent(ghSvcStopEvent);
  100. ServiceStatus.dwCurrentState := SERVICE_STOP_PENDING;
  101. SetServiceStatus(StatusHandle, ServiceStatus);
  102. end;
  103. SERVICE_CONTROL_PAUSE:
  104. begin
  105. AppService.Status := TSvcStatus.ssPaused;
  106. ServiceStatus.dwcurrentstate := SERVICE_PAUSED;
  107. SetServiceStatus(StatusHandle, ServiceStatus);
  108. end;
  109. SERVICE_CONTROL_CONTINUE:
  110. begin
  111. AppService.Status := TSvcStatus.ssRunning;
  112. ServiceStatus.dwCurrentState := SERVICE_RUNNING;
  113. SetServiceStatus(StatusHandle, ServiceStatus);
  114. end;
  115. SERVICE_CONTROL_INTERROGATE: SetServiceStatus(StatusHandle, ServiceStatus);
  116. SERVICE_CONTROL_SHUTDOWN:
  117. begin
  118. AppService.Status := TSvcStatus.ssStopped;
  119. AppService.DoStop;
  120. end;
  121. end;
  122. end;
  123. procedure RegisterService(dwArgc: DWORD; var lpszArgv: PChar); stdcall;
  124. begin
  125. ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
  126. ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
  127. ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_PAUSE_CONTINUE;
  128. ServiceStatus.dwServiceSpecificExitCode := 0;
  129. ServiceStatus.dwWin32ExitCode := 0;
  130. ServiceStatus.dwCheckPoint := 0;
  131. ServiceStatus.dwWaitHint := 0;
  132. StatusHandle := RegisterServiceCtrlHandler(PChar(AppService.ServiceName), @ServiceCtrlHandler);
  133. if StatusHandle <> 0 then
  134. begin
  135. AppService.ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
  136. try
  137. AppService.Status := TSvcStatus.ssRunning;
  138. AppService.Execute;
  139. finally
  140. AppService.ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
  141. end;
  142. end;
  143. end;
  144. constructor TAppService.Create;
  145. begin
  146. inherited;
  147. fServiceName := DEF_SERVICENAME;
  148. fDisplayName := DEF_DISPLAYNAME;
  149. fFileName := ParamStr(0);
  150. fSilent := True;
  151. fStatus := TSvcStatus.ssStopped;
  152. fCanInstallWithOtherName := False;
  153. fOnExecute := nil;
  154. end;
  155. destructor TAppService.Destroy;
  156. begin
  157. fOnStart := nil;
  158. fOnStop := nil;
  159. fOnExecute := nil;
  160. if fSCMHandle <> 0 then CloseServiceHandle(fSCMHandle);
  161. if fSvHandle <> 0 then CloseServiceHandle(fSvHandle);
  162. inherited;
  163. end;
  164. procedure TAppService.ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
  165. begin
  166. //fill in the SERVICE_STATUS structure
  167. ServiceStatus.dwCurrentState := dwCurrentState;
  168. ServiceStatus.dwWin32ExitCode := dwWin32ExitCode;
  169. ServiceStatus.dwWaitHint := dwWaitHint;
  170. if dwCurrentState = SERVICE_START_PENDING then ServiceStatus.dwControlsAccepted := 0
  171. else ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP;
  172. case (dwCurrentState = SERVICE_RUNNING) or (dwCurrentState = SERVICE_STOPPED) of
  173. True: ServiceStatus.dwCheckPoint := 0;
  174. False: ServiceStatus.dwCheckPoint := 1;
  175. end;
  176. //report service status to SCM
  177. SetServiceStatus(StatusHandle,ServiceStatus);
  178. end;
  179. procedure TAppService.Execute;
  180. begin
  181. //we have to do something or service will stop
  182. ghSvcStopEvent := CreateEvent(nil,True,False,nil);
  183. if ghSvcStopEvent = 0 then
  184. begin
  185. ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
  186. Exit;
  187. end;
  188. if Assigned(fOnStart) then fOnStart;
  189. //report running status when initialization is complete
  190. ReportSvcStatus(SERVICE_RUNNING,NO_ERROR,0);
  191. //perform work until service stops
  192. while True do
  193. begin
  194. //external callback process
  195. if Assigned(fOnExecute) then fOnExecute;
  196. //check whether to stop the service.
  197. WaitForSingleObject(ghSvcStopEvent,INFINITE);
  198. ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
  199. Exit;
  200. end;
  201. end;
  202. procedure TAppService.DoStop;
  203. begin
  204. if Assigned(fOnStop) then fOnStop;
  205. end;
  206. procedure TAppService.Remove;
  207. const
  208. cRemoveMsg = 'Service "%s" removed successfully!';
  209. var
  210. SCManager: SC_HANDLE;
  211. Service: SC_HANDLE;
  212. begin
  213. SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  214. if SCManager = 0 then
  215. Exit;
  216. try
  217. Service := OpenService(SCManager,PChar(fServiceName),SERVICE_ALL_ACCESS);
  218. ControlService(Service,SERVICE_CONTROL_STOP,ServiceStatus);
  219. DeleteService(Service);
  220. CloseServiceHandle(Service);
  221. if fSilent then Writeln(Format(cRemoveMsg,[fServiceName]))
  222. else MessageBox(0,cRemoveMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  223. finally
  224. CloseServiceHandle(SCManager);
  225. if Assigned(fAfterRemove) then fAfterRemove;
  226. end;
  227. end;
  228. procedure TAppService.Install;
  229. const
  230. cInstallMsg = 'Service "%s" installed successfully!';
  231. cSCMError = 'Error trying to open SC Manager (you need admin permissions)';
  232. begin
  233. fSCMHandle := OpenSCManager(nil,nil,SC_MANAGER_ALL_ACCESS);
  234. if fSCMHandle = 0 then
  235. begin
  236. if fSilent then Writeln(cSCMError)
  237. else MessageBox(0,cSCMError,PChar(fServiceName),MB_ICONERROR or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  238. Exit;
  239. end;
  240. fSvHandle := CreateService(fSCMHandle,
  241. PChar(fServiceName),
  242. PChar(fDisplayName),
  243. SERVICE_ALL_ACCESS,
  244. SERVICE_WIN32_OWN_PROCESS,
  245. SERVICE_AUTO_START,
  246. SERVICE_ERROR_IGNORE,
  247. PChar(fFileName),
  248. 'System Reserved',
  249. nil,
  250. nil,
  251. nil, //user
  252. nil); //password
  253. if fSvHandle <> 0 then
  254. begin
  255. if fSilent then Writeln(Format(cInstallMsg,[fServiceName]))
  256. else MessageBox(0,cInstallMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  257. end;
  258. end;
  259. procedure TAppService.Help;
  260. begin
  261. Writeln('HELP:');
  262. if fCanInstallWithOtherName then
  263. begin
  264. Writeln(Format('%s [/instance:<Service name>] [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
  265. WriteLn(' [/instance:<service name>]'+#9+'Install service with a custom name');
  266. end
  267. else Writeln(Format('%s [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
  268. WriteLn(' [/console]'+#9#9#9+'Run as a console application');
  269. WriteLn(' [/install]'+#9#9#9+'Install as a service');
  270. WriteLn(' [/remove]'+#9#9#9+'Remove service');
  271. WriteLn(' [/h /help]'+#9#9#9+'This help');
  272. end;
  273. procedure TAppService.CheckParams;
  274. var
  275. svcname : string;
  276. begin
  277. if ParamCount > 0 then
  278. begin
  279. if (ParamFindSwitch('h')) or (ParamFindSwitch('help')) then Self.Help
  280. else if ParamFindSwitch('install') then
  281. begin
  282. if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
  283. begin
  284. fServiceName := svcname;
  285. fDisplayName := svcname;
  286. end;
  287. Self.Install;
  288. end
  289. else if ParamFindSwitch('remove') then
  290. begin
  291. if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
  292. begin
  293. fServiceName := svcname;
  294. fDisplayName := svcname;
  295. end;
  296. Self.Remove;
  297. end
  298. else Writeln('Unknow parameter specified!');
  299. end
  300. else
  301. begin
  302. //initialize as a service
  303. if Assigned(fOnInitialize) then fOnInitialize;
  304. ServiceTable[0].lpServiceName := PChar(fServiceName);
  305. ServiceTable[0].lpServiceProc := @RegisterService;
  306. ServiceTable[1].lpServiceName := nil;
  307. ServiceTable[1].lpServiceProc := nil;
  308. StartServiceCtrlDispatcher(ServiceTable[0]);
  309. end;
  310. end;
  311. class function TAppService.ConsoleParamPresent : Boolean;
  312. begin
  313. Result := ParamFindSwitch('console');
  314. end;
  315. class function TAppService.InstallParamsPresent : Boolean;
  316. begin
  317. Result := (ParamFindSwitch('install') or ParamFindSwitch('remove') or ParamFindSwitch('help') or ParamFindSwitch('h'));
  318. end;
  319. class function TAppService.IsRunningAsService : Boolean;
  320. begin
  321. Result := (IsService and not ConsoleParamPresent) or InstallParamsPresent;
  322. end;
  323. class function TAppService.IsRunningAsConsole : Boolean;
  324. begin
  325. Result := (not IsService) or (ConsoleParamPresent);
  326. end;
  327. initialization
  328. AppService := TAppService.Create;
  329. finalization
  330. AppService.Free;
  331. end.