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 c
Top shelf has been a good "framework" for Windows Services.
It provides good functionality for the routine stuff that all windows services share.
Especially "install".
The basics are:
https://www.nuget.org/packages/Topshelf/
Note the nuget above can be run under NetStandard2.0.
Now below. You can create MyWindowsServiceExe.csproj .. and code it as an Exe/2.1 or Exe/3.1 (shown when you open up the csproj). Note that 2.2 is not Long Term Support anymore and I would avoid writing new code to 2.2. (off topic, but that link is https://dotnet.microsoft.com/platform/support/policy/dotnet-core )
public class LoggingService : TopShelf.ServiceControl
{
private const string logFileFullName = @"C:\temp\servicelog.txt";
private void MyLogMe(string logMessage)
{
Directory.CreateDirectory(Path.GetDirectoryName(logFileFullName));
File.AppendAllText(logFileFullName, DateTime.UtcNow.ToLongTimeString() + " : " + logMessage + Environment.NewLine);
}
public bool Start(HostControl hostControl)
{
MyLogMe("Starting");
return true;
}
public bool Stop(HostControl hostControl)
{
MyLogMe("Stopping");
return true;
}
}
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.Service<LoggingService>();
x.EnableServiceRecovery(r => r.RestartService(TimeSpan.FromSeconds(333)));
x.SetServiceName("MyTestService");
x.StartAutomatically();
}
);
}
and build/publish it to windows:
dotnet publish -r win-x64 -c Release
and then the helper methods I mentioned
MyWindowsServiceExe.exe install
(now check windows-services under control panel to see it installed) (check "if crash, what should I do" tab as well).
and finally (another helper), you can STOP the the windows-service , you can run it from the command line (outside of windows-services) This is my favorite for debugging.
MyWindowsServiceExe.exe start
You no longer need to copy-paste a lot of code to do it. All you need is to install the package Microsoft.Extensions.Hosting.WindowsServices
Then:
UseWindowsService()
to the HostBuilder. This will also configure your application to use the EventLog logger.Microsoft.NET.Sdk.Worker
(<Project Sdk="Microsoft.NET.Sdk.Worker">
).<OutputType>Exe</OutputType>
)<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
to the project file.Debug your service like a regular console application, and then run dotnet publish
, sc create ...
, etc.
That's it. This also works for .NET Core 3.0/3.1. Read more here.
The minimal code example is shown below.
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="3.1.3" />
</ItemGroup>
</Project>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace NetcoreWindowsService
{
class Program
{
static void Main()
{
new HostBuilder()
.ConfigureServices(services => services.AddHostedService<MyService>())
.UseWindowsService()
.Build()
.Run();
}
}
}
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
namespace NetcoreWindowsService
{
internal class MyService : IHostedService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger) => _logger = logger;
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The service has been started");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The service has been stopped");
return Task.CompletedTask;
}
}
}
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:
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:
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();
}
}
}
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.