Search code examples
c#.netwindows.net-corewindows-services

How to make a Windows Service from .NET Core 2.1/2.2


Recently I had a need to convert a .NET Core 2.1 or 2.2 console application into a Windows Service.

As I didn't have a requirement to port this process to Linux, I could dispense with the multiple platform solutions that I had seen on Stack Overflow that dealt with any combination of .NET Framework, .NET Standard and .NET Core.


Solution

  • In this post I will describe the steps required to set up a .NET Core 2.1 or 2.2 process as a Windows Service.

    As I have no requirement for Linux, I could look for a solution that was Windows-specific.

    A bit of digging turned up some posts from Steve Gordon (thanks!), in particular where he presents the Microsoft.Extensions.Hosting package and Windows hosting (click here for post and here for his GitHub sample).

    Here are the steps required:

    • First create a .NET Core console application.
    • Set the language version to at least 7.1 to support async Task for the Main method.  (Access the language version from the project settings->Build->Advanced->Language Settings.
    • Add the Microsoft.Extensions.Hosting and the System.ServiceProcess.ServiceController packages.
    • Edit the project .csproj file and include in the PropertyGroup: <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
    • Ensure you have in the PropertyGroup  <OutputType>Exe</OutputType>

    Now go to Program.cs and copy the following:

    using System.Diagnostics;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace AdvancedHost
    {
        internal class Program
        {
            private static async Task Main(string[] args)
            {
                var isService = !(Debugger.IsAttached || args.Contains("--console"));
    
                var builder = new HostBuilder()
                    .ConfigureServices((hostContext, services) =>
                    {
                        services.AddHostedService<LoggingService>();
                    });
    
                if (isService)
                {
                    await builder.RunAsServiceAsync();
                }
                else
                {
                    await builder.RunConsoleAsync();
                }
            }
        }
    }
    

    This code will support interactive debugging and production execution, and runs the example class LoggingService.

    Here is a skeleton example of the service itself:

    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Collections.Concurrent;
    
    namespace AdvancedHost
    {
        public class LoggingService : IHostedService, IDisposable
        {
    
            public Task StartAsync(CancellationToken cancellationToken)
            {
                // Startup code
    
                return Task.CompletedTask;
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                // Stop timers, services
                return Task.CompletedTask;
            }
    
            public void Dispose()
            {
                // Dispose of non-managed resources
            }
        }
    }
    

    The final two files necessary to complete the project:

    File ServiceBaseLifetime.cs:

    using Microsoft.Extensions.Hosting;
    using System;
    using System.ServiceProcess;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace AdvancedHost
    {
    
        public class ServiceBaseLifetime : ServiceBase, IHostLifetime
        {
            private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
    
            public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
            {
                ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
            }
    
            private IApplicationLifetime ApplicationLifetime { get; }
    
            public Task WaitForStartAsync(CancellationToken cancellationToken)
            {
                cancellationToken.Register(() => _delayStart.TrySetCanceled());
                ApplicationLifetime.ApplicationStopping.Register(Stop);
    
                new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
                return _delayStart.Task;
            }
    
            private void Run()
            {
                try
                {
                    Run(this); // This blocks until the service is stopped.
                    _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
                }
                catch (Exception ex)
                {
                    _delayStart.TrySetException(ex);
                }
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                Stop();
                return Task.CompletedTask;
            }
    
            // Called by base.Run when the service is ready to start.
            protected override void OnStart(string[] args)
            {
                _delayStart.TrySetResult(null);
                base.OnStart(args);
            }
    
            // Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
            // That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
            protected override void OnStop()
            {
                ApplicationLifetime.StopApplication();
                base.OnStop();
            }
        }
    }
    

    File ServiceBaseLifetimeHostExtensions.cs:

    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace AdvancedHost
    {
    
        public static class ServiceBaseLifetimeHostExtensions
        {
            public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
            {
                return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
            }
    
            public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
            {
                return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
            }
        }
    }
    

    In order to install, run or delete the service I use the 'sc' utility:

    sc create AdvancedHost binPath="C:\temp\AdvancedHost\AdvancedHost.exe"
    

    where AdvancedHost is the service name and the value for binPath is the compiled executable.

    Once the service is created, to start:

    sc start AdvancedHost
    

    To stop:

    sc stop AdvancedHost
    

    And finally to delete (once stopped):

    sc delete AdvancedHost
    

    There are many more features contained in sc; just type 'sc' alone on the command line.

    The results of sc can be seen in the services Windows control panel.