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, code, overseer, microservices, console, cli

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