Quick.AppService.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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 : 01/12/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. {$i QuickLib.inc}
  23. interface
  24. {$IFNDEF FPC}
  25. {$IFDEF DELPHI2010_UP}
  26. {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
  27. {$WEAKLINKRTTI ON}
  28. {$ENDIF}
  29. {$ENDIF}
  30. uses
  31. {$IFDEF MSWINDOWS}
  32. Windows,
  33. {$ENDIF}
  34. SysUtils,
  35. {$IFNDEF FPC}
  36. WinSvc,
  37. {$ENDIF}
  38. Quick.Commons;
  39. const
  40. DEF_SERVICENAME = 'QuickAppService';
  41. DEF_DISPLAYNAME = 'QuickAppService';
  42. NUM_OF_SERVICES = 2;
  43. type
  44. TSvcStatus = (ssStopped = SERVICE_STOPPED,
  45. ssStopping = SERVICE_STOP_PENDING,
  46. ssStartPending = SERVICE_START_PENDING,
  47. ssRunning = SERVICE_RUNNING,
  48. ssPaused = SERVICE_PAUSED);
  49. TSvcStartType = (stAuto = SERVICE_AUTO_START,
  50. stManual = SERVICE_DEMAND_START,
  51. stDisabled = SERVICE_DISABLED);
  52. TSvcInitializeEvent = procedure of object;
  53. {$IFDEF FPC}
  54. TSvcAnonMethod = procedure of object;
  55. {$ELSE}
  56. TSvcAnonMethod = reference to procedure;
  57. {$ENDIF}
  58. TSvcRemoveEvent = procedure of object;
  59. TAppService = class
  60. private
  61. fSCMHandle : SC_HANDLE;
  62. fSvHandle : SC_HANDLE;
  63. fServiceName : string;
  64. fDisplayName : string;
  65. fLoadOrderGroup : string;
  66. fDependencies : string;
  67. fDesktopInteraction : Boolean;
  68. fUsername : string;
  69. fUserPass : string;
  70. fStartType : TSvcStartType;
  71. fFileName : string;
  72. fSilent : Boolean;
  73. fStatus : TSvcStatus;
  74. fCanInstallWithOtherName : Boolean;
  75. fOnInitialize : TSvcInitializeEvent;
  76. fOnStart : TSvcAnonMethod;
  77. fOnStop : TSvcAnonMethod;
  78. fOnExecute : TSvcAnonMethod;
  79. fAfterRemove : TSvcRemoveEvent;
  80. procedure ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
  81. procedure Execute;
  82. procedure Help;
  83. procedure DoStop;
  84. public
  85. constructor Create;
  86. destructor Destroy; override;
  87. property ServiceName : string read fServiceName write fServiceName;
  88. property DisplayName : string read fDisplayName write fDisplayName;
  89. property LoadOrderGroup : string read fLoadOrderGroup write fLoadOrderGroup;
  90. property Dependencies : string read fDependencies write fDependencies;
  91. property DesktopInteraction : Boolean read fDesktopInteraction write fDesktopInteraction;
  92. property UserName : string read fUserName write fUserName;
  93. property UserPass : string read fUserPass write fUserPass;
  94. property StartType : TSvcStartType read fStartType write fStartType;
  95. property FileName : string read fFileName write fFileName;
  96. property Silent : Boolean read fSilent write fSilent;
  97. property CanInstallWithOtherName : Boolean read fCanInstallWithOtherName write fCanInstallWithOtherName;
  98. property Status : TSvcStatus read fStatus write fStatus;
  99. property OnStart : TSvcAnonMethod read fOnStart write fOnStart;
  100. property OnStop : TSvcAnonMethod read fOnStop write fOnStop;
  101. property OnExecute : TSvcAnonMethod read fOnExecute write fOnExecute;
  102. property OnInitialize : TSvcInitializeEvent read fOnInitialize write fOnInitialize;
  103. property AfterRemove : TSvcRemoveEvent read fAfterRemove write fAfterRemove;
  104. procedure Install;
  105. procedure Remove;
  106. procedure CheckParams;
  107. class function InstallParamsPresent : Boolean;
  108. class function ConsoleParamPresent : Boolean;
  109. class function IsRunningAsService : Boolean;
  110. class function IsRunningAsConsole : Boolean;
  111. end;
  112. var
  113. ServiceStatus : TServiceStatus;
  114. StatusHandle : SERVICE_STATUS_HANDLE;
  115. ServiceTable : array [0..NUM_OF_SERVICES] of TServiceTableEntry;
  116. ghSvcStopEvent: Cardinal;
  117. AppService : TAppService;
  118. implementation
  119. procedure ServiceCtrlHandler(Control: DWORD); stdcall;
  120. begin
  121. case Control of
  122. SERVICE_CONTROL_STOP:
  123. begin
  124. AppService.Status := TSvcStatus.ssStopping;
  125. SetEvent(ghSvcStopEvent);
  126. ServiceStatus.dwCurrentState := SERVICE_STOP_PENDING;
  127. SetServiceStatus(StatusHandle, ServiceStatus);
  128. end;
  129. SERVICE_CONTROL_PAUSE:
  130. begin
  131. AppService.Status := TSvcStatus.ssPaused;
  132. ServiceStatus.dwcurrentstate := SERVICE_PAUSED;
  133. SetServiceStatus(StatusHandle, ServiceStatus);
  134. end;
  135. SERVICE_CONTROL_CONTINUE:
  136. begin
  137. AppService.Status := TSvcStatus.ssRunning;
  138. ServiceStatus.dwCurrentState := SERVICE_RUNNING;
  139. SetServiceStatus(StatusHandle, ServiceStatus);
  140. end;
  141. SERVICE_CONTROL_INTERROGATE: SetServiceStatus(StatusHandle, ServiceStatus);
  142. SERVICE_CONTROL_SHUTDOWN:
  143. begin
  144. AppService.Status := TSvcStatus.ssStopped;
  145. AppService.DoStop;
  146. end;
  147. end;
  148. end;
  149. procedure RegisterService(dwArgc: DWORD; var lpszArgv: PChar); stdcall;
  150. begin
  151. ServiceStatus.dwServiceType := SERVICE_WIN32_OWN_PROCESS;
  152. ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
  153. ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP or SERVICE_ACCEPT_PAUSE_CONTINUE;
  154. ServiceStatus.dwServiceSpecificExitCode := 0;
  155. ServiceStatus.dwWin32ExitCode := 0;
  156. ServiceStatus.dwCheckPoint := 0;
  157. ServiceStatus.dwWaitHint := 0;
  158. StatusHandle := RegisterServiceCtrlHandler(PChar(AppService.ServiceName), @ServiceCtrlHandler);
  159. if StatusHandle <> 0 then
  160. begin
  161. AppService.ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
  162. try
  163. AppService.Status := TSvcStatus.ssRunning;
  164. AppService.Execute;
  165. finally
  166. AppService.ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
  167. end;
  168. end;
  169. end;
  170. constructor TAppService.Create;
  171. begin
  172. inherited;
  173. fServiceName := DEF_SERVICENAME;
  174. fDisplayName := DEF_DISPLAYNAME;
  175. fLoadOrderGroup := '';
  176. fDependencies := '';
  177. fDesktopInteraction := False;
  178. fUserName := '';
  179. fUserPass := '';
  180. fStartType := TSvcStartType.stAuto;
  181. fFileName := ParamStr(0);
  182. fSilent := True;
  183. fStatus := TSvcStatus.ssStopped;
  184. fCanInstallWithOtherName := False;
  185. fOnExecute := nil;
  186. IsQuickServiceApp := True;
  187. end;
  188. destructor TAppService.Destroy;
  189. begin
  190. fOnStart := nil;
  191. fOnStop := nil;
  192. fOnExecute := nil;
  193. if fSCMHandle <> 0 then CloseServiceHandle(fSCMHandle);
  194. if fSvHandle <> 0 then CloseServiceHandle(fSvHandle);
  195. inherited;
  196. end;
  197. procedure TAppService.ReportSvcStatus(dwCurrentState, dwWin32ExitCode, dwWaitHint: DWORD);
  198. begin
  199. //fill in the SERVICE_STATUS structure
  200. ServiceStatus.dwCurrentState := dwCurrentState;
  201. ServiceStatus.dwWin32ExitCode := dwWin32ExitCode;
  202. ServiceStatus.dwWaitHint := dwWaitHint;
  203. if dwCurrentState = SERVICE_START_PENDING then ServiceStatus.dwControlsAccepted := 0
  204. else ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP;
  205. case (dwCurrentState = SERVICE_RUNNING) or (dwCurrentState = SERVICE_STOPPED) of
  206. True: ServiceStatus.dwCheckPoint := 0;
  207. False: ServiceStatus.dwCheckPoint := 1;
  208. end;
  209. //report service status to SCM
  210. SetServiceStatus(StatusHandle,ServiceStatus);
  211. end;
  212. procedure TAppService.Execute;
  213. begin
  214. //we have to do something or service will stop
  215. ghSvcStopEvent := CreateEvent(nil,True,False,nil);
  216. if ghSvcStopEvent = 0 then
  217. begin
  218. ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
  219. Exit;
  220. end;
  221. if Assigned(fOnStart) then fOnStart;
  222. //report running status when initialization is complete
  223. ReportSvcStatus(SERVICE_RUNNING,NO_ERROR,0);
  224. //perform work until service stops
  225. while True do
  226. begin
  227. //external callback process
  228. if Assigned(fOnExecute) then fOnExecute;
  229. //check whether to stop the service.
  230. WaitForSingleObject(ghSvcStopEvent,INFINITE);
  231. ReportSvcStatus(SERVICE_STOPPED,NO_ERROR,0);
  232. Exit;
  233. end;
  234. end;
  235. procedure TAppService.DoStop;
  236. begin
  237. if Assigned(fOnStop) then fOnStop;
  238. end;
  239. procedure TAppService.Remove;
  240. const
  241. cRemoveMsg = 'Service "%s" removed successfully!';
  242. var
  243. SCManager: SC_HANDLE;
  244. Service: SC_HANDLE;
  245. begin
  246. SCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  247. if SCManager = 0 then
  248. Exit;
  249. try
  250. Service := OpenService(SCManager,PChar(fServiceName),SERVICE_ALL_ACCESS);
  251. ControlService(Service,SERVICE_CONTROL_STOP,ServiceStatus);
  252. DeleteService(Service);
  253. CloseServiceHandle(Service);
  254. if fSilent then Writeln(Format(cRemoveMsg,[fServiceName]))
  255. else MessageBox(0,cRemoveMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  256. finally
  257. CloseServiceHandle(SCManager);
  258. if Assigned(fAfterRemove) then fAfterRemove;
  259. end;
  260. end;
  261. procedure TAppService.Install;
  262. const
  263. cInstallMsg = 'Service "%s" installed successfully!';
  264. cSCMError = 'Error trying to open SC Manager (you need admin permissions)';
  265. var
  266. servicetype : Cardinal;
  267. starttype : Cardinal;
  268. svcloadgroup : PChar;
  269. svcdependencies : PChar;
  270. svcusername : PChar;
  271. svcuserpass : PChar;
  272. begin
  273. fSCMHandle := OpenSCManager(nil,nil,SC_MANAGER_ALL_ACCESS);
  274. if fSCMHandle = 0 then
  275. begin
  276. if fSilent then Writeln(cSCMError)
  277. else MessageBox(0,cSCMError,PChar(fServiceName),MB_ICONERROR or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  278. Exit;
  279. end;
  280. //service interacts with desktop
  281. if fDesktopInteraction then servicetype := SERVICE_WIN32_OWN_PROCESS and SERVICE_INTERACTIVE_PROCESS
  282. else servicetype := SERVICE_WIN32_OWN_PROCESS;
  283. //service load order
  284. if fLoadOrderGroup.IsEmpty then svcloadgroup := nil
  285. else svcloadgroup := PChar(fLoadOrderGroup);
  286. //service dependencies
  287. if fDependencies.IsEmpty then svcdependencies := nil
  288. else svcdependencies := PChar(fDependencies);
  289. //service user name
  290. if fUserName.IsEmpty then svcusername := nil
  291. else svcusername := PChar(fUserName);
  292. //service user password
  293. if fUserPass.IsEmpty then svcuserpass := nil
  294. else svcuserpass := PChar(fUserPass);
  295. fSvHandle := CreateService(fSCMHandle,
  296. PChar(fServiceName),
  297. PChar(fDisplayName),
  298. SERVICE_ALL_ACCESS,
  299. servicetype,
  300. Cardinal(fStartType),
  301. SERVICE_ERROR_NORMAL,
  302. PChar(fFileName),
  303. svcloadgroup,
  304. nil,
  305. svcdependencies,
  306. svcusername, //user
  307. svcuserpass); //password
  308. if fSvHandle <> 0 then
  309. begin
  310. if fSilent then Writeln(Format(cInstallMsg,[fServiceName]))
  311. else MessageBox(0,cInstallMsg,PChar(fServiceName),MB_ICONINFORMATION or MB_OK or MB_TASKMODAL or MB_TOPMOST);
  312. end;
  313. end;
  314. procedure TAppService.Help;
  315. begin
  316. Writeln('HELP:');
  317. if fCanInstallWithOtherName then
  318. begin
  319. Writeln(Format('%s [/instance:<Service name>] [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
  320. WriteLn(' [/instance:<service name>]'+#9+'Install service with a custom name');
  321. end
  322. else Writeln(Format('%s [/console] [/install] [/remove] [/h] [/help]',[ExtractFileName(ParamStr(0))]));
  323. WriteLn(' [/console]'+#9#9#9+'Force run as a console application (when runned from another service)');
  324. WriteLn(' [/install]'+#9#9#9+'Install as a service');
  325. WriteLn(' [/remove]'+#9#9#9+'Remove service');
  326. WriteLn(' [/h /help]'+#9#9#9+'This help');
  327. end;
  328. procedure TAppService.CheckParams;
  329. var
  330. svcname : string;
  331. begin
  332. if ParamCount > 0 then
  333. begin
  334. if (ParamFindSwitch('h')) or (ParamFindSwitch('help')) then Self.Help
  335. else if ParamFindSwitch('install') then
  336. begin
  337. if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
  338. begin
  339. fServiceName := svcname;
  340. fDisplayName := svcname;
  341. end;
  342. Self.Install;
  343. end
  344. else if ParamFindSwitch('remove') then
  345. begin
  346. if (fCanInstallWithOtherName) and (ParamGetSwitch('instance',svcname)) then
  347. begin
  348. fServiceName := svcname;
  349. fDisplayName := svcname;
  350. end;
  351. Self.Remove;
  352. end
  353. else if ParamFindSwitch('console') then
  354. begin
  355. Writeln('Forced console mode');
  356. end
  357. else Writeln('Unknow parameter specified!');
  358. end
  359. else
  360. begin
  361. //initialize as a service
  362. if Assigned(fOnInitialize) then fOnInitialize;
  363. ServiceTable[0].lpServiceName := PChar(fServiceName);
  364. ServiceTable[0].lpServiceProc := @RegisterService;
  365. ServiceTable[1].lpServiceName := nil;
  366. ServiceTable[1].lpServiceProc := nil;
  367. {$IFDEF FPC}
  368. StartServiceCtrlDispatcher(@ServiceTable[0]);
  369. {$ELSE}
  370. StartServiceCtrlDispatcher(ServiceTable[0]);
  371. {$ENDIF}
  372. end;
  373. end;
  374. class function TAppService.ConsoleParamPresent : Boolean;
  375. begin
  376. Result := ParamFindSwitch('console');
  377. end;
  378. class function TAppService.InstallParamsPresent : Boolean;
  379. begin
  380. Result := (ParamFindSwitch('install') or ParamFindSwitch('remove') or ParamFindSwitch('help') or ParamFindSwitch('h'));
  381. end;
  382. class function TAppService.IsRunningAsService : Boolean;
  383. begin
  384. Result := (IsService and not ConsoleParamPresent) or InstallParamsPresent;
  385. end;
  386. class function TAppService.IsRunningAsConsole : Boolean;
  387. begin
  388. Result := (not IsService) or (ConsoleParamPresent);
  389. end;
  390. initialization
  391. AppService := TAppService.Create;
  392. finalization
  393. AppService.Free;
  394. end.