A WindowsService class to expose a windows service.
This class also exposes some techniques such as
- Delegating events.
- Using Interlocked.CompareExchange to reduce the threads waiting in a timer delegate.
- The use of a System.Threading.Timer type timer.
- 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