Writing a Useful Windows Service in .NET in Five Minutes [Dave Fetterman]

Writing a Useful Windows Service in .NET in Five Minutes [Dave Fetterman]

Rate This
  • Comments 16

When opening up a new project in Visual Studio, one finds many, many options for deploying your new killer app: Windows Applications, Console Applications, and Web Services are all popular choices.  Even when creating a simple daemon-type process, however, the Windows Service option is likely overlooked, because it interacts with the system in what many perceive to be an alien way, and it just looks more difficult.

Not so. 

Creating a useful Windows service with the .NET Framework, even for those without VS, couldn't be simpler. 
Having just come from a highly UNIX-oriented background, I thought I could use a cron-type application.  For those unfamiliar, cron is a background daemon which will, at at certain times or time intervals, execute given command lines (say, logging, source synchornization or file cleanup).  We'll build the bones for this step-by-step, straight from code.  Services operate under rules akin to pretty much any other application, so it's not surprising that the code should look so similar.

Creating a ServiceBase

To start, simply derive a class from ServiceBase and implement a Main() method, with or without string [] args param list. 
ServiceBase provides many event-style overrides, so you should use these as appropriate:

using System;
using System.ServiceProcess;

public class CronService : ServiceBase
{
  public CronService()
  {
    this.ServiceName = "Cron";
    this.CanStop = true;
    this.CanPauseAndContinue = false;
    this.AutoLog = true;
  }

  protected override void OnStart(string [] args)
  {
    // do startup stuff
  }
 
  protected override void OnStop()
  {
   // do shutdown stuff
  }
 
  public static void Main()
  {
   System.ServiceProcess.ServiceBase.Run(new CronService());
  }
}

Essential lines to watch are the ServiceName, AutoLog, and the content of Main(). 
* The ServiceName is the name of the application as recorded by the Windows Service Control Manager.  If you've ever used "net start <name>", you've used a ServiceName.
* AutoLog should be set to true, to get any system logs, especially when debugging.
* Main should Run() your new service.

Creating an Installer

This is a good starter skeleton for writing a simple service app.
However, Windows Service applications cannot simply be started and stopped like a standalone app.  Thus, we must add a set of installers for the Service Control Manager (SCM).  Below is a class that will do just that.  ServiceInstaller instances take care of installing an individual service class;
a ServiceProcessInstaller installs an executable containing such classes. 

using System.ComponentModel;
using System.Configuration.Install;

[RunInstaller(true)]
public class CronInstaller : Installer
{
  private ServiceProcessInstaller processInstaller;
  private ServiceInstaller serviceInstaller;

  public CronInstaller()
  {
    processInstaller = new ServiceProcessInstaller();
    serviceInstaller = new ServiceInstaller();

    processInstaller.Account = ServiceAccount.LocalSystem;
    serviceInstaller.StartType = ServiceStartMode.Manual;
    serviceInstaller.ServiceName = "Cron";

    Installers.Add(serviceInstaller);
    Installers.Add(processInstaller);
  } 
}  

For a cron-type job, the ServiceInstaller's StartType might be better served as a ServiceStartMode.Automatic (started at system startup).  The Manual mode will give you a better feel for getting started.  Points of note:

* The RunInstaller line is necessary for invoking the System.Configuration.Install.Installer class during service install.  We'll touch on this later.
* The ServiceName should match that of your service base-derived class (here, CronService).
* We need to include a ServiceInstaller for each service class.
* We need to include a ServiceProcessInstaller for each installed executable.
* Installers should be Added in the constructor.

Without getting too deep, this forms the skeleton of your service process.

Adding actual tasks

And, for our final code snippet in this app, let's actually add a task for the service to accomplish.  You can imagine this as running a shell command you find useful.  This is where your crontab task would go, for those familiar.

public class CronJob
{
  // The state object is necessary for a TimerCallback.
  public void DoSomething(object stateObject)
  {
    // do something  
  }
}

To make this actually interesting, we'll add some more to the service and we're done!  Instead of just saying "//do startup stuff", let's add a timer to call our DoSomething task with the red lines:

using System.Threading;
public class CronService : ServiceBase

  private CronJob job;
  private Timer stateTimer;
  private TimerCallback timerDelegate
;
  ...

  protected override void OnStart(string [] args)
  {
    job = new CronJob();
    timerDelegate = new TimerCallback(job.DoSomething);
    stateTimer = new Timer(timerDelegate, null, 1000, 1000);

  }

  protected override void OnStop()
  {
    stateTimer.Dispose();
  }
  ...

This will have a Timer class fire the timerDelegate every second.  You may wish to "do something" with a different frequency.
Put your installer, your service, and your task code into a file called, say, cron.cs.  Build as normal (csc).

Installing and Using your new Service

There's one final step.  As mentioned before, services are managed by the Windows SCM, so we can't just install and control them in the usual way.  Locate the .NET InstallUtil executable next to your csc and run:

InstallUtil /LogToConsole=true cron.exe

The flag is optional but nice.  Assuming all went well, you can check out your Computer Management window and click "Services" under "Services and Applications".  A service named "Cron" should show up.

You can either start and stop with this window, automatically, (at startup, as explained earlier), or with 
net start cron and net stop cron.

And there you are!  Once every second, you're DoingSomething.  Let no one say you're not productive.
Windows Services are really easy to write in .NET, and are often the overlooked perfect solution to the problem at hand.

One Final Caveat

Some people have asked about some unexplained behavior of Windows Services, installed via .NET or no.  Because of security policy, Windows requires that you explicitly allow services to access the desktop.  I am not aware that this will change in .NET 2.0.  To allow Cron (or your newly-installed service) to start processes, follow these steps.

1. Start from the Computer Management console. (My Computer -- right click --> Manage)
2. Find your service under the "Services" node.
3. Right click and go to Properties.
4. At the Log On tab, check the box "Allow Service to Interact with Desktop" and Apply.

When your service is restarted, your Processes should be good to go.  This works whether you start a process with UseShellExecute=true or false.

To our discerning readers: Thanks for catching this!  Please let the BCL know how your shiny new service projects turn out.