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   
Questionwill it install without using newer version of installutil .exe incase our windows service is built in newer version only if latest client profile and runtime is intalled.memberShabanaParveen4 Dec '12 - 21:42 
will it install without using newer version of installutil .exe incase our windows service is built in newer version only if latest client profile and runtime is intalled. Logically it should not be able to do. In that can anyone help me with the approach. I do not want VS 2010 to install on the machine.
QuestionInstall two services from the same assemblymemberP. de Jong30 Aug '11 - 4:23 
Based on this great article, and the very useful added service template I'd like to share what I've experienced while searching for a way to install two services from the same assembly:
 
This might be a bit tricky, but the way to differentiate which service is actually being started is the Service's status:
 
if (new ServiceController("Service1").Status == ServiceControllerStatus.StartPending)
    ServiceBase.Run(new Service1());
if (new ServiceController("Service2").Status == ServiceControllerStatus.StartPending)
    ServiceBase.Run(new Service2());
 
When you decide to launch using the override for ServiceBase.Run, as a matter of fact only the first one is actually started.
 
StartPending is the state assigned to the service immediately after having selected 'Start' from the Services menu.
 
Best regards,
 
Piet
GeneralService TemplatememberSteve Foster (Havok Multimedia)26 Sep '10 - 11:09 
Based off of this excellent article and the many additions made in the comments, I created a 'template' windows service file, that I thought might be useful for others. The code assumes that you have setup the default assembly attributes, and have some type of 'bootstrap' class that has a default constructor and implements IDisposable. Most of the configuration is contained in the WindowsServiceConfiguration. Thanks again for the great article.
 
To use it, just create a Console Project, replace the Program.cs file with this code, setup the attributes in the AssemblyInfo.cs file, and point the THREAD field to a class to run.
You will need to add references to System.Configuration, System.Configuration.Install, and System.ServiceProcess.
 
 
#region Imports
 
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
 
#endregion
 
namespace MyNamespace
{
    public static class WindowsServiceConfiguration
    {
        #region Constants
 
        /// <summary>
        /// If SERVICE_USERNAME is left blank then use this account to run the service. 
        /// </summary>
        public static readonly ServiceAccount SERVICE_ACCOUNT = ServiceAccount.LocalSystem;
 
        /// <summary>
        /// The service description, defaulted to the Assembly Description attribute.
        /// </summary>
        public static readonly String SERVICE_DESCRIPTION = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyDescriptionAttribute), true).Cast<AssemblyDescriptionAttribute>().First().Description;
 
        /// <summary>
        /// The service name, defaulted to the Assembly Title attribute.
        /// </summary>
        public static readonly String SERVICE_NAME = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyTitleAttribute), true).Cast<AssemblyTitleAttribute>().First().Title;
 
        public static readonly ServiceStartMode START_MODE = ServiceStartMode.Automatic;
 
        /// <summary>
        /// The thread to start and stop. Must have a default constructor and implement IDisposable.
        /// Most likely this class will create a System.Thread in it's constructor and launch it.
        /// It could also do something like start an IoC container, Web Server, etc.
        /// </summary>
        public static readonly Type THREAD = typeof (Container);
 
        #endregion
 
        #region User Account Info
 
        // if SERVICE_USERNAME isn't set, default to SERVICE_ACCOUNT as user
        public static readonly String SERVICE_DOMAIN;
        public static readonly String SERVICE_PASSWORD;
        public static readonly String SERVICE_USERNAME;
 
        #endregion
    }
 
    public class Program
    {
        #region Constants
 
        public static readonly Assembly ASM = Assembly.GetExecutingAssembly();
        public static readonly String NL = Environment.NewLine;
        public static readonly String SERVICE_ASSEMBLY_VERSION = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyFileVersionAttribute), true).Cast<AssemblyFileVersionAttribute>().First().Version;
        public static readonly String SERVICE_COPYRIGHT = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyCopyrightAttribute), true).Cast<AssemblyCopyrightAttribute>().First().Copyright;
        public static readonly String SERVICE_VERSION = Assembly.GetExecutingAssembly().GetName().Version.ToString();
 
        #endregion
 
        public static int Main(string[] args)
        {
            // Started by user
            if (Environment.UserInteractive)
            {
                // Show version information
                Console.Write(
                    NL + WindowsServiceConfiguration.SERVICE_NAME + " " +
                    // If the version isn't the same as the file version, display both.
                    (SERVICE_VERSION == SERVICE_ASSEMBLY_VERSION ? SERVICE_VERSION : SERVICE_VERSION + " " + "[" + SERVICE_ASSEMBLY_VERSION + "]") +
                    NL + SERVICE_COPYRIGHT);
 
                // Parsing command line
                if (args != null && args.Length >= 1)
                {
                    if (args[0].ToLower() == "/i") return MainInstaller(true);
                    if (args[0].ToLower() == "/u") return MainInstaller(false);
                    if (args[0].ToLower() == "/c") return LaunchConsole();
                }
 
                // Show usage
                Console.WriteLine(
                    NL + NL + "Usage: " + Path.GetFileName(ASM.Location) + " [/i | /u | /c]" +
                    NL + NL + "Where:" +
                    NL + "\t/i - install service." +
                    NL + "\t/u - uninstall service." +
                    NL + "\t/c - run in console.");
            }
            else
            {
                // Started by SCM
                ServiceBase.Run(new WindowsService());
            }
 
            return 0;
        }
 
        private static int MainInstaller(bool shouldInstall)
        {
            String[] args;
            if (shouldInstall)
            {
                // InstallHelper with /u switch set adds CRLF here, but with /i switch set it does not. So...
                Console.WriteLine();
                args = new[] {"/LogFile=", ASM.Location};
            }
            else
            {
                args = new[] {"/u", "/LogFile=", ASM.Location};
            }
 
            try
            {
                ManagedInstallerClass.InstallHelper(args);
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(" --- ERROR --- " + NL + e);
                return -1;
            }
        }
 
        public static int LaunchConsole()
        {
            //Console.SetBufferSize(1000, 1000);
            Console.WriteLine(NL + NL + "Starting... Press 'Q' or 'Esc' to exit");
 
            try
            {
                using (CreateThread())
                {
                    while (true)
                    {
                        var key = Console.ReadKey(true).Key;
                        if (key == ConsoleKey.Q) break;
                        if (key == ConsoleKey.Escape) break;
                    }
                    Console.WriteLine(NL + "Exiting" + NL);
                }
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(" --- ERROR --- " + NL + e);
                return -1;
            }
        }
 
        public static IDisposable CreateThread()
        {
            return (IDisposable) Activator.CreateInstance(WindowsServiceConfiguration.THREAD);
        }
    }
 
    public class WindowsService : ServiceBase
    {
        #region Constructors
 
        public WindowsService()
        {
            ServiceName = WindowsServiceConfiguration.SERVICE_NAME;
        }
 
        #endregion
 
        #region Fields
 
        private IDisposable thread;
 
        #endregion
 
        #region Service Methods
 
        protected override void OnStart(string[] args)
        {
            if (thread != null) return;
            thread = Program.CreateThread();
        }
 
        protected override void OnStop()
        {
            if (thread != null)
            {
                thread.Dispose();
                thread = null;
            }
        }
 
        #endregion
    }
 
    [RunInstaller(true)]
    public class WindowsServiceInstaller : Installer
    {
        #region Constructors
 
        public WindowsServiceInstaller()
        {
            if (WindowsServiceConfiguration.SERVICE_USERNAME == null)
            {
                processInstaller.Account = WindowsServiceConfiguration.SERVICE_ACCOUNT;
            }
            else
            {
                if (WindowsServiceConfiguration.SERVICE_DOMAIN == null)
                {
                    processInstaller.Username = WindowsServiceConfiguration.SERVICE_USERNAME;
                }
                else
                {
                    processInstaller.Username = WindowsServiceConfiguration.SERVICE_DOMAIN + "\\" + WindowsServiceConfiguration.SERVICE_USERNAME;
                }
 
                processInstaller.Password = WindowsServiceConfiguration.SERVICE_PASSWORD;
            }
 
            serviceInstaller.ServiceName = WindowsServiceConfiguration.SERVICE_NAME;
            serviceInstaller.Description = WindowsServiceConfiguration.SERVICE_DESCRIPTION;
            serviceInstaller.DisplayName = WindowsServiceConfiguration.SERVICE_NAME;
            serviceInstaller.StartType = WindowsServiceConfiguration.START_MODE;
 
            var installer = new Installer[] {processInstaller, serviceInstaller};
 
            Installers.AddRange(installer);
        }
 
        #endregion
 
        #region Fields
 
        private readonly ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
        private readonly ServiceInstaller serviceInstaller = new ServiceInstaller();
 
        #endregion
    }
}
 

GeneralRe: Service TemplatememberComplexityChaos14 Oct '10 - 6:26 
The code as is will do nothing in console mode. Would you provide an example where the OnStart event is triggered (or pseudo triggered) in console mode? Assume, please, no IOC container.
Thanks
GeneralRe: Service TemplatememberSteven D. Foster15 Oct '10 - 5:05 
The OnStart method calls the same CreateThread method as the console mode, so triggering OnStart would do (as intended) the exact same as the Console mode.
 
Replace the Program.CreateThread() method to return a timer or some other thread instead. If you need information on threads, check out something like System.Timers.Timer
 
Here is an example (I haven't tested, just going off the top of my head).
 
public static IDisposable CreateThread()
{
   // return (IDisposable) Activator.CreateInstance(WindowsServiceConfiguration.THREAD);
   var timer = new System.Timers.Timer(5000) {AutoReset = true};
   timer.Elapsed += ((sender, e) => Console.WriteLine("Hello World: " + DateTime.Now));
   timer.Start();
   return timer;
}

GeneralNice butmentorGiorgi Dalakishvili6 Mar '10 - 10:18 
When I try to start the service I am getting Error 1053: the service did not respond to the start or control request in a timely fashion. Using an ordinary installer the service works fine.
 
Any ideas?
Giorgi Dalakishvili
 
#region signature
My Articles
 
Browsing xkcd in a windows 7 way[^]
 
#endregion

Questionhow do i get the service name from another exememberjbencic19 Dec '09 - 15:56 
I am creating a deployment app to auto deploy many msi's
 
currently we use WIX msi's and the installutil to install the services
 

 
what i was trying to do was use the ManagedInstallerClass to install the services
 
problem is how can i retrieve the service's names passing the exe path?
i need to store and restore the service state
 

        public static bool InstallService(string _exePath, string svcName)
        {
            try
            {
                // TODO: get service name from the executable
                if (IsServiceInstalled(svcName)) return false;
 
                ManagedInstallerClass.InstallHelper(new string[] { _exePath });
            
                // verify results
                if (!IsServiceInstalled(svcName)) return true;
            }
            catch(Exception ex)
            {
                Logger.Log(ex);
            }
            return false;
        }

QuestionError 1053: The service did not respond to the start or control request in a timely fashion.memberkhayyam221 Sep '09 - 20:15 
When i install my service using the installUtil.exe i dont get any error and service runs successfully. but when i implement the above code in my service project i get following error message.
 
Error 1053: The service did not respond to the start or control request in a timely fashion.
 
Please help
GeneralTwo lines are confusing me please helpmemberkhayyam221 Sep '09 - 20:11 
case "console":
                    case "c":
                        MyConsoleHost.Launch();
                    break;
                }
            }
            else
            MyWinServiceHost.Launch();

GeneralConfused Stepsmemberkhayyam221 Sep '09 - 7:50 
hi all,
 
i am reviewing this code for 3 to 4 days continously but didnt get an idea that
 
1. what do you mean by service executable. Should i put my below code in windows service project
 
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;
        }
    }
}
 
2. The main() code is given later in this article, again should i put this in my windows service project
if i put that in service project then should i delete the prevoius code
 
ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 
                        { 
                            new WindowsService1.Service1() 
                        };
                ServiceBase.Run(ServicesToRun);
 

plz help me i very upset

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

Permalink | Advertise | Privacy | Mobile
Web01 | 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