Click here to Skip to main content
Click here to Skip to main content

Windows Services Can Install Themselves

By , 22 Nov 2007
 

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.

using System.Reflection;
using System.Configuration.Install;

namespace gotnet.biz.Utilities.WindowsServices
{
    public static class SelfInstaller
    {
        private static readonly string _exePath =
            Assembly.GetExecutingAssembly().Location;
        public static bool InstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }

        public static bool UninstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { "/u", _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }
    }
}

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.

using gotnet.biz.Utilities.WindowsServices;

namespace MyService.WinHost
{
    static class Program
    {
        public static void Main( string[] args )
        {
            if (args != null && args.Length == 1 && args[0].Length > 1
                && (args[0][0] == '-' || args[0][0] == '/'))
            {
                switch (args[0].Substring( 1 ).ToLower())
                {
                    default:
                        break;
                    case "install":
                    case "i":
                        SelfInstaller.InstallMe();
                        break;
                    case "uninstall":
                    case "u":
                        SelfInstaller.UninstallMe();
                        break;
                    case "console":
                    case "c":
                        MyConsoleHost.Launch();
                        break;
                }
            }
            else
                MyWinServiceHost.Launch();
        }
    }
}

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.

History

  • 22 Nov, 2007 - Initial publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

W. Kevin Hazzard
Web Developer
United States United States
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralInstall on first run [modified]memberAshley van Gerven21 Dec '07 - 17:55 
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:
 
if (args.Length == 0)
{
	ServiceController sc = new ServiceController("AshFMS2");
	try
	{
		string s = sc.Status.ToString();  // throws exception if service does not exist
	}
	catch
	{
		if (System.Windows.Forms.MessageBox.Show("Install this service?          ", "Confirm", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
		{
			SelfInstaller(SelfInstallOptions.Install);
			return;
		}
	}
}
else
{
	switch (args[0])
	{
		case "-i" :
		case "/i" :
			SelfInstaller(SelfInstallOptions.Install);
			return;
		case "-u" :
		case "/u" :
			SelfInstaller(SelfInstallOptions.Uninstall);
			return;
	}
}
 
BTW SelfInstaller is my method containing code based on yours.
 
------------------------------
My Latest CP article: SmartPager - a Flickr-style pager control with go-to-page popup layer.
modified on Saturday, December 22, 2007 12:01:11 AM

GeneralRe: Install on first runmemberW. Kevin Hazzard26 Dec '07 - 3:54 
Very good, Ashley. Nice extension of the concept. It would be interesting to make
your code detect the version of the installed service and upgrade it on demand.
 

GeneralRe: Install on first runmemberAshley van Gerven26 Dec '07 - 4:13 
I think if you stop the service you can replace the .exe no problem.
 
cheers
Ashley
 

"For fifty bucks I'd put my face in their soup and blow." - George Costanza

CP article: SmartPager - a Flickr-style pager control with go-to-page popup layer.


GeneralCan't start service.memberRicky27 Nov '07 - 14:09 
..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.Wink | ;-)
..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.
 
Look sharp, be sharp, see sharp!

GeneralRe: Can't start service.memberW. Kevin Hazzard29 Nov '07 - 10:00 
I suppose I would start by looking around in the registry at HKLM\System\CurrentControlSet\Services to see how your service got registered. Is the ImagePath value OK for your service?
 

GeneralRe: Can't start service.memberRicky30 Nov '07 - 12:44 
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 Confused | :confused:
 
Look sharp, be sharp, see sharp!

GeneralRe: Can't start service.memberRicky30 Nov '07 - 12:54 
It works!!! No substituted drive pathing allowed. *.msi files have similar problems.
Now I can breathe and thank you for a great article.
Ricky Big Grin | :-D
 
Look sharp, be sharp, see sharp!

GeneralI took a different tack [modified]memberPIEBALDconsult26 Nov '07 - 16:12 
The EXE takes command line parameters. If the first parameter is "INSTALL" then it does:
 
System.Diagnostics.Process.Start
(
    "INSTALLUTIL"
,
    "\"" + System.Windows.Forms.Application.ExecutablePath + "\""
) ;
 
With similar code for UNINSTALL. START and STOP can also be done from the command line.
 
(I chose this method when I wrote my first Windows service three years ago and haven't revisited the code since, I guess I'll have to now.)
 

 
-- modified at 9:33 Thursday 29th November, 2007
 
SWEET! Thanks!
GeneralRe: I took a different tackmembercplas14 Jan '09 - 14:04 
The whole point of this article is NOT to use InstallUtil.
GeneralYou can go further (an alternative approach)memberOleg Shilo26 Nov '07 - 15:18 
Good job.
 
I do agree with all your reasons for improving standard service installation approach. Using InstallUtil.exe in the way MS intended is a pain. I think your approach is great however you can go even further (and in "more standard" way) by directly addressing the issue of "not knowing" InstallUtil.exe path. After all you have already guessed where to get help - Reflection.
 
The following code represents complete solution for service self-installation. It uses InstallUtil.exe thus all its functionality (e.g. logging to file) is still available.
 
I've been using this approach for more than a year and I had no problems with it. It is no surprise as it is exactly the way of service installation, which is regarded as a standard service installation. I just use a couple of tricks to get path information at runtime and one of these tricks is the same as yours.
 
BTW AppDomain.ExecuteAssembly even allows you to see InstallUtil.exe console output as yours.
Also note that InstallUtil.exe is executed in the same process space as the service application so no new (external) process is started.

 
string installUtil = Path.Combine(Path.GetDirectoryName(typeof(string).Assembly.Location), 
                                  "InstallUtil.exe");

AppDomain dom = AppDomain.CreateDomain("temp");

if (args[0].ToLower() == "/i")
{
    dom.ExecuteAssembly(installUtil, null, new string[] { 
        "/LogFile="
        "/LogToConsole=false"
        Application.ExecutablePath });
}
else if (args[0].ToLower() == "/u")
{
    dom.ExecuteAssembly(installUtil, null, new string[] { 
        "/u"
        "/LogFile="
        "/LogToConsole=false"
        Application.ExecutablePath });
}

 
Cheers,
Oleg

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 22 Nov 2007
Article Copyright 2007 by W. Kevin Hazzard
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid