Installing a Managed Service with a Custom Name [Robert Villahermosa]

Title: Installing a Managed Service with a Custom Name

Author: Robert Villahermosa (RobVi)

An interesting customer question came to my attention a few weeks ago. The customer had created a managed service, and was installing it with a custom name that was provided at install time by the user. I’ve seen several customer applications that do this. Upon uninstall, they were encountering a failure – this seemed bizarre, as during install time everything seemed to have gone well with no errors. What had happened here?

Let’s take a look at how Services get installed and how we can install a simple service with the name of our choice. From a very high level, you first create a service that you want to install. You then create an installer that specifies the actions you want to take place at install and uninstall time. Finally you install your component using the install utility (InstallUtil.exe). InstallUtil.exe is a tool that lets you install/uninstall server resources based on components in an assembly. This tool ships with the .NET Framework Redistributable. Ok, now it’s time to drill down and take a look at how to do all this…

The ServiceBase class

There are a few things that need to be understood before we get started. Let’s begin by taking a look at the actual service. All services must derive from the ServiceBase class.

The ServiceBase class has several protected methods that you can override in your derived class. For a service to be useful, you probably want to override at least the OnStart and OnStop methods.

OnStart is executed when a Start command is sent to the service by the Service Control Manager.

OnStop is executed when a Stop command is sent to the service by the Service Control Manager.

Below is the code snippet for a service that we can install. By default, a service has the AutoLog property set to true, which means that it will log an event log message whenever it is started or stopped. You can modify the OnStart and OnStop methods to do something useful.

public class SimpleService : ServiceBase

{

    public static void Main()

    {

        ServiceBase.Run(new SimpleService());

    }

    public SimpleService()

    {

        CanPauseAndContinue = true;

      // Here we are setting a service name to show you don't need a custom // name if you don't want one

        // This gets overriden in this example later

        ServiceName = "SimpleService";

    }

    protected override void OnStart(string[] args)

    {

       //This is where your service would do something useful when it starts

    }

    protected override void OnStop()

    {

        //This is where your service would do something useful when it stops

    }

}

The Installer class

Now that we have our simple service to install, let’s take a look at how we can install it.

Custom installers in the .NET Framework all derive from the Installer class. There are several methods that you can override in this class, but we won’t go into detail about these here except for one property called Installers. This property is an InstallerCollection of all the separate installers that this instance of this type contains. In order to install an application, you create your own ProjectInstaller class that derives from Installer, and then add all the installers to the InstallerCollection.

The ServiceProcessInstaller and ServiceInstaller classes

A service application can consist of multiple services. The ServiceProcessInstaller class installs a service application and does work common to all services in it. You need one ServiceProcessInstaller per service application. The ServiceInstaller class installs a class that extends ServiceBase to implement a service, so you need one ServiceInstaller per service.

Confused? Let’s look at the code sample below to clarify things.

[RunInstallerAttribute(true)]

public class ProjectInstaller : Installer

{

    private ServiceInstaller serviceInstaller;

    private ServiceProcessInstaller processInstaller;

    public ProjectInstaller()

    {

        processInstaller = new ServiceProcessInstaller();

        serviceInstaller = new ServiceInstaller();

        // Service will run under system account

        processInstaller.Account = ServiceAccount.LocalSystem;

        // Service will have Start Type of Manual

        serviceInstaller.StartType = ServiceStartMode.Manual;

      // Service will have the following name (optional) this just shows

        // you don't need to have a custom name, you can omit this though

        serviceInstaller.ServiceName = "SimpleService";

        // Hook up some custom events prior to the install and uninstall

        BeforeInstall += new InstallEventHandler(BeforeInstallEventHandler);

        BeforeUninstall += new InstallEventHandler(BeforeUninstallEventHandler);

        Installers.Add(serviceInstaller);

        Installers.Add(processInstaller);

    }

.

.

.

   

As seen above, one ProjectInstaller is created that derives from Installer. Then, a processInstaller and serviceInstaller are added to this instance. I’m setting some properties above as defaults, specifically we’re saying we want our sample service to run using the LocalSystem account and we want to start it manually. I almost forgot to mention, you need an attribute RunInstallerAttribute set to true. This gets read by InstallUtil.exe to determine what installer components in a specified assembly get executed.

What happened to specifying a custom name?

Ahh, I haven’t forgotten – this post was created to see how to let the user choose their own custom service name. Notice how I’ve hooked up two eventhandlers in my Project Installer code. BeforeInstall gets called at execution time prior to the service being installed and BeforeUninstall gets called prior to the service being uninstalled.

You can ask the user to specify the name of the service they’d like installed I the BeforeInstall event handler. Here, I’m just asking the user to enter the name of the service they’d like. When this gets installed using InstallUtil.exe, the install will wait for the user to enter a service name before continuing. There’s no magic involved – once InstallUtil.exe completes a service with the name of your choice appears in your service manager (run services.msc) to see it. You can also see that it gets registered in the registry under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services.

At uninstall time, you need to know the name of the service that was installed in order to uninstall it. This brings me back to the initial problem that the customer had encountered – their service installed correctly, but failed to uninstall. What had happened? They failed to persist the service name in their install logic, so at uninstall time their installer was failing. Persisting the right data is one of the classic install/uninstall problems.

How do I persist the data?

In my example below, the service name gets peristed in install time into a text file called “Service.config”. Looking at the UninstallEventHandler, it just reads the name back out of this text file at uninstall time to remove the service. Of course, there are many ways you could persist the service name. I wrote it to a file for simplicity in this example but you could of course persist it somewhere else, such as in a registry key under your company name

Here’s the rest of the listing for the Installer:

     private void BeforeInstallEventHandler(object sender, InstallEventArgs e)

    {

        // Add steps to perform any actions before the install process.

        Console.WriteLine("BeforeInstallEventHandler Called");

        Console.WriteLine("Enter the name you would like this service installed as:");

        serviceInstaller.ServiceName = Console.ReadLine();

        PersistServiceName(serviceInstaller.ServiceName);

        Console.WriteLine("Attempting to install service as: " + serviceInstaller.ServiceName);

    }

   

    private void BeforeUninstallEventHandler(object sender, InstallEventArgs e)

    {

        Console.WriteLine("BeforeUninstallEventHandler Called");

        serviceInstaller.ServiceName = RetrieveServiceName();

        // Add steps to perform any actions before the Uninstall process.

        Console.WriteLine("Code for BeforeUninstallEventHandler");

    }

    private void PersistServiceName(string serviceName)

    {

        //This method stores the service name,

            you can choose whatever method to persist this data as you like

        TextWriter tw = new StreamWriter("Service.config", false);

        tw.WriteLine(serviceName);

        tw.Close();

    }

    private string RetrieveServiceName()

    {

        string serviceName;

        //This method retrieves the service name,

            and should mirror the Perist method above

        TextReader tr = new StreamReader("Service.config");

        serviceName = tr.ReadLine();

        tr.Close();

      Console.WriteLine("ServiceName" + serviceName);

        return serviceName;

    }

}

Bringing it all together

Ok, that’s it for the explanation – let’s see how to actually run all this and watch it work.

Here are the steps to follow:

1.) Create a simple service that we want to install

2.) Create a ProjectInstaller class specifying some defaults and handing custom install/uninstall events

3.) Compile this service, run “csc MyService.cs” (this will generate MyService.exe)

4.) Install this service, run “InstallUtil.exe MyService.exe”

a. You’ll get prompted for a service name here

b. You’ll see this name gets persisted to a file Service.config

5.) Check to see if the service installed (run services.msc or check the registry under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services)

6.) To uninstall the service, run “InstallUtil.exe MyService.exe /u”

That’s it! As you can see, you can now install/uninstall a service with the name of your choice. Now go add something useful to this service so it makes you happy. The code is listed here so you can build this without copying/pasting.