Quick.AppService.pas 12 KB

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