Windows Services Can Install Themselves

Never use the InstallUtil.exe utility that ships with the .NET SDK again.

Introduction

Using the InstallUtil.exe utility that ships with the .NET SDK can be a real pain. It's rarely in the PATH so you probably have to hunt down the utility when you are working on QA and production servers as I do. Installing a Windows Service should be easier. In this short article, I'll show you a way to make your Windows Services install themselves without needing InstallUtil.exe at all.

Assumptions

Let's assume that your service project has a service installer, a service process installer and a class derived from System.Configuration.Install.Installer already. If not, check out Mahmoud Nasr's excellent article on Windows Service development, then come back here.

Key Information

Thanks to Reflector for .NET by Lutz Roeder, it's easy to discover how the InstallUtil.exe utility does its job. After some setup, the InstallUtil.exe tool jumps to a method called InstallHelper in the ManagedInstallerClass in the System.Configuration.Install namespace. And what's really interesting is that the command line arguments passed to InstallUtil.exe as an array of strings are then passed directly to this helper method.

Well, this made me think, "If all InstallUtil.exe does is call the ManagedInstallerClass' InstallHelper method, why can't my service executable do the same thing to install itself on command?" The little class below makes it simple to do just that.

Using the Code

Create a new CS file in your service executable project containing the following code. You may also need to add a reference to the System.Configuration.Install.dll from the Global Assembly Cache if you don't already have one.

Now you need to come up with some sort of convention for knowing when to invoke the installer. Below is just an example of how you might handle this in your Main() method, the entry point to your service. I like the convention of using -i or -install parameters to install the service and -u or -uninstall to uninstall it. I also like to use -c or -console to mean starting the application in a console rather than as a service. However, that's a topic for a different article.

Now, assuming my executable is named MyWinSvcHost.exe, I can invoke the installer by running:

C:\> MyWinSvcHost.exe -install

Or to uninstall my service, I would use this:

C:\> MyWinSvcHost.exe -uninstall

Other Ideas

This little bit of code called the SelfInstaller is full of possibilities. You could pass parameters to the InstallMe method to pass on to the ServiceProcessInstaller in your program, for example. Perhaps the domain name, user name and password used to start your service could be passed all the way from your Main() method to the ServiceProcessInstaller. Cool, right? I thought you would like that.

Share

About the Author

After 16 years as an ardent C++ aficionado, Kevin switched to C# in 2001. Recently, Kevin's been dabbling in dynamically typed languages. Kevin is the Software Architect for Snagajob.com, the #1 source for hourly and part-time employment on the web.

Kevin loves welding, riding motorcycles and spending time with his family. Kevin has also been an adjunct professor teaching software engineering topics at a college in his hometown of Richmond, Virginia since 2000. Check out Kevin's technical blog at www.gotnet.biz for more goodies.

It uses the native API and the latest versions (available through CodePlex as part of a greater project: http://www.codeplex.com/aspnetSuite[^]) support UAC and 32/64 bit support on Windows, Vista, and beyond. It will auto-detect if it's not installed and attempt to install it. You can also provide command line options to install, uninstall, etc. It also provides install/uninstall/start/stop/etc. utils for any service on the system (each w/ UAC support, if needed) and will automatically degrade to a typical console-based application if running in user interactive mode. It also provides more control than the .net framework-provided one in terms of what messages you can respond to (e.g. system shutdown, power states, etc.), your error handling capabilities, etc.

You can write an entire service w/ all of that functionality w/ ~2 methods.

After trying your suggestions inside a windows service I'm programming, there is no console output. Surely it has something to do with the type of the project (window service instead of console application), but I don't know what to change in the project properties to get something. Of course, if I start with a console appl I get no service

pd.- by console output I mean everything that gets out from system.console.writeline(), for example.

I combined the enhancements by Aleksei Nickolayev and Ashley van Gerven, and now have my services accepting command line switches and offering to install or uninstall if launched interactively. This is the best! No more batch files!

Thanks for a great article, and thanks to the other users for the great enhancement ideas!

I have a problem I can install a Windows service by InstallUtil but I can't do it when I try to do it with ManagerInstallerClass. It's strange, I reflected the Code (is the same that InstallUtil) and it doesn't work. Context data? This is My Code:

Excellent article! I have found non-command ways to do all things service related, apart from uninstalling...

I have created a helper service (Service1Updater) for my main service (Service1). Service1Updater does two things
-Checks a network folder to see if there's any updates for Service1 and installs them
-Checks to see if Service1 is running, if not then it tries to start it

The reason I'm using a service to do the updating:
-I don't want Service1 to fail when updating, I don't want to manually fix things
-I don't want any user interaction during updates
-I have a large cluster of computers to roll out the updates, I can't do it manually would take ages
-I want Service1 to stay alive if something unforeseen happens, so I use a helper process

Between "uninstall Service1 -> delete old Service1 files" I always get Access denied or UnauthorizedAccessException as the InstallHelper seams to still have a handle open on the old Service1. If I use a Process command then I have no problem deleteing the old files:

Although this works it would be nice to have an InstallHelper solution especially as InstallUtil maybe in a different location. Does anyone have any ideas? Would I have to host the InstallHelper in a different thread/process and wait for it to exit?

Not only does it put a lock on the file when you uninstall it, also, if you check to see if it is a installable service (using AssemblyInstaller.CheckIfInstallable) then you cannot delete the file either unless you use this method.

I have one little improvement. In case of your code user must know what switches service executable accepts. I suppose it's a good idea to show all acceptable switches to user when the service was started from console, not from SCM.
The method that I use to determine how service was started is quite simple — checking the value of Environment.UserInteractive.

Firstly, very nice simple solution - thanks for sharing that tip. I decided to try it out and came up with the idea of installing the service if it's not already installed (i.e. just double click it in Explorer, and confirm whether or not to install it). Worked well for me, using this code:

..When I try to start the command line installed service from the Computer Management tool,
I get a Services message box (!), quoting "Could not start the MyCLI_Service on Local Computer. Error 3: The system cannot find the path specified." Huh?
..The CLI Install appears to have gotten the correct path, so what gives?
..The Uninstall appears to work. (I have done it several times.
..The original project was setup as a Windows Service project and the solution included a setup/deployment project which got the service into the Services list of the Computer Management tool. This original version of the service could be started, paused (required a property setting change), and stopped.

The ImagePath appears okay. However, I am using a fake drive/path, that is, I have substituted a C: path with J: drive. I will try placing all the files in a real path on the C: drive. Will update results. Thanks for feedback. Ricky