Creating a Windows Service with .NET Core 3.0

In a previous version of a Professional C# book I’ve written a complete chapter on how to create Windows Services with the .NET Framework. Using .NET Core 3.0, it’s a lot easier to create Windows Services: just a single line of code is needed to convert a background worker to a Windows Service.

See Creating a Windows Service with .NET 6 for a new version!

Windows Services

Create a Worker

With .NET Core 3.0, a background worker can be created using Visual Studio or the dotnet CLI command dotnet new worker.

With this template, a Program class is created that uses the Host class. The method CreateDefaultBuilder is used to setup the dependency injection container, configuration, and logging. The dependency injection container managed by the Host class is configured by invoking the method ConfigureServices. In the generated code, the extension method AddHostedService is used to register a background class that implements the interface IHostedService. This interface is indirectly implemented by the Worker class by deriving from the base class BackgroundService. The interface IHostedService defines the methods StartAsync and StopAsync. Adding a hosted service, invoking the Run method of the host starts the host and in turn invokes the startup of the IHostedService.

public class Program
{
  public static void Main(string[] args)
  {
    CreateHostBuilder(args).Build().Run();
  }

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureServices((hostContext, services) =>
      {
        services.AddHostedService<Worker>()
          .Configure<EventLogSettings>(config =>
          {
            config.LogName = "Sample Service";
            config.SourceName = "Sample Service Source";
          });
        });
    }

The Host class is also used by ASP.NET Core 3.0 Web projects. The WebHost class from .NET Core 2.0 is replaced by the more generic Host class.

The Worker class derives from the class BackgroundService. BackgroundService implements the interface IHostedService and defines the abstract method ExecuteAsync. This abstract method is called by the StartAsync method in the BackgroundService. StartAsync is defined by the IHostedService interface. With the implementation of the Worker class, ExecuteAsync uses an endless loop (until cancellation is requested) and writes a log message once a second.

public class Worker : BackgroundService
{
  private readonly ILogger<Worker> _logger;

  public Worker(ILogger<Worker> logger)
  {
    _logger = logger;
  }

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    while (!stoppingToken.IsCancellationRequested)
    {
      _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
      await Task.Delay(1000, stoppingToken);
    }
  }
}

The main functionality for the Host class is creating the dependency injection container, configuration, and logging. Using CreateDefaultBuilder, configuration is read from the configuration files appsettings.json, appsettings.{env.EnvironmentName}.json, environmental variables, and the command line.

Source Code Host class

Logging configuration is read from the section Logging within the configuration settings. Using the worker template, the configuration file appsettings.json defines logging based on the log level – with Microsoft sources to turn on logging for the Warning level, with the exception of Microsoft.Hosting.Lifetime: here logging is turned on for the Information level. The default configuration is also for the Information level:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

If the application runs on a Windows system, the method CreateDefaultBuilder also adds logging to the Windows event log and sets a filter provider to only log warnings and more critical issues to this provider.

Running the application, log information is written to the console. The worker writes a message every second.

info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:14 +02:00
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\github\MoreSamples\DotnetCore\WindowsServiceSample\WindowsServiceSample
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:15 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:16 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:17 +02:00
info: WindowsServiceSample.Worker[0]
      Worker running at: 10/13/2019 10:21:18 +02:00
info: WindowsServiceSample.Worker[0]

Convert to a Windows Service

To make a Windows Service of this, you just need to add the NuGet package Microsoft.Extensions.Hosting.WindowsServices, and add the method invocation UseWindowsService to the IHostBuilder fluent API:

public static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureLogging(
      options => options.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Information))
    .ConfigureServices((hostContext, services) =>
    {
      services.AddHostedService<Worker>()
        .Configure<EventLogSettings>(config =>
      {
        config.LogName = "Sample Service";
        config.SourceName = "Sample Service Source";
      });
    }).UseWindowsService();

To see information level logging in the Windows event log, the filter is explicitly applied with the ConfigureLogging method used with the host builder.

Installing and Managing the Windows Service

After building the application, the new Windows Service can be published using dotnet publish (or using Visual Studio):

dotnet publish -c Release -o c:\sampleservice

To control Windows Services, the sc command can be used. Creating a new Windows Service is done using sc create passing the name of the service and the binPath parameter referencing the executable:

sc create “Sample Service” binPath=c:\sampleservice\WindowsServiceSample.exe

The status of the service can be queried using the Services MMC, or with the command line sc query:

sc query “Sample Service”

After the service is created, it is stopped and need to be started:

sc start “Sample Service”

To stop and delete the service, the sc stop and sc delete commands can be used.

After starting the service, log information can be seen with the Windows Event Viewer:

Windows Event Viewer Log

Web Application as Windows Service

What about hosting Kestrel as a Windows Service? There’s not a lot difference – the package Microsoft.Extensions.Hosting.WindowsServices needs to be added, and the API UseWindowsService invoked.

A Web API project can be created using dotnet new api. This template creates an API returning random weather information.

What’s changed to run the API as a Windows Service is a call to UseWindowsService, and a configuration to what port number the server should listen to. Port 80 is not used to not get in conflict with a local IIS configuration.

public class Program
{
  public static void Main(string[] args)
  {
    CreateHostBuilder(args).Build().Run();
  }

  public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
      .ConfigureServices(services =>
      {
        services.Configure<EventLogSettings>(config =>
        {
          config.LogName = "Sample API Service";
          config.SourceName = "Sample API Service Source";
        });
      })
      .ConfigureWebHostDefaults(webBuilder =>
      {
        webBuilder.UseStartup<Startup>();
      })
      .ConfigureWebHost(config =>
      {
        config.UseUrls("http://*:5050");
      }).UseWindowsService();
}

Now the service can be build, published, and configured as a Windows Service. Opening a browser to reference the configured port with the controller route WeatherForecast returns JSON information from the API service:

http://localhost:5050/WeatherForecast

Weather Forecast

Accessing the Windows Service from a different system, the Firewall needs to be configured to allow accessing this port from the outside.

Take away

The Host class which allows configuration, logging, and dependency injection services configuration in one place, also offers an easy way to create Windows Services. Adding the NuGet package Microsoft.Extensions.Hosting.WindowsServices along with the extension method UseWindowsService practically is all what’s needed.

This way, background functionality based on the worker template, but also hosting a Kestrel server for offering ASP.NET Core Web applications is an easy task.

While Windows Services are only offered on Windows Systems, similar functionality can be offered on Linux systems. On Linux, the NuGet package Microsoft.Extensions.Hosting.SystemD along with the extension method UseSystemD can be used. Using the UseWindowsService API call on a Linux system doesn’t break the server throwing an exception, but instead it just does nothing on a Linux system. Similarly invoking UseSystemD on Windows, nothing is done. This way it’s easy to create both a Windows Service and a SystemD daemon.

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

You can get the complete sample code.

Enjoy learning and programming!

Christian

Links

Hosting DI Container with .NET Core 3.0

Source Code Host class

More information on C# and programming .NET Core applications is in my book Professional C# 7 and .NET Core 2.0, and in my workshops.

Windows Cleaners Image ID 24554590 © Andrey Koturanov | Dreamstime.com

35 thoughts on “Creating a Windows Service with .NET Core 3.0

  1. What will be the same application behavior in Linux?
    In .net core 2.1 i have used PeterKottas.DotNetCore.WindowsService via which i have same application running as windows service in Windows Platform and have setup same application as Linux Service via systemd/systemctl funtionality

    Like

    1. Hi Adam,

      if you need some functionality started when the operating system starts you can create a Windows Service, without the need for a user to login to the system. An example is background functionality that can e.g. check for files in a specific folder and act on the files received. Internet Information Server itself starts up as a Windows Service. You can directly host the Kestrel server to start on your Windows system without the need for IIS. You can also create a listener using GRPC with .NET Core 3. If you start “Services” on your system (or use the command line “sc query” you’ll find many services running, e.g. Windows Time to update the time, Windows Push Notifications to receive notifications, AdobeUpdateService to check if updates are available for the Adobe tools, and many more.

      Christian

      Like

  2. Hi Christian,

    I added  use the package Microsoft.Extensions.Hosting.WindowsService from nugget package manager as suggested but when I go to add .usewindowsservice() in CreateHostBuilder I can’t add it. It doesn’t appear in the intelligence.

    Please guide..

    Thanks

    Like

    1. I ran into this too. What I did to resolve it was run:
      Uninstall-Package Microsoft.Extensions.Hosting
      Install-Package Microsoft.Extensions.HOsting.WindowsServices

      Like

  3. NOTE: When using Powershell to install a Windows Service, you must use `sc.exe create …` instead of just `sc create …`. I had to go and find this answer. `sc` alone is for the traditional command line.

    Liked by 1 person

    1. Thanks for the information. I just tried it out with Powershell of the new Windows Terminal, but here `sc` is working as well.

      The problem indeed is with other PowerShell environments. Using the older “Windows PowerShell” environment, `sc` is a cmdlet of PowerShell for Set-Content. That’s why `sc.exe` is needed instead.

      `sc` invokes \System32\sc.exe if not overwritten.

      Like

      1. Interesting. I thought I had the later version installed. I’ll have to look at that. Thanks!

        Like

  4. Great article, but, at first, I could not get the ConfigureWebHost to appear in intelligence. Turned out that my project was a console type. Changed it to “” It worked.

    Add in case someone else had this issue.

    Like

  5. Hi, in all my windows service i have something like a OnStart and OnStop method. Are they gone, or how can get them in a .net core application ?

    Like

  6. Thanks for the great article! I spent 3 days trying to get a .NET5 Windows Service going with a self-hosted SignalR streaming hub before ending up here and realising it was all about the Program and Startup classes. So, thanks!

    Like

  7. Ciao Christian
    I read your fantastic article, simple and very clear: I would like to ask you is it possible to have a simple starting example for the new .Net 6 in particular for a WebApplication as a windows service?
    Grazie!

    Like

      1. Chris can you please do a tutorial for https based .net core api as a windows service.. i am stuck with this issue from past 2 days. The http works just fine but https wont load. I already have the certificates installed.

        Your help will be highly appreciated.

        Like

  8. Hi, I use .Net Core Web Api with Net Core 5, and i was write Program.cs like code bellow
    public class Program
    {
    public static void Main(string[] args)
    {
    CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
    webBuilder.UseStartup();
    }).ConfigureWebHost(config =>
    {
    config.UseUrls(“http://*:5050”);

    }).UseWindowsService();
    }
    but, when start my windows service API not working but when run manually helperService.exe it is work perfect. Do you know what the problem may be?

    Like

  9. Can I please ask about controlling the services that you create with .net core.
    I followed your example above but found it could not accept command line arguments from the SC command.
    Also when I tried to start the service using services.msc it ignored anything entered into the Start Parameters box.
    I wanted my service to work with the windows scheduler and have it start with different values depending on when it was started. But I have so far been unable to have the service take the arguments from the commandline in .bat files.
    If you could help I would appreciate it.

    Like

    1. To clarify, its when calling SC Start that I can’t get the service to see the arguments…
      The docs for SC say I should be able to do:

      sc [] start []

      but how do I get at the ServiceArguments.

      Like

      1. Thanks, but I have tried that and it didn’t work when doing SC Start or when starting from Services.msc …it does not pick up the command line args or the Start parameters.

        [I should say I am running on windows server 2016 and have tried this with .net core 3.1 and 5.0.]

        So, for example, this does not work:

        SC START MyService Argvalue1 Argvalue2

        All I get is arg[0] which is the path of the executeable.

        The only time it picks up other args is on SC Create..eg:

        sc create “MyService” binpath=”C:\MyService\MyService.exe Arg1 Arg2″

        I was hoping I could SC Stop and then SC Start the service with different Arg values..
        I’m starting to think I would need to revert to using a.net framework instead of .net core

        Like

  10. Defining the command line arguments for the application to start can be defined with the service (sc create). You can check the service configured with Windows – several services use the same binary but with different arguments. With my new blog article, I also mentioned this – but you’ve already succeeded with that, accessing the command line arguments from the service configuration.

    You can communicate with a running service to do some actions by creating a pipe, and sending custom commands to the pipe – or send HTTP requests.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.