Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect/listen for service start and stop state changes

I have a C# application that uses a Windows service that is not always on and I want to be able to send an email notification when the service starts and when it shuts down. I have the email script written, but I cannot seem to figure out how to detect the service status changes.

I have been reading up on the ServiceController class and I think that the WaitForStatus() method might be what I need, but I haven't been able to find an example with it being used on a service that is not yet started. EDIT: Due to the fact that the WaitForStatus() method busy-waits and I need to be executing the rest of the program run by the service while listening for the service to start/stop, I don't think that this is the method for me, unless someone has a solution that uses this method combined with multithreading and is clean and efficient.

 

More:

  • the service will not be started by the application - the application user will be starting that directly from the Services window in the Administrative Tools.
  • the service used is not a default Windows service - it is a custom service designed for use with this application

 

Thanks for your help!

 

P.S. please note that I'm fairly new to C# and am learning as I go here.

 

UPDATE:

I have managed to get the alert email to send each time the service starts: As I continued to read through the code that I have (which I, unfortunately, cannot post here), I noticed that the class used to create the service was extending the ServiceBase class and that someone made a custom OnStart() method to override the default one. I added the necessary method calls to the new OnStart() method and it successfully sent the notifications.

I attempted to do the same thing for the OnStop() method, but that did not work out so well for me - before I continue, I would like to add that I have been programming in Java for several years, and I am very familiar with Java design patterns.

What I attempted to do, which would have worked in Java, was override the ServiceBase class's OnStop() method with one that calls the email notification, cast MyService to be of type ServiceBase and then re-call the ServiceBase class's Stop() method (NOTE: OnStop() is a protected method so it could not be called directly - the Stop() method calls OnStop() and then continues with the necessary code to stop the service). I thought that casting to type ServiceBase would force the default OnStop() method to be called, instead of my custom one.

As you may imagine, I ended up with just under 10,000 emails successfully sent to my inbox before I managed to force my computer into a hard shutdown.

What I need now is a way to either use my overridden OnStop() method and then have it call the default method, or another solution to this problem. Any and all help is much appreciated. Thanks so much.

 

FOR THOSE OF YOU WITH MULTITHREADING SOLUTIONS:

protected override void OnStart(string[] args) {
     string subject = "Notice: Service Started";
     string body = "This message is to notify you that the service " +
         "has been started. This message was generated automatically.";
     EmailNotification em = new EmailNotification(subject, body);
     em.SendNotification();

     ...INITIALIZE LISTENER FOR SERVICE STOPPING HERE...

     ...custom stuff to be run on start...
 }

Also, remember that the class that this method is called in, let's call it Service, extends the ServiceBase class.

 

UPDATE TWO:

In regards the suggestion that I use NotifyServerStatusChange I have learned that it is not permitted for the solution to use system functions, due to various reasons. To clarify, only solutions that are purely within the scope of C# and .NET are viable. Thanks, again, for your help!

like image 627
Zachary Kniebel Avatar asked Jan 15 '23 23:01

Zachary Kniebel


2 Answers

Here is the solution and why I could not find it before: As I said earlier, my class extended the ServiceBase class. In my first update, I posted that I attempted to solve this in the same way I would have solved it with Java: through casting. However, in C# casting apparently doesn't let you call the base method if you overrode it in the derived class. One of the things that I did not know when I first posted this question and this update (and clearly one of the things that no one thought of) was that C# includes the base constructor that can be used to call methods of the base class from a derived class. As the base constructor can be used for any class in C# it does not appear in the ServiceBase Class documentation.

Once I learned this, I was able to take my original solution and modify it to use the base class:

    protected override void OnStop() {
        string subject = "Notice: Service Stopped";
        string body = "This message is to notify you that the service has " +
            "been stopped. This message was generated automatically.";
        EmailNotification em = new EmailNotification(subject, body);
        em.SendNotification();
        base.OnStop();
    }

I figured this out when I was playing around with the code in Visual Studio and noticed base in my IntelliSense. I clicked to go to its definition and it sent me to ServiceBase (where it was obviously not defined). After noticing that base was not defined in my code and that it was an instance of the ServiceBase class I realized that it must have been some sort of constructor. After a quick Google search, I found what I was looking for. Way to go IntelliSense!

Thank you everyone for all your help!

like image 130
Zachary Kniebel Avatar answered Jan 28 '23 19:01

Zachary Kniebel


If you want a .NET solution with no win32 api then check out my solution below. I'm inheriting from the ServiceController class and using the WaitForStatus() inside of a Task to make it non-blocking then raising events when the status changes. Perhaps it needs more testing but works for me:

CLASS DEFINITION

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ServiceProcess; // not referenced by default

public class ExtendedServiceController: ServiceController
{
    public event EventHandler<ServiceStatusEventArgs> StatusChanged;
    private Dictionary<ServiceControllerStatus, Task> _tasks = new Dictionary<ServiceControllerStatus, Task>();

    new public ServiceControllerStatus Status
    {
        get
        {
            base.Refresh();
            return base.Status;
        }
    }

    public ExtendedServiceController(string ServiceName): base(ServiceName)
    {
        foreach (ServiceControllerStatus status in Enum.GetValues(typeof(ServiceControllerStatus)))
        {
            _tasks.Add(status, null);
        }
        StartListening();
    }

    private void StartListening()
    {
        foreach (ServiceControllerStatus status in Enum.GetValues(typeof(ServiceControllerStatus)))
        {
            if (this.Status != status && (_tasks[status] == null || _tasks[status].IsCompleted))
            {
                _tasks[status] = Task.Run(() =>
                {
                    try
                    {
                        base.WaitForStatus(status);
                        OnStatusChanged(new ServiceStatusEventArgs(status));
                        StartListening();
                    }
                    catch
                    {
                        // You can either raise another event here with the exception or ignore it since it most likely means the service was uninstalled/lost communication
                    }
                });
            }
        }
    }

    protected virtual void OnStatusChanged(ServiceStatusEventArgs e)
    {
        EventHandler<ServiceStatusEventArgs> handler = StatusChanged;
        handler?.Invoke(this, e);
    }
}

public class ServiceStatusEventArgs : EventArgs
{
    public ServiceControllerStatus Status { get; private set; }
    public ServiceStatusEventArgs(ServiceControllerStatus Status)
    {
        this.Status = Status;
    }
}

USAGE

static void Main(string[] args)
{
    ExtendedServiceController xServiceController = new ExtendedServiceController("myService");
    xServiceController.StatusChanged += xServiceController_StatusChanged;
    Console.Read();

    // Added bonus since the class inherits from ServiceController, you can use it to control the service as well.
}

// This event handler will catch service status changes externally as well
private static void xServiceController_StatusChanged(object sender, ServiceStatusEventArgs e)
{
    Console.WriteLine("Status Changed: " + e.Status);
}
like image 20
Narbs Avatar answered Jan 28 '23 18:01

Narbs