This is part of a series on creating and installing Windows services.
- How to Create a Windows Service
- How to Install a Windows Service
- How to Run a Windows Service from Visual Studio
- How to Avoid Windows Service Start-up Timeouts
- How to Make a Windows Service Install Itself
- How to Handle Windows Service Errors
- How to Build a Windows Service Framework
Last time, we created a bare-bones one-line Windows service application and installed it using the sc
command. This time, we will modify our service so that it can be installed with InstallUtil
. Using this approach has the following benefits:
- Installations are performed in a transaction, so they are an all-or-nothing operation. There is no need to fear that your system will be left in a bad state if an installation goes wrong.
- Information about the install and any errors encountered during the install are logged, both to the console and to a file. This provides a useful audit trail for tracking your installation activity.
- An event source specific to the service will be created in the Windows Event Log‘s application log. This means that service lifetime events (start, stop, etc) will be logged to the application log with a “source” named after our application. We also gain the ability to write custom messages to the application event log using this source. Note that this is in addition to the Service Control Manager’s logging to the system event log that we saw before.
As with the first entry in this series, we will not rely on Visual Studio templates to accomplish this, as we wouldn’t learn anything that way. Let’s begin with our Demo application from last time:
class Program { static void Main(string[] args) { ServiceBase.Run(new MyService()); } } class MyService : ServiceBase { }
First, let’s specify the ServiceName of our ServiceBase implementation. This is the name the service will use as the Event Log Source when it logs lifecycle events to the Windows event log. Note that actually registering the event source for our service will be handled by the installation process, which we haven’t yet set up.
class MyService : ServiceBase { public const string Name = "DemoService"; public MyService() { ServiceName = Name; } }
InstallUtil
works by reflecting on the assembly passed to it as a parameter. It finds all classes derived from Installer (with RunInstaller set to true), creates an instance of each, and calls each instance’s Install or Uninstall method (install is the default operation, uninstall is performed when the /u
option is passed to InstallUtil
). Therefore, to work with InstallUtil
, our assembly (DemoService.exe) must contain a public implementation of Installer, tagged with the RunInstallerAttribute:
[RunInstaller(true)] public class MyInstaller : Installer { }
This requires a reference to System.Configuration.Install, and that the same namespace be imported. This installer does nothing as of yet, so it won’t get the job done. To get this installer to register our service with the Service Control Manager, we need to have it create and run a ServiceProcessInstaller and a ServiceInstaller. The former registers our executable as a service host and allows us to configure the account under which the service will run. The latter registers the service (our ServiceBase implementation) itself, and allows configuration of the service name, description, and start type, among other things. These installers are separate because a single service process can host multiple services — note that ServiceBase.Run has an overload that accepts an array of ServiceBase implementations. We will focus on the one-service-per-process approach for now. Our installer needs to create and configure these two installers and add them to its Installers collection. All the installers in the collection will be run when InstallUtil
executes our installer.
[RunInstaller(true)] public class MyInstaller : Installer { public MyInstaller() { Installers.Add(new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem }); Installers.Add(new ServiceInstaller { ServiceName = MyService.Name, DisplayName = MyService.Name, Description = "This is a demo service" }); } }
Note that we’ve set the ServiceName of the ServiceInstaller to the same thing as the ServiceName of the ServiceBase implementation. This is critical to get everything to function correctly. We now have a working installer that will play nice with InstallUtil
. Let’s try it out. Fire up the Visual Studio command prompt, navigate to your project’s output directory (after running a build, of course), and execute the following command:
InstallUtil DemoService.exe
If everything went well, you should see the following output:
Note that InstallUtil
produced a log file in the directory containing our executable called DemoService.InstallLog. The path to the log file can be changed by passing the /LogFile=[file]
argument to the InstallUtil
command. You can also have InstallUtil log the same information to the console by passing /LogToConsole=true
.
If we bring up services.msc, we see that the service has been installed:
Note that the DisplayName we specified in the ServiceInstaller is what shows up as the “Name” in services.msc. However, any programatic access to the service controller (via the sc
command, for example) will require you to use the ServiceName as a handle to the service. For this reason, I strongly recommend setting “ServiceName” and “DisplayName” to the same thing — making them different just causes confusion.
If we start and stop the service using services.msc, and then head over to event viewer, we will now see the start and stop events logged from “Source” “DemoService” under the “Application” log:
As mentioned earlier, there are lots of knobs you can turn with the ServiceInstaller and ServiceProcessInstaller. I recommend playing around with them to get a feel for what is possible. Also play around with error cases — installing a service that is already installed, uninstalling a service that does not exist — to see how InstallUtil
reacts.
We have now spent two full articles creating and installing a service that doesn’t actually do anything. Next time, we will focus on the service itself.
For all of my Windows Service work lately, I’ve been using the open-source TopShelf library that will handle all of the service control logic. It’s as simple as creating a console app and setting a few parameters and you can run it via F-5 in Visual Studio, as a console app, or install as a full-fledged Windows Service. I would highly recommend.
Wags, thanks for the comment — TopShelf looks like a really interesting product, thanks for introducing it to us. Basically, in this series of articles, we’re going to end up building a service framework that looks a lot like TopShelf.
I use TopShelf too. It just doesn’t get easier…
In order to get VS to accept my RunInstaller attribute, I had to add:
using System.ComponentModel;
Also, in the previous step, a service named DemoService was created. Because of that, use of InstallUtil in this step fails. The previous service will have to be removed first. Step 1 should have shown how to do that.