home *** CD-ROM | disk | FTP | other *** search
/ Delphi Anthology / aDELPHI.iso / Runimage / Delphi50 / Source / Vcl / svcmgr.pas < prev    next >
Pascal/Delphi Source File  |  1999-08-11  |  28KB  |  980 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Borland Delphi Visual Component Library         }
  5. {       Services                                        }
  6. {                                                       }
  7. {       Copyright (c) 1997,99 Inprise Corporation       }
  8. {                                                       }
  9. {*******************************************************}
  10.  
  11. unit SvcMgr;
  12.  
  13. {$J+,H+,X+}
  14.  
  15. interface
  16.  
  17. uses
  18.   Windows, Messages, SysUtils, Classes, WinSvc, Forms;
  19.  
  20. type
  21.  
  22.   { TEventLogger }
  23.  
  24.   TEventLogger = class(TObject)
  25.   private
  26.     FName: String;
  27.     FEventLog: Integer;
  28.   public
  29.     constructor Create(Name: String);
  30.     destructor Destroy; override;
  31.     procedure LogMessage(Message: String; EventType: DWord = 1;
  32.       Category: Word = 0; ID: DWord = 0);
  33.   end;
  34.  
  35.   { TDependency }
  36.  
  37.   TDependency = class(TCollectionItem)
  38.   private
  39.     FName: String;
  40.     FIsGroup: Boolean;
  41.   protected
  42.     function GetDisplayName: string; override;
  43.   published
  44.     property Name: String read FName write FName;
  45.     property IsGroup: Boolean read FIsGroup write FIsGroup;
  46.   end;
  47.  
  48.   { TDependencies }
  49.  
  50.   TDependencies = class(TCollection)
  51.   private
  52.     FOwner: TPersistent;
  53.     function GetItem(Index: Integer): TDependency;
  54.     procedure SetItem(Index: Integer; Value: TDependency);
  55.   protected
  56.     function GetOwner: TPersistent; override;
  57.   public
  58.     constructor Create(Owner: TPersistent);
  59.     property Items[Index: Integer]: TDependency read GetItem write SetItem; default;
  60.   end;
  61.  
  62. { TServiceThread }
  63.  
  64. const
  65.  
  66.   CM_SERVICE_CONTROL_CODE = WM_USER + 1;
  67.  
  68. type
  69.  
  70.   TService = class;
  71.  
  72.   TServiceThread = class(TThread)
  73.   private
  74.     FService: TService;
  75.   protected
  76.     procedure Execute; override;
  77.   public
  78.     constructor Create(Service: TService);
  79.     procedure ProcessRequests(WaitForMessage: Boolean);
  80.   end;
  81.  
  82.   { TService }
  83.  
  84.   TServiceController = procedure(CtrlCode: DWord); stdcall;
  85.  
  86.   TServiceType = (stWin32, stDevice, stFileSystem);
  87.  
  88.   TCurrentStatus = (csStopped, csStartPending, csStopPending, csRunning,
  89.     csContinuePending, csPausePending, csPaused);
  90.  
  91.   TErrorSeverity = (esIgnore, esNormal, esSevere, esCritical);
  92.  
  93.   TStartType = (stBoot, stSystem, stAuto, stManual, stDisabled);
  94.  
  95.   TServiceEvent = procedure(Sender: TService) of object;
  96.   TContinueEvent = procedure(Sender: TService; var Continued: Boolean) of object;
  97.   TPauseEvent = procedure(Sender: TService; var Paused: Boolean) of object;
  98.   TStartEvent = procedure(Sender: TService; var Started: Boolean) of object;
  99.   TStopEvent = procedure(Sender: TService; var Stopped: Boolean) of object;
  100.  
  101.   TService = class(TDataModule)
  102.   private
  103.     FAllowStop: Boolean;
  104.     FAllowPause: Boolean;
  105.     FDependencies: TDependencies;
  106.     FDisplayName: String;
  107.     FErrCode: DWord;
  108.     FErrorSeverity: TErrorSeverity;
  109.     FEventLogger: TEventLogger;
  110.     FInteractive: Boolean;
  111.     FLoadGroup: String;
  112.     FParams: TStringList;
  113.     FPassword: String;
  114.     FServiceStartName: String;
  115.     FServiceThread: TServiceThread;
  116.     FServiceType: TServiceType;
  117.     FStartType: TStartType;
  118.     FStatus: TCurrentStatus;
  119.     FStatusHandle: THandle;
  120.     FTagID: DWord;
  121.     FWaitHint: Integer;
  122.     FWin32ErrorCode: DWord;
  123.     FBeforeInstall: TServiceEvent;
  124.     FAfterInstall: TServiceEvent;
  125.     FBeforeUninstall: TServiceEvent;
  126.     FAfterUninstall: TServiceEvent;
  127.     FOnContinue: TContinueEvent;
  128.     FOnExecute: TServiceEvent;
  129.     FOnPause: TPauseEvent;
  130.     FOnShutdown: TServiceEvent;
  131.     FOnStart: TStartEvent;
  132.     FOnStop: TStopEvent;
  133.     function GetDisplayName: String;
  134.     function GetParamCount: Integer;
  135.     function GetParam(Index: Integer): String;
  136.     procedure SetStatus(Value: TCurrentStatus);
  137.     procedure SetDependencies(Value: TDependencies);
  138.     function GetNTDependencies: String;
  139.     function GetNTServiceType: Integer;
  140.     function GetNTStartType: Integer;
  141.     function GetNTErrorSeverity: Integer;
  142.     function GetNTControlsAccepted: Integer;
  143.     procedure SetOnContinue(Value: TContinueEvent);
  144.     procedure SetOnPause(Value: TPauseEvent);
  145.     procedure SetOnStop(Value: TStopEvent);
  146.     function GetTerminated: Boolean;
  147.     function AreDependenciesStored: Boolean;
  148.     procedure SetInteractive(Value: Boolean);
  149.     procedure SetPassword(const Value: string);
  150.     procedure SetServiceStartName(const Value: string);
  151.   protected
  152.     procedure Main(Argc: DWord; Argv: PLPSTR);
  153.     procedure Controller(CtrlCode: DWord);
  154.     procedure DoStart; virtual;
  155.     function DoStop: Boolean; virtual;
  156.     function DoPause: Boolean; virtual;
  157.     function DoContinue: Boolean; virtual;
  158.     procedure DoInterrogate; virtual;
  159.     procedure DoShutdown; virtual;
  160.     function DoCustomControl(CtrlCode: DWord): Boolean; virtual;
  161.   public
  162.     constructor CreateNew(AOwner: TComponent; Dummy: Integer); override;
  163.     destructor Destroy; override;
  164.     function GetServiceController: TServiceController; virtual; abstract;
  165.     procedure ReportStatus;
  166.     procedure LogMessage(Message: String; EventType: DWord = 1;
  167.       Category: Integer = 0; ID: Integer = 0);
  168.     property ErrCode: DWord read FErrCode write FErrCode;
  169.     property ParamCount: Integer read GetParamCount;
  170.     property Param[Index: Integer]: String read GetParam;
  171.     property ServiceThread: TServiceThread read FServiceThread;
  172.     property Status: TCurrentStatus read FStatus write SetStatus;
  173.     property Terminated: Boolean read GetTerminated;
  174.     property Win32ErrCode: DWord read FWin32ErrorCode write FWin32ErrorCode;
  175.   published
  176.     property AllowStop: Boolean read FAllowStop write FAllowStop default True;
  177.     property AllowPause: Boolean read FAllowPause write FAllowPause default True;
  178.     property Dependencies: TDependencies read FDependencies write SetDependencies stored AreDependenciesStored;
  179.     property DisplayName: String read GetDisplayName write FDisplayName;
  180.     property ErrorSeverity: TErrorSeverity read FErrorSeverity write FErrorSeverity default esNormal;
  181.     property Interactive: Boolean read FInteractive write SetInteractive default False;
  182.     property LoadGroup: String read FLoadGroup write FLoadGroup;
  183.     property Password: String read FPassword write SetPassword;
  184.     property ServiceStartName: String read FServiceStartName write SetServiceStartName;
  185.     property ServiceType: TServiceType read FServiceType write FServiceType default stWin32;
  186.     property StartType: TStartType read FStartType write FStartType default stAuto;
  187.     property TagID: DWord read FTagID write FTagID default 0;
  188.     property WaitHint: Integer read FWaitHint write FWaitHint default 5000;
  189.     property BeforeInstall: TServiceEvent read FBeforeInstall write FBeforeInstall;
  190.     property AfterInstall: TServiceEvent read FAfterInstall write FAfterInstall;
  191.     property BeforeUninstall: TServiceEvent read FBeforeUninstall write FBeforeUninstall;
  192.     property AfterUninstall: TServiceEvent read FAfterUninstall write FAfterUninstall;
  193.     property OnContinue: TContinueEvent read FOnContinue write SetOnContinue;
  194.     property OnExecute: TServiceEvent read FOnExecute write FOnExecute;
  195.     property OnPause: TPauseEvent read FOnPause write SetOnPause;
  196.     property OnShutdown: TServiceEvent read FOnShutdown write FOnShutdown;
  197.     property OnStart: TStartEvent read FOnStart write FOnStart;
  198.     property OnStop: TStopEvent read FOnStop write SetOnStop;
  199.   end;
  200.  
  201.   { TServiceApplication }
  202.  
  203.   TServiceApplication = class(TComponent)
  204.   private
  205.     FEventLogger: TEventLogger;
  206.     FTitle: string;
  207.     procedure OnExceptionHandler(Sender: TObject; E: Exception);
  208.     function GetServiceCount: Integer;
  209.   protected
  210.     procedure DoHandleException(E: Exception); dynamic;
  211.     procedure RegisterServices(Install, Silent: Boolean);
  212.     procedure DispatchServiceMain(Argc: DWord; Argv: PLPSTR);
  213.     function Hook(var Message: TMessage): Boolean;
  214.   public
  215.     constructor Create(AOwner: TComponent); override;
  216.     destructor Destroy; override;
  217.     property ServiceCount: Integer read GetServiceCount;
  218.     // The following uses the current behaviour of the IDE module manager
  219.     procedure CreateForm(InstanceClass: TComponentClass; var Reference); virtual;
  220.     procedure Initialize; virtual;
  221.     procedure Run; virtual;
  222.     property Title: string read FTitle write FTitle;
  223.   end;
  224.  
  225. var
  226.   Application: TServiceApplication = nil;
  227.  
  228. implementation
  229.  
  230. uses
  231.   Dialogs, Consts;
  232.  
  233. { TEventLogger }
  234.  
  235. constructor TEventLogger.Create(Name: String);
  236. begin
  237.   FName := Name;
  238.   FEventLog := 0;
  239. end;
  240.  
  241. destructor TEventLogger.Destroy;
  242. begin
  243.   if FEventLog <> 0 then
  244.     DeregisterEventSource(FEventLog);
  245.   inherited Destroy;
  246. end;
  247.  
  248. procedure TEventLogger.LogMessage(Message: String; EventType: DWord;
  249.   Category: Word; ID: DWord);
  250. var
  251.   P: Pointer;
  252. begin
  253.   P := PChar(Message);
  254.   if FEventLog = 0 then
  255.     FEventLog := RegisterEventSource(nil, PChar(FName));
  256.   ReportEvent(FEventLog, EventType, Category, ID, nil, 1, 0, @P, nil);
  257. end;
  258.  
  259. { TDependency }
  260.  
  261. function TDependency.GetDisplayName: string;
  262. begin
  263.   if Name <> '' then
  264.     Result := Name else
  265.     Result := inherited GetDisplayName;
  266. end;
  267.  
  268. { TDependencies }
  269.  
  270. constructor TDependencies.Create(Owner: TPersistent);
  271. begin
  272.   FOwner := Owner;
  273.   inherited Create(TDependency);
  274. end;
  275.  
  276. function TDependencies.GetItem(Index: Integer): TDependency;
  277. begin
  278.   Result := TDependency(inherited GetItem(Index));
  279. end;
  280.  
  281. procedure TDependencies.SetItem(Index: Integer; Value: TDependency);
  282. begin
  283.   inherited SetItem(Index, TCollectionItem(Value));
  284. end;
  285.  
  286. function TDependencies.GetOwner: TPersistent;
  287. begin
  288.   Result := FOwner;
  289. end;
  290.  
  291. { TServiceThread }
  292.  
  293. constructor TServiceThread.Create(Service: TService);
  294. begin
  295.   FService := Service;
  296.   FreeOnTerminate := True;
  297.   inherited Create(False);
  298. end;
  299.  
  300. procedure TServiceThread.Execute;
  301. var
  302.   msg: TMsg;
  303.   Started: Boolean;
  304. begin
  305.   PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE); { Create message queue }
  306.   try
  307.     FService.Status := csStartPending;
  308.     Started := True;
  309.     if Assigned(FService.OnStart) then FService.OnStart(FService, Started);
  310.     if not Started then Exit;
  311.     try
  312.       FService.Status := csRunning;
  313.       if Assigned(FService.OnExecute) then
  314.         FService.OnExecute(FService) else
  315.         ProcessRequests(True);
  316.       ProcessRequests(False);
  317.     except
  318.       on E: Exception do
  319.         FService.LogMessage(Format(SServiceFailed,[SExecute, E.Message]));
  320.     end;
  321.   except
  322.     on E: Exception do
  323.       FService.LogMessage(Format(SServiceFailed,[SStart, E.Message]));
  324.   end;
  325. end;
  326.  
  327. procedure TServiceThread.ProcessRequests(WaitForMessage: Boolean);
  328. const
  329.   ActionStr: array[1..5] of String = (SStop, SPause, SContinue, SInterrogate,
  330.     SShutdown);
  331. var
  332.   msg: TMsg;
  333.   OldStatus: TCurrentStatus;
  334.   ErrorMsg: String;
  335.   ActionOK, Rslt: Boolean;
  336. begin
  337.   while True do
  338.   begin
  339.     if Terminated and WaitForMessage then break;
  340.     if WaitForMessage then
  341.       Rslt := GetMessage(msg, 0, 0, 0)
  342.     else
  343.       Rslt := PeekMessage(msg, 0, 0, 0, PM_REMOVE);
  344.     if not Rslt then break;
  345.     if msg.hwnd = 0 then { Thread message }
  346.     begin
  347.       if msg.message = CM_SERVICE_CONTROL_CODE then
  348.       begin
  349.         OldStatus := FService.Status;
  350.         try
  351.           ActionOK := True;
  352.           case msg.wParam of
  353.             SERVICE_CONTROL_STOP: ActionOK := FService.DoStop;
  354.             SERVICE_CONTROL_PAUSE: ActionOK := FService.DoPause;
  355.             SERVICE_CONTROL_CONTINUE: ActionOK := FService.DoContinue;
  356.             SERVICE_CONTROL_SHUTDOWN: FService.DoShutDown;
  357.             SERVICE_CONTROL_INTERROGATE: FService.DoInterrogate;
  358.           else
  359.             ActionOK := FService.DoCustomControl(msg.wParam);
  360.           end;
  361.           if not ActionOK then
  362.             FService.Status := OldStatus;
  363.         except
  364.           on E: Exception do
  365.           begin
  366.             if msg.wParam <> SERVICE_CONTROL_SHUTDOWN then
  367.               FService.Status := OldStatus;
  368.             if msg.wParam in [1..5] then
  369.               ErrorMsg := Format(SServiceFailed, [ActionStr[msg.wParam], E.Message])
  370.             else
  371.               ErrorMsg := Format(SCustomError,[msg.wParam, E.Message]);
  372.             FService.LogMessage(ErrorMsg);
  373.           end;
  374.         end;
  375.       end else
  376.         DispatchMessage(msg);
  377.     end else
  378.       DispatchMessage(msg);
  379.   end;
  380. end;
  381.  
  382. { TService }
  383.  
  384. constructor TService.CreateNew(AOwner: TComponent; Dummy: Integer);
  385. begin
  386.   inherited CreateNew(AOwner);
  387.   FWaitHint := 5000;
  388.   FInteractive := False;
  389.   FServiceType := stWin32;
  390.   FParams := TStringList.Create;
  391.   FDependencies := TDependencies.Create(Self);
  392.   FErrorSeverity := esNormal;
  393.   FStartType := stAuto;
  394.   FTagID := 0;
  395.   FAllowStop := True;
  396.   FAllowPause := True;
  397. end;
  398.  
  399. destructor TService.Destroy;
  400. begin
  401.   FDependencies.Free;
  402.   FParams.Free;
  403.   FEventLogger.Free;
  404.   inherited Destroy;
  405. end;
  406.  
  407. function TService.GetDisplayName: String;
  408. begin
  409.   if FDisplayName <> '' then
  410.     Result := FDisplayName else
  411.     Result := Name;
  412. end;
  413.  
  414. procedure TService.SetInteractive(Value: Boolean);
  415. begin
  416.   if Value = FInteractive then Exit;
  417.   if Value then
  418.   begin
  419.     Password := '';
  420.     ServiceStartName := '';
  421.   end;
  422.   FInteractive := Value;
  423. end;
  424.  
  425. procedure TService.SetPassword(const Value: string);
  426. begin
  427.   if Value = FPassword then Exit;
  428.   if Value <> '' then
  429.     Interactive := False;
  430.   FPassword := Value;
  431. end;
  432.  
  433. procedure TService.SetServiceStartName(const Value: string);
  434. begin
  435.   if Value = FServiceStartName then Exit;
  436.   if Value <> '' then
  437.     Interactive := False;
  438.   FServiceStartName := Value;
  439. end;
  440.  
  441. procedure TService.SetDependencies(Value: TDependencies);
  442. begin
  443.   FDependencies.Assign(Value);
  444. end;
  445.  
  446. function TService.AreDependenciesStored: Boolean;
  447. begin
  448.   Result := FDependencies.Count > 0;
  449. end;
  450.  
  451. function TService.GetParamCount: Integer;
  452. begin
  453.   Result := FParams.Count;
  454. end;
  455.  
  456. function TService.GetParam(Index: Integer): String;
  457. begin
  458.   Result := FParams[Index];
  459. end;
  460.  
  461. procedure TService.SetOnContinue(Value: TContinueEvent);
  462. begin
  463.   FOnContinue := Value;
  464.   AllowPause := True;
  465. end;
  466.  
  467. procedure TService.SetOnPause(Value: TPauseEvent);
  468. begin
  469.   FOnPause := Value;
  470.   AllowPause := True;
  471. end;
  472.  
  473. procedure TService.SetOnStop(Value: TStopEvent);
  474. begin
  475.   FOnStop := Value;
  476.   AllowStop := True;
  477. end;
  478.  
  479. function TService.GetTerminated: Boolean;
  480. begin
  481.   Result := False;
  482.   if Assigned(FServiceThread) then
  483.     Result := FServiceThread.Terminated;
  484. end;
  485.  
  486. function TService.GetNTDependencies: String;
  487. var
  488.   i, Len: Integer;
  489.   P: PChar;
  490. begin
  491.   Result := '';
  492.   Len := 0;
  493.   for i := 0 to Dependencies.Count - 1 do
  494.   begin
  495.     Inc(Len, Length(Dependencies[i].Name) + 1); // For null-terminator
  496.     if Dependencies[i].IsGroup then Inc(Len);
  497.   end;
  498.   if Len <> 0 then
  499.   begin
  500.     Inc(Len); // For final null-terminator;
  501.     SetLength(Result, Len);
  502.     P := @Result[1];
  503.     for i := 0 to Dependencies.Count - 1 do
  504.     begin
  505.       if Dependencies[i].IsGroup then
  506.       begin
  507.         P^ := SC_GROUP_IDENTIFIER;
  508.         Inc(P);
  509.       end;
  510.       P := StrECopy(P, PChar(Dependencies[i].Name));
  511.       Inc(P);
  512.     end;
  513.     P^ := #0;
  514.   end;
  515. end;
  516.  
  517. function TService.GetNTServiceType: Integer;
  518. const
  519.   NTServiceType: array[TServiceType] of Integer = ( SERVICE_WIN32_OWN_PROCESS,
  520.     SERVICE_KERNEL_DRIVER, SERVICE_FILE_SYSTEM_DRIVER);
  521. begin
  522.   Result := NTServiceType[FServiceType];
  523.   if (FServiceType = stWin32) and Interactive then
  524.     Result := Result or SERVICE_INTERACTIVE_PROCESS;
  525.   if (FServiceType = stWin32) and (Application.ServiceCount > 1) then
  526.     Result := (Result xor SERVICE_WIN32_OWN_PROCESS) or SERVICE_WIN32_SHARE_PROCESS;
  527. end;
  528.  
  529. function TService.GetNTStartType: Integer;
  530. const
  531.   NTStartType: array[TStartType] of Integer = (SERVICE_BOOT_START,
  532.     SERVICE_SYSTEM_START, SERVICE_AUTO_START, SERVICE_DEMAND_START,
  533.     SERVICE_DISABLED);
  534. begin
  535.   Result := NTStartType[FStartType];
  536.   if (FStartType in [stBoot, stSystem]) and (FServiceType <> stDevice) then
  537.     Result := SERVICE_AUTO_START;
  538. end;
  539.  
  540. function TService.GetNTErrorSeverity: Integer;
  541. const
  542.   NTErrorSeverity: array[TErrorSeverity] of Integer = (SERVICE_ERROR_IGNORE,
  543.     SERVICE_ERROR_NORMAL, SERVICE_ERROR_SEVERE, SERVICE_ERROR_CRITICAL);
  544. begin
  545.   Result := NTErrorSeverity[FErrorSeverity];
  546. end;
  547.  
  548. function TService.GetNTControlsAccepted: Integer;
  549. begin
  550.   Result := SERVICE_ACCEPT_SHUTDOWN;
  551.   if AllowStop then Result := Result or SERVICE_ACCEPT_STOP;
  552.   if AllowPause then Result := Result or SERVICE_ACCEPT_PAUSE_CONTINUE;
  553. end;
  554.  
  555. procedure TService.LogMessage(Message: String; EventType: DWord; Category, ID: Integer);
  556. begin
  557.   if FEventLogger = nil then
  558.     FEventLogger := TEventLogger.Create(Name);
  559.   FEventLogger.LogMessage(Message, EventType, Category, ID);
  560. end;
  561.  
  562. procedure TService.ReportStatus;
  563. const
  564.   LastStatus: TCurrentStatus = csStartPending;
  565.   NTServiceStatus: array[TCurrentStatus] of Integer = (SERVICE_STOPPED,
  566.     SERVICE_START_PENDING, SERVICE_STOP_PENDING, SERVICE_RUNNING,
  567.     SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING, SERVICE_PAUSED);
  568.   PendingStatus: set of TCurrentStatus = [csStartPending, csStopPending,
  569.     csContinuePending, csPausePending];
  570. var
  571.   ServiceStatus: TServiceStatus;
  572. begin
  573.   with ServiceStatus do
  574.   begin
  575.     dwWaitHint := FWaitHint;
  576.     dwServiceType := GetNTServiceType;
  577.     if FStatus = csStartPending then
  578.       dwControlsAccepted := 0 else
  579.       dwControlsAccepted := GetNTControlsAccepted;
  580.     if (FStatus in PendingStatus) and (FStatus = LastStatus) then
  581.       Inc(dwCheckPoint) else
  582.       dwCheckPoint := 0;
  583.     LastStatus := FStatus;
  584.     dwCurrentState := NTServiceStatus[FStatus];
  585.     dwWin32ExitCode := Win32ErrCode;
  586.     dwServiceSpecificExitCode := ErrCode;
  587.     if ErrCode <> 0 then
  588.       dwWin32ExitCode := ERROR_SERVICE_SPECIFIC_ERROR;
  589.     if not SetServiceStatus(FStatusHandle, ServiceStatus) then
  590.       LogMessage(SysErrorMessage(GetLastError));
  591.   end;
  592. end;
  593.  
  594. procedure TService.SetStatus(Value: TCurrentStatus);
  595. begin
  596.   FStatus := Value;
  597.   if not (csDesigning in ComponentState) then
  598.     ReportStatus;
  599. end;
  600.  
  601. procedure TService.Main(Argc: DWord; Argv: PLPSTR);
  602. type
  603.   PPCharArray = ^TPCharArray;
  604.   TPCharArray = array [0..1024] of PChar;
  605. var
  606.   i: Integer;
  607.   Controller: TServiceController;
  608. begin
  609.   for i := 0 to Argc - 1 do
  610.     FParams.Add(PPCharArray(Argv)[i]);
  611.   Controller := GetServiceController();
  612.   FStatusHandle := RegisterServiceCtrlHandler(PChar(Name), @Controller);
  613.   if (FStatusHandle = 0) then
  614.     LogMessage(SysErrorMessage(GetLastError)) else
  615.     DoStart;
  616. end;
  617.  
  618. procedure TService.Controller(CtrlCode: DWord);
  619. begin
  620.   PostThreadMessage(ServiceThread.ThreadID, CM_SERVICE_CONTROL_CODE, CtrlCode, 0);
  621.   if ServiceThread.Suspended then ServiceThread.Resume;
  622. end;
  623.  
  624. procedure TService.DoStart;
  625. begin
  626.   try
  627.     Status := csStartPending;
  628.     try
  629.       FServiceThread := TServiceThread.Create(Self);
  630.       FServiceThread.WaitFor;
  631.     finally
  632.       Status := csStopped;
  633.     end;
  634.   except
  635.     on E: Exception do
  636.       LogMessage(Format(SServiceFailed,[SExecute, E.Message]));
  637.   end;
  638. end;
  639.  
  640. function TService.DoStop: Boolean;
  641. begin
  642.   Result := True;
  643.   Status := csStopPending;
  644.   if Assigned(FOnStop) then FOnStop(Self, Result);
  645.   if Result then ServiceThread.Terminate;
  646. end;
  647.  
  648. function TService.DoPause: Boolean;
  649. begin
  650.   Result := True;
  651.   Status := csPausePending;
  652.   if Assigned(FOnPause) then FOnPause(Self, Result);
  653.   if Result then
  654.   begin
  655.     Status := csPaused;
  656.     ServiceThread.Suspend;
  657.   end;
  658. end;
  659.  
  660. function TService.DoContinue: Boolean;
  661. begin
  662.   Result := True;
  663.   Status := csContinuePending;
  664.   if Assigned(FOnContinue) then FOnContinue(Self, Result);
  665.   if Result then
  666.     Status := csRunning;
  667. end;
  668.  
  669. procedure TService.DoInterrogate;
  670. begin
  671.   ReportStatus;
  672. end;
  673.  
  674. procedure TService.DoShutdown;
  675. begin
  676.   Status := csStopPending;
  677.   try
  678.     if Assigned(FOnShutdown) then FOnShutdown(Self);
  679.   finally
  680.     { Shutdown cannot abort, it must stop regardless of any exception }
  681.     ServiceThread.Terminate;
  682.   end;
  683. end;
  684.  
  685. function TService.DoCustomControl(CtrlCode: DWord): Boolean;
  686. begin
  687.   Result := True;
  688. end;
  689.  
  690. { TServiceApplication }
  691.  
  692. type
  693.   TServiceClass = class of TService;
  694.  
  695. procedure ServiceMain(Argc: DWord; Argv: PLPSTR); stdcall;
  696. begin
  697.   Application.DispatchServiceMain(Argc, Argv);
  698. end;
  699.  
  700. procedure DoneServiceApplication;
  701. begin
  702.   with Forms.Application do
  703.   begin
  704.     if Handle <> 0 then ShowOwnedPopups(Handle, False);
  705.     ShowHint := False;
  706.     Destroying;
  707.     DestroyComponents;
  708.   end;
  709.   with Application do
  710.   begin
  711.     Destroying;
  712.     DestroyComponents;
  713.   end;
  714. end;
  715.  
  716. constructor TServiceApplication.Create(AOwner: TComponent);
  717. begin
  718.   inherited Create(AOwner);
  719.   FEventLogger := TEventLogger.Create(ExtractFileName(ParamStr(0)));
  720.   Forms.Application.HookMainWindow(Hook);
  721. end;
  722.  
  723. destructor TServiceApplication.Destroy;
  724. begin
  725.   FEventLogger.Free;
  726.   Forms.Application.OnException := nil;
  727.   Forms.Application.UnhookMainWindow(Hook);
  728.   inherited Destroy;
  729. end;
  730.  
  731. procedure TServiceApplication.DispatchServiceMain(Argc: DWord; Argv: PLPSTR);
  732. var
  733.   i: Integer;
  734. begin
  735.   for i := 0 to ComponentCount - 1 do
  736.     if (Components[i] is TService) and
  737.        (AnsiCompareText(PChar(Argv^), Components[i].Name) = 0) then
  738.     begin
  739.       TService(Components[i]).Main(Argc, Argv);
  740.       break;
  741.     end;
  742. end;
  743.  
  744. function TServiceApplication.GetServiceCount: Integer;
  745. var
  746.   i: Integer;
  747. begin
  748.   Result := 0;
  749.   for i := 0 to ComponentCount - 1 do
  750.     if Components[i] is TService then
  751.       Inc(Result);
  752. end;
  753.  
  754. procedure TServiceApplication.RegisterServices(Install, Silent: Boolean);
  755.  
  756.   procedure InstallService(Service: TService; SvcMgr: Integer);
  757.   var
  758.     TmpTagID, Svc: Integer;
  759.     PTag, PSSN: Pointer;
  760.     Path: string;
  761.   begin
  762.     Path := ParamStr(0);
  763.     with Service do
  764.     begin
  765.       if Assigned(BeforeInstall) then BeforeInstall(Service);
  766.       TmpTagID := TagID;
  767.       if TmpTagID > 0 then PTag := @TmpTagID else PTag := nil;
  768.       if ServiceStartName = '' then
  769.         PSSN := nil else
  770.         PSSN := PChar(ServiceStartName);
  771.       Svc := CreateService(SvcMgr, PChar(Name), PChar(DisplayName),
  772.         SERVICE_ALL_ACCESS, GetNTServiceType, GetNTStartType, GetNTErrorSeverity,
  773.         PChar(Path), PChar(LoadGroup), PTag, PChar(GetNTDependencies),
  774.         PSSN, PChar(Password));
  775.       TagID := TmpTagID;
  776.       if Svc = 0 then
  777.         RaiseLastWin32Error;
  778.       try
  779.         try
  780.           if Assigned(AfterInstall) then AfterInstall(Service);
  781.         except
  782.           on E: Exception do
  783.           begin
  784.             DeleteService(Svc);
  785.             raise;
  786.           end;
  787.         end;
  788.       finally
  789.         CloseServiceHandle(Svc);
  790.       end;
  791.     end;
  792.   end;
  793.  
  794.   procedure UninstallService(Service: TService; SvcMgr: Integer);
  795.   var
  796.     Svc: Integer;
  797.   begin
  798.     with Service do
  799.     begin
  800.       if Assigned(BeforeUninstall) then BeforeUninstall(Service);
  801.       Svc := OpenService(SvcMgr, PChar(Name), SERVICE_ALL_ACCESS);
  802.       if Svc = 0 then RaiseLastWin32Error;
  803.       try
  804.         if not DeleteService(Svc) then RaiseLastWin32Error;
  805.       finally
  806.         CloseServiceHandle(Svc);
  807.       end;
  808.       if Assigned(AfterUninstall) then AfterUninstall(Service);
  809.     end;
  810.   end;
  811.  
  812.  
  813. var
  814.   SvcMgr: Integer;
  815.   i: Integer;
  816.   Success: Boolean;
  817.   Msg: string;
  818. begin
  819.   Success := True;
  820.   SvcMgr := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  821.   if SvcMgr = 0 then RaiseLastWin32Error;
  822.   try
  823.     for i := 0 to ComponentCount - 1 do
  824.       if Components[i] is TService then
  825.       try
  826.         if Install then
  827.           InstallService(TService(Components[i]), SvcMgr) else
  828.           UninstallService(TService(Components[i]), SvcMgr)
  829.       except
  830.         on E: Exception do
  831.         begin
  832.           Success := False;
  833.           if Install then
  834.             Msg := SServiceInstallFailed else
  835.             Msg := SServiceUninstallFailed;
  836.           with TService(Components[i]) do
  837.             MessageDlg(Format(Msg, [DisplayName, E.Message]), mtError, [mbOK],0);
  838.         end;
  839.       end;
  840.     if Success and not Silent then
  841.       if Install then
  842.         MessageDlg(SServiceInstallOK, mtInformation, [mbOk], 0) else
  843.         MessageDlg(SServiceUninstallOK, mtInformation, [mbOk], 0);
  844.   finally
  845.     CloseServiceHandle(SvcMgr);
  846.   end;
  847. end;
  848.  
  849. function TServiceApplication.Hook(var Message: TMessage): Boolean;
  850. begin
  851.   Result := Message.Msg = WM_ENDSESSION;
  852. end;
  853.  
  854. procedure TServiceApplication.CreateForm(InstanceClass: TComponentClass;
  855.   var Reference);
  856. begin
  857.   if InstanceClass.InheritsFrom(TService) then
  858.   begin
  859.     try
  860.       TComponent(Reference) := InstanceClass.Create(Self);
  861.     except
  862.       TComponent(Reference) := nil;
  863.       raise;
  864.     end;
  865.   end else
  866.     Forms.Application.CreateForm(InstanceClass, Reference);
  867. end;
  868.  
  869. procedure TServiceApplication.DoHandleException(E: Exception);
  870. begin
  871.   FEventLogger.LogMessage(E.Message);
  872. end;
  873.  
  874. procedure TServiceApplication.Initialize;
  875. begin
  876.   Forms.Application.ShowMainForm :=False;
  877.   Forms.Application.Initialize;
  878. end;
  879.  
  880. procedure TServiceApplication.OnExceptionHandler(Sender: TObject; E: Exception);
  881. begin
  882.   DoHandleException(E);
  883. end;
  884.  
  885. type
  886.   TServiceTableEntryArray = array of TServiceTableEntry;
  887.  
  888.   TServiceStartThread = class(TThread)
  889.   private
  890.     FServiceStartTable: TServiceTableEntryArray;
  891.   protected
  892.     procedure DoTerminate; override;
  893.     procedure Execute; override;
  894.   public
  895.     constructor Create(Services: TServiceTableEntryArray);
  896.   end;
  897.  
  898. constructor TServiceStartThread.Create(Services: TServiceTableEntryArray);
  899. begin
  900.   FreeOnTerminate := False;
  901.   ReturnValue := 0;
  902.   FServiceStartTable := Services;
  903.   inherited Create(False);
  904. end;
  905.  
  906. procedure TServiceStartThread.DoTerminate;
  907. begin
  908.   inherited DoTerminate;
  909.   PostMessage(Forms.Application.Handle, WM_QUIT, 0, 0);
  910. end;
  911.  
  912. procedure TServiceStartThread.Execute;
  913. begin
  914.   if StartServiceCtrlDispatcher(FServiceStartTable[0]) then
  915.     ReturnValue := 0 else
  916.     ReturnValue := GetLastError;
  917. end;
  918.  
  919. procedure TServiceApplication.Run;
  920.  
  921.   function FindSwitch(const Switch: string): Boolean;
  922.   begin
  923.     Result := FindCmdLineSwitch(Switch, ['-', '/'], True);
  924.   end;
  925.  
  926. var
  927.   ServiceStartTable: TServiceTableEntryArray;
  928.   ServiceCount, i, J: Integer;
  929.   StartThread: TServiceStartThread;
  930. begin
  931.   AddExitProc(DoneServiceApplication);
  932.   if FindSwitch('INSTALL') then
  933.     RegisterServices(True, FindSwitch('SILENT')) else
  934.   if FindSwitch('UNINSTALL') then
  935.     RegisterServices(False, FindSwitch('SILENT')) else
  936.   begin
  937.     Forms.Application.OnException := OnExceptionHandler;
  938.     ServiceCount := 0;
  939.     for i := 0 to ComponentCount - 1 do
  940.       if Components[i] is TService then Inc(ServiceCount);
  941.     SetLength(ServiceStartTable, ServiceCount + 1);
  942.     FillChar(ServiceStartTable[0], SizeOf(TServiceTableEntry) * (ServiceCount + 1), 0);
  943.     J := 0;
  944.     for i := 0 to ComponentCount - 1 do
  945.       if Components[i] is TService then
  946.       begin
  947.         ServiceStartTable[J].lpServiceName := PChar(Components[i].Name);
  948.         ServiceStartTable[J].lpServiceProc := @ServiceMain;
  949.         Inc(J);
  950.       end;
  951.     StartThread := TServiceStartThread.Create(ServiceStartTable);
  952.     try
  953.       while not Forms.Application.Terminated do
  954.         Forms.Application.HandleMessage;
  955.       Forms.Application.Terminate;
  956.       if StartThread.ReturnValue <> 0 then
  957.         FEventLogger.LogMessage(SysErrorMessage(GetLastError));
  958.     finally
  959.       StartThread.Free;
  960.     end;
  961.   end;
  962. end;
  963.  
  964. procedure InitApplication;
  965. begin
  966.   Application := TServiceApplication.Create(nil);
  967. end;
  968.  
  969. procedure DoneApplication;
  970. begin
  971.   Application.Free;
  972.   Application := nil;
  973. end;
  974.  
  975. initialization
  976.   InitApplication;
  977. finalization
  978.   DoneApplication;
  979. end.
  980.