3 September 2017

Running a .Net Core console application as a Windows Service

Building an application to run as a Windows Service is pretty straightforward with the .Net Framework. You can either override the ServiceBase class in a console application or use an abstraction library like TopShelf to simplify the implementation. Whatever route you take, you have a programming model that covers the full life cycle of the service, from initial registration to running, pausing and stopping.

There is no direct support for creating Windows Services in .Net Core. The ServiceBase class has been left out of the implementation, presumably because it contains too much Windows implementation detail for what is supposed to be a cross-platform framework.

ASP.Net Core does expose an API that allows you to run web applications as Windows Services, but this cannot be applied to simple console applications. If you want to create a service that is not running ASP.Net then you have three main options:

  • Using a service manager like NSSM to manage the registration and running of the service.
  • Hacking P\Invoke in .Net Core to make the necessary Windows calls directly
  • Implementing your application as a .Net Standard library and hosting it in a .Net Framework service application

Using a service manager

The most direct way of creating a Windows Service in .Net Core is to create a simple console application and use an external service manager like NSSM to take care of the service aspects. You don’t even have to compile your .Net Core console application for Windows as NSSM can be configured to call a batch file.

To install a .Net Core application as a windows service just create a batch file called run.bat in your application root that contains the following command:

dotnet run

You can register this batch file as a windows service called “testservice” by dropping NSSM.EXE into your application directory and running the following:

nssm install testservice

This will display the installation dialog where you enter the full path to your run.bat file in the Path field.

The catch with using an external service manager is there is no direct support for handling aspects of the service lifecycle beyond registering and starting the service. Most importantly, there’s no equivalent of the OnStop event in ServiceBase that allows you to clean up resources gracefully when a service is stopped. There isn’t even a reliable event that you can hook into when the process ends or console window closes.

With an NSSM-based service the only way to hook into a service stop is to intercept the CTRL+C command – this is available in .Net Core using the Console object as shown below:

Console.CancelKeyPress += Console_CancelKeyPress;

private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
    // Put your clean-up code here
}

When you register a service using NSSM make sure that it is configured to issue a CTRL+C command when shutting down the service. This is done by default but you can check it by looking at the ShutDown tab on the service registry dialog:

Note that although this allows you to clean up when a service is stopped, this approach can only support a rudimentary service that can be switched on and off. There’s still no support for other aspects of the service lifecycle including pausing, continuing and responding to power events.

Using P/Invoke to mimic ServiceBase

Another option is to directly mimic ServiceBase by wrapping a bunch of P/Invoke calls into a .Net Standard library. There is a basic implementation of this available that allows you to implement a self-contained Windows Service application that handles registration as well as exposing Start() and Stop() methods. It doesn’t currently support any other events but these can always be added if you are feeling particularly brave.

Clever though this approach may be, it feels a little like a thought experiment rather than a genuinely compelling solution. If you embed calls to the guts of Windows into a .Net Core application then you are effectively kissing goodbye to any cross-platform capability. This does seem to defeat the object of using .Net Core in the first place?

Compromising with a .Net Framework service host

If you want your implementation to respond to the full range of service events then you currently need to implement it as a .Net Framework application that derives from ServiceBase. You can still implement your functionality as a .Net Standard 2.0 library and just use a lightweight .Net 4.6.1 based application as a host for the application.

This could be seen as a neat compromise. You are implementing your functionality in a cross-platform library and keeping your options open, but you are still able to implement a Windows Service that can respond to the full range of service events.

Filed under .Net Core, C#, Net Framework.