June 29, 2012

Windows Service Class

A WindowsService class to expose a windows service. This class also exposes some techniques such as
  1. Delegating events.
  2. Using Interlocked.CompareExchange to reduce the threads waiting in a timer delegate.
  3. The use of a System.Threading.Timer type timer.
  4. Implementing IDisposable
/// <summary>
/// WIP: Exposes a window service including an event 
/// that fires when the service status changes
/// </summary>
public class WindowsService : IDisposable
{
  ServiceController serviceController = null;
 
  /// <summary>
  /// Use this method to check that the named service exists 
  /// before creating a WindowsService object for it.
  /// </summary>
  /// <param name="serviceDisplayName">The user friendly name for the service</param>
  /// <param name="machineName">Machine upon which the service is created</param>
  /// <returns>true if a service by the given name and on the given machine exists</returns>
  public static bool CheckServiceExists(string serviceDisplayName, 
                                        string machineName = ".")
  {      
    ServiceController[] scs = ServiceController.GetServices(machineName);
    ServiceController scFound = scs.FirstOrDefault(
      (ServiceController sc) => sc.DisplayName == serviceDisplayName);
    return (scFound != null);
  }
 
  public WindowsService(string serviceDisplayName, string machineName = ".")
  {
    serviceController = new ServiceController(serviceDisplayName, machineName);
    timer = new Timer(new TimerCallback(TimerProc));
  }
 
  public WindowsService(ServiceController sc)
  {
    // Check sc, how?
    string str = sc.DisplayName;
    serviceController = sc;
    timer = new Timer(new TimerCallback(TimerProc));
  }
 
  private string Path
  {
    get
    {
      string path = "\\\\" + this.serviceController.MachineName +
        "\\root\\cimv2:Win32_Service.Name='" + 
        this.serviceController.ServiceName + "'";
      return path;
    }
  }
 
  public string MachineName
  {
    get { return this.serviceController.MachineName; }
  }
 
  public string ServiceName
  {
    get { return this.serviceController.ServiceName; }
  }
 
  public void Start()
  {
    this.serviceController.Start();
  }
 
  public void Stop()
  {
    this.serviceController.Stop();
  }
 
  object statusRefreshlock = new object();
 
  private ServiceControllerStatus GetStatus() // thread safe
  {
    ServiceControllerStatus scs;
    lock (this.statusRefreshlock)
    {
      this.serviceController.Refresh();
      scs = this.serviceController.Status;
    }
    return scs;
  }
 
  public ServiceControllerStatus Status // thread safe
  {
    get { return this.GetStatus(); }
  }
 
  public bool CanPauseAndContinue
  {
    get { return this.serviceController.CanPauseAndContinue; }
  }
 
  public bool CanShutdown
  {
    get { return this.serviceController.CanShutdown; }
  }
 
  public bool CanStop
  {
    get { return this.serviceController.CanStop; }
  }
 
  public string DisplayName
  {
    get { return this.serviceController.DisplayName; }
  }
 
  #region Extra properties
 
  public string Description
  {
    get
    {
      string res = "";
      ManagementPath mPath = new ManagementPath(this.Path);
      //construct the management object
      using (ManagementObject ManagementObj = new ManagementObject(mPath))
      {
        object obj = ManagementObj["Description"];
        if (obj != null)
        {
          res = obj.ToString();
        }
      }
      return res;
    }
  }
 
  public string FilePath
  {
    get
    {
      string res = "";
      ManagementPath mPath = new ManagementPath(this.Path);
      //construct the management object
      using (ManagementObject ManagementObj = new ManagementObject(mPath))
      {
        object obj = ManagementObj["PathName"];
        if (obj != null)
        {
          res = obj.ToString();
        }
      }
      return res;
    }
  }
 
 
  /// <summary>
  /// Gets or sets the start mode.
  /// </summary>
  public ServiceStartMode StartMode
  {
    get
    {
      ManagementPath mPath = new ManagementPath(this.Path);
      string mode = "";
      using (ManagementObject manager = new ManagementObject(mPath))
      {
        mode = manager["StartMode"].ToString();
      }
      ServiceStartMode res = ServiceStartMode.Disabled;
      Enum.TryParse<ServiceStartMode>(mode, out res);
      return res;
    }
    set
    {
      ManagementPath mPath = new ManagementPath("Win32_Service.Name='" + this.serviceController.ServiceName + "'");
      using (ManagementObject manager = new ManagementObject(mPath))
      {
        manager.InvokeMethod("ChangeStartMode"new object[] { value.ToString() });
      }
    }
  }
 
  #endregion Extra properties
 
  /// <summary>
  /// Send an event when the status of the windows service has changed, 
  /// Beware: this event will be invoked on a foreign thread!
  /// </summary>
  public event EventHandler<StatusChangedEventArgs> StatusChanged
  {
    add
    {
      serviceStatusChanged += value;
      ++count;
      if (count == 1) // Start listening as soon as someone registers for an event
        timer.StartPeriodic(updatePeriod);
    }
    remove
    {
      serviceStatusChanged -= value;
      --count;
      if (count == 0) // Stop listening as soon as there is no-one registered for events
        timer.Stop();
    }
  }
 
  #region ServiceStatusChanged Implementation
 
  // Normally implementation of add/remove are hidden 
  // but it is still possible to define them explicitly
  private EventHandler<StatusChangedEventArgs> serviceStatusChanged;
  int count = 0; // count the number of event handlers added
 
 
  public class StatusChangedEventArgs : EventArgs
  {
    public StatusChangedEventArgs(ServiceControllerStatus oldState, 
                                  ServiceControllerStatus newState)
    {
      OldStatus = oldState;
      NewStatus = newState;
    }
 
    public ServiceControllerStatus OldStatus
    {
      get;
      private set;
    }
 
    public ServiceControllerStatus NewStatus
    {
      get;
      private set;
    }
 
  }
 
  const int updatePeriod = 500; // update period in millseconds
 
  Timer timer = null;
  // prevStatus only ever accessed in the TimerProc method by one thread at a time
  ServiceControllerStatus prevStatus = ServiceControllerStatus.Stopped;
  int timerProcInUse = 0;
 
  // The Timer delegate to be invoked on a callback (from 
  // another thread)
  private void TimerProc(object obj)
  {
    // Only allow one thread to enter but dont block other threads 
    // they will simply exit, but this does not matter 
    // as only one update at a time is required.
    // This will stop them running into each other if the 
    // PC slows down or when debugging
    if (System.Threading.Interlocked.CompareExchange(
        ref timerProcInUse, 1, 0) == 0)
    {
      // Careful. TimeOut callback occurs on another thread
      // GetStatus() is thread safe
      ServiceControllerStatus newStatus = this.GetStatus(); 
      if (newStatus != this.prevStatus)
      {
        this.prevStatus = newStatus;
        if (this.serviceStatusChanged != null)
        {
          this.serviceStatusChanged(this, 
            new StatusChangedEventArgs(prevStatus, newStatus));
        }
      }
      // No longer in use
      timerProcInUse = 0;
    }
  }
 
  #endregion ServiceStatusChanged Implementation
 
  #region Dispose Implementation
 
  private void DisposeTimer()
  {
    if (this.timer != null)
    {
      this.timer.Dispose();
      this.timer = null;
    }
  }
 
  // Use C# destructor syntax for finalization code.
  ~WindowsService()
  {
    Debug.Assert(false"This object was not disposed of");
    Dispose(false);
  }
 
  private bool m_Disposed = false;
 
  //Implement IDisposable.
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
 
  protected virtual void Dispose(bool disposing)
  {
    if (m_Disposed)
      return;
 
    // leanup unmanaged resources in WindowsService
    DisposeTimer();
 
    m_Disposed = true;
  }
 
  #endregion Dispose Implementation
 
}
Uses the System.Threading.Timer extension methods

No comments: