This post contains gist code samples. My experiments indicate that these do not display in Google Reader, so if you are reading this there, you may want to click through to the post.
I recently had to estimate the effort for creating a one-off Windows Service for massaging some document indexing data as it passed between a capture system and an ECM system. I was quite confident in my ability to code up the "massaging" part, but the service part was unfamiliar territory. So I decided to do some investigation and figure out just what it takes to get a service set up, what special considerations need to be made for logging and injecting configuration, and how to preserve good design sensibilities through it all.
The easiest way to create a Windows service is to use the associated project template in Visual Studio. I specifically wanted to work in .NET 4.0 for the investigation, as I would be on the actual project, but unfortunately I don't personally have a license for the full version of Visual Studio 2010, and VS Express 2010 doesn't have a project template for Windows services. Fortunately, I do have VS 2008, and the BCL infrastructure for services hasn't changed between 3.5 and 4. So I decided to generate what I could in VS 2008, add some googling, and use the combined information to create a service from scratch in VS Express.
This week, we'll analyze what VS will give us for free with the project template and special code-generating actions. We'll identify the purpose of the different pieces of code, and make note of how we can use this information to build our own service, complete with installers, from scratch.
So to start, let's take a look at the structure that the project template creates for us. Here's a shot of what the initial solution looks like in a brand new Windows Service project.
Note the two structural differences between this template project and a blank or console project. There's an extra reference to System.ServiceProcess.dll, which we'll need to remember, and there is an extra component class called Service1. There's also a .Designer.cs file for Service1, but this is really just because Visual Studio automatically adds these any time an item that is a subclass of Component is added to your project, either via a template, or the "Add New Item" command.
Before we take a look inside the service class itself, we should see how the Program class differs from other types of project templates. Here's the Program class.
There are a few differences to note here. The biggest difference is obviously that the main method contains some generated code. It actually looks similar to what is generated with the new Windows Forms project template. We have the service class, representing the actual core of the program, being instantiated and then passed to a runner method provided by the framework that handles spinning up the appropriate context.
Now lets take a look at the service class. It comes in two parts: the main class file, and the designer file. First the main class file.
The most prominent features here is the base class, ServiceBase, and the two override methods, OnStart and OnStop. ServiceBase is, of course, the common base class that all .NET windows service classes must derive from, in order to take advantage of the framework support. The overrides are two of the possible entry points into the service. They correspond directly to the actions that you can take on a service in the Services control panel. OnStart is where you would place the code that processes the arguments, spins up workers, etc. Naturally OnStop is the other end of things, where you'd put your cleanup code to prepare for termination. There's also a call to InitializeComponent in the constructor, which is typical of generated Component-derived classes. We'll see if there's anything interesting happening there next, in the designer file.
There's a bunch of generated code here, but there's actually less going on. So what is all this doing? We can easily see an implementation of a Dispose idiom, which is disposing of any components contained inside this one. But when we look back at the InitializeComponent method, the Container is constructed and then left empty. The only significant code here is the assignment of the ServiceName property. We'll need to remember this for when we create our own service class from scratch. Everything else in this file could be dropped. The ServiceName initialization could just as easily be put directly into the constructor.
What we've seen so far is the absolute minimum service. If you put some behavior into the OnStart method, you could run from the command-line, and it would operate. But it wouldn't show up in the Services control panel, because it doesn't have an installer to register it. And that means you couldn't Stop it once it was started.
What does it take to register a service with the OS? VS offers a shortcut to generate the code for this as well. If you right-click the service class Service1.cs in the solution explorer, the context menu has an "Add Installer" option, which adds a new class to the solution, called ProjectInstaller. This is the last piece of our example puzzle, and it's another two-parter. First the base class file.
There's not much code here either. Like the service, we see this is a Component-derived class, by way of Installer) with an InitializeComponent method called in the constructor. We'll take al ook inside there in a moment. First, make note of the attribute on the class. The RunInstaller attribute is a signpost for the InstallUtil.exe installer tool that comes with the .NET Framework, or for the VS Setup Package Custom Action Installer. The Boolean true argument tells the installers that that they should indeed invoke this installer class when they find it in the assembly.
The attribute is important, but it's not the meat of the class. That's found in the designer file.
The form of this file is very similar to that of the service's designer file (and any designer file, really). Once again we see the disposal idiom, and once again, the container that it disposes is never populated with anything. The body of the InitializeComponent method is concerned with creating and initializing two more installer objects. One is a ServiceInstaller, which is a class that knows how to install a particular service in the registry and such, given a ServiceBase representing it. The ServiceProcessInstaller handles install tasks common to all services, and so there should be only one inside a given project installer.
The MSDN documentation doesn't go into any more detail about what the responsibility differences are between these two types of installer, or why they are peers, as far as the main Installer is concerned. But it is clear about how they should be used. The installer objects need to have properties initialized to indicate how the install should proceed, and then they must be placed in the Installers collection. This also could just as easily be done in the constructor, eliminating the need for the designer file.
One important thing the documentation highlights is that the ServiceName property on the ServiceInstaller must match the same property on the service which it is associated with, since this is the only thing that links the service registration with the actual implementation. We'll examine the other installation-guiding properties, on both objects, in detail when we put together our own service from scratch.
For now, we've covered everything in the project template. What we have here is a complete, spartan service that does nothing. It will install, simply by compiling the executable, and then running InstallUtil.exe from the .NET framework folder against it. And it will run from the service control panel... but it has no visible side-effects.
Next time, we'll get to work replicating the moving pieces of this solution in a spanking new, blank C# 4 project. It will look a little different, structurally, but all the prominent features will still be there.
P.S. All the code that appears in this series will be available on my github page. For now, I have it in this repo: https://github.com/cammerman/turbulent-intellect. The code for this post in particular is found here: https://github.com/cammerman/turbulent-intellect/tree/master/ServiceExample35. I expect to reorganize this a bit in the future, but I'll update this link when I do so.