How to enable your Windows Service to have its name configured during installation

In this posting I will show you how you can configure the name of a Windows Service as it is installed. Once we have tweaked the service installer properly, we will be able to have InstallUtil.exe install our service under a name which we supply as a command-line parameter.

Let’s say we’ve implemented a Windows Service in C#. To install the service, we’ve implemented an Installer, i.e. something like the following:

    [RunInstaller(true)]
    public class WindowsServiceInstaller : Installer
    {
        public WindowsServiceInstaller()
        {
            ServiceProcessInstaller serviceProcessInstaller =
                               new ServiceProcessInstaller();
            ServiceInstaller serviceInstaller = new ServiceInstaller();

            //# Service Account Information
            serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
            serviceProcessInstaller.Username = null;
            serviceProcessInstaller.Password = null;

            //# Service Information
            serviceInstaller.StartType = ServiceStartMode.Automatic;

            serviceInstaller.DisplayName = "My Windows Service";
            serviceInstaller.ServiceName = "my_windows_service";

            this.Installers.Add(serviceProcessInstaller);
            this.Installers.Add(serviceInstaller);
        }
    }

Assuming that the service is contained in Installable.exe, we install the service using InstallUtil.exe like so:

c:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /i c:\service1\Installable.exe

Now, what happens if we want to have two instances of this service running? We might want to have multiple services running, each associated with different databases. If we place Installable.exe and its configuration file in a new folder (say c:\service2) we might try to install this service via

c:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /i c:\service2\Installable.exe

InstallUtil will throw an error: "System.ComponentModel.Win32Exception: The specified service already exists". InstallUtil complains because a service named my_windows_service is already installed.

So, what can we do? The obvious thing to do is to just change the lines

            serviceInstaller.DisplayName = "My Windows Service";
            serviceInstaller.ServiceName = "my_windows_service";

in WindowsServiceInstaller, recompile and run InstallUtil.exe again. While this works, it doesn’t play well with source control: what should the serviceInstaller.ServiceName property be set to in the code committed to the repository? How do you ensure that you remember to verify that this property is set correctly, before you compile and deploy the code?

What we really need is a way to supply the service name to the install procedure. Ideally, we would like to be able to do something like

c:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /i c:\service1\Installable.exe /servicename="my_service_instance_1" /servicedisplayname="My Service Instance 1"

but InstallUtil doesn’t support these arguments. However, InstallUtil won’t bail out if it encounters unknown arguments either. This observation is going to be half the  solution. The other half is knowing that you can use System.Environment.GetCommandLineArgs() to get at the command line arguments provided for the current process.

Thus, in the installer we can access the command line arguments provided to InstallUtil, parse these arguments ourselves and set the service name accordingly.

To do this, we add the following private methods to the installer

private void SetServicePropertiesFromCommandLine(ServiceInstaller serviceInstaller)
{
	string[] commandlineArgs = Environment.GetCommandLineArgs();

	string servicename;
	string servicedisplayname;
	ParseServiceNameSwitches(commandlineArgs, out servicename, out servicedisplayname);

	serviceInstaller.ServiceName = servicename;
	serviceInstaller.DisplayName = servicedisplayname;
}

private void ParseServiceNameSwitches(string[] commandlineArgs, out string serviceName, out string serviceDisplayName)
{
	var servicenameswitch = (from s in commandlineArgs where s.StartsWith("/servicename") select s).FirstOrDefault();
	var servicedisplaynameswitch = (from s in commandlineArgs where s.StartsWith("/servicedisplayname") select s).FirstOrDefault();

	if (servicenameswitch == null)
		throw new ArgumentException("Argument 'servicename' is missing");
	if (servicedisplaynameswitch == null)
		throw new ArgumentException("Argument 'servicedisplayname' is missing");
	if (!(servicenameswitch.Contains('=') || servicenameswitch.Split('=').Length < 2))
		throw new ArgumentNullException("The /servicename switch is malformed");

	if (!(servicedisplaynameswitch.Contains('=') || servicedisplaynameswitch.Split('=').Length < 2))
		throw new ArgumentNullException("The /servicedisplaynameswitch switch is malformed");

	serviceName = servicenameswitch.Split('=')[1];
	serviceDisplayName = servicedisplaynameswitch.Split('=')[1];

	serviceName = serviceName.Trim('"');
	serviceDisplayName= serviceDisplayName.Trim('"');
}

The SetServicePropertiesFromCommandLine method retrieves the command line arguments and configures the installer to set the service’s name properties. The second method, ParseServiceNameSwitches, is just a utility method for SetServicePropertiesFromCommandLine.

We use these methods from InstallableServiceInstaller’s constructor:

public InstallableServiceInstaller()
{
	ServiceProcessInstaller serviceProcessInstaller =  new ServiceProcessInstaller();
	ServiceInstaller serviceInstaller = new ServiceInstaller();

	//# Service Account Information
	serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
	serviceProcessInstaller.Username = null;
	serviceProcessInstaller.Password = null;

	SetServicePropertiesFromCommandLine(serviceInstaller);

	//# Service Information
	serviceInstaller.StartType = ServiceStartMode.Automatic;
	this.Installers.Add(serviceProcessInstaller);
	this.Installers.Add(serviceInstaller);
}

We can now install our service, providing the service names to InstallUtil as desired:

c:\windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe /i c:\service1\Installable.exe /servicename="my_service_instance_1" /servicedisplayname="My Service Instance 1"