-
Notifications
You must be signed in to change notification settings - Fork 28
/
Services.pas
421 lines (389 loc) · 13.1 KB
/
Services.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
{
Slim Services and EventLog support for Windows
Author: Wanderlan Santos dos Anjos, wanderlan.anjos@gmail.com
Date: jun-2008
License: <extlink http://www.opensource.org/licenses/bsd-license.php>BSD</extlink>
}
unit Services;
interface
uses
{$IFDEF FPC}Windows{$ELSE}WinSvc{$ENDIF}, Classes;
{$R *.res}
type
TFuncBool = function : boolean; // Procedural type to stop and start functions, used in <link TService.Run, Run> method.
TEventType = (EventError = 1, EventWarning = 2, EventInformation = 4); // Event severity
// Implements Windows Service with <link TService.ReportEventLog, report EventLog> support
TService = class
private
FName : pchar;
FDescription,
FParamStr : string;
FManager,
FService : SC_Handle;
FTimeout,
FExitCode,
FParamCount : integer;
FSource : THandle;
FStatus : TServiceStatus;
FStopEvent : THandle;
FReportStartStop : boolean;
FStatusHandle : Service_Status_Handle;
FServiceThreads : array[0..10] of TThread;
FMaxThreads : integer;
FServiceBegin,
FServiceEnd : TFuncBool;
procedure StopNow;
function GetName : string;
function ReportNoError(Estado : integer) : boolean;
function ReportServiceStatus(CurrentState, Win32ExitCode, CheckPoint, WaitHint: integer): boolean;
public
constructor Create(ServiceName : string; Description : string = '');
destructor Destroy; override;
function GetServiceError: integer;
function GetServiceErrorMessage: string;
function GetState : cardinal;
function Install : boolean;
function Uninstall : boolean;
procedure Insert(Exec: string);
procedure Delete;
function Run(ServThreads : array of TThread; ServBegin : TFuncBool = nil; ServEnd : TFuncBool = nil) : boolean;
function Exists : boolean;
function Stop : integer;
function Start : integer;
function ReportStart : boolean;
function ReportStop : boolean;
procedure ReportEventLog(EventType : TEventType; EventCode : word; Message : string);
procedure Reset;
property Timeout : integer read FTimeout write FTimeout; // Time before to generate an error. Default 20000 milliseconds
property ExitCode : integer read FExitCode write FExitCode; // Exit code to return to Service Manager
property Name : string read GetName; // Service Name
property ParamStr : string read FParamStr; // Parameter list passed when the service was started
property ParamCount : integer read FParamCount; // Number of parameters passed when the service was started
end;
var
Service : TService; // Global var, use it to initialize a service
implementation
uses
{$IFNDEF FPC}Windows,{$ENDIF} SysUtils, Registry;
function TService.GetName : string; begin
Result := string(FName);
end;
// Closes service handle
procedure TService.Reset; begin
CloseServiceHandle(FService);
FService := 0;
end;
// Returns if service is initialized
function TService.Exists : boolean; begin
Result := FService <> 0;
end;
{
Installs a service.
@param Exec Executable file with path
@exception RaiseLastOSError if not succeded
@see Install
@see Delete
}
procedure TService.Insert(Exec : string); begin
FService := CreateService(FManager, FName, FName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL, pchar(Exec), nil, nil, nil, nil, nil);
if not Exists then RaiseLastOSError;
with TRegistry.Create do begin
Access := KEY_ALL_ACCESS;
RootKey := HKey_Local_Machine;
OpenKey('\SYSTEM\CurrentControlSet\Services\EventLog\Application\' + FName, true);
WriteString('EventMessageFile', Exec);
OpenKey('\SYSTEM\CurrentControlSet\Services\' + FName, true);
WriteString('Description', FDescription);
Free;
end;
end;
{
Installs a service using command line. In command line use \<application\> -INSTALL to install a service
@return True if succeded else False
@see Insert
@see Uninstall
}
function TService.Install : boolean; begin
if FindCmdLineSwitch('INSTALL', ['-', '/'], true) and (FService = 0) then begin
Insert(system.ParamStr(0));
Result := true;
end
else
Result := false
end;
{
Uninstalls a service.
@exception RaiseLastOSError if not succeded
@see Insert
@see Uninstall
}
procedure TService.Delete; begin
if not Exists then RaiseLastOSError;
if not DeleteService(FService) then RaiseLastOSError;
with TRegistry.Create do begin
Access := KEY_ALL_ACCESS;
RootKey := HKey_Local_Machine;
DeleteKey('\SYSTEM\CurrentControlSet\Services\EventLog\Application\' + FName);
Free;
end;
end;
{
Uninstalls a service using command line. In command line use \<application\> -UNINSTALL to uninstall a service
@return True if succeded else False
@see Delete
@see Install
}
function TService.Uninstall : boolean; begin
if FindCmdLineSwitch('UNINSTALL', ['-', '/'], true) then begin
Delete;
Result := true;
end
else
Result := false
end;
// Returns last error code
function TService.GetServiceError : integer; begin
Result := GetLastError;
if Result = 0 then Result := -1
end;
// Returns last error message
function TService.GetServiceErrorMessage : string; begin
Result := SysErrorMessage(GetServiceError)
end;
{
Stops the service
@return 0 if succeeded else last error code
}
function TService.Stop : integer; begin
Result := 0;
if Exists then begin
if not ControlService(FService, SERVICE_CONTROL_STOP, FStatus) then Result := GetLastError;
end
else
Result := GetServiceError;
end;
{
Starts the service
@return 0 if succeeded else last error code
}
function TService.Start : integer;
const
Param : pansichar = nil;
begin
Result := 0;
if FService = 0 then FService := OpenService(FManager, FName, SERVICE_ALL_ACCESS);
if Exists then begin
if not StartServiceA(FService, 0, Param) then Result := GetServiceError;
end
else
Result := GetServiceError;
end;
function TService.GetState : cardinal; begin
if QueryServiceStatus(FService, FStatus) then
Result := FStatus.dwCurrentState
else
Result := 77;
end;
{
Writes an event log.
@param EventType
@param EventCode User code
@param Message User message
}
procedure TService.ReportEventLog(EventType : TEventType; EventCode : word; Message : string);
var
Mensagem : pchar;
begin
Mensagem := pchar(Message);
ReportEvent(FSource, word(EventType), 1000 + EventCode, 0, nil, 1, 0, @Mensagem, nil);
end;
// StopNow can be used within the service to stop the service
procedure TService.StopNow; begin
SetLastError(0);
SetEvent(FStopEvent)
end;
function TService.ReportServiceStatus(CurrentState, Win32ExitCode, CheckPoint, WaitHint : integer) : boolean; begin
SetLastError(0);
with FStatus do begin
dwServiceType := SERVICE_WIN32_OWN_PROCESS;
dwServiceSpecificExitCode := 0;
// Desabilita requisições até o serviço estar startado
if CurrentState = SERVICE_START_PENDING then
dwControlsAccepted := 0
else
dwControlsAccepted := SERVICE_ACCEPT_STOP + SERVICE_ACCEPT_PAUSE_CONTINUE;
dwCurrentState := CurrentState;
dwCheckPoint := CheckPoint;
dwWaitHint := WaitHint;
if ExitCode = 0 then
dwWin32ExitCode := Win32ExitCode
else begin
dwWin32ExitCode := ERROR_SERVICE_SPECIFIC_ERROR;
dwServiceSpecificExitCode := ExitCode;
end;
// Manda o status do service para o service manager.
Result := SetServiceStatus(FStatusHandle, FStatus);
if not Result then StopNow;
end;
end;
function TService.ReportNoError(Estado : integer) : boolean; begin
Result := ReportServiceStatus(Estado, NO_ERROR, 0, 0)
end;
// Reports that the service is in start pending status. Use it when to initialize a service.
function TService.ReportStart : boolean;
const
ChkPoint : integer = 0;
begin
Result := false;
if FReportStartStop and Exists then begin
inc(ChkPoint);
Result := ReportServiceStatus(SERVICE_START_PENDING, NO_ERROR, ChkPoint, Timeout);
end;
end;
// Reports that the service is in stop pending status. Use it when to stop a service
function TService.ReportStop : boolean;
const
ChkPoint : integer = 0;
begin
Result := false;
if FReportStartStop and Exists then begin
inc(ChkPoint);
Result := ReportServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, ChkPoint, Timeout);
end;
end;
// Is called by Windows Server Manager
procedure ServController(Command : integer); stdcall;
var
I : integer;
begin
with Service do
case Command of
SERVICE_CONTROL_PAUSE: if FStatus.dwCurrentState = SERVICE_RUNNING then begin
for I := 0 to FMaxThreads do
FServiceThreads[I].Suspend;
ReportNoError(SERVICE_PAUSED);
end;
SERVICE_CONTROL_CONTINUE: if FStatus.dwCurrentState = SERVICE_PAUSED then begin
for I := 0 to FMaxThreads do
FServiceThreads[I].Resume;
ReportNoError(SERVICE_RUNNING);
end;
SERVICE_CONTROL_STOP: begin
FReportStartStop := true;
ReportStop;
// Request all threads to terminate
for I := 0 to FMaxThreads do
FServiceThreads[I].Terminate;
// Wait to termination and free them
for I := 0 to FMaxThreads do
with FServiceThreads[I] do begin
WaitFor;
Free;
end;
ReportStop;
StopNow;
end;
else
ReportNoError(SERVICE_RUNNING);
end;
end;
{
Starts the service, telling Service Manager each step of the process,
then resumes the service threads, waits the stop event and back to
StartServiceCtrlDispatcher in RunService
}
procedure ServiceMain(ArgC : integer; ArgV : pchar); stdcall;
var
I : integer;
InitOk : boolean;
begin
with Service do begin
FParamCount := ArgC;
FParamStr := strpas(ArgV);
SetLastError(0);
FStatusHandle := RegisterServiceCtrlHandler(FName, @ServController);
if FStatusHandle <> 0 then begin
if ReportStart then begin
// Cria o evento que irá sinalizar o fim do service
SetLastError(0);
FStopEvent := CreateEvent(nil, true, false, nil);
if FStopEvent <> 0 then begin
// Roda a rotina de inicialização do Service
InitOk := true;
if @FServiceBegin <> nil then InitOk := FServiceBegin;
if InitOk then begin
ReportStart;
FReportStartStop := false;
// Starta as threads do service
for I := 0 to FMaxThreads do
FServiceThreads[I].Resume;
ReportEventLog(EventInformation, 0, 'Started');
if ReportNoError(SERVICE_RUNNING) then
// Espera indefinidamente até o StopEvent ocorrer
WaitForSingleObject(FStopEvent, INFINITE);
FReportStartStop := true;
ReportStop;
// Desaloca as Threads
for I := 0 to FMaxThreads do
FServiceThreads[I].Terminate;
ReportEventLog(EventInformation, 1, 'Stopped');
ReportStop;
SetLastError(0);
if @FServiceEnd <> nil then FServiceEnd; // Roda a rotina de finalização
end;
CloseHandle(FStopEvent);
end;
end;
ReportServiceStatus(SERVICE_STOPPED, GetLastError, 0, 0);
end;
end;
end;
{
Runs a service. Calls StartServiceCtrlDispatcher to register a main service thread.
When the API returns, the service was stopped, then halt.
@param
@param ServBegin Function called before to start the service. It should return true if initializing was Ok.
@param ServEnd Function called after to stop the service.
}
function TService.Run(ServThreads : array of TThread; ServBegin : TFuncBool = nil; ServEnd : TFuncBool = nil) : boolean;
var
ServTable : array[0..1] of TServiceTableEntry;
I : integer;
begin
FServiceBegin := ServBegin;
FServiceEnd := ServEnd;
FMaxThreads := high(ServThreads);
for I := 0 to FMaxThreads do
FServiceThreads[I] := ServThreads[I];
fillchar(ServTable, sizeof(ServTable), 0);
with ServTable[0] do begin
lpServiceName := FName;
lpServiceProc := @ServiceMain
end;
SetLastError(0);
Result := StartServiceCtrlDispatcher({$IFDEF FPC}@{$ENDIF}ServTable[0])
end;
{
Creates a new service, but not installs it. Use <link TService.Insert> to install.
@param ServiceName to show in Service Manager
@param Description to show in Service Manager
}
constructor TService.Create(ServiceName : string; Description : string = ''); begin
inherited Create;
FName := pchar(ServiceName);
FDescription := Description;
FSource := OpenEventLog(nil, FName);
FManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if FManager = 0 then RaiseLastOSError;
FService := OpenService(FManager, FName, SERVICE_ALL_ACCESS);
FTimeout := 20000;
FReportStartStop := true;
end;
// Frees a service but not uninstalls it. Use <link TService.Delete> method to uninstall.
destructor TService.Destroy; begin
CloseServiceHandle(FService);
CloseEventLog(FSource);
CloseServiceHandle(FManager);
end;
end.