unit ChangeStatusForm;

interface

uses
  System.Drawing, System.Collections, System.ComponentModel,
  System.Windows.Forms, System.Data, System.ServiceProcess;

type
  TChangeType = (ctStart, ctStop, ctPause, ctResume);

  TChangeStatusForm = class(System.Windows.Forms.Form)
  {$REGION 'Designer Managed Code'}
  strict private
    /// <summary>
    /// Required designer variable.
    /// </summary>
    components: System.ComponentModel.IContainer;
    pbChangeProgress: System.Windows.Forms.ProgressBar;
    timProgress: System.Windows.Forms.Timer;
    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    procedure InitializeComponent;
    procedure TChangeStatusForm_Load(sender: System.Object; e: System.EventArgs);
    procedure timProgress_Tick(sender: System.Object; e: System.EventArgs);
    procedure TChangeStatusForm_Closing(sender: System.Object; e: System.ComponentModel.CancelEventArgs);
  {$ENDREGION}
  strict protected
    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    procedure Dispose(Disposing: Boolean); override;
  private
    FService: ServiceController;
    FChangeType: TChangeType;
    FStartParams: string;
    procedure PopulateControls;
    procedure DoStatusChange;
    function GetNewStatus: ServiceControllerStatus;
  public
    constructor Create;
    class function GetChangeTypeString(const pChangeType: TChangeType): string;
    class function ShowChangeStatusForm(pService: ServiceController;
      pChangeType: TChangeType; pStartParams: string = ''): System.Windows.Forms.DialogResult;
    //class function ShowChangeStatusForm(pService: ServiceController; pChangeType: TChangeType;
      //pStartParams: string = nil): System.Windows.Forms.DialogResult;
  end;

  [assembly: RuntimeRequiredAttribute(TypeOf(TChangeStatusForm))]

implementation

uses
  SysUtils;

const
  //The maximum time (in seconds) to allow for a service to change it's status
  STATUS_CHANGE_TIMEOUT = 10;

{$REGION 'Windows Form Designer generated code'}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
procedure TChangeStatusForm.InitializeComponent;
begin
  Self.components := System.ComponentModel.Container.Create;
  Self.pbChangeProgress := System.Windows.Forms.ProgressBar.Create;
  Self.timProgress := System.Windows.Forms.Timer.Create(Self.components);
  Self.SuspendLayout;
  //
  // pbChangeProgress
  //
  Self.pbChangeProgress.Location := System.Drawing.Point.Create(6, 10);
  Self.pbChangeProgress.Maximum := 5;
  Self.pbChangeProgress.Name := 'pbChangeProgress';
  Self.pbChangeProgress.Size := System.Drawing.Size.Create(280, 23);
  Self.pbChangeProgress.Step := 1;
  Self.pbChangeProgress.TabIndex := 0;
  //
  // timProgress
  //
  Self.timProgress.Interval := 1000;
  Include(Self.timProgress.Tick, Self.timProgress_Tick);
  //
  // TChangeStatusForm
  //
  Self.AutoScaleBaseSize := System.Drawing.Size.Create(5, 13);
  Self.ClientSize := System.Drawing.Size.Create(292, 44);
  Self.ControlBox := False;
  Self.Controls.Add(Self.pbChangeProgress);
  Self.FormBorderStyle := System.Windows.Forms.FormBorderStyle.FixedSingle;
  Self.Name := 'TChangeStatusForm';
  Self.ShowInTaskbar := False;
  Self.StartPosition := System.Windows.Forms.FormStartPosition.CenterScreen;
  Self.Text := 'Starting...';
  Include(Self.Closing, Self.TChangeStatusForm_Closing);
  Include(Self.Load, Self.TChangeStatusForm_Load);
  Self.ResumeLayout(False);
end;
{$ENDREGION}

procedure TChangeStatusForm.Dispose(Disposing: Boolean);
begin
  if Disposing then
  begin
    if Components <> nil then
      Components.Dispose();
  end;
  inherited Dispose(Disposing);
end;

constructor TChangeStatusForm.Create;
begin
  inherited Create;
  //
  // Required for Windows Form Designer support
  //
  InitializeComponent;
  //
  // TODO: Add any constructor code after InitializeComponent call
  //
end;

procedure TChangeStatusForm.TChangeStatusForm_Load(sender: System.Object; e: System.EventArgs);
begin
  PopulateControls;
  DoStatusChange;
end;

/// <summary>
/// Shows this form as a ModalDialog, and attempts to change the status of the
/// specified service based on the specified change type.
/// </summary>
/// <param name='pService'>A ServiceController instance to change the status for.</param>
/// <param name='pChangeType'>The type of status change to perform.</param>
/// <returns>
/// Returns the DialogResult for the form. It should be DialogResult.OK
/// if the status change completed successfully, and DialogResult.Cancel if the
/// operation timed out.
/// </returns>
class function TChangeStatusForm.ShowChangeStatusForm(
  pService: ServiceController; pChangeType: TChangeType; pStartParams: string = ''): System.Windows.Forms.DialogResult;
var
  lChangeStatusForm: TChangeStatusForm;
begin
  lChangeStatusForm := TChangeStatusForm.Create;
  lChangeStatusForm.FService := pService;
  lChangeStatusForm.FChangeType := pChangeType;
  lChangeStatusForm.FStartParams := pStartParams;
  Result := lChangeStatusForm.ShowDialog;
end;

procedure TChangeStatusForm.TChangeStatusForm_Closing(sender: System.Object;
    e: System.ComponentModel.CancelEventArgs);
begin
  timProgress.Enabled := False;
end;

/// <summary>
/// Check whether the status has been successfully changed, and if not increment
/// the progress bar. The operation will time out when the progress bar value
/// hits its maximum settings.
/// </summary>
procedure TChangeStatusForm.timProgress_Tick(sender: System.Object; e: System.EventArgs);
begin
  FService.Refresh;
  if FService.Status = GetNewStatus then
  begin
    pbChangeProgress.Value := pbChangeProgress.Maximum;
    Self.DialogResult := System.Windows.Forms.DialogResult.OK;
  end
  else
  begin
    pbChangeProgress.Value := pbChangeProgress.Value + 1;
    if pbChangeProgress.Value = pbChangeProgress.Maximum then
      Self.DialogResult := System.Windows.Forms.DialogResult.Cancel;
  end;
  pbChangeProgress.Refresh;
end;

/// <summary>
/// Return what the the ServiceControllerStatus should be once the status
/// change has completed successfully.
/// </summary>
function TChangeStatusForm.GetNewStatus: ServiceControllerStatus;
begin
  case FChangeType of
    ctStart,
    ctResume: Result := ServiceControllerStatus.Running;
    ctStop  : Result := ServiceControllerStatus.Stopped;
    ctPause : Result := ServiceControllerStatus.Paused;
  else
    //Should never get here, but the compiler complains about an undefined
    //return function.
    Result := FService.Status;
  end;
end;

/// <summary>
/// Show which service we are changing the status for, what we are attempting
/// to do, initialize the progress bar, and start the timer.
/// </summary>
procedure TChangeStatusForm.PopulateControls;
begin
  Text := Format('%s %s...', [GetChangeTypeString(FChangeType), FService.DisplayName]);
  pbChangeProgress.Step := 0;
  pbChangeProgress.Maximum := STATUS_CHANGE_TIMEOUT;
  timProgress.Enabled := True;
end;

/// <summary>
/// Return a string representing the specified TChangeType.
/// </summary>
/// <param name ="pChangeType">
/// A TChangeType enumerated type to return the string representation for.
/// </param>
class function TChangeStatusForm.GetChangeTypeString(const pChangeType: TChangeType): string;
begin
  case pChangeType of
    ctStart : Result := 'Starting';
    ctStop  : Result := 'Stopping';
    ctPause : Result := 'Pausing';
    ctResume: Result := 'Resuming';
  end;
end;

/// <summary>
/// Perform the status change.
/// </summary>
procedure TChangeStatusForm.DoStatusChange;
var
  lStartParams: array[0..0] of string;
begin
  try
    case FChangeType of
      ctStart : begin
                  if FStartParams = '' then
                    FService.Start
                  else
                  begin
                    lStartParams[0] := FStartParams;
                    FService.Start(lStartParams);
                  end;
                end;
      ctStop  : FService.Stop;
      ctPause : FService.Pause;
      ctResume: FService.Continue;
    end;
  except
    begin
      //Ensure the timer is disabled if there is an error changing the status
      timProgress.Enabled := False;
      raise;
    end;
  end;
end;

end.
