Coder Social home page Coder Social logo

dasmulli / dotnet-win32-service Goto Github PK

View Code? Open in Web Editor NEW
452.0 31.0 60.0 1.03 MB

Helper classes to set up and run as windows services directly on .net core. A ServiceBase alternative.

License: MIT License

C# 100.00%
windows-service dotnet-core dotnet-standard

dotnet-win32-service's Introduction

.NET Standard-based Windows Service support for .NET

This repo contains a library for running a .NET Core application as Windows service without the need for a wrapper assembly or the full (desktop) .NET Framework. It is built using P/Invoke calls into native Windows assemblies.

Usage scenarios include:

  • Running on Windows Nano Server (no full framework but can run Windows services)
  • Shipping a modern service application using the latest .NET Core version to systems where you cannot upgrade to new versions of .NET, but you want to use new framework features.
  • Build truly portable applications that can for example run as service on Windows and as daemon on Linux, just using runtime checks / switches

How to use the example application

Prerequisites:

  • .NET Core SDK 2.0.3 or higher (.csproj based tooling)
  • Windows machine
  • Elevated command prompt: Run cmd as administrator.
> cd samples\TestService
> dotnet restore
> dotnet run --register-service --urls http://*:5080
...
Successfully registered and started service "Demo .NET Core Service" ("Demo ASP.NET Core Service running on .NET Core")

Open http://localhost:5080 in a browser. You should see Hello world.

The "Services" administrative tool should show the service: running service running service

> dotnet run --unregister-service
...
Successfully unregistered service "Demo .NET Core Service" ("Demo ASP.NET Core Service running on .NET Core")

The service may show up as disabled for some time until all tools accessing the Windows services APIs have been closed. See this Stack Overflow question.

API

Add a NuGet package reference to DasMulli.Win32.ServiceUtils.

Write a Windows service using:

using DasMulli.Win32.ServiceUtils;

class Program
{
    public static void Main(string[] args)
    {
        var myService = new MyService();
        var serviceHost = new Win32ServiceHost(myService);
        serviceHost.Run();
    }
}

class MyService : IWin32Service
{
    public string ServiceName => "Test Service";

    public void Start(string[] startupArguments, ServiceStoppedCallback serviceStoppedCallback)
    {
        // Start coolness and return
    }

    public void Stop()
    {
        // shut it down again
    }
}

You can then register your service via sc.exe (run cmd / powershell as administrator!):

sc.exe create MyService DisplayName= "My Service" binpath= "C:\Program Files\dotnet\dotnet.exe C:\path\to\MyService.dll --run-as-service"

Now go to Services or Task Manager and start your service.

sc will install your service as SYSTEM user which has way to many access rights to run things like web apps. See its reference for more options.

If you want to get rid of it again, use:

sc.exe delete MyService

You can also create a service that registers itself like the example provided by taking a look at the sample source.

Also take a look at the ASP.NET Core MVC sample which has additional logic to set the correct working directory. When running it in development and not from the published output, be sure to pass --preserve-working-directory to it when registering so that it will run from the project directory (e.g. run dotnet run --register-service --preserve-working-directory from and administrative command prompt).

Pause & Continue support

To create a service that supports being paused and later continued or stopped, implement IPausableWin32Service which extends IWin32Service by Pause() and Continue() methods you can use to implement your pause & continue logic.

Limitations

  • No custom exceptions / error codes. Everything will throw a Win32Exception if something goes wrong (Its message should be interpretable on Windows).
  • All exceptions thrown by the service implementation will cause the service host to report exit code -1 / 0xffffffff to the service control manager.
  • Currently, no direct support for services supporting commands such as power event and system shutdown
    • However, consumers can now use IWin32ServiceStateMachine to implement custom behavior. Copy SimpleServiceStateMachine as a starting point to implement extended services.

dotnet-win32-service's People

Contributors

dasmulli avatar jnm2 avatar ravualhemio avatar rydergillen-compacsort avatar trapov avatar trydis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dotnet-win32-service's Issues

Error 109: The pipe has been ended while trying to stop service

I'm facing a little problem with ending my service. I have an aplication with main loop that is controlled by CancellationTokenSource. This loop is in task that i use to wait.

It looks like that (code is much bigger, so i'll try to focus on issue):

Service is starting like that:

public Task Run()
        {
            var token = cancellationTokenSource.Token;
            logger.LogInformation("Running dispacher");
            return Task.Run(async () => // don't run it on main thread
            {
                while (true)
                {
                    try
                    {
                        token.ThrowIfCancellationRequested();

                        await Task.Delay(TimeSpan.FromMilliseconds(100), token).ConfigureAwait(false); // application logic simulation
                    }
                    catch (TaskCanceledException)
                    {
                        break;
                    }
                    catch (Exception e)
                    {
                        logger.LogError(e, "Exception in dispatcher loop");
                    }
                }
                logger.LogInformation("Dispacher shut down");
            }, token);
        }

Then i keep the reference to this task as mainTask and in Stop method i'm doing something like this:

var cts = Container.GetService<CancellationTokenSource>();
cts.Cancel();
mainTask.Wait();

The problem is application is crashing with error Error 109: The pipe has been ended when i try to stop it as a service. When i'm testing it in console application it is working just fine.

A little debugging showed that crash if happening on line cts.Cancel(); and i'm really stuck in here. There is no exception that i can catch, application just.. crashes.

Have you and idea what am i doing wrong?

Pause/Continue

I've created a custom service state machine implementation that should handle pause and continue events, but unfortunately, the options are grayed out - the service doesn't allow pause and continue. I've taken a close look at the documentation regarding the installation of the service - it looks like, using the ServiceControlAccessRights, the current setting of "All" should allow pause/continue functionality, but it doesn't seem to work.

Has anyone else encountered this issue? Is there something else I need to do? I've been hunting around for a good answer to this question for awhile, without much luck. Any help would be appreciated.

Thanks!

Request: Support adding service dependencies

I'd like to be able to a service start dependencies when creating a new service.

In case it helps, my work around for this is to add them independently using the below:

public static void SetDependencies(ServiceDefinition serviceDefinition, string[] dependencies)
{
    if (dependencies == null || !dependencies.Any())
        return;

    var dependenciesConcat = string.Join('\0', dependencies);
    var serviceHandle = new ServiceController(serviceDefinition.ServiceName).ServiceHandle;
    if (!Win32.ChangeServiceConfig(
        serviceHandle,
        Win32.SERVICE_NO_CHANGE,
        Win32.SERVICE_NO_CHANGE,
        Win32.SERVICE_NO_CHANGE,
        null,
        null,
        IntPtr.Zero,
        dependenciesConcat,
        null,
        null,
        null))
    {
        throw new Win32Exception();
    }
}

Track: Work required to publish a beta version

  • Throw PlatformNotSupported exception on non-windows OS
  • Improve error handling slightly: Ensure the calling main function has a chance of knowing what went wrong (should be a simple TrySetException() on the task completion source).
    • moved to logging work
  • Add logging to windows event log to test service application
    • Introduce event log apis later as own library or add logging handlers (same ILog abstraction as asp.net core)
  • Move test service application to a demo / example folder
  • Add functional tests for state handling logic.
    • Not sure yet if a layer of abstraction over the Interop methods make sense..
  • Introduce a Callback on IWin32Service to report when the service unexpectedly stops.

Prio 2:

  • Add an end-to-end-test for the example app
    • Will potentially conflict with open services mmc snap-in / taskmgr, existing service and has to be run as administrator.
    • decided to completely abstract introp functions for tests for beta2 / rc
  • Review exported symbols that aren't required by consumers.

Unable to stop service after making SOAP request

I am running a .NET Core console application as a service using this library. The application makes SOAP requests to another service using the dotnet/wcf library (https://github.com/dotnet/wcf). If the application makes any SOAP requests then it stops responding to the stop command, and when I try to stop the service I get an error "Error 1061: The service cannot accept control messages at this time.". If the application does not make any SOAP requests then it can be stopped without an issue.

If the application creates a channel like so, then it cannot be stopped.

using (var factory = new ChannelFactory<IRccService>(new BasicHttpBinding(BasicHttpSecurityMode.None), new EndpointAddress(Settings.Default.ArbiterEndpoint)))
{
    var proxy = factory.CreateChannel();
    factory.Close();
}

However if the following line is commented out, everything works.

var proxy = factory.CreateChannel();

IWebHost extension

Hey,

I've added a reference to the latest nuget package available but there isn't a reference to RunAsService(...). Are you planning on releasing a package soon with that extension method?

DasMulli.Win32.ServiceUtils

Hello,

  • I have Create a simple example (Console App .Net Core) :*

    public class ServiceHost : IWin32Service
    {
    public string ServiceName => "Test Service";

      public void Start(string[] startupArguments, ServiceStoppedCallback serviceStoppedCallback)
      {
          
      }
    
      public void Stop(){}
    

    }

static void Main(string[] args)
{
var host = new ServiceHost();
var win32Host = new Win32ServiceHost(host);
win32Host.Run();
}

  • I have install the service using the command "sc create" .
  • The Service has been installed on the windows services.
  • When I try to start it , Error Occurred
    "The Test Api Service service failed to start due to the following error:
    The service did not respond to the start or control request in a timely fashion."
  • Why This Error Occurred although I do not write any code on the start method.
    capture

Support for Net.core 1.1.0 projects

Hi, first of all many thanks for sharing this code.
I've been using successfully your library for executing as a windows service a Net.Core 1.0.1 project.
Problem is when I updated the project to Net.Core 1.1.0 I receive a weird error message when the app services starts: Error: 0xfffffffff 0xffffffff.

Maybe the reason is your library doesn't support Net.Core 1.1.0.
If that is the reason, do you plan to support it?

NLog not writing logs when running as a service

Thanks for this library. It's let me create a simple Windows Service on .NET Core 1.1 without needing all of the AspNetCore libs.

I realize this issue may not be related to your library, but I figured I'd ask in case you have any ideas.

I am using the .NET Core ILogger along with NLog. When I run my service as a console app, the logging works correctly, both to console and to file. But when I run it as a service, I get no logging, and nothing in the internal NLog log to explain why. The service itself still runs.

Here's the code from Main():

        var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
        loggerFactory.AddNLog();

        var myServiceClass = serviceProvider.GetService<MyServiceClass>();

        if (Debugger.IsAttached || args.Contains("--console"))
        {
            // Logging works here
            threadManager.Start(new string[0], () => { });

            while (Console.ReadKey().KeyChar != 'q') { }

            threadManager.Stop();
        }
        else
        {
            // No logging when I run it this way
            var serviceHost = new Win32ServiceHost(threadManager);
            serviceHost.Run();
        }

Stopping and starting a service without admin rights

Hello Daniel,

I've succesfully used your windows service library in a number of applications running under net core, over Peter Kottas library. So thanks a lot for the nice integratable solution.

I just have one issue.

I have a service, installed as an admin user (necessary for the application).
The current logged in user is a normal user, with no option to elevate rights.
For updates, new files are downloaded, the service is stopped, files are renamed, and the service is started.

What I'm noticing is that for starting and stopping a service, installed/running under an admin account, elevated rights are necessary.

Is there a way we can call the install command, so that start and stop rights are granted to all users?

I've found a similar question/response on stack overflow, where there is an instructionset on how to do it using windows, but it would be perfect if those rights could be set using the install command.

Link:
https://stackoverflow.com/questions/10850595/windows-service-start-and-stop-without-admin-privileges

If this is implemented in the dasMulli library, I'll pick it up with Peter (or fork the library), so that it can also be wrapped there.

Can't serve static files out of wwwroot

I've got an asp.net core 2.0 app and I've added in your service helpers. I can run the app/register the service, everything seems fine, but...

And I'm sure this is something I'm doing wrong, but I've got a directory under wwwroot that I'm serving static files from. When I debug the app or run it from the publish directory, everything works fine. But, when I run it as a service, the files from that directory (under wwwroot) are not found. Other MS sample files can be found, but not the ones I've added. I can see them under the publish folder, but they aren't getting served.

Any idea what this may be? The only reason I'm posting here is because it only happens when running as a service.

Windows could not stop service, using IWin32ServiceStateMachine

So I have implemented an IWin32ServiceStateMachine. my goal is to allow time for my program to gracefully exit. however, keep getting an error "Windows could not stop the service"

I have a Task.Delay(15000) in my OnStopPending() in my service implementation class. This is to simulate a long shutdown.

Stop is now called by the serviceImplemenation using a delegate.

here is my code.

Am I doing this correctly

// <summary>
/// Called by the service host when a command was received from Windows' service system.
/// </summary>
/// <param name="command">The received command.</param>
/// <param name="commandSpecificEventType">Type of the command specific event. See description of dwEventType at https://msdn.microsoft.com/en-us/library/windows/desktop/ms683241(v=vs.85).aspx </param>
public void OnCommand(ServiceControlCommand command, uint commandSpecificEventType)
{
	if (command == ServiceControlCommand.Stop)
	{
		statusReportCallback(ServiceState.StopPending, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: 0, waitHint: 15000);

			serviceImplementation.OnStopPending();
	   

		}

		//statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode, waitHint: 0);
	}
}

public void Stop(int win32ExitCode)
{
	statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode, waitHint: 0);
}

Shared Code between the 2 methods

StartupLogger.Debug("Running As a Service");
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;

var theTask = new Task(() =>
{
    Task.Delay(10000, token).Wait();
    StartupLogger.Debug($"Done Waiting, Was Canceled = {token.IsCancellationRequested}");
    StartupLogger.Debug("_______________");
});

this my Code For OnStart

StartupLogger.Debug("Service Started");
StartupLogger.Debug("_______________");
StartupLogger.Debug($"Start theTask");
theTask.Start();
StartupLogger.Debug("Service Startup Done");

This my Code for OnStop

StartupLogger.Debug("Service Stop Called");
StartupLogger.Debug("_______________");
StartupLogger.Debug($"theTask status  = {theTask.Status}");


source.Cancel();
StartupLogger.Debug("Cancel Called");
StartupLogger.Debug("_______________");
StartupLogger.Debug($"Waiting for theTask to complete");
theTask.Wait(); //** Service Throws Error while waiting here

StartupLogger.Debug("Service Stop Done");

//** Tell Service Host to Stop
self.Stop(0);

StartupLogger.Debug("Service Stop Done Done");

Log.Logger = StartupLogger;
Log.CloseAndFlush();

Error occurs on .net Core 2.0 based Services

Hi,
Library fails while running on .net Core 2.0 based service applications.

Error that occurs while application running:
Unhandled Exception: System.ComponentModel.Win32Exception: The service process could not connect to the service controller
at DasMulli.Win32.ServiceUtils.Win32ServiceHost.RunAsync()
at DasMulli.Win32.ServiceUtils.Win32ServiceHost.Run()
at QRAuth.UsageService.Program.Main(String[] args) in C:\Users...\Program.cs:line ...

Event Viewer logs:
applicationerror_details
applicationerror_general
information_details
information_general

Subsequent failures options

I'm using some 1-2 years old copied versions of this win32 service and Peter Kottas' wrapper for an ordinary .NET 4.7 project. Due to use of some 3rd party legacy API I have a demand to programmatically restart service occasionally to release that API's unmanaged resources. Old bugger leaks memory and handles. So, somewhere deep in code this block

Environment.ExitCode = -1; // Results in a fake failure
var p = Process.GetCurrentProcess();                        
p.Kill();

initializes a stops service. The only thing lacks for a proper restart are the following options:
image
Not the end of the world, but local admin may forget to set these up manually after update.
As I haven't sync'd this project with my copy for a while, are these options already implemented? If not, are there plans for these properties' implementation?

example project with apublished executable fails to run as a service

When using a publish command like:

dotnet publish "C:\dev\projects\opensource\dotnet-win32-service\samples\TestService\TestService.csproj" -c Release -r win10-x64 -o "C:\dev\projects\opensource\dotnet-win32-service\samples\published\TestService"

and registering the resulting .exe as a windows service

sc.exe create TestService DisplayName= "TestService" binpath= "C:\dev\projects\opensource\dotnet-win32-service\samples\published\TestService\TestService.exe"

attempting to start the service results in:

Error 1053: The service did not respond to the start or control request in a timely fashion.

If I register it with dotnet.exe it works fine. I would prefer it if a dotnet.exe installation wasn't required to run the windows service. Is this behavior intended?

Debugging when running as a service

Nice library. Thanks for making this.

Interactive, registering, and unregistering works well. When starting as a service, the app will time-out after 30 seconds and gets torn-down by the SCM.

Any hints on debugging? Trying to attach to the process doesn't work. I changed over to running the app as a .exe but the debugger sees the executable as being out of date and no breakpoints get hit.

Can't get the asp.net core mvc sample work

Run the following cmds in elevated powershell prompt

cd samples\MvcTestService
dotnet restore
dotnet run --register-service --preserve-working-directory

And open up http://localhost:50250/ (url is from launchsetting.json) but says "This site can’t be reached"

Also tried it in the interactive mode which seemed to work fine.

Anything else I forgot to do to make it work? Many thanks.

Add ASP.NET Core 2.0 samples

Current sample works but does not follow the hosting model (default builder) of ASP.NET Core 2.0 apps and if you don't remember to set the content root it will blow up since services are run out of the system32 directory

Running multiple Windows services concurrently not working

I have a windows service developed using DasMulli.Win32.ServiceUtils. With different service home directories and also different configuration files, I installed several (2~4) Windows services with the same executable on the same box. The first Windows service works smoothly but it doesn't allow a second service to start unless the first one is stopped.
image

It looks on the same box it only allows one Windows service running at a time. Could you please check and fix it? Thanks.

DeleteService should stop the service

Otherwise, it stays running and in the Services list until the next time it stops for some other reason.

Would it ever make sense to delete and leave running? If so, could you add a bool stopImmediately parameter to DeleteService to mirror the startImmediately parameter on CreateService?

Moving dll location from build path

When I copy the dll from the visual studio build path to any other location and create the service using the new path (ex: C:\Program Files (x86)\Folder\ex.dll). The service never starts and times out giving error 1053. If I use the original output build path it will start correctly.

Unhandled exception – provide the application access to the exception details.

Hi Martin

If I understood the code correctly, the library is catching unhandled exceptions and then calls Win32ServiceHost.ReportServiceStatus without throwing the exception?

Is there a way to notify the application about such an exception?
…So that the application for example can write a last “emergency” log statement bevor it exits.
Maybe an event similar to AppDomain.CurrentDomain.UnhandledException?

Btw.
Really cool library, thanks for sharing it.

Track: Ship 1.1.0

(Tracking issue)

  • Write release notes
  • Tag release => Create GitHub release
  • Upload to NuGet

Add ability to update/modify a service

(Tracking issue)

In addition to creating and deleting a service, provide ability to change the configuration of a servcie. Useful for modifying startup arguments.

Ideally, this should allow for an CreateOrUpdate() call.

An error ocurred: The service did not respond to the start or control request in a timely fashion

Hello, I've been banging with my head on the wall with this one.

When I try to register the service with "c:\Services\KBBService_PT>dotnet.exe C:\Services\KBBService_PT\KBBService.dll --register-service", it just wont start, but creates it.
If I open a cmd (always admin mode) and run it as "c:\Services\KBBService_PT>dotnet.exe C:\Services\KBBService_PT\KBBService.dll --interactive" it runs the service in the console as suposed.
If I manually call the service with "--run-as-service" happens the same as when I call the "--register-service".
I'm running the latest version of the code, on a windows 10 LTSB, and also tried on a Windows Server 2012 R2.

Bellow is a call stack and error message:

C:\Services\KBBService_PT>dotnet.exe C:\Services\KBBService_PT\KBBService.dll --register-service
2017-12-05 17:36:22.174 - An error ocurred: The service did not respond to the start or control request in a timely fashion

------ STACK TRACE: ------
at DasMulli.Win32.ServiceUtils.ServiceHandle.Start(Boolean throwIfAlreadyRunning) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\ServiceHandle.cs:line 44

at DasMulli.Win32.ServiceUtils.Win32ServiceManager.DoUpdateService(ServiceHandle existingService, ServiceDefinition serviceDefinition, Boolean startIfNotRunning) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\Win32ServiceManager.cs:line 193

at DasMulli.Win32.ServiceUtils.Win32ServiceManager.CreateOrUpdateService(ServiceDefinition serviceDefinition, Boolean startImmediately) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\Win32ServiceManager.cs:line 165

at KBBService.Program.RegisterService() in E:_Git\KBB_MVC\KBBService\Program.cs:line 122

at KBBService.Program.Main(String[] args) in E:_Git\KBB_MVC\KBBService\Program.cs:line 37System.ComponentModel.Win32Exception (0x80004005): The service did not respond to the start or control request in a timely fashion
at DasMulli.Win32.ServiceUtils.ServiceHandle.Start(Boolean throwIfAlreadyRunning) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\ServiceHandle.cs:line 44
at DasMulli.Win32.ServiceUtils.Win32ServiceManager.DoUpdateService(ServiceHandle existingService, ServiceDefinition serviceDefinition, Boolean startIfNotRunning) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\Win32ServiceManager.cs:line 193
at DasMulli.Win32.ServiceUtils.Win32ServiceManager.CreateOrUpdateService(ServiceDefinition serviceDefinition, Boolean startImmediately) in E:_Git\KBB_MVC\DasMulli.Win32.ServiceUtils\Win32ServiceManager.cs:line 165
at KBBService.Program.RegisterService() in E:_Git\KBB_MVC\KBBService\Program.cs:line 122
at KBBService.Program.Main(String[] args) in E:_Git\KBB_MVC\KBBService\Program.cs:line 37

And bellow my Program.cs (Configs appear to be loading correctly):

using DasMulli.Win32.ServiceUtils;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

using static System.Console;

namespace KBBService
{
	public class Program
	{
		private const string RunAsServiceFlag = "--run-as-service";
		private const string ServiceWorkingDirectoryFlag = "--working-directory";
		private const string RegisterServiceFlag = "--register-service";
		private const string PreserveWorkingDirectoryFlag = "--preserve-working-directory";
		private const string UnregisterServiceFlag = "--unregister-service";
		private const string InteractiveFlag = "--interactive";
		private const string HelpFlag = "--help";

		public static void Main(string[] args)
		{
			//cd to the directory where the project files are
			//dotnet.exe C:\Program Files\dotnet\dotnet.exe C:\Services\KBBService_PT\KBBService.dll --register-service
			////sc.exe create KBBService_PT DisplayName= "KBBService_PT" binpath= "C:\Program Files\dotnet\dotnet.exe C:\Services\KBBService_PT\KBBService.dll --run-as-service"
			Configs.Load();

			try
			{
				if (args.Contains(RunAsServiceFlag))
				{
					RunAsService(args);
				}
				else if (args.Contains(RegisterServiceFlag))
				{
					RegisterService();
				}
				else if (args.Contains(UnregisterServiceFlag))
				{
					UnregisterService();
				}
				else if (args.Contains(HelpFlag))
				{
					DisplayHelp();
				}
				else
				{
					RunInteractive(args);
				}
			}
			catch (Exception ex)
			{
				Service.Log(true, $"An error ocurred: {ex.Message}", ex);
			}
		}

		private static void RunAsService(string[] args)
		{
			// easy fix to allow using default web host builder without changes
			var wdFlagIndex = Array.IndexOf(args, ServiceWorkingDirectoryFlag);
			if (wdFlagIndex >= 0 && wdFlagIndex < args.Length - 1)
			{
				var workingDirectory = args[wdFlagIndex + 1];
				Directory.SetCurrentDirectory(workingDirectory);
			}
			else
			{
				Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
			}
			var mvcService = new Service(args.Where(a => a != RunAsServiceFlag).ToArray());
			var serviceHost = new Win32ServiceHost(mvcService);
			serviceHost.Run();
		}

		private static void RunInteractive(string[] args)
		{
			var mvcService = new Service(args.Where(a => a != InteractiveFlag).ToArray());
			mvcService.Start(new string[0], () => { });
			WriteLine("Running interactively, press enter to stop.");
			ReadLine();
			mvcService.Stop();
		}

		private static void RegisterService()
		{
			// Environment.GetCommandLineArgs() includes the current DLL from a "dotnet my.dll --register-service" call, which is not passed to Main()
			var commandLineArgs = Environment.GetCommandLineArgs();

			var serviceArgs = commandLineArgs
				.Where(arg => arg != RegisterServiceFlag && arg != PreserveWorkingDirectoryFlag)
				.Select(EscapeCommandLineArgument)
				.Append(RunAsServiceFlag);

			var host = Process.GetCurrentProcess().MainModule.FileName;

			if (!host.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase))
			{
				// For self-contained apps, skip the dll path
				serviceArgs = serviceArgs.Skip(1);
			}

			if (commandLineArgs.Contains(PreserveWorkingDirectoryFlag))
			{
				serviceArgs = serviceArgs
					.Append(ServiceWorkingDirectoryFlag)
					.Append(EscapeCommandLineArgument(Directory.GetCurrentDirectory()));
			}

			var fullServiceCommand = host + " " + string.Join(" ", serviceArgs);

			// Do not use LocalSystem in production.. but this is good for demos as LocalSystem will have access to some random git-clone path
			// Note that when the service is already registered and running, it will be reconfigured but not restarted
			var serviceDefinition = new ServiceDefinitionBuilder(Configs.ServiceName)
				.WithDisplayName(Configs.ServiceDisplayName)
				.WithDescription(Configs.ServiceDescription)
				.WithBinaryPath(fullServiceCommand)
				.WithCredentials(Win32ServiceCredentials.LocalSystem)
				.WithAutoStart(true)
				.Build();

			new Win32ServiceManager().CreateOrUpdateService(serviceDefinition, startImmediately: true);

			WriteLine($@"Successfully registered and started service ""{Configs.ServiceDisplayName}"" (""{Configs.ServiceDescription}"")");
		}

		private static void UnregisterService()
		{
			new Win32ServiceManager()
				.DeleteService(Configs.ServiceName);

			WriteLine($@"Successfully unregistered service ""{Configs.ServiceDisplayName}"" (""{Configs.ServiceDescription}"")");
		}

		private static void DisplayHelp()
		{
			WriteLine(Configs.ServiceDescription);
			WriteLine();
			WriteLine("This demo application is intened to be run as windows service. Use one of the following options:");
			WriteLine();
			WriteLine("  --register-service            Registers and starts this program as a windows service named \"" + Configs.ServiceDisplayName + "\"");
			WriteLine("                                All additional arguments will be passed to ASP.NET Core's WebHostBuilder.");
			WriteLine();
			WriteLine("  --preserve-working-directory  Saves the current working directory to the service configuration.");
			WriteLine("                                Set this wenn running via 'dotnet run' or when the application content");
			WriteLine("                                is not located nex to the application.");
			WriteLine();
			WriteLine("  --unregister-service          Removes the windows service creatd by --register-service.");
			WriteLine();
			WriteLine("  --interactive                 Runs the underlying asp.net core app. Useful to test arguments.");
		}

		private static string EscapeCommandLineArgument(string arg)
		{
			// http://stackoverflow.com/a/6040946/784387
			arg = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");
			arg = "\"" + Regex.Replace(arg, @"(\\+)$", @"$1$1") + "\"";
			return arg;
		}
	}

	public class Service : IWin32Service
	{
		private readonly string[] commandLineArguments;

		public string ServiceName => Configs.ServiceName;

		public Service(string[] commandLineArguments)
		{
			this.commandLineArguments = commandLineArguments;
		}

		public void Start(string[] startupArguments, ServiceStoppedCallback serviceStoppedCallback)
		{
			Log(false, $"Service {ServiceName} Started");
			ErrorHandler.Log($"Service {ServiceName} Started", ExceptionLevel.Warning);
			ThreadManager.Start();//aka Do Stuff
		}

		public void Stop()
		{
			Log(false, $"Service {ServiceName} Stopped");
			ErrorHandler.Log($"Service {ServiceName} Stopped", ExceptionLevel.Warning);
			ThreadManager.Stop();//aka Stop Stuff
		}

		public static void Log(bool error, string message = null, Exception ex = null, ExceptionLevel exceptionLevel = ExceptionLevel.Regular)
		{
			if (message == null)
			{
				message = string.Empty;
			}

			lock (typeof(Service))
			{
				if (error)
				{
					if (ex == null)
					{
						ErrorHandler.Log(message, exceptionLevel);
					}
					else
					{
						ErrorHandler.Log(ex, message, exceptionLevel);
						message += "\r\n--------------------------\r\n";
						message += GetStack(ex);
					}
				}

				DateTime dtNow = DateTime.UtcNow;
				message += (ex == null ? string.Empty : ex.ToString());

				Console.WriteLine(dtNow.ToString("yyyy-MM-dd HH:mm:ss.fff") + " - " + message);

				string sFileName = dtNow.Year + "_" + dtNow.Month + "_" + dtNow.Day + ".txt";

				if (!Directory.Exists(Configs.LogPath))
				{
					Directory.CreateDirectory(Configs.LogPath);
				}

				File.AppendAllText(Configs.LogPath + "\\" + sFileName, dtNow.ToString("yyyy-MM-dd HH:mm:ss.fff") + " - " + message + Environment.NewLine + Environment.NewLine + Environment.NewLine);
			}
		}
		private static string GetStack(Exception ex)
		{
			if (ex == null)
			{
				return string.Empty;
			}

			string stacktrace = "\r\n------ STACK TRACE: ------";

			if (ex.StackTrace != null)
			{
				stacktrace += ex.StackTrace.ToString();
				stacktrace = stacktrace.Replace("at ", "\r\nat ");
			}

			return stacktrace;
		}
	}
}

If you need any more info, just let me know.

Ability to request additional time

Some services may take longer to start/stop than default (seems 60 seconds at max). Tried this by putting Thread.Sleep(Timespan.FromSeconds(120)) in the Stop() method implementation and then trying to sc.exe stop SERVICE_NAME. After 1 minute it failed with the following message: The service did not respond to the start or control request in a timely fashion..

Is there currently a way to request additional time for stopping/starting?

The service process could not connect to the service controller

Hi,

I'm utilising a signed version of your project to host some distributed services as Win32 services. The integration works fine, but when I try and run the project from VS, or from the command prompt, I keep getting the same result:

The service process could not connect to the service controller

Literally the only thing I have changed is to sign the code (we have an internal policy about code signing), and have deployed it to our internal NuGet package feed.

OS: Windows 10 (1607 - 14393.1944)
.NET Core version: 2.0.0

Sounds like it is having issues with the following error code - ERROR_FAILED_SERVICE_CONTROLLER_CONNECT? This seems to be an intrinsic error when running a service as a console app - I'm not sure how to tell Windows otherwise :-/

How to run this as a daemon on linux

How should I get this working as daemon on linux? Any samples would help

"Build truly portable applications that can for example run as service on windows and as daemon on linux, just using runtime checks / switches"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.