Search code examples
c#windowsserviceworker

Starting a c# service with sc


Im writing a small selfcontained service for windows and macos using worker service template in c# visual studio.

Its using the same Codebase hence the check in the Program.cs

I've written the service, and it works on windows, when started from within visual studio.

I've published it using

dotnet publish .\WorkerServiceTest2\ -c Release -r win-x64 -- self-contained true /p:PublishSingleFile=true /p:PublishedTrimmed=true

and tried to install it using

runas /user:MYUSERNAME "sc.exe create WorkerServiceTest2 c:\Users\MYYUSERNAME\Documents\bla\bla\bla\WorkerServiceTest2.exe"

But it does not show up in the services list, and

sc.exe start WorkerServiceTest2 

says this service is not installed.

Is there anywhere i can see how the sc.exe create worked out ? Or perhaps someone can see what I'm doing wrong ?

Sincerely Thankyou

My Service Program.cs looks like this

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Runtime.InteropServices;

namespace WorkerServiceTest2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
                Console.WriteLine("WinOS");
                CreateHostBuilderWin(args).Build().Run(); 
            } else
            {
                Console.WriteLine("MacOS");
                CreateHostBuilderMac(args).Build().Run();
            }

        }

        private static void configureServices(HostBuilderContext context, IServiceCollection services)
        {
            services.AddHostedService<Worker>();
        }

        public static IHostBuilder CreateHostBuilderWin(string[] args) =>
           Host.CreateDefaultBuilder(args)
           .UseWindowsService()
           .ConfigureServices((hostContext, services) =>
           {
               services.AddHostedService<Worker>();
           });

        public static IHostBuilder CreateHostBuilderMac(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .ConfigureServices(configureServices);
    }

}

My Worker.cs looks like this

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
using WorkerServiceTest2.SocketService;

namespace WorkerServiceTest2

{
    public class Worker : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                //Her skal business logic være.
                SocketServer socketServer = new SocketServer();
                await socketServer.start();
            }
        }
    }
}


Solution

  • This is a script that you can use. It will check if the service is installed or not. If it already exists, it will uninstall it and install the new one. Save it as MyScript.ps1 (or your own preference) and run like:

    .\MyScript.ps1 -serviceName name_of_service -serviceUsername some_username -servicePassword some_password -binaryPath "C:\yourProgram.exe"
    

    Script:

    # Sample: howto run ps-script from power-shell:
    #.\Install-WindowsService_v3.ps1 -serviceName aTestservice -serviceUsername some_username -servicePassword some_password -binaryPath "C:\yourProgram.exe"
    
    param
         (
         [string]$serviceName,
         [string]$serviceUsername,
         [string]$servicePassword,
         [string]$binaryPath,
         [string]$startupType='Automatic',
         [string]$dependsOn
         )
    
    $secpasswd = ConvertTo-SecureString  $servicePassword -AsPlainText -Force
    
    Write-Output "########################################"
    Write-Output "Starting installation of windows service."
    
    Write-Output "[serviceName] = $serviceName"
    Write-Output "[serviceUsername] = $serviceUsername" -verbose
    Write-Output "[binaryPath] = $binaryPath"
    
    #Check Parameters
    if (!$binaryPath) { throw "[binaryPath] parameter missing" }
    if ((Test-Path $binaryPath)-eq $false)
    {
        Write-Output "Path doesn't exist: $binaryPath"
        Write-Output "Service will not be installed."
        throw [System.IO.FileNotFoundException] "$binaryPath doesn't exist."
    }
    
    # verify if the service already exists, and if yes remove it first
    if (Get-Service $serviceName -ErrorAction SilentlyContinue)
    {
        Stop-Service -Name $serviceName
        # using WMI to remove Windows service because PowerShell does not have CmdLet for this
        $serviceToRemove = Get-WmiObject -Class Win32_Service -Filter "name='$serviceName'"
        $serviceToRemove.delete()
        Write-Output "Service $serviceName was stopped and uninstalled."
    }
    else
    {
        Write-Output "Service didn't exist on the server"
    }
    
    
    if ($startupType -eq "AutomaticDelayedStart" ) 
    { 
        $startupType = "Automatic"
        $enableDelayed = "true" 
    }
    
    
    Write-Output "Installing service"
    
    # creating credentials which can be used to run my windows service
    $mycreds = New-Object System.Management.Automation.PSCredential ($serviceUsername, $secpasswd)
    
    # creating windows service using all provided parameters
    New-Service -name $serviceName -binaryPathName $binaryPath -displayName $serviceName -startupType $startupType -credential $mycreds -DependsOn $dependsOn
    
    
    # Set "automatic delayed" after service was installed, since it is not a valid argument when using "New-Service"
    if ($enableDelayed -eq "true" ) 
    {   
        $command = "sc.exe config $serviceName start= delayed-auto"
        $Output = Invoke-Expression -Command $Command -ErrorAction Stop
        if($LASTEXITCODE -ne 0){
           Write-Host "$Computer : Failed to set $serviceName to delayed start. 
            More details: $Output" -foregroundcolor red
           $failedcomputers +=$ComputerName
        } else {
           Write-Host "$Computer : Successfully changed $serviceName  
            to delayed start" -foregroundcolor green
           $successcomputers +=$ComputerName
        }
    }
    
    # verify if the service exists after installation
    if (Get-Service $serviceName -ErrorAction SilentlyContinue)
    {
        Write-Output "Installation complete."
    }
    else
    {
        throw "Installation failed."
    } 
    Write-Output "########################################"
    

    Also, in all my application I start them up like so:

        static async Task Main(string[] args)
        {
            isService = !(Debugger.IsAttached || args.Contains("--console"));
            IWebHost host = CreateWebHostBuilder(args).Build();
    
            if (isService)
            {
                var hostService = new MyCustomWebService(host);
                ServiceBase.Run(hostService);
            }
            else
            {
                await host.RunAsync();
            }
        }
    
    
    public class MyCustomWebService: WebHostService
    {
        private ILogger<MyCustomWebService> logger;
    
        public MyCustomWebService(IWebHost host) : base(host)
        {
            var loggerFactory = host.Services.GetService<ILoggerFactory>();
            logger = loggerFactory.CreateLogger<MyCustomWebService>();
            logger.LogInformation("Starting...");
        }
    
        protected override void OnStopped()
        {
            logger.LogInformation("Will stop now.");
            base.OnStopped();
        }
    }
    

    It requires Microsoft.AspNetCore.Hosting.WindowsServices

    Further recommended reading: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-5.0&tabs=visual-studio

    https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.windowsservices?view=aspnetcore-5.0