Skip to content

Self installing services in .NET

I have some service applications that I deploy with Wise for Windows. These particular services are .NET assemblies. The usual way of registering the .NET assembly as a service is to use the installutil.exe that comes with the .NET Framework. Wise made it easy to register the assemblies by adding a checkbox in the file properties for self installation. Behind the scenes, Wise must be calling installutil, because it fails when you have multiple versions of the .NET Framework installed. Installutil is not compatible across Frameworks. You can’t install a 1.1 assembly with the 2.0 installutil, and vice versa.

Wise does not let you specify which version of the Framework is being used by a particuliar assembly. It should be able to tell through Reflection, but it doesn’t. This means I can’t specify the correct installutil to use for my services. This is not good and causes my install projects to go down in flames. I really can’t wait for Wise to fix this.

I could call installutil directly, but that means putting all sorts of fugly code into the install project to correctly locate the appropriate version of installutil. And that code would probably break the minute Microsoft updates the .NET Framework. So we move to Plan B, self-installing services. You would think that this would be a simple walk through the MSDN garden, but their code examples assume that that task is being handled manually via installutil or through a Windows Installer project.

After a bit of Googling, I found a reference to an undocumented method call, InstallHelper, in the System.Configuration.Install.ManagedInstallerClass class. By using this method, I can install or uninstall the service from the command line.

I augmented the Main() function in the service class to look like this:

static void Main(string[] args)

{

if (args.Length > 0)

{

if (args[0] == “/i”)

{

System.Configuration.Install.ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });

}

else if (args[0] == “/u”)

{

System.Configuration.Install.ManagedInstallerClass.InstallHelper(new string[] { “/u”, Assembly.GetExecutingAssembly().Location });

}

else if (args[0] == “/d”)

{

CollectorService MyService = new CollectorService();

MyService.OnStart(null);

System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

}

}

else

{

System.ServiceProcess.ServiceBase[] ServicesToRun;

ServicesToRun = new System.ServiceProcess.ServiceBase[] { new CollectorService() };

System.ServiceProcess.ServiceBase.Run(ServicesToRun);

}

}

The “/d” part hasn’t been tested yet. That should allow me to debug the service as an application from within the Visual Studio IDE. As much as I dislike having to use an undocumented class, I’m not going to lose any sleep over it. Microsoft obsoleted documented functions going from Visual Studio 2003 to 2005, I’m not going to worry about one method.

[Edited on 6/8/06]
I updated the block of code for the “/d” part. I needed a timeout to keep the service running, otherwise it just runs through the startup and then exits. You can make it fancier, I just use that code for testing from within the IDE and I can break out of the service when I am done testing it.

[Edited on 7/20/06]
After a few go arounds with Wise Technical Support, I sent them a sample installer project that easily duplicate this bug and they did confirm that it was a problem with their current product. There is also a similiar problem where you can’t install .NET 1.1 services under similiar circumstances. Their fix for my problem will fix the .NET 1.1 service problem too. According to the email that I had received, this is tentatively scheduled for the next release. That would probably be the version 7.0 release. In the meantime, I’ll stick with my work around.

[Edited on 1/27/08]
The MyService object in the above code is an instance of a System.ServiceProcess.ServiceBase descendant class that I created in my code.  The descendant class opens up access to the protecteded OnStart() method.  I had created a descendant to ServiceBase and had assumed that was the standard pattern.  I should have been more clear about that part.  This is one of the many reasons why I abandoned Wise for InstallAware.