root/trunk/bevutils/ServiceBase.d

Revision 20, 22.5 kB (checked in by teales, 1 year ago)

Bevutils files initial check-in.

Line 
1 /**
2  * Authors: Steve Teale - steve.teale@britseyeview.com
3  *
4  * Date: 2007/05/19
5  * History: V0.1
6  * License: Use freely for any purpose.
7  */
8 module bevutils.servicebase;
9
10 import std.stdio;
11 import std.c.windows.windows;
12 import std.string;
13 import bevutils.eventlogger;
14
15 enum FMT_MSG : uint
16 {
17     FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100,
18     FORMAT_MESSAGE_IGNORE_INSERTS  = 0x00000200,
19     FORMAT_MESSAGE_FROM_STRING     = 0x00000400,
20     FORMAT_MESSAGE_FROM_HMODULE    = 0x00000800,
21     FORMAT_MESSAGE_FROM_SYSTEM     = 0x00001000,
22     FORMAT_MESSAGE_ARGUMENT_ARRAY  = 0x00002000,
23     FORMAT_MESSAGE_MAX_WIDTH_MASK  = 0x000000FF
24 }
25
26 const int SERVICE_WIN32_OWN_PROCESS = 0x00000010;
27 enum ServiceControlManagerType : int
28 {
29     SC_MANAGER_CONNECT = 0x1,
30     SC_MANAGER_CREATE_SERVICE = 0x2,
31     SC_MANAGER_ENUMERATE_SERVICE = 0x4,
32     SC_MANAGER_LOCK = 0x8,
33     SC_MANAGER_QUERY_LOCK_STATUS = 0x10,
34     SC_MANAGER_MODIFY_BOOT_CONFIG = 0x20,
35     SC_MANAGER_ALL_ACCESS =
36         SC_MANAGER_CONNECT |
37         SC_MANAGER_CREATE_SERVICE |
38         SC_MANAGER_ENUMERATE_SERVICE |
39         SC_MANAGER_LOCK |
40         SC_MANAGER_QUERY_LOCK_STATUS |
41         SC_MANAGER_MODIFY_BOOT_CONFIG
42 }
43
44 enum ACCESS_TYPE : int
45 {
46     SERVICE_QUERY_CONFIG = 0x1,
47     SERVICE_CHANGE_CONFIG = 0x2,
48     SERVICE_QUERY_STATUS = 0x4,
49     SERVICE_ENUMERATE_DEPENDENTS = 0x8,
50     SERVICE_START = 0x10,
51     SERVICE_STOP = 0x20,
52     SERVICE_PAUSE_CONTINUE = 0x40,
53     SERVICE_INTERROGATE = 0x80,
54     SERVICE_USER_DEFINED_CONTROL = 0x100,
55     SERVICE_ALL_ACCESS =
56         SERVICE_QUERY_CONFIG |
57         SERVICE_CHANGE_CONFIG |
58         SERVICE_QUERY_STATUS |
59         SERVICE_ENUMERATE_DEPENDENTS |
60         SERVICE_START |
61         SERVICE_STOP |
62         SERVICE_PAUSE_CONTINUE |
63         SERVICE_INTERROGATE |
64         SERVICE_USER_DEFINED_CONTROL,
65         DELETE = 0x00010000
66 }
67
68 enum SERVICE_COMMANDS : int
69 {
70     SERVICE_CONTROL_STOP                   = 1,
71     SERVICE_CONTROL_PAUSE                  = 2,
72     SERVICE_CONTROL_CONTINUE               = 3,
73     SERVICE_CONTROL_INTERROGATE            = 4,
74     SERVICE_CONTROL_SHUTDOWN               = 5,
75     SERVICE_CONTROL_PARAMCHANGE            = 6,
76     SERVICE_CONTROL_NETBINDADD             = 7,
77     SERVICE_CONTROL_NETBINDREMOVE          = 8,
78     SERVICE_CONTROL_NETBINDENABLE          = 9,
79     SERVICE_CONTROL_NETBINDDISABLE         = 10,
80     SERVICE_CONTROL_DEVICEEVENT            = 11,
81     SERVICE_CONTROL_HARDWAREPROFILECHANGE  = 12,
82     SERVICE_CONTROL_POWEREVENT             = 13,
83     SERVICE_CONTROL_SESSIONCHANGE          = 14
84 }
85
86 enum SERVICE_STATES : uint
87 {
88     SERVICE_STOPPED = 1,
89     SERVICE_START_PENDING,
90     SERVICE_STOP_PENDING,
91     SERVICE_RUNNING,
92     SERVICE_CONTINUE_PENDING,
93     SERVICE_PAUSE_PENDING,
94     SERVICE_PAUSED
95 }
96
97 enum SERVICE_START_TYPE : uint
98 {
99     SERVICE_BOOT_START             = 0x00000000,
100     SERVICE_SYSTEM_START           = 0x00000001,
101     SERVICE_AUTO_START             = 0x00000002,
102     SERVICE_DEMAND_START           = 0x00000003,
103     SERVICE_DISABLED               = 0x00000004
104 }
105
106 enum SERVICE_CONTROL_TYPE : uint
107 {
108     SERVICE_ERROR_IGNORE           = 0x00000000,
109     SERVICE_ERROR_NORMAL           = 0x00000001,
110     SERVICE_ERROR_SEVERE           = 0x00000002,
111     SERVICE_ERROR_CRITICAL         = 0x00000003
112 }
113 enum ACCEPT_COMMANDS : uint
114 {
115     SERVICE_ACCEPT_STOP                    = 0x00000001,
116     SERVICE_ACCEPT_PAUSE_CONTINUE          = 0x00000002,
117     SERVICE_ACCEPT_SHUTDOWN                = 0x00000004,
118     SERVICE_ACCEPT_PARAMCHANGE             = 0x00000008,
119     SERVICE_ACCEPT_NETBINDCHANGE           = 0x00000010,
120     SERVICE_ACCEPT_HARDWAREPROFILECHANGE   = 0x00000020,
121     SERVICE_ACCEPT_POWEREVENT              = 0x00000040,
122     SERVICE_ACCEPT_SESSIONCHANGE           = 0x00000080
123 }
124
125 enum EVENTLOG_TYPES : int
126 {
127     EVENTLOG_SUCCESS                = 0x0000,
128     EVENTLOG_ERROR_TYPE             = 0x0001,
129     EVENTLOG_WARNING_TYPE           = 0x0002,
130     EVENTLOG_INFORMATION_TYPE       = 0x0004,
131     EVENTLOG_AUDIT_SUCCESS          = 0x0008,
132     EVENTLOG_AUDIT_FAILURE          = 0x0010
133 }
134
135 alias void* SC_HANDLE;
136 alias void* SERVICE_STATUS_HANDLE;
137 alias PVOID PSID;
138
139 struct SERVICE_TABLE_ENTRY
140 {
141     char* ServiceName;
142     void* ServiceMain;
143 }
144 struct SERVICE_STATUS
145 {
146     uint dwServiceType;
147     uint dwCurrentState;
148     uint dwControlsAccepted;
149     uint dwWin32ExitCode;
150     uint dwServiceSpecificExitCode;
151     uint dwCheckPoint;
152     uint dwWaitHint;
153 }
154
155 extern (Windows)
156 {
157     SC_HANDLE OpenServiceA(
158             SC_HANDLE hSCManager,
159             char* lpServiceName,
160             ACCESS_TYPE dwDesiredAccess);
161     SC_HANDLE OpenSCManagerA(
162             char* lpMachineName, char* lpDatabaseName,
163             ServiceControlManagerType dwDesiredAccess);
164     BOOL CloseServiceHandle(
165             SC_HANDLE hSCObject);
166     BOOL DeleteService(SC_HANDLE hSCObject);
167     BOOL StartServiceCtrlDispatcherA(SERVICE_TABLE_ENTRY*);
168     SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerA(char*, void*);
169     BOOL SetServiceStatus(SERVICE_STATUS_HANDLE, SERVICE_STATUS*);
170     SC_HANDLE CreateServiceA(
171             SC_HANDLE hSCManager,
172             LPCTSTR lpServiceName,
173             LPCTSTR lpDisplayName,
174             DWORD dwDesiredAccess,
175             DWORD dwServiceType,
176             DWORD dwStartType,
177             DWORD dwErrorControl,
178             LPCTSTR lpBinaryPathName,
179             LPCTSTR lpLoadOrderGroup,
180             LPDWORD lpdwTagId,
181             LPCTSTR lpDependencies,
182             LPCTSTR lpServiceStartName,
183             LPCTSTR lpPassword);
184     BOOL ControlService(
185             SC_HANDLE hService,
186             DWORD dwControl,
187             SERVICE_STATUS* lpServiceStatus);
188     BOOL QueryServiceStatus(
189             SC_HANDLE hService,
190             SERVICE_STATUS* lpServiceStatus);
191     HANDLE CreateEventA(LPSECURITY_ATTRIBUTES lpEventAttributes,
192             BOOL bManualReset,
193             BOOL bInitialState,
194             LPCTSTR lpName);
195     BOOL SetEvent(HANDLE hEvent);
196     HANDLE RegisterEventSourceA(LPCTSTR lpUNCServerName, LPCTSTR lpSourceName);
197     BOOL ReportEventA(HANDLE hEventLog,
198             WORD wType,
199             WORD wCategory,
200             DWORD dwEventID,
201             PSID lpUserSid,
202             WORD wNumStrings,
203             DWORD dwDataSize,
204             LPCTSTR* lpStrings,
205             LPVOID lpRawData);
206     BOOL DeregisterEventSource(HANDLE hEventLog);
207     BOOL Beep(DWORD dwFreq, DWORD dwDuration);
208
209
210 }
211
212 /++
213  + A class to provide the internals of a windows service, including installation and removal.
214  +
215  + The class has few methods that you would call from a host program.
216  + All detailed implementation must be provided by a derived class.
217  +
218  + An example of its use to create a service executable is presented below.
219  +
220  + To install the service run derived.exe -i, to remove it run derived.exe -r.
221  +
222  + -----------------------------------------------------
223 // Derive a class from ServiceBase implementing the abstract methods.
224 class NullService : ServiceBase
225 {
226     this()
227     {
228         super("Dummy", false);
229     }
230
231     public void onServiceStart(char[][] args)
232     {
233         ServiceBase.EventLog.logMessage("NullService onServiceStart was called");
234     }
235
236     public void onServiceStop()
237     {
238         ServiceBase.EventLog.logMessage("NullService onServiceStop was called");
239     }
240 }
241
242 // Create an object of the derived class, then call the base class implementMain method.
243 void main(char[][] args)
244 {
245     try
246     {
247         ServiceBase sb = new NullService();
248         ServiceBase.implementMain(args);
249     }
250     catch (Exception ex)
251     {
252         writefln(ex.toString());
253     }
254 }
255  + -----------------------------------------------------
256  +/
257 class ServiceBase
258 {
259 protected:
260     static char[] _serviceName;
261     static SERVICE_TABLE_ENTRY[2] _sta;
262     static bool _debug;
263     static SERVICE_STATUS _ss;
264     static SERVICE_STATUS_HANDLE _ssh;
265     static char[][] _args;
266     static int _inited;
267     static ServiceBase _self;
268     static HANDLE _stopEvent;
269     static EventLogger _elog;
270
271     this(char[] serviceName)
272     {
273         _ssh = null;
274         if (_inited == 0)
275         {
276             //_debug = dBG;
277             _serviceName = serviceName;
278             _sta[0].ServiceName = toStringz(_serviceName);
279             _sta[0].ServiceMain = cast(void*) &service_main;
280             _sta[1].ServiceName = null;
281             _sta[1].ServiceMain = null;
282             _elog = new EventLogger();
283             _self = this;
284         }
285         _inited = 1;
286     }
287
288     static void StartService()
289     {
290         if (!StartServiceCtrlDispatcherA(cast(SERVICE_TABLE_ENTRY *) &_sta[0]))
291         {
292             _elog.logWindowsError("StartServiceCtrlDispatcherA failed.");
293         }
294     }
295
296     void ServiceStart (char[][] args)
297     {
298         try
299         {
300             if (!ReportStatusToSCMgr(SERVICE_STATES.SERVICE_START_PENDING,  // service state
301                                      0,                                     // exit code
302                                      3000))                                 // wait hint
303                 throw new Exception("Failed to report status to SCM");
304             _stopEvent = CreateEventA(
305                                  null,      // no security attributes
306                                  1,         // manual reset event
307                                  0,         // not-signalled
308                                  null);     // no name
309             if (_stopEvent == null)
310                 throw new Exception("Failed to create stop event");
311             if (!ReportStatusToSCMgr(SERVICE_STATES.SERVICE_START_PENDING,
312                                      0,
313                                      3000))
314                 throw new Exception("Failed to report status to SCM");
315             onServiceStart(args);
316             if (!ReportStatusToSCMgr(SERVICE_STATES.SERVICE_RUNNING,       // service state
317                                      0,                                    // exit code
318                                      0))                                   // wait hint
319                 throw new Exception("Failed to report status to SCM");
320             _elog.logInfo(_serviceName ~ " started.");
321             WaitForSingleObject(_stopEvent, INFINITE);
322            _elog.logInfo(_serviceName ~ " stopped.");
323             ReportStatusToSCMgr(SERVICE_STATES.SERVICE_STOPPED, 0, 0);
324         }
325         finally {}
326     }
327
328     void ServicePause()
329     {
330       onServicePause();
331       ReportStatusToSCMgr(SERVICE_STATES.SERVICE_PAUSED, 0, 0);
332       _elog.logInfo(_serviceName ~ " paused.");
333     }
334
335     void ServiceContinue()
336     {
337       onServiceContinue();
338       ReportStatusToSCMgr(SERVICE_STATES.SERVICE_RUNNING, 0, 0);
339       _elog.logInfo(_serviceName ~ " resumed.");
340     }
341
342     void ServiceStop()
343     {
344         onServiceStop();
345         if (_stopEvent)
346             SetEvent(_stopEvent);
347     }
348
349     public abstract void onServiceStart(char[][] args)
350     {
351     }
352
353     public abstract void onServiceStop()
354     {
355         // Override this method to shut down the worker thread(s) of your service
356     }
357
358     public void onServicePause()
359     {
360         // Override this method to pause the worker thread(s) of your service
361     }
362
363     public void onServiceContinue()
364     {
365         // Override this method to resume the worker thread(s) of your service
366     }
367
368     static void InstallService()
369     {
370         SC_HANDLE   schService;
371         SC_HANDLE   schSCManager;
372
373         char[512] szPath;
374         if (GetModuleFileNameA(null, szPath.ptr, 512) == 0 )
375         {
376             writefln("Unable to get executable path - service installation failed");
377             return;
378         }
379
380         schSCManager = OpenSCManagerA(
381                                 null,                       // machine (NULL == local)
382                                 null,                       // database (NULL == default)
383                                 ServiceControlManagerType.SC_MANAGER_ALL_ACCESS  // access required
384                                 );
385         if (schSCManager)
386         {
387             schService = CreateServiceA(
388                                 schSCManager,               // SCManager database
389                                 CName(),                    // name of service
390                                 CName(),                    // name to display
391                                 ACCESS_TYPE.SERVICE_QUERY_STATUS,         // desired access
392                                 SERVICE_WIN32_OWN_PROCESS,  // service type
393                                 SERVICE_START_TYPE.SERVICE_DEMAND_START,  // start type
394                                 SERVICE_CONTROL_TYPE.SERVICE_ERROR_NORMAL,// error control type
395                                 toStringz(szPath),                 // service's binary
396                                 null,                       // no load ordering group
397                                 null,                       // no tag identifier
398                                 null,                       // dependencies
399                                 null,                       // LocalSystem account
400                                 null);                      // no password
401
402             if (schService)
403             {
404                 writefln("%s installed", _serviceName);
405                 CloseServiceHandle(schService);
406             }
407             else
408             {
409                 uint err = GetLastError();
410                 char[] errMsg = _elog.getWindowsErrorText(err);
411                 writefln("CreateService failed (%d) - %s", err, errMsg);
412             }
413
414             CloseServiceHandle(schSCManager);
415         }
416         else
417         {
418             uint err;
419             char[] errMsg = _elog.getWindowsErrorText(err);
420             writefln("OpenSCManager failed (%d) - %s", err, errMsg);
421         }
422     }
423
424     static void RemoveService()
425     {
426         SC_HANDLE   schService;
427         SC_HANDLE   schSCManager;
428
429         schSCManager = OpenSCManagerA(
430                                null,                   // machine (NULL == local)
431                                null,                   // database (NULL == default)
432                                ServiceControlManagerType.SC_MANAGER_CONNECT);      // access required
433         if ( schSCManager )
434         {
435             schService = OpenServiceA(schSCManager, CName(),
436                                         ACCESS_TYPE.DELETE | ACCESS_TYPE.SERVICE_STOP | ACCESS_TYPE.SERVICE_QUERY_STATUS);
437
438             if (schService)
439             {
440                 // try to stop the service
441                 if (ControlService(schService, SERVICE_COMMANDS.SERVICE_CONTROL_STOP, &_ss))
442                 {
443                     writefln("Stopping %s.", _serviceName);
444                     Sleep(1000);
445
446                     while (QueryServiceStatus( schService, &_ss ))
447                     {
448                         if ( _ss.dwCurrentState == SERVICE_STATES.SERVICE_STOP_PENDING )
449                         {
450                             writef(".");
451                             Sleep( 1000 );
452                         }
453                         else
454                             break;
455                     }
456
457                     if (_ss.dwCurrentState == SERVICE_STATES.SERVICE_STOPPED)
458                         writefln("\n%s stopped.", _serviceName);
459                     else
460                         writefln("\n%s failed to stop.\n", _serviceName);
461
462                  }
463
464                  // now remove the service
465                  if (DeleteService(schService))
466                      writefln("%s removed.", _serviceName);
467                  else
468                  {
469                      uint err = GetLastError();
470                      char[] errMsg = _elog.getWindowsErrorText(err);
471                      writefln("DeleteService failed (%d) - %s", err, errMsg);
472                  }
473
474                  CloseServiceHandle(schService);
475             }
476             else
477             {
478                 uint err = GetLastError();
479                 char[] errMsg = _elog.getWindowsErrorText(err);
480                 writefln("OpenService failed (%d) - %s", err, errMsg);
481             }
482             CloseServiceHandle(schSCManager);
483         }
484         else
485         {
486             uint err = GetLastError();
487             char[] errMsg = _elog.getWindowsErrorText(err);
488             writefln("OpenSCManager failed (%d) - %s", err, errMsg);
489         }
490     }
491
492 public:
493
494     /**
495      * Get the service name as a C style string.
496      */
497     static char *CName() { return toStringz(_serviceName); }
498
499     /**
500      * Get a reference to the services event logger.
501      */
502     static EventLogger EventLog() { return _elog; }
503
504     /**
505      * Get the service name as a D style string.
506      */
507     static char[] Name() { return _serviceName; }
508
509     /**
510      * This method should be called only by the SCM.
511      */
512     extern (Windows) static export void service_ctrl(uint dwCtrlCode)
513     {
514         // Handle the requested control code.
515         switch (dwCtrlCode)
516         {
517             // Stop the service.
518             //
519             // SERVICE_STOP_PENDING should be reported before
520             // setting the Stop Event - hServerStopEvent - in
521             // ServiceStop().  This avoids a race condition
522             // which may result in a 1053 - The Service did not respond...
523             // error.
524             case SERVICE_COMMANDS.SERVICE_CONTROL_STOP:
525                 ReportStatusToSCMgr(SERVICE_STATES.SERVICE_STOP_PENDING, 0, 0);
526                 _self.ServiceStop();
527                 return;
528             case SERVICE_COMMANDS.SERVICE_CONTROL_PAUSE:
529                 ReportStatusToSCMgr(SERVICE_STATES.SERVICE_PAUSE_PENDING, 0, 0);
530                 _self.ServicePause();
531                 return;
532             case SERVICE_COMMANDS.SERVICE_CONTROL_CONTINUE:
533                 ReportStatusToSCMgr(SERVICE_STATES.SERVICE_CONTINUE_PENDING, 0, 0);
534                 _self.ServiceContinue();
535                 return;
536
537             // Update the service status.
538             //
539             case SERVICE_COMMANDS.SERVICE_CONTROL_INTERROGATE:
540                 break;
541
542             // invalid control code
543             //
544             default:
545                 break;
546         }
547
548         ReportStatusToSCMgr(_ss.dwCurrentState, 0, 0);
549     }
550
551     /**
552      * This method should be called only by the SCM.
553      */
554     extern (Windows) static export void service_main(uint dwArgc, char **args)
555     {
556         _args.length = dwArgc;
557         for (uint u = 0; u < dwArgc; u++)
558             _args[u] = std.string.toString(args[u]);
559         _ssh = RegisterServiceCtrlHandlerA(toStringz(_serviceName), &service_ctrl);
560
561         // SERVICE_STATUS members that don't change in example
562         _ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
563         _ss.dwServiceSpecificExitCode = 0;
564
565
566         // report the status to the service control manager.
567         if (!ReportStatusToSCMgr(
568                            SERVICE_STATES.SERVICE_START_PENDING,    // service state
569                            0,                                       // exit code
570                            3000))                                   // wait hint
571         {
572             if (_ssh)
573                 ReportStatusToSCMgr(SERVICE_STATES.SERVICE_STOPPED, 0, 0);
574         }
575
576         _self.ServiceStart(_args);
577     }
578
579     /**
580      * This method can be called by a derived class during service stop to keep
581      * the SCM appraised of progress.
582      */
583     public static bool ReportStatusToSCMgr(uint dwCurrentState,
584                                     uint dwWin32ExitCode,
585                                     uint dwWaitHint)
586     {
587         uint dwCheckPoint = 1;
588         bool fResult = true;
589
590
591         if (!_debug) // when debugging we don't report to the SCM
592         {
593             if (dwCurrentState == SERVICE_STATES.SERVICE_START_PENDING)
594                 _ss.dwControlsAccepted = 0;
595             else
596                 _ss.dwControlsAccepted = ACCEPT_COMMANDS.SERVICE_ACCEPT_STOP;
597
598             _ss.dwCurrentState = dwCurrentState;
599             _ss.dwWin32ExitCode = dwWin32ExitCode;
600             _ss.dwWaitHint = dwWaitHint;
601
602             if (dwCurrentState == SERVICE_STATES.SERVICE_RUNNING ||
603                 dwCurrentState == SERVICE_STATES.SERVICE_STOPPED)
604                 _ss.dwCheckPoint = 0;
605              else
606                 _ss.dwCheckPoint = dwCheckPoint++;
607
608
609             // Report the status of the service to the service control manager.
610             //
611             fResult = (SetServiceStatus( _ssh, &_ss) == 1);
612         }
613         return fResult;
614     }
615
616
617     /**
618      * This method should be called by your main() function to implement the service.
619      */
620     static void implementMain(char[][] args)
621     {
622         if (args.length == 2 && (args[1][0] == '-' || args[1][0] == '/'))
623         {
624             if (args[1][1] == 'i' || args[1][1] == 'I')
625             {
626                 writefln("Installing service: %s", ServiceBase.Name);
627                 ServiceBase.InstallService();
628                 return;
629             }
630             else if (args[1][1] == 'r' || args[1][1] == 'R')
631             {
632                 writefln("Removing service: %s", ServiceBase.Name);
633                 ServiceBase.RemoveService();
634                 return;
635             }
636             else if (args[1][1] == 'd' || args[1][1] == 'D')
637             {
638                 writefln("Service - debug mode requested");
639                 return;
640             }
641             else
642             {
643                 writefln("Usage: <service Name> [-i|-I|-r|-R|-d|-D|/i|/I|/r|/R|/d|/D]");
644                 return;
645             }
646         }
647         else if (args.length == 1)
648             ServiceBase.StartService();
649         else