Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed typos and capitalization and improved XML doc comments #78

Merged
merged 2 commits into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<GitCommitCountPadLength>3</GitCommitCountPadLength>

<!--
Set this to true when asp.net cor extensions are built out of band.
Set this to true when ASP.NET Core extensions are built out of band.
This will be needed whenever a change is made to the extensions that don't need an update to the
service utils package.
It should be false for every other case.
Expand Down
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# .NET Standard based Windows Service support for .NET
# .NET Standard-based Windows Service support for .NET

This repo contains a library for running a .NET Core application as windows service, without
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.
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
* 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,
* 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
Expand All @@ -34,14 +34,14 @@ The "Services" administrative tool should show the service:
...
Successfully unregistered service "Demo .NET Core Service" ("Demo ASP.NET Core Service running on .NET Core")
```
Note that the service may show up as `disabled` for some time until all tools accessing the windows services apis have been closed.
See this [Stackoverflow question](http://stackoverflow.com/questions/20561990/how-to-solve-the-specified-service-has-been-marked-for-deletion-error).
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](http://stackoverflow.com/questions/20561990/how-to-solve-the-specified-service-has-been-marked-for-deletion-error).

## API

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

Write a windows service using:
Write a Windows service using:

```c#
using DasMulli.Win32.ServiceUtils;
Expand Down Expand Up @@ -76,10 +76,10 @@ You can then register your service via sc.exe (run cmd / powershell as administr

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

Now go the services console / task manager and start your service.
Now go to Services or Task Manager and start your service.

Not that `sc` will install your service as `SYSTEM` user which has way to many access rights to run things like web apps.
See [it's reference](https://technet.microsoft.com/en-us/library/cc990289(v=ws.11).aspx) for more options.
`sc` will install your service as `SYSTEM` user which has way to many access rights to run things like web apps.
See [its reference](https://technet.microsoft.com/en-us/library/cc990289(v=ws.11).aspx) for more options.

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

Expand All @@ -88,20 +88,20 @@ If you want to get rid of it again, use:
You can also create a service that registers itself like the example provided by
taking a look at [the sample source](./samples/TestService/Program.cs).

Also take a look at the [ASP.NET Core MVC sample](./samples/MvcTestService), which has additional logic to set the correct working directory.
Also take a look at the [ASP.NET Core MVC sample](./samples/MvcTestService) 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.
you can use to implement your pause & continue logic.

## Limitations

* No custom exceptions / error codes. Everything will throw a `Win32Exception` if something goes wrong (It's message should be
interpretable on windows).
* 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
Expand Down
22 changes: 11 additions & 11 deletions samples/MvcTestService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static void Main(string[] args)
}
catch (Exception ex)
{
WriteLine($"An error ocurred: {ex.Message}");
WriteLine($"An error occurred: {ex.Message}");
}
}

Expand All @@ -73,7 +73,7 @@ private static void RunAsService(string[] args)
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
}

BuildWebHost(args).RunAsService(ServiceName);
}

Expand Down Expand Up @@ -110,7 +110,7 @@ private static void RegisterService()
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
// When the service is already registered and running, it will be reconfigured but not restarted
var serviceDefinition = new ServiceDefinitionBuilder(ServiceName)
.WithDisplayName(ServiceDisplayName)
.WithDescription(ServiceDescription)
Expand All @@ -136,18 +136,18 @@ private static void DisplayHelp()
{
WriteLine(ServiceDescription);
WriteLine();
WriteLine("This demo application is intened to be run as windows service. Use one of the following options:");
WriteLine("This demo application is intended 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 \"" + ServiceDisplayName + "\"");
WriteLine(" --register-service Registers and starts this program as a Windows service named \"" + 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(" Set this when running via 'dotnet run' or when the application content");
WriteLine(" is not located next to the application.");
WriteLine();
WriteLine(" --unregister-service Removes the windows service creatd by --register-service.");
WriteLine(" --unregister-service Removes the Windows service created by --register-service.");
WriteLine();
WriteLine(" --interactive Runs the underlying asp.net core app. Useful to test arguments.");
WriteLine(" --interactive Runs the underlying ASP.NET Core app. Useful to test arguments.");
}

private static string EscapeCommandLineArgument(string arg)
Expand All @@ -160,10 +160,10 @@ private static string EscapeCommandLineArgument(string arg)

public static IWebHost BuildWebHost(string[] args)
{
/*
/*
* create an override configuration based on the command line args
* to work around ASP.NET Core issue https://github.com/aspnet/MetaPackages/issues/221
* wich should be fixed in 2.1.0.
* which should be fixed in 2.1.0.
*/
var configuration = new ConfigurationBuilder().AddCommandLine(args).Build();

Expand Down
12 changes: 6 additions & 6 deletions samples/TestService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static void Main(string[] args)
}
catch (Exception ex)
{
Console.WriteLine($"An error ocurred: {ex.Message}");
Console.WriteLine($"An error occurred: {ex.Message}");
}
}

Expand Down Expand Up @@ -83,7 +83,7 @@ private static void RegisterService()
var fullServiceCommand = host + " " + string.Join(" ", remainingArgs);

// 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
// When the service is already registered and running, it will be reconfigured but not restarted
var serviceDefinition = new ServiceDefinitionBuilder(ServiceName)
.WithDisplayName(ServiceDisplayName)
.WithDescription(ServiceDescription)
Expand All @@ -109,11 +109,11 @@ private static void DisplayHelp()
{
Console.WriteLine(ServiceDescription);
Console.WriteLine();
Console.WriteLine("This demo application is intened to be run as windows service. Use one of the following options:");
Console.WriteLine(" --register-service Registers and starts this program as a windows service named \"" + ServiceDisplayName + "\"");
Console.WriteLine("This demo application is intended to be run as Windows service. Use one of the following options:");
Console.WriteLine(" --register-service Registers and starts this program as a Windows service named \"" + ServiceDisplayName + "\"");
Console.WriteLine(" All additional arguments will be passed to ASP.NET Core's WebHostBuilder.");
Console.WriteLine(" --unregister-service Removes the windows service creatd by --register-service.");
Console.WriteLine(" --interactive Runs the underlying asp.net core app. Useful to test arguments.");
Console.WriteLine(" --unregister-service Removes the Windows service created by --register-service.");
Console.WriteLine(" --interactive Runs the underlying ASP.NET Core app. Useful to test arguments.");
}

private static string EscapeCommandLineArgument(string arg)
Expand Down
2 changes: 1 addition & 1 deletion samples/TestService/TestWin32Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void Start(string[] startupArguments, ServiceStoppedCallback serviceStopp
.UseConfiguration(config)
.Build();

// Make sure the windows service is stopped if the
// Make sure the Windows service is stopped if the
// ASP.NET Core stack stops for any reason
webHost
.Services
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
namespace DasMulli.AspNetCore.Hosting.WindowsServices
{
/// <summary>
/// Extensions to web host builder for windows service hosting scenarios
/// Extensions to <see cref="IWebHost"/> for Windows service hosting scenarios.
/// </summary>
public static class WebHostExtensions
{
/// <summary>
/// Runs the specified web application inside a Windows service and blocks until the service is stopped.
/// </summary>
/// <param name="host">An instance of the <see cref="IWebHost"/> to host in the Windows service.</param>
/// <param name="serviceName">The name of the service to run</param>
/// <param name="serviceName">The name of the service to run.</param>
[PublicAPI]
public static void RunAsService(this IWebHost host, string serviceName = null) =>
new Win32ServiceHost(new WebHostService(host, serviceName)).Run();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace DasMulli.AspNetCore.Hosting.WindowsServices
{
/// <inheritdoc />
/// <summary>
/// Provides an implementation of a service that hosts an asp.net core application
/// Provides an implementation of a service that hosts an ASP.NET Core application.
/// </summary>
/// <seealso cref="T:DasMulli.Win32.ServiceUtils.IWin32Service" />
[PublicAPI]
Expand All @@ -21,10 +21,10 @@ public class WebHostService : IWin32Service
public string ServiceName { get; }

/// <summary>
/// Initializes a new instance of the <see cref="WebHostService"/> class which hosts the sepcified host as a windows service.
/// Initializes a new instance of the <see cref="WebHostService"/> class which hosts the specified host as a Windows service.
/// </summary>
/// <param name="host">The host to run as a service.</param>
/// <param name="serviceName">Name of the service to run. If <c>null</c>, set to the name of the entry assembly</param>
/// <param name="serviceName">The name of the service to run. If <see langword="null"/>, the name of the entry assembly is used.</param>
public WebHostService(IWebHost host, string serviceName = null)
{
if (serviceName == null)
Expand Down
2 changes: 1 addition & 1 deletion src/DasMulli.Win32.ServiceUtils/ErrorSeverity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace DasMulli.Win32.ServiceUtils
{
/// <summary>
/// Specifies the severity of the error if the service fails to start during boot
/// Specifies the severity of the error if the service fails to start during boot.
/// </summary>
[PublicAPI]
public enum ErrorSeverity : uint
Expand Down
2 changes: 1 addition & 1 deletion src/DasMulli.Win32.ServiceUtils/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace DasMulli.Win32.ServiceUtils
{
/// <summary>
/// Simplifies the work of hashing.
/// Taken from https://rehansaeed.com/gethashcode-made-easy/", and modified with Reshaper
/// Taken from https://rehansaeed.com/gethashcode-made-easy/ and modified with ReSharper.
/// </summary>
internal struct HashCode
{
Expand Down
8 changes: 4 additions & 4 deletions src/DasMulli.Win32.ServiceUtils/IPausableWin32Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
{
/// <inheritdoc />
/// <summary>
/// Interface to allow implementing windows services that can start and stop as well as pause and continue in between.
/// Note that after a call to Pause(), the service can receive either Continue() or Stop()
/// Represents a Windows service that pause and continue between starting and stopping.
/// After a call to <see cref="Pause"/>, the service can receive either <see cref="Continue"/> or <see cref="IWin32Service.Stop"/>.
/// </summary>
public interface IPausableWin32Service : IWin32Service
{
/// <summary>
/// Pauses the service.
/// Expect either Continue() or Stop() to be called afterwards.
/// Expect either <see cref="Continue"/> or <see cref="IWin32Service.Stop"/> to be called afterwards.
/// </summary>
void Pause();

/// <summary>
/// Continues/Resumes the service.
/// Continues (resumes) the service.
/// </summary>
void Continue();
}
Expand Down
16 changes: 7 additions & 9 deletions src/DasMulli.Win32.ServiceUtils/IWin32Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,30 @@
{
/// <summary>
/// Callback type that is used to allow implementations of <see cref="IWin32Service"/> to notify
/// the service manager in case it stopped prematurely / without being requested to stop.
/// the service manager in case it stopped without being requested to stop.
/// </summary>
public delegate void ServiceStoppedCallback();

/// <summary>
/// Interface to allow implementing simple windows services that can start and stop
/// <summary>.
/// Represents a simple Windows service that can start and stop.
/// </summary>
public interface IWin32Service
{
/// <summary>
/// Returns the name of the service.
/// </summary>
/// <value>
/// The name of the service.
/// </value>
string ServiceName { get; }

/// <summary>
/// Starts the service with the startup arguments received from windows.
/// Called by the service host to start the service. When called by <see cref="Win32ServiceHost"/>,
/// the service startup arguments received from Windows are specified.
/// </summary>
/// <param name="startupArguments">The startup arguments.</param>
/// <param name="serviceStoppedCallback">The service stopped callback the service can call if it stopped without being requested to stop.</param>
/// <param name="serviceStoppedCallback">Notifies the service manager that the service stopped without being requested to stop.</param>
void Start(string[] startupArguments, ServiceStoppedCallback serviceStoppedCallback);

/// <summary>
/// Stops the service.
/// Called by the service host to stop the service.
/// </summary>
void Stop();
}
Expand Down
14 changes: 7 additions & 7 deletions src/DasMulli.Win32.ServiceUtils/IWin32ServiceStateMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
namespace DasMulli.Win32.ServiceUtils
{
/// <summary>
/// Interface to implement advanced services through custom handling all service state events.
///
/// Provides custom handling of service state events.
/// See <see cref="SimpleServiceStateMachine"/> for a sample implementation.
/// </summary>
[PublicAPI]
public interface IWin32ServiceStateMachine
{
/// <summary>
/// Called when the service is started.
/// Use the provided <paramref name="statusReportCallback"/> to notify the service manager about
/// Called by the service host to start the service. When called by <see cref="Win32ServiceHost"/>,
/// the service startup arguments received from Windows are specified.
/// Use the provided <see cref="ServiceStatusReportCallback"/> to notify the service manager about
/// state changes such as started, paused etc.
/// </summary>
/// <param name="startupArguments">The startup arguments passed via windows' service configuration.</param>
/// <param name="statusReportCallback">The status report callback.</param>
/// <param name="startupArguments">The startup arguments.</param>
/// <param name="statusReportCallback">Notifies the service manager of a status change.</param>
void OnStart(string[] startupArguments, ServiceStatusReportCallback statusReportCallback);

/// <summary>
/// Called when a command was received from windows' service system.
/// 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>
Expand Down
Loading