Saturday, January 9, 2010

How to Write a .NET Windows Service with Console Mode

Windows Services are applications that run in the background and provide some functionality to the system. They have virtually no user interface – except Windows’ built in “start” and “stop” functionality and a few settings to control security, etc.  This makes them great for standalone processes that ‘just work’ and require no user intervention, but it also makes them notoriously difficult to debug and develop.  Because of this, I typically create two projects when creating windows services – the windows service for production, and a console app for development.  However, recently, I came up with a way to combine the two, so that I can have both the windows service AND the console app in the same project.  Here’s what I did:

First, in general, it’s good to separate your actual logic into a separate class library that contains the bulk of your service logic.  I name this something like “MyCompany.App”, and generally have one class that the Windows Service can use fairly easily– that is, it has a Start and Stop method.

   1: public class MyService{
   2:    public void Start(){//Tip:BackgroundWorkers are great here!...}
   3:    public void Stop(){...}
   4: }

Next, add a Windows Service project and in the ServiceBase code that is generated, new up the above class and use it:

   1: public partial class MyServiceWindowsService : ServiceBase
   2: {
   3:    private MyService service;
   4:  
   5:    public AlertFmMonitoringStationService()
   6:    {
   7:        InitializeComponent();
   8:    }
   9:  
  10:    protected override void OnStart(string[] args)
  11:    {
  12:        this.service = new MyService();
  13:        service.Start();           
  14:    }
  15:  
  16:    protected override void OnStop()
  17:    {
  18:        service.Stop();
  19:    }
  20: }

Add a ProjectInstaller (open the service in design mode, view properties, and click ‘Add Installer’), and you have the windows service, ready for installation with installutil.exe.   To wire up the console app, a few more steps are required.  See, Windows Services are .exes, just like any other app – they just happen to follow a certain convention that makes them Windows Services (namely, they have a ProjectInstaller and one or more instances of ServiceBase).   As long as we follow that convention, Windows is happy, and we can do whatever else we want.  So:

  • Right click the Windows Service project and click ‘properties’
  • Set Output type to ‘Console Application’, and Startup Object to the ‘Program’ class.
  • Under ‘Debug’ enter ‘-console’ in the ‘Startup Options’ ‘Command Line Arguments’ section
  • Inside the ‘Program’ class, add a switch to enable console mode
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{           
  if (args.Contains("-console"))
  {
      System.Console.WriteLine("press 'q' to quit.");
      var app = new MyService();
      app.Start();
      while (Console.ReadKey().KeyChar != 'q')
      {
      }
      app.Stop();
  }
  else
  {                
      ServiceBase[] ServicesToRun;
      ServicesToRun = new ServiceBase[]
                          {
                              new MyServiceWindowsService()
                          };
      ServiceBase.Run(ServicesToRun);
  }
}

And that’s it!  The end result is a command line app that can be run using the ‘-console’ switch, or installed as a Windows Service.  When debugging locally, it’s run as a console app.  Ideally, you would build a Setup package to install it, but to install manually, you would use the installutil tool that ships with .NET.

8 comments:

am said...

Thanks. Great blog post.

Have thought about this for a while and searched for it.

Nice and simple.

Unknown said...

Instead of checking -console argument, I beleive it would be better use System.Environment.UserInteractive.

Unknown said...

Instead of checking -console argument, I beleive it would be better use System.Environment.UserInteractive.

Unknown said...

Thank you for your idea and code. :-)

samuel fernandes said...

This is awesome!! really helpful for me. Thanks for sharing with us. Following links also helped me to complete my task.

http://www.mindstick.com/Articles/e91aa075-0e62-4b05-8498-148c936bc370/?Windows%20Service%20in%20Net

http://msdn.microsoft.com/en-us/library/d56de412(v=vs.80).aspx

Fumbles WithCode said...

Hi Daniel,

I have been working with your approach, using Environment.UserInteractive to enable console mode, and like it very much.

The problem is that I need to stop the service if certain critical resources are not available during startup. I have my primary logic located in a separate class library from my Windows Service project and the ServiceBase, much the same as you do in your example.

My service installs and starts correctly and my log shows the class library's Stop() method is entered as expected when the resources are not found.

Unfortunately, I don't seem to be able to engage ServiceBase.Stop() from my class library. I am using a ServiceController to connect to the running service (the class library cannot reference the service directly because it creates a circular reference) and using it to issue a Stop() but the ServiceBase.Stop() method is not being entered. I know this because of the logging breadcrumbs I'm leaving everywhere.

I am able to stop the service from the Services Console. I'm just not able to do it programmatically.

Have you encountered this before?

Many thanks.

Daniel Root said...

@Fumbles I would accomplish this by adding an event to the primary logic class, then subscribing to it in the Windows Service. Something like

var service = new MyService();
public void Start(){
service.MissingResources += MissingResourceHandler;
service.Start()
}

public void MissingResourceHandler()
{
service.Stop();
}

This way, the logic code is decoupled from the service architecture completely. Alternatively, you could throw an exception from the Start() method.

Fumbles WithCode said...

Hi Daniel. Thanks for the quick response.

I ended up throwing an exception from the class library's Start() method. The "critical resources" that determine if the service can run are based on database lookups that take place in the class library. Because of this I could not use your first suggestion (decoupling).

Throwing up an exception did work though.

Thanks again,
Fumbles.