Creating Windows Services In .NET Core – Part 1 – The “Microsoft” Way

This article is part of a series on creating Windows Services in .NET Core.

Part 1 – The “Microsoft” Way
Part 2 – The “Topshelf” Way
Part 3 – The “.NET Core Worker” Way


Creating Windows Services to do batch jobs or in general do background work used to be a pretty common pattern, but you don’t often come across them anymore due to the proliferation of cloud services such as Amazon Lambda, Azure WebJobs or Azure Functions taking their place. Personally, I’m a big fan of using Azure WebJobs these days as it basically means I can write a console application without any thought to running it in the cloud, and then with a single batch file, turn it into an automated job that can run 24/7.

But maybe you’re still on bare metal. Or maybe you have a bunch of legacy apps that are running as Windows Services that you want to convert to .NET Core, but not go the whole way to converting them to “serverless”. This is the tutorial for you.

In many respects, a Windows Service in .NET Core is exactly the same as one in .NET Framework. But there are a few little tricks that you might stumble across along the way. Furthermore, there is the “Microsoft” way of building a Windows Service which we will look at in this article, and 3rd party libraries such as TopShelf that make the process much easier (We will look at Topshelf in Part 2).

Looking to monitor your service?

Just a quick note from our sponsor elmah.io. Once your Windows Service is actually up and running, how are you intending to monitor it? elmah.io provides an awesome feature called “Heartbeats” to monitor how your background tasks are getting on, and if they are running as they should be. elmah.io comes with a 21 day, no credit card required trial so there’s no reason not to jump in and have a play.

Setup

There is no Visual Studio “template” for creating a Windows Service, so instead create a regular .NET Core console application project.

Once created, we need to install a nuget package that adds in a bunch of Windows specific API’s into .NET Core. These are API’s that are actually already available in full framework, but many of them are very specific to Windows e.x. Windows Services. For that reason they aren’t in the .NET Core base, but can be added via a single handy dandy package. (You can read more about the package here : https://devblogs.microsoft.com/dotnet/announcing-the-windows-compatibility-pack-for-net-core/)

Let’s go ahead and run the following from our Package Manager Console :

Install-Package Microsoft.Windows.Compatibility

The Code

What we are most interested in from the above Nuget is the ServiceBase class. This is a base class for writing Windows Services, and provides “hooks” for events involved in the service e.g. Start, Stop, Pause etc.

We are going to create a class in our code that does a simple logging job to a temp file, just so we can see how it works. Our code looks like :

class LoggingService : ServiceBase
{
    private const string _logFileLocation = @"C:\temp\servicelog.txt";

    private void Log(string logMessage)
    {
        Directory.CreateDirectory(Path.GetDirectoryName(_logFileLocation));
        File.AppendAllText(_logFileLocation, DateTime.UtcNow.ToString() + " : " + logMessage + Environment.NewLine);
    }

    protected override void OnStart(string[] args)
    {
        Log("Starting");
        base.OnStart(args);
    }

    protected override void OnStop()
    {
        Log("Stopping");
        base.OnStop();
    }

    protected override void OnPause()
    {
        Log("Pausing");
        base.OnPause();
    }
}

So you’ll notice we are inheriting from the “ServiceBase”, and we are just overriding a couple of events to log. A typical pattern would involve kicking off a background thread “OnStart”, and then aborting that thread “OnStop”. Heavy lifting should not be happening inside the OnStart event at all!

From our Main entry method, we want to kick off this service. It’s actually pretty easy :

static void Main(string[] args)
{
    ServiceBase.Run(new LoggingService());
}

That’s it!

Deploying Our Service

When publishing our service, we can’t just rely on a bog standard Visual Studio build to get what we want, we need to be building specifically for the Windows Runtime. To do that, we can run the following from a command prompt in the root of our project. Notice that we pass in the -r flag to tell it which platform to build for.

dotnet publish -r win-x64 -c Release

If we check our /bin/release/netcoreappX.X/publish directory, we will have the output from our publish, but most importantly we will have an exe of our application in here. If we didn’t specify the runtime, we would instead get a .NET Core .dll which we are unable to use as a Windows Service.

We can move this publish directory anywhere to install, but let’s just work directly from the publish folder for now.

For this next part, you will need to open a command prompt as an administrator. Then use the following command :

sc create TestService BinPath=C:\full\path\to\publish\dir\WindowsServiceExample.exe

The SC command is a bog standard windows command (Has nothing to do with .NET Core), that installs a windows service. We say that we are creating a windows service, and we want to call it “TestService”. Importantly, we pass in the full path to our windows service exe.

We should be greeted with :

[SC] CreateService SUCCESS

Then all we need to do is start our service :

sc start TestService

We can now check our log file and see our service running!

To stop and delete our service, all we need to do is :

sc stop TestService
sc delete TestService

Debugging Our Service

Here is where I really think doing things the Microsoft way falls down. Debugging our service becomes a real chore.

For starters our overridden methods from the ServiceBase are set to be protected. This means that we can’t access them outside our class, which makes debugging them even harder. The best way I found was to have a public method for each event that the protected method also calls. Bit confusing, but works like this :

public void OnStartPublic(string[] args)
{
    Log("Starting");
}

protected override void OnStart(string[] args)
{
    OnStartPublic(args);
    base.OnStart(args);
}

This atleast allows us to do things like :

static void Main(string[] args)
{
    var loggingService = new LoggingService();
    if (true)   //Some check to see if we are in debug mode (Either #IF Debug etc or an app setting)
    {
        loggingService.OnStartPublic(new string[0]);
        while(true)
        {
            //Just spin wait here. 
            Thread.Sleep(1000);
        }
        //Call stop here etc. 
    }
    else
    {
        ServiceBase.Run(new LoggingService());
    }
}

This to me is rough as hell involves lots of jiggery pokery to get things going.

Your other option is to do a release in debug mode, actually install the service, and then attach the debugger. This is actually how Microsoft recommends you debug your services, but I think it’s just a hell of a lot of messing about.

What’s Next

There’s actually some other really helpful things we could do here. Like creating an install.bat file that runs our SC Create  command for us, but in my opinion, the debugging issues we’ve seen above are enough for me to immediately tap out. Luckily there is a helpful library called Topshelf that takes a tonne of the pain away, and in the next part of this series we will be looking at how we can get going with that.

13 thoughts on “Creating Windows Services In .NET Core – Part 1 – The “Microsoft” Way”

  1. .NET Core 3.0 has a new template for it:
    > dotnet new worker
    Then add a reference to `Microsoft.Extensions.Hosting.WindowsServices` package.
    Now `.UseWindowsService()` in program.cs to convert your background service to a Windows NT service.

    Reply
  2. Another way to debug your service is to move the entry point (Main) method into your service and ditch the “Program.cs”.

         static void Main(string[] args)
            {
                var service = new LoggingService ();
    
                if (Environment.UserInteractive)
                {
                    // Start as console app
                    Console.WriteLine("Starting service...");
                    service.OnStart(args);
                    Console.WriteLine("Service started.");
                    
                    Console.WriteLine("Press any key to stop service.");
                    Console.ReadKey(true);
                    
                    Console.WriteLine("Stopping service...");
                    service.OnStop();
                    Console.WriteLine("Service stopped.");
                }
                else
                {
                    // Start as service
                    Run(service);    
                }
            }
    
    Reply
  3. I try with System.Threading and System.Timer to use a delay for a Windows job, the service can run, but cannot fire the event, the code compiles fine and setup ok, any sugest could help?

    Reply
  4. Thanks for the post, really helped!

    this way a made a workaround of debugging and releasing the service

     static void Main(string[] args)
            {
                var s = new TestService();
    #if DEBUG
                s.MyStart();
                s.MyPause();
                s.MyStop();
    #endif
    #if RELEASE
                ServiceBase.Run(s);
    #endif
            }
    
    Reply
  5. You know, I was researching this topic because I wanted to run a background .NET Core task in my system, and after reading this I realized…Windows Services kinda suck.

    Like, compared to a simple .exe that runs on system start. They don’t add almost anything other than complexity.

    I know having a standardized user interface to launch and stop background tasks is good and all, but why does it have to require a specific interface? It should just be, build a .exe, open the “Services” GUI, and then have an option that says “add service” and navigate to the .exe and bam, it gets registered as one.

    Reply
    • 100% agree. If you think about Azure WebJobs, this is exactly what they are. “Here’s an EXE, run it for me on a timer/continuously for me please” and that’s it. The janky nature of the SC commands etc just add such unneeded complexity.

      Reply
  6. After installing Microsoft.Windows.Compatibility (5.0.1) and pasting the code, I get error CS0246: The type or namespace name ‘ServiceBase’ could not be found. Checking the package, System.ServiceProcess.ServiceController exists, but no System.ServiceProcess.ServiceBase. Any advice?

    Reply
    • And you have the correct using? (using System.ServiceProcess;). I don’t include usings generally in my examples because intellisense is usually enough.

      Reply
  7. It would be really helpful if you provided the source code for this project. The examples here are not complete missing references etc. I’m sure with some trial and error I can get this working but a code sample would be really nice.

    Reply
  8. Saved my butt 😉

    Coming from .NET Framework 4.8 to .NET 6 there are some new ways of doing things + a lot of missing or changed packages :O

    Finally i got my ServiceBase back up and working! Thanks 🙂

    Reply

Leave a Comment