Services

Using Custom Commands with Windows Services and PowerShell

A feature I recently came across when reading through the MSDN documentation was the ability to implement “custom commands” for windows services.  After reading about it, I wish I would have known about it sooner.

A custom command is a way to essentially send a signal to a service that you want it to process a command.  The command can be any integer from 128 to 255, but has no parameters, and returns no value.

There are a few situations where I immediately thought this may be useful.

1. A service health check.  I’ve seen in several scenarios in the past where at definite intervals a windows service will do something to say it is still alive.  This could be used to immediately force the service to do a health check, log out to a file, etc.

2. A service wake up.  For some windows services, they will just poll some sort of queue to see if work is available.  If it is not they will sleep for a given amount of time.  While this works, if someone is trying to get something done, the service may not respond to the request for due to the wait time.  Implementing a command to wake up the service could have it check the queue for work to process immediately.

Implementing a custom command on a service is very simple.  In a service class that inherits from ServiceBase, simply override the OnCustomCommand method.

Here’s a very simple bit sample code I wrote to demonstrate this functionality.

public enum CustomCommands
{
    WakeUp = 128,
}

private void Log(string text)
{
    File.AppendAllLines("C:\\wakeupservice.log", new[] { string.Format("[{0:yyyy-MM-dd HH:mm:ss}] - {1}", DateTime.Now, text) });
}

protected override void OnCustomCommand(int command)
{
    switch ((CustomCommands)command)
    {
        case CustomCommands.WakeUp:
            Log("Wake up!");
            break;
    }
}

Now to actually execute the command.  This can be done through the ServiceController class, or if you prefer to do it through the command line, can be done through PowerShell as well.

(Get-Service "CustomCommandService").Start()
(Get-Service "CustomCommandService").ExecuteCommand(128)
(Get-Service "CustomCommandService").Stop()

My log file now shows the following:

[2013-07-19 23:34:31] - Started!
[2013-07-19 23:35:04] - Wake up!
[2013-07-19 23:35:12] - Stopped!

That’s all there is to this post.  I hope you find this useful!

A full solution is available on my BitBucket samples repository.

Advertisements

Debugging a Windows Services

Have you ever had this message when writing a windows service in Visual Studio?

Cannot start service from the command line or a debugger.  A Windows Service must first be installed (using installutil.exe) and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.

I was a bit surprised the first time I hit the debug button in Visual Studio and ended up having to manually attach to the service every time I wanted to debug it.  After doing this over and over again, I learned a nice little trick. I like to put inside a lot of windows services I write as it easily enables me to get past that pesky error and debug straight from the IDE.

// If the debugger is attached, the program runs inline rather
// than it being run as a service.
MyService serviceToRun = new MyService();

#if DEBUG
if (Debugger.IsAttached)
{
    serviceToRun.Start(args);

    while (Debugger.IsAttached)
    {
        Thread.Sleep(1000);
    }
}
else
#endif
{
    ServiceBase.Run(new[] { serviceToRun });
}

Essentially after the program executes, it checks if the debugger is attached.  If it is, rather than running as a service it calls directly into the service to execute and continually checks for the debugger to detach.  Once it has the program ends. The conditional compile directive ensures that this behavior will no longer be possible once a Release build is made.

The Start method is a public method that simply calls to the overriden OnStart method for the service.

You can see a full VS solution on my BitBucket repository.