unit Service;
{*******************************************************************************
  ServiceController Demo
  Written by David Clegg, davidclegg@optusnet.com.au.

  Class to wrap and expose existing properties not exposed by the
  ServiceController class.
*******************************************************************************}

interface

uses
  System.ServiceProcess, System.Management;

type
  /// <summary>
  /// The ServiceController class doesn't expose all the methods I would like,
  /// so I am extending it to provide the missing information.
  /// </summary>
  TService = class(ServiceController)
  private
    FServiceObject: ManagementObject;
    procedure InitServiceObject;
    function GetDescription: string;
    function GetExePath: string;
    function GetStartMode: string;
    procedure SetStartMode(const Value: string);
    procedure ReallySetStartMode;
  public
    property Description: string read GetDescription;
    property ExePath: string read GetExePath;
    property StartMode: string read GetStartMode write SetStartMode;
    procedure ShowProperties;
    procedure Save;
    constructor Create(const pServiceName: string); overload;
    constructor Create(const pServiceName, pMachineName: string); overload;
  end;

implementation

uses
  SysUtils, System.Collections, System.Windows.Forms, System.Text;

/// <summary>
/// Creates a new TService instance, quering WMI using the specified service
/// name to retrieve additional properties not exposed by the
/// ServiceController class.
/// <param name='pServiceName'>The name of the Service.</param>
/// </summary>
constructor TService.Create(const pServiceName: string);
begin
  inherited Create(pServiceName);
  InitServiceObject;
end;

/// <summary>
/// Creates a new TService instance, quering WMI using the specified service
/// and machine names to retrieve additional properties not exposed by the
/// ServiceController class.
/// </summary>
/// <param name='pServiceName'>The name of the Service.</param>
/// <param name='pMachineName'>
/// The name of the machine where the service resides.
/// </param>
constructor TService.Create(const pServiceName, pMachineName: string);
begin
  inherited Create(pServiceName, pMachineName);
  InitServiceObject;
end;

/// <summmary>
/// Returns the detailed description for the service.
/// </summary>
function TService.GetDescription: string;
begin
  Result := '';
  if Assigned(FServiceObject) then
    if Assigned (FServiceObject['Description']) then
      Result := FServiceObject['Description'].ToString;
end;

/// <summary>
/// Returns the path to the service's executable.
/// </summary>
function TService.GetExePath: string;
begin
  Result := '';
  if Assigned(FServiceObject) then
    if Assigned(FServiceObject['PathName']) then
      Result := FServiceObject['PathName'].ToString;
end;

/// <summary>
/// Returns the start mode for the service.
/// </summary>
function TService.GetStartMode: string;
begin
  Result := '';
  if Assigned(FServiceObject) then
    if Assigned(FServiceObject['StartMode']) then
      Result := FServiceObject['StartMode'].ToString;
end;

/// <summmary>
/// Create a ManagementObject to query WMI for additional information about
/// the service.
/// </summary>
procedure TService.InitServiceObject;
var
  lPath: ManagementPath;
begin
  lPath := ManagementPath.Create(Format('Win32_Service.Name=''%s''', [ServiceName]));
  lPath.Server := MachineName;
  FServiceObject := ManagementObject.Create(lPath);
end;

/// <summary>
/// Save changes to the service. For now, the only property that can be changed
/// is the start mode.
/// </summary>
procedure TService.Save;
begin
  if Assigned(FServiceObject) then
    ReallySetStartMode;
end;

/// <summary>
/// Set the start mode for the service.
/// </summary>
/// <param name='Value'>The new start mode.</param>
procedure TService.SetStartMode(const Value: string);
begin
  if Assigned(FServiceObject) then
    FServiceObject['StartMode'] := Value;
end;

/// <summary>
/// Calls the ChangeStartMdoe method of the ManagementObject to change
/// the service's start mode.
/// </summary>
procedure TService.ReallySetStartMode;
var
  lParam: string;
begin
  //Setting the 'StartMode' property of the ManagementObject instance doesn't
  //actually update the property of the service application. The
  //ChangeStartMode method must be invoked instead. I have seperated this out
  //from the StartMode property setter, as that changes the 'StartMode'
  //parameter so the GUI reflects the correctly chosen value, but the service
  //application won't actually have the property changed until TService.Save
  //is called.
  if StartMode = 'Auto' then
    //Even thought ManagementObject['StartMode'] returns 'Auto',
    //ChangeStartMode expects 'Automatic'
    lParam := 'Automatic'
  else
    lParam := StartMode;
  FServiceObject.InvokeMethod('ChangeStartMode', [lParam]);
end;

/// <summary>
/// Shows all properties exposed by the ManagementObject class.
/// </summary>
procedure TService.ShowProperties;
var
  lProperty: PropertyData;
  lProperties: PropertyDataCollection;
  lEnumerator: IEnumerator;
  lStringBuilder: StringBuilder;
begin
  if Assigned(FServiceObject) then
  begin
    lStringBuilder := StringBuilder.Create;
    lProperties := FServiceObject.Properties;
    lEnumerator := lProperties.GetEnumerator;
    lEnumerator.Reset;
    while lEnumerator.MoveNext do
    begin
      lProperty := lEnumerator.Current as PropertyData;
      lStringBuilder.Append(Format('%s=%s', [lProperty.Name, lProperty.Value]) + #13#10);
    end;
    MessageBox.Show(lStringBuilder.ToString, Format('%s properties', [Self.DisplayName]));
  end;
end;

end.
