Make a Windows Service Install Itself

This is part of a series on creating and installing Windows services.

  1. How to Create a Windows Service
  2. How to Install a Windows Service
  3. How to Run a Windows Service from Visual Studio
  4. How to Avoid Windows Service Start-up Timeouts
  5. How to Make a Windows Service Install Itself
  6. How to Handle Windows Service Errors
  7. How to Build a Windows Service Framework

Earlier in our series, we described a couple mechanisms for installing a windows service:

  • sc – a simple, bare bones mechanism that does not require any custom installation code
  • InstallUtil – a more complex mechanism which provides some extra features, like logging, but requires us to write some custom code (an Installer implementation)

In this episode, we will get the best of both worlds — a full-featured install without any custom code. We will accomplish this by adding self install functionality to the service infrastructure we have built so far. Our self installing services will be able to register themselves with the service control manager when invoked with a particular command line argument (we will use “i”) and uninstall with another (we will use “u”). By adding this functionality to our infrastructure, our applications will get consistent, tested, and simple install/uninstall functionality “for free” without the need to write any extra code.

We begin with the BasicServiceStarter static class we created last time and add the functionality to install/uninstall when the application is running in interactive mode:

public static void Run<T>(string serviceName) where T : IService, new()
{
    if (Environment.UserInteractive)
    {
        var cmd =
            (Environment.GetCommandLineArgs().Skip(1).FirstOrDefault() ?? "")
            .ToLower();
        switch (cmd)
        {
            case "i":
            case "install":
                Console.WriteLine("Installing {0}", serviceName);
                BasicServiceInstaller.Install(serviceName);
                break;
            case "u":
            case "uninstall":
                Console.WriteLine("Uninstalling {0}", serviceName);
                BasicServiceInstaller.Uninstall(serviceName);
                break;
            default:
                using (var service = new T())
                {
                    service.Start();
                    Console.WriteLine(
                        "Running {0}, press any key to stop", serviceName);
                    Console.ReadKey();
                }
                break;
        }
    }
    else
    {
        ServiceBase.Run(new BasicService<T> { ServiceName = serviceName });
    }
}

As you can see, when “i” or “install” is passed, we will install the service. When “u” or “uninstall” is passed, we will uninstall the service. Otherwise, we preserve the existing behavior — running the service as a console application. As we fill out the BasicServiceInstaller class, notice the similarities with the custom Installer (for use by InstallUtil) that we created earlier:

static class BasicServiceInstaller
{
    public static void Install(string serviceName)
    {
        CreateInstaller(serviceName).Install(new Hashtable());
    }

    public static void Uninstall(string serviceName)
    {
        CreateInstaller(serviceName).Uninstall(null);
    }

    private static Installer CreateInstaller(string serviceName)
    {
        var installer = new TransactedInstaller();
        installer.Installers.Add(new ServiceInstaller
        {
            ServiceName = serviceName,
            DisplayName = serviceName,
            StartType = ServiceStartMode.Manual
        }); 
        installer.Installers.Add(new ServiceProcessInstaller
        {
            Account = ServiceAccount.LocalSystem
        });
        var installContext = new InstallContext(
            serviceName + ".install.log", null);
        installContext.Parameters["assemblypath"] =
            Assembly.GetEntryAssembly().Location;
        installer.Context = installContext;
        return installer;
    }
}

As before, we create a ServiceInstaller and a ServiceProcessInstaller. Instead of including them in a custom installer implementation, we inject them into a TransactedInstaller, which we execute directly via the Install and Uninstall functions. The most important difference is that we need to tell the installer the location of our assembly via the context’s “assemblypath” parameter. This parameter tells the installer what assembly is being installed as the service — we need to pass the entry assembly’s location. We’ve also configured the installer to write a log file to the local directory, named after our service.

With this infrastructure in place, we can now exercise our install logic. To install, execute the service with the “i” parameter:

RunInstall

And to uninstall, pass the “u” parameter:

RunUnInstall

If we are content with the self-installing behavior, we can remove our Installer implementation from our demo service. This will prevent InstallUtil from working on our service, but we have a better alternative now.

Leave a Reply

Your email address will not be published. Required fields are marked *