A single project Windows Service and Console

30 Aug 2015

I have found that when developing MicroServices, I often want to run them from within Visual Studio, or just as a console application, and not have to bother with the hassle of installing as windows services.

In the past I have seen this achieved by creating a Class Library project with all the actual implementation inside it, and then both a Console Application and Windows Service project referencing the library and doing nothing other than calling a .Start() method or similar.

While this works, it has always bugged me as there should be a straight forward way of achieving a single exe to do both roles. It turns out there is an easy way to do it too…

Creating the Project

First, create a WindowsService project in VisualStudio: New Windows Service

Then open the project properties, and change the project type to Console Application and set the startup object: Service Type

Next, open Service1.cs and add a new method (and rename it to Service if you feel the need!):

public void StartConsole()
{
	Console.WriteLine("Press any key to exit...");
	OnStart(new string[] { });

	Console.ReadKey();
	OnStop();
}

Finally open Program.cs and replace the Main method:

static void Main()
{
	var service = new Service();

	if (Environment.UserInteractive)
	{
		service.StartConsole();
	}
	else
	{
		ServiceBase.Run(new ServiceBase[] { service });
	}
}

Displaying Output

Calling Console.Write* and Console.Read* methods when running as a windows service will cause exceptions to be thrown, which suggest that you should redirect the console streams to use them under a windows service.

As a MicroService you shouldn’t need to be reading keys from the console (other than the one in our StartConsole method), but writing output would be useful…

To do this I like to use my logging library of choice (Serilog), which I have setup to write to files and to a console:

private void InitializeLogging()
{
	var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
	var logs = Path.Combine(baseDirectory, "logs");

	Directory.CreateDirectory(logs);

	Log.Logger = new LoggerConfiguration()
		.MinimumLevel.Debug()
		.WriteTo.ColoredConsole()
		.WriteTo.RollingFile(Path.Combine(logs, "{Date}.log"))
		.CreateLogger();
}

And call this method inside the Service1 constructor:

public Service()
{
	InitializeComponent();
	InitializeLogging();
}

The Edge Case

There is one slight edge case which I am aware of, which is that the Environment.UserInteractive property can return true even when running as a windows service if when you install the service you tick Allow service to interact with desktop checkbox:

Service-Logon

My only solution to this is: Don’t tick that box. I don’t think I have ever used that option anyway!

Wrapping Up

Using this method means less code and projects to maintain, and a very easy path to go from running a service as a desktop application to service.

design, overseer, microservices, console, cli

« Don't Let The Database Dictate Your Design Running microservices in Docker with Mono »