1

I develop .NET Core Console Application (which is prepared to run as Windows Service on server with Windows 10) in Visual Studio 2019 on Windows and my continuous devlivery tool is on CentOS 8 (GoCD).

When I'm publishing that application on Windows and run as service everything's going great. When I'm trying to publish on Linux and run that builded application on Windows 10 I get:

Unhandled exception. System.PlatformNotSupportedException: ServiceController enables manipulating and accessing Windows services and it is not applicable for other operating systems. at System.ServiceProcess.ServiceBase..ctor() at appIV.Scheduler.ServiceBaseLifetime..ctor(IApplicationLifetime applicationLifetime) in /opt/gocd-agent-1/go-agent-19.12.0/pipelines/app-SCHEDULER-PRODUCTION/app-SCHEDULER-PRODUCTION/appIV/appIV.Scheduler/Service/ServiceBaseLifetime.cs:line 15

Envoriement on CentOS 8:

dotnet --list-runtimes
Microsoft.AspNetCore.App 3.1.3 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.3 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

dotnet --list-sdks
3.1.201 [/usr/share/dotnet/sdk]

I publish application with the following command

dotnet publish -o /tmp/app/scheduler/bin/Release/netcoreapp3.1/win-x64/publish/ -c Release -r win-x64 --self-contained true

I was thinking that -r win-x64 parameter will be good enough to dotnet publish on Linux with Windows target, but obviously I'm wrong. Application is working as long as it's running as regular console appliaction (I can run that service as a console app with --console parameter). But for some reason I don't understand appliaction published on Linux cannot run on Windows as a service.

Class managing running as a service, where the error occurs:

using Microsoft.Extensions.Hosting;
using NLog;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;

namespace App.Scheduler
{
    public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
    private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
    private NLog.Logger _logger = LogManager.GetCurrentClassLogger();

    public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
    {
        ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
    }

    private IApplicationLifetime ApplicationLifetime { get; }

    public Task WaitForStartAsync(CancellationToken cancellationToken)
    {
        _logger.Debug("WaitForStartAsync");
        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
        {
            _logger.Debug("Run");
            Run(this); // This blocks until the service is stopped.
            _delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
            _delayStart.TrySetException(ex);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.Debug("StopAsync");
        Stop();
        return Task.CompletedTask;
    }

    // Called by base.Run when the service is ready to start.
    protected override void OnStart(string[] args)
    {
        _logger.Debug("OnStart");
        _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()
    {
        _logger.Debug("OnStop");
        ApplicationLifetime.StopApplication();
        base.OnStop();
    }
}

}

0 Answers0