Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Install a Windows Service in a smart way instead of using the Windows Installer MSI package

0.00/5 (No votes)
30 Jul 2007 1  
Install a Windows Service dynamically (even with username and password) using the WindowsServiceInstallUtil C# class

Introduction

The usual way to install a Windows service is to use an MSI install file. In fact, using an MSI is only one of several ways to install a Windows service. In some particular situations, a Windows service may be needed to install and uninstall many times. Someone might even think of a way to "install it with a programming language" instead of clicking on MSI or running the InstallUtil (.NET SDK utility) command line.

This article will explain how to install a Windows service in a smart way. In addition, the installer class has been extended to support both the usual installation way with MSI or InstallUtil and the new dynamic way in which we can pass in a username and password -- from a DB, for instance -- to start a Windows service with that specific user.

Background

I came up with the idea to deliver this solution when we made an automated integration testing system in which there are executables and Windows services that have to be started before doing testing. The process of installing, uninstalling, starting and stopping executables and Windows services must be automatic. The only way to do that is for our testing system to be able to install and uninstall Windows services instead of using MSI or installutil.

I tried to look for any available C# or even VB.NET coding class from the Internet that could help me solve this problem, but that was useless. Luckily, I found an interesting article submitted by gtamir. What he did was actually just change the installer class and create a BAT file, replacing the installutil command line. However, the idea of extending arguments of installutil is truly great and I was able to create an advanced C# installutil class from there.

DynamicInstaller

The .NET SDK InstallUtil.exe utility invokes the ProjectInstaller module in your assembly. For installation, InstallUtil monitors all installation steps and rolls back the installation if an error occurs. For uninstalling, InstallUtil runs the Uninstall code in your ProjectInstaller module. For more information about the install process, see MSDN documentation for the ServiceInstaller class.

Screenshot - InstallUtil.gif

using System;
using System.Collections;
using System.Configuration.Install;
using System.ServiceProcess;
using System.ComponentModel;

[RunInstallerAttribute(true)]
public class ProjectInstaller : Installer{
    private ServiceInstaller serviceInstaller1;
    private ServiceInstaller serviceInstaller2;
    private ServiceProcessInstaller processInstaller;

    public MyProjectInstaller(){
        // Instantiate installers for process and services.

        processInstaller = new ServiceProcessInstaller();
        serviceInstaller1 = new ServiceInstaller();
        serviceInstaller2 = new ServiceInstaller();

        // The services run under the system account.

        processInstaller.Account = ServiceAccount.LocalSystem;

        // The services are started manually.

        serviceInstaller1.StartType = ServiceStartMode.Manual;
        serviceInstaller2.StartType = ServiceStartMode.Manual;

        // ServiceName must equal those on ServiceBase derived classes.

        serviceInstaller1.ServiceName = "Hello-World Service 1";
        serviceInstaller2.ServiceName = "Hello-World Service 2";

        // Add installers to collection. Order is not important.

        Installers.Add(serviceInstaller1);
        Installers.Add(serviceInstaller2);
        Installers.Add(processInstaller);
    }
}

Since the InstallUtil command line doesn't support any arguments containing accountType, userName or password when installing a Windows service, all settings such as serviceName, accountType, userName, password, etc. will be taken from the Installer inside the Windows service assembly. What can you do if you want to install a Windows service to start under a user account? When using the default Installer, there are only two ways to do that: type in the username and password box during installation or have a fixed username and password inside the Installer code.

Screenshot - SetServiceLogin.gif

Therefore DynamicInstaller was created in order to pass in the username and password dynamically from anywhere you want, e.g. a database. The beautiful thing is that it still keeps the original features of the default Installer so that if you want to use MSI or InstallUtil as usual, that's also possible and even much easier with the properties created.

Screenshot - DynamicInstallerProperties.gif

Because DynamicInstaller is stored inside a separated assembly, ServiceInstaller.dll, it's easy to be inherited by a different ProjectInstaller on different projects.

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
using Microsoft.Win32;
namespace ServiceInstaller
{
    /// <summary>

    /// This is a custom project installer.

    /// Applies a unique name to the service using the /name switch

    /// Sets user name and password using the /user and /password switches

    /// Allows the use of a local account using the /account switch

    /// </summary>

    [RunInstaller(true)]
    public class DynamicInstaller : Installer
    {
        public string ServiceName
        {
            get { return serviceInstaller.ServiceName; }
            set { serviceInstaller.ServiceName = value; }
        }
        public string DisplayName
        {
            get { return serviceInstaller.DisplayName; }
            set { serviceInstaller.DisplayName = value; }
        }
        public string Description
        {
            get { return serviceInstaller.Description; }
            set { serviceInstaller.Description = value; }
        }
        public ServiceStartMode StartType
        {
            get { return serviceInstaller.StartType; }
            set { serviceInstaller.StartType = value; }
        }
        public ServiceAccount Account
        {
            get { return processInstaller.Account; }
            set { processInstaller.Account = value; }
        }
        public string ServiceUsername
        {
            get { return processInstaller.Username; }
            set { processInstaller.Username = value; }
        }
        public string ServicePassword
        {
            get { return processInstaller.Password; }
            set { processInstaller.Password = value; }
        }
        private ServiceProcessInstaller processInstaller;
        private System.ServiceProcess.ServiceInstaller serviceInstaller;
        public DynamicInstaller()
        {
            processInstaller = new ServiceProcessInstaller();
            processInstaller.Account = ServiceAccount.LocalService;
            processInstaller.Username = null;
            processInstaller.Password = null;
            serviceInstaller = new System.ServiceProcess.ServiceInstaller();
            serviceInstaller.StartType = ServiceStartMode.Automatic;
            serviceInstaller.ServiceName = "MyService";
            serviceInstaller.DisplayName = "";
            serviceInstaller.Description = "";
            Installers.AddRange(new Installer[] {
            processInstaller,
            serviceInstaller});
        }
        #region Access parameters
        /// <summary>

        /// Return the value of the parameter in dictated by key

        /// </summary>

        /// <PARAM name="key">Context parameter key</PARAM>

        /// <returns>Context parameter specified by key</returns>

        public string GetContextParameter(string key)
        {
            string sValue = "";
            try
            {
                sValue = this.Context.Parameters[key].ToString();
            }
            catch
            {
                sValue = "";
            }
            return sValue;
        }
        #endregion
        /// <summary>

        /// This method is run before the install process.

        /// This method is overridden to set the following parameters:

        /// service name (/name switch)

        /// account type (/account switch)

        /// for a user account user name (/user switch)

        /// for a user account password (/password switch)

        /// Note that when using a user account,

        /// if the user name or password is not set,

        /// the installing user is prompted for the credentials to use.

        /// </summary>

        /// <PARAM name="savedState"></PARAM>

        protected override void OnBeforeInstall(IDictionary savedState)
        {
            base.OnBeforeInstall(savedState);
            bool isUserAccount = false;

            // Decode the command line switches

            string name = GetContextParameter("name").Trim();
            if (name != "")
            {
                serviceInstaller.ServiceName = name;
            }
            string desc = GetContextParameter("desc").Trim();
            if (desc != "")
            {
                serviceInstaller.Description = desc;
            }
            // What type of credentials to use to run the service

            string acct = GetContextParameter("account");
            switch (acct.ToLower())
            {
                case "user":
                    processInstaller.Account = ServiceAccount.User;
                    isUserAccount = true;
                    break;
                case "localservice":
                    processInstaller.Account = ServiceAccount.LocalService;
                    break;
                case "localsystem":
                    processInstaller.Account = ServiceAccount.LocalSystem;
                    break;
                case "networkservice":
                    processInstaller.Account = ServiceAccount.NetworkService;
                    break;
            }
            // User name and password

            string username = GetContextParameter("user").Trim();
            string password = GetContextParameter("password").Trim();
            // Should I use a user account?

            if (isUserAccount)
            {
                // If we need to use a user account,

                // set the user name and password

                if (username != "")
                {
                    processInstaller.Username = username;
                }
                if (password != "")
                {
                    processInstaller.Password = password;
                }
            }
        }
        /// <summary>

        /// Uninstall based on the service name

        /// </summary>

        /// <PARAM name="savedState"></PARAM>

        protected override void OnBeforeUninstall(IDictionary savedState)
        {
            base.OnBeforeUninstall(savedState);
            // Set the service name based on the command line

            string name = GetContextParameter("name").Trim();
            if (name != "")
            {
                serviceInstaller.ServiceName = name;
            }
        }
    }//end class

}

Now it becomes easy to install a Windows service with a programming language by using WindowsServiceInstallUtil.cs. This class contains many options in constructors for convenient usage. The fully customized way to install a Windows service is by passing in every piece of information, such as serviceName, description, assemblyLocation, accountType, username, password, etc.

//

// Installing windows service to start with username and password

//

//If install with local user:

//wsInstallInfo = new WindowsServiceInstallInfo

    ("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
// WindowsServiceAccountType.User, @".\username", @"password");

//If install with network user:

//wsInstallInfo = new WindowsServiceInstallInfo

    ("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
// WindowsServiceAccountType.User, @"networkdomain\username", @"password");

wsInstallInfo = new WindowsServiceInstallInfo
    ("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
WindowsServiceAccountType.User, @".\username", @"");
wsInstallUtil = new WindowsServiceInstallUtil(wsInstallInfo);
//Log to see any error

//wsInstallUtil.Install(@"C:\test.txt");

result = wsInstallUtil.Install();
Console.WriteLine("Installed : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
//Log to see any error

//wsInstallUtil.Uninstall(@"C:\test.txt");

result = wsInstallUtil.Uninstall();
Console.WriteLine("Uninstalled : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();

Screenshot - MyService.gif

Points of interest

Please bear in mind that you can still install this Windows service assembly via MSI as usual. You can even create a *.bat file to execute the InstallUtil command instead of using the C# installutil class. This is installService.bat:

@echo off
set SERVICE_HOME=<service executable directory>
set SERVICE_EXE=<service executable name>
REM the following directory is for .NET 1.1, your mileage may vary
set INSTALL_UTIL_HOME=C:\WINNT\Microsoft.NET\Framework\v1.1.4322
REM Account credentials if the service uses a user account
set USER_NAME=<user account>
set PASSWORD=<user password>

set PATH=%PATH%;%INSTALL_UTIL_HOME%

cd %SERVICE_HOME%

echo Installing Service...
installutil /name=<service name>
  /account=<account type> /user=%USER_NAME% /password=%

PASSWORD% %SERVICE_EXE%

echo Done.

The variables set at the top are for convenience only. You can certainly hardcode all information directly on the installutil line. This is uninstallService.bat:

@echo off
set SERVICE_HOME=<service executable directory>
set SERVICE_EXE=<service executable name>
REM the following directory is for .NET 1.1, your mileage may vary
set INSTALL_UTIL_HOME=C:\WINNT\Microsoft.NET\Framework\v1.1.4322

set PATH=%PATH%;%INSTALL_UTIL_HOME%

cd %SERVICE_HOME%

echo Uninstalling Service...
installutil /u /name=<service name> %SERVICE_EXE%

echo Done.

History

  • 5 July, 2007 -- Original version posted
  • 7 July, 2007 -- Article moved
  • 30 July, 2007 -- Downloads updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here