4

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!

4

6 回答 6

4

这是解决方案以及我之前找不到的原因:正如我之前所说,我的类扩展了 ServiceBase 类。在我的第一次更新中,我发布了我试图以与使用 Java 解决它相同的方式来解决这个问题:通过强制转换。但是,在 C# 中,如果您在派生类中覆盖它,显然不允许您调用基方法。当我第一次发布这个问题和这个更新时,我不知道的一件事(显然是没有人想到的一件事)是 C# 包含base可用于从派生类。由于base构造函数可用于 C# 中的任何类,因此它不会出现在ServiceBase 类文档中。

一旦我了解了这一点,我就能够采用我原来的解决方案并将其修改为使用基类:

    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();
    }

当我在 Visual Studio 中玩代码并base在我的 IntelliSense 中注意到时,我发现了这一点。我点击转到它的定义,它把我送到了 ServiceBase(显然没有定义)。在注意到我的代码中没有定义 base 并且它是 ServiceBase 类的一个实例之后,我意识到它一定是某种构造函数。快速谷歌搜索后,我找到了我要找的东西。智能感知的好方法!

谢谢大家的帮助!

于 2012-07-31T16:15:18.827 回答
3

如果您想要一个没有 win32 api 的 .NET 解决方案,请查看下面的解决方案。我从 ServiceController 类继承并在 Task 内部使用 WaitForStatus() 使其成为非阻塞,然后在状态更改时引发事件。也许它需要更多测试但对我有用:

类定义

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;
    }
}

用法

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);
}
于 2021-06-28T09:11:15.860 回答
1

您可以使用 wmi 来监控事件:technet.microsoft.com/en-us/library/ff730927.aspx

于 2012-07-31T13:35:11.850 回答
1

如果您不能 PInvoke NotifyServiceStatusChange,那么您将不得不轮询该服务。例如:

ServiceController sc = new ServiceController("Some Service");
Console.WriteLine("Status = " + sc.Status);
于 2012-07-31T14:29:00.077 回答
1

ServiceController类有一个WaitForStatus方法。不过,它在内部进行轮询。

于 2012-07-30T21:16:05.613 回答
0

使用 NotifyServiceStatusChange() 时要非常小心,因为它仅在 Windows Vista/Windows 2008(及更高版本)上受支持。如果您针对以下任何平台,则无法使用该 API。(仍然有很多 XP/Windows 2000-2003 系统。)

更糟糕的是,在服务重新启动的情况下,轮询并不总是可靠的,因为如果您在非常快的系统(SSD 驱动器或虚拟机上的预缓冲 I/O)上轮询服务,服务可能会重新启动两次投票之间。

于 2013-05-30T09:37:51.363 回答