.NET Windows Services - Tips & Tricks

Windows Services Windows Services

If you've ever worked with windows services, you'll know that they're a very powerful tool to have in your background processing arsenal. Unfortunately they can be also quite a pain to work with in developer land. Recently we've been spinning up a lot of new windows service projects in work as part of a Business Intelligence Data Processing Project. I thought this would be a good time to brain dump some of the tips & tricks I've come across over the past few years for dealing with .Net Windows Services.

I'll look at the basics for getting a service up and going, using the built project installer & Install Util. Then I'll take a look at easier ways of running the service inside the IDE, and how to run the service in user interactive mode.

Finally I'll look at ways to make the service self-installing without having to rely upon the InstallUtil.exe as well as gaining access to configuration settings during the installation process.

[important]The completed solution can be found on GitHub at https://github.com/eoincampbell/demo-windows-service [/important]

Windows Services Basics

I'm going to build a very trivial Windows Service. The service will start a timer, which will then log the current time every 100ms to a file in the same path  as the executable. It will also attempt to write to the console if there is one attached. A few notes. Don't leave this running. It will devour your disk space. You'll also need to make sure that the account you install the service as has permission to write to that path on the file system.

When I first create a new windows service, there are a number of setup tasks I perform to keep the service organised. YMMV with these.

  1. If I'm only adding a single Service.cs in this project, then I'll rename the service class to the same name as the project. This will make the Service Name match with the Executable Name which I've found saves confusion. In my example the Project, Service Class, Namespace & Executable are all DemoWindowsService.
  2. Open the Service.Designer.cs file and ensure that the property this.ServiceName  also matches this naming convention
  3. Add an installer to your service by Right-Clicking the design canvas of the service and choosing "Add Installer" (See Image Below)
  4. Once you've added your ProjectInstaller open that file also and ensure that the property this.serviceInstaller1.ServiceName also matches this naming convention
  5. We'll also  specify a Description on the serviceInstaller1 object to give our service a meaningful description when it appears in services.msc listing

Add Installer to Windows Service Add Installer to Windows Service

After building your windows service application, you can then use InstallUtil.exe from the command line to install or uninstall your applicaiton.

[text]InstallUtil.exe DemoWindowsService.exe
net start DemoWindowsService
net stop DemoWindowsService
InstallUtil.exe /u DemoWindowsService.exe[/text]

Debugging Windows Services

Relying on InstallUtil is all well and good but it doesn't lend itself to an easy developer debugging experience. If you attempt to press F5 in order to start your Windows Service from within the IDE, you'll be presented with the following rather unhelpful popup.

Windows Service Start FailureWindows Service Start Failure

This is because the default code which is added to program.cs relies on launching the Service using the static ServiceBase.Run() method. This is the entry point for "the scum". (The SCM, or Service Control Manager is the external program which controls launching the services). We can still debug our Application from here but it's a little bit round about.

  1. Build the Service.
  2. install it using InstallUtil.exe
  3. NET START the service.
  4. Attach to that Process using the Debug menu in Visual Studio.
  5. Find a bug.
  6. Detachn & NET STOP the service.
  7. Uninstall, re-build, wash-rinse-repeat.

Using Debugger.Launch & Debugger.Break

Another option available to trigger debugging is to embed calls to the Debugger directly in your code. These calls could be surrounded by Conditional attributes to prevent Debug Launch statements leaking into release versions of code.

protected override void OnStart(string[] args)
        {
            DebugLaunch();
            MainTimer.Enabled = true;
        }

        private void MainTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            DebugBreak();
            var path = Assembly.GetExecutingAssembly().Location;
            var dir = Path.GetDirectoryName(path);
            var text = string.Format("Tick: {0:yyyy-MM-dd HH:mm:ss.fff}{1}", DateTime.Now, Environment.NewLine);
            File.AppendAllText(dir + "\output.log", text);
            Console.Write(text);
        }

        [Conditional("DEBUG")]
        public void DebugLaunch()
        {
            Debugger.Launch();
        }

        [Conditional("DEBUG")]
        public void DebugBreak()
        {
            Debugger.Break();
        }

[notice]Unfortunately it would appear that this doesn't work in Windows 8. Microsoft have slowly been phasing out the ability of Windows Services to run in Interactive mode and interact with the desktop. Since the Debugger.Launch statement needs to load a GUI for the user to interact with, the Windows Service would appear to hang on this statement. [/notice]

Launch Windows Service with F5

What would be very helpful is if we could just launch our application from within the Debugger as needed. Well it turns out we can by conditionally launching the service using the ServiceBase.Run() method or by just launching it as a regular instantiated class.

static class Program
{
    static void Main(string [] args)
    {
        var service = new DemoService();

        if (Debugger.IsAttached)
        {
            service.InteractiveStart(args);
            Console.WriteLine("Press any key to stop!");
            Console.Read();
            service.InteractiveStop();
        }
        else
        {
            ServiceBase.Run(service);
        }
    }
}

Now if I press F5, the application entry point will check if the application is in Debug Mode (Debugger Attached) and if so will simply launch the application as a console application. In order to get this to work I've had to make a few small changes.

  1. Go to the Project Properties screen & change the application type from "Windows Application" to "Console Application"
  2. Add two public wrapper methods to the service for InteractiveStart & InteractiveStop since the OnStart & OnStop methods are protected
  3. Add a Console.Read() between calling Start and Stop to prevent the Service from immediately shutting down.

Command Line Switch Driven Behaviour

Relying on the Debugger being attached is all well and good but what if I just want to run my application optionally in stand-alone mode. We could extend the runtime condition from the last code snippet to also test whether the application is running in InteractiveMode. But I think I'd prefer a little more power so I've added a command line switch to create this behavior. Now I have the option to Install from the command line using InstallUtil, run from the command line interactively, and I can set a startup switch argument in Project Properties -> Debug -> Start Options to run the application in Console mode from the IDE.

static class Program
{
    static void Main(string [] args)
    {
        var service = new DemoService();

        if (args.Any() && args[0].ToLowerInvariant() == "--console")
        {
            RunInteractive(service,args);
        }
        else
        {
            ServiceBase.Run(service);
        }
    }

    private static void RunInteractive(DemoService service, string [] args)
    {
        service.InteractiveStart(args);
        Console.WriteLine("Press any key to stop!");
        Console.Read();
        service.InteractiveStop();
    }
}

Self Managed Installation

My service is becoming more self-sufficient and useful but I still have this external dependency on InstallUtil. Wouldn't it be great if I could just drop my application on a server and have it install itself. Enter the ManagedInstallerClass

static void Main(string [] args)
{
    var service = new DemoService();
    var arguments = string.Concat(args);
    switch(arguments)
    {
        case "--console":
            RunInteractive(service,args);
            break;
        case "--install":
            ManagedInstallerClass.InstallHelper(new [] { Assembly.GetExecutingAssembly().Location });
            break;
        case "--uninstall":
            ManagedInstallerClass.InstallHelper(new [] { "/u", Assembly.GetExecutingAssembly().Location });
            break;
        default:
            ServiceBase.Run(service);
            break;
    }
}

If you want to simplify matters even further, you can modify your ProjectInstaller.Designer.cs file and specify that the Service should be of type AutomaticStart

// 
// serviceInstaller1
// 
this.serviceInstaller1.ServiceName = "DemoWindowsService";
this.serviceInstaller1.StartType = ServiceStartMode.Automatic;

InstallUtil Install-Time Configuration

One final thing you might find useful is the ability to query values from your configuration file when using Intsall Util. The problem here is that the AppSettings collection during installation is not the collection in the service.config but the collection in the InstallUtil app.config. Have a read of the following link for getting access to your own config at install time (e.g. in order to install with Default Supplied UserName & Password).

http://trycatch.me/installutil-windows-services-projectinstallers-with-app-config-settings/

Future Proofing

One last piece of advice. If you're building any sort of sizeable windows service/back-end processor, I would strongly suggest you NOT implement any logic in the service class itself. Typically I will create some sort of "Engine" class that encapsulates all my functionality with 3 external accessible members.

  • A public constructor called from Service Constructor
  • A public Start or Init method, called from OnStart()
  • A public Stop or End method, called from OnStop()

The reason for this is simply to prevent lock-in & tight coupling to the windows service treating it only as a helpful host. This allows me to easily spin up my Engine class in a standalone Console Application, inside a web apps Global.asax or in an Azure Worker role with a minimum of refactoring in order to extract the functionality.

~Eoin Campbell

Eoin Campbell

Eoin Campbell
Dad, Husband, Coder, Architect, Nerd, Runner, Photographer, Gamer. I work primarily on the Microsoft .NET & Azure Stack for ChannelSight

CPU Spikes in Azure App Services

Working with Azure App Services and plans which have different CPU utilization profiles Continue reading

Building BuyIrish.com

Published on November 05, 2020

Data Partitioning Strategy in Cosmos DB

Published on June 05, 2018