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.
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(){
processInstaller = new ServiceProcessInstaller();
serviceInstaller1 = new ServiceInstaller();
serviceInstaller2 = new ServiceInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller1.StartType = ServiceStartMode.Manual;
serviceInstaller2.StartType = ServiceStartMode.Manual;
serviceInstaller1.ServiceName = "Hello-World Service 1";
serviceInstaller2.ServiceName = "Hello-World Service 2";
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.

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.

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
{
[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
public string GetContextParameter(string key)
{
string sValue = "";
try
{
sValue = this.Context.Parameters[key].ToString();
}
catch
{
sValue = "";
}
return sValue;
}
#endregion
protected override void OnBeforeInstall(IDictionary savedState)
{
base.OnBeforeInstall(savedState);
bool isUserAccount = false;
string name = GetContextParameter("name").Trim();
if (name != "")
{
serviceInstaller.ServiceName = name;
}
string desc = GetContextParameter("desc").Trim();
if (desc != "")
{
serviceInstaller.Description = desc;
}
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;
}
string username = GetContextParameter("user").Trim();
string password = GetContextParameter("password").Trim();
if (isUserAccount)
{
if (username != "")
{
processInstaller.Username = username;
}
if (password != "")
{
processInstaller.Password = password;
}
}
}
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
string name = GetContextParameter("name").Trim();
if (name != "")
{
serviceInstaller.ServiceName = name;
}
}
}
}
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.
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
wsInstallInfo = new WindowsServiceInstallInfo
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
WindowsServiceAccountType.User, @".\username", @"");
wsInstallUtil = new WindowsServiceInstallUtil(wsInstallInfo);
result = wsInstallUtil.Install();
Console.WriteLine("Installed : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
result = wsInstallUtil.Uninstall();
Console.WriteLine("Uninstalled : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();

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