Click here to Skip to main content
15,867,750 members
Articles / Programming Languages / C#
Article

A ServiceInstaller Extension That Enables Recovery and Autostart Configuration

Rate me:
Please Sign up or sign in to vote.
4.95/5 (48 votes)
2 May 2004CPOL12 min read 230.2K   3.1K   95   49
An extension assembly that allows configuring the "advanced" service configuration options for recovery actions.

Contents

This article is organized as follows:

  1. Introduction
  2. The Solution
  3. Implementing the Functionality
  4. Putting it all together: Using the ServiceInstallerEx class
  5. Conclusion

Part I: Introduction

I'd like to begin by providing a brief background of services and their capabilities to put the forthcoming solution into context. Windows services are a feature of NT operating systems such as NT 4, Windows 2000 and XP. Services are not available on legacy "desktop" operating systems such as Windows 95, 98, and the short lived Me. They are analogous to the daemon processes that run on UNIX servers and workstations.

Simply put, a service is just another process, except that it conforms to the service API which enables it to run in the background without a user having to explicitly start it. It should be noted, however, that it is possible for a single service process to run multiple services, thereby sharing the same process space. Services can be configured to start up automatically at boot time before the user logs on, which is critical in situations such as "headless" machines that host mission critical applications in isolated server closets. Another useful feature of services is that they can be stopped, paused and resumed locally as well as remotely using the Windows computer management console. This enables system administrators to, for example, shutdown and restart a SQL server database engine.

Prior to the release of Windows 2000, services did not have fault tolerance built into the interface. In other words, if a service generated a fault and died, there was no way to restart the service. What happened was that good developers ended up writing "keep-alive" code using the Win32 Process or PSAPI.dll interfaces to insure that their services were always available. Microsoft duly noted the significance of this deficiency and introduced extensions to the service configuration API that enabled fine grain recovery options. These new extensions, now permitted a service to configure actions to take, should a service fail. These actions include restarting the service, rebooting the machine, running a user specified command, or taking no action at all (the default). Though the computer management console user interface only allows configuration and display of up to three actions, it is a little known fact that a service can actually be configured for any number of recovery actions.

Management Console Screen Shot

Service Interfaces in .NET

The task of creating services has been greatly eased with the classes in the System.ServiceProcess namespace in the .NET Framework. Of these, the following classes are of particular interest:

  • System.ServiceProcess.ServiceController

    This class provides a rich and easy to use interface to control and obtain information from a local or remote service. Callers can start, stop, or pause services as well as execute custom commands on the service.

  • System.ServiceProcess.ServiceProcessInstaller

    This class provides a simple mechanism to install one or more services when called from a Windows installer utility such as installutil.exe. The service process installer is easily added to any Windows service project in Visual Studio .NET by right clicking in design view of the service and selecting "Add Installer".

  • System.ServiceProcess.ServiceInstaller

    This class provides a mechanism for installing a single service. Multiple services are installed by having multiple instances of this class in a ServiceProcessInstaller instance. The ServiceInstaller class provides properties and configuration information for the particular service.

Deficiencies in the .NET class libraries

Though useful and simple, Microsoft did not provide interfaces to configure the "advanced" service options such as the recovery options. They even failed to provide an interface to set the description for the service. I found this to be a little disappointing since fault tolerance in services is nothing to joke about. Another deficiency with service installations is that there is no way to configure them to start right after installation. In order to start our new service, we would have to:

  1. Create a setup project and custom action to start the service
  2. Create a startup process to start the service and instruct the user to run this after installation.
  3. Instruct the user to restart the machine or tell them to use the management console (NO!). This is not desirable since it is quite unnecessary to restart the entire machine (that could be running mission critical applications) simply to start one itty bitty service.

It certainly would be more pragmatic to be able to configure your service to start right after installation – hands free.

PART II: The Solution

Microsoft is quite aware of folks like me, and did the correct thing by not sealing the ServiceInstaller class. This allows us to easily extend it by simply deriving from it and adding our own capabilities. For this solution, I created a new class called ServiceInstallerEx (ala Win32 extensions) and derived all the base class functionality that already exists such as setting dependencies, events etc.

C#
public class ServiceInstallerEx : System.ServiceProcess.ServiceInstaller

In the constructor for the class, I simply register two event handlers for the Committed event of the base class. When a service is installed, there are many events thrown, however the ones we are mostly concerned with are the AfterInstall and Committed events. The AfterInstall event is fired when the service is initially installed. The Committed event is fired when the service has been installed and fully committed. Since our steps are explicitly modifying the service configuration, it is best to do our dirty work after the service is fully committed, instead of doing it after the AfterInstall event. The constructor for our ServiceInstallerEx class registers two delegates to perform the work after the service has been committed.

C#
public ServiceInstallerEx() : base()
{

  FailureActions = new ArrayList();
  base.Committed += new InstallEventHandler( this.UpdateServiceConfig );
  base.Committed += new InstallEventHandler( this.StartIfNeeded );
  logMsgBase = "ServiceInstallerEx : " + base.ServiceName + " : ";
}

The UpdateServiceConfig() method handles the configuration of the service description and recovery actions, whereas the StartIfNeeded() method starts the service if so configured through the class properties. Our ServiceInstallerEx class exposes some properties for the caller as follows:

  • FailureActions

    This is a System.Collections.ArrayList object that holds instances of FailureAction objects. Each FailureAction instance represents a successive action to take on service failure. For example, item at slot 0 (first item) would indicate the first failure action and so on. A FailureAction object has two properties that must be set: the RecoverAction and the Delay. The RecoverAction is an enumerated value that can be set to None, Restart, RunCommand, or Reboot. The Delay is a period of time in seconds to wait after the failure before taking the configured action.

  • Description

    A string that describes the service.

  • FailCountResetTime

    Time specified in seconds. If a failure occurs, this is the time that must elapse with no subsequent failures before the fail count for the service is reset to 0.

  • FailRebootMsg

    This is a string to broadcast on the subnet when rebooting the machine. If a recovery action specifies that the system should reboot, then this message will be broadcasted.

  • FailRunCommand

    This is a command line string including all arguments as would be passed to a CreateProcess() call, when a Run Command action is specified.

  • StartOnInstall

    This is a Boolean flag (default false), that indicates we want the service to start right after installation. This should be sparingly used if services are dependent upon other services.

  • StartTimeout

    This is a wait timeout value in seconds (default 15), that we will wait for the service to start when the StartOnInstall flag is set to true.

PART III: Implementing the functionality

The ChangeServiceConfig2() method in advapi.dll is used to set the description and failure actions. This method takes essentially a (void *) that points to a structure or structure array that holds the SERVICE_DESCRIPTION or SERVICE_FAILURE_ACTIONS structure. To resolve the two versions, we abstract the calls to the ChangeServiceConfig2() using the DllImportAttribute class EntryPoint property as follows:

C#
[DllImport("advapi32.dll", EntryPoint="ChangeServiceConfig2")]
public static extern bool 
ChangeServiceFailureActions( IntPtr hService, int dwInfoLevel,
[ MarshalAs( UnmanagedType.Struct ) ] ref SERVICE_FAILURE_ACTIONS lpInfo );
 

[DllImport("advapi32.dll", EntryPoint="ChangeServiceConfig2")]
public static extern bool 
ChangeServiceDescription( IntPtr hService, int dwInfoLevel, 
[ MarshalAs( UnmanagedType.Struct ) ] ref SERVICE_DESCRIPTION lpInfo );

The UpdateServiceConfig() method of the ServiceInstallerEx class conforms to the InstallEventHandler delegate and is registered to run after the service has been committed. There are two issues to take note of in this method:

Issue 1

First is that the Marshal class in System.Runtime.InteropServices does not provide a clean way to marshal arrays of structures, but does provide methods to marshal arrays of value types in the .NET common type system. Since the SC_ACTION Win32 structure is merely two integers that are packed sequentially, we, in essence, cheat here by transposing the array of structures into an array of integers twice the size.

C#
int[] actions = new int[numActions*2];
int currInd = 0;

foreach( FailureAction fa in FailureActions ){

  actions[currInd] = (int)fa.Type;
  actions[++currInd] = fa.Delay;
  currInd++;
}

tmpBuf = Marshal.AllocHGlobal( numActions*8 );
Marshal.Copy( actions, 0, tmpBuf, numActions*2 );

SERVICE_FAILURE_ACTIONS sfa = new SERVICE_FAILURE_ACTIONS();
sfa.lpsaActions=tmpBuf.ToInt32();
. . .

bool rslt = ChangeServiceFailureActions( svcHndl, 
                 SERVICE_CONFIG_FAILURE_ACTIONS,ref sfa );

Issue 2

The second thing to note is the issue of a reboot action. Microsoft has implemented security features that require code to obtain special privileges to perform major system functions such as shutdowns and reboots. The ChangeServiceConfig2() function we use for our service configuration, requires the caller to have Shutdown Privileges when setting a service failure action to Reboot. To do this, we follow Microsoft's guidance on doing this but implement it using interop.

C#
if( needShutdownPrivilege ){

rslt = this.GrandShutdownPrivilege();
if( !rslt ) return;

}

The GrantShutdownPrivilege method is privately scoped and not accessible to the caller. It does the interop work to grant shutdown privileges to the user. This method is called assuming the calling process will terminate soon after installation and therefore will not need to explicitly revoke the privilege.

I placed all the meaningful code in a try-catch-finally block to clean up handles, release locks, and free memory. To facilitate debugging any problems, I added a primitive message logging method to log to console and the system Application event log.

Starting the service upon installation

This is the easiest part of it all. We added a method StartIfNeeded() to our ServiceInstallerEx class that conforms to the InstallEventHandler delegate signature that is responsible for starting the service upon installation. If the StartOnInstall property is set to true, this method starts the service using the ServiceController class in the System.ServiceProcess namespace. The timeout to wait for the service start is also configurable by the StartTimeout property that has a default value of 15 seconds.

C#
ServiceController sc = new ServiceController( base.ServiceName );
sc.Start();
sc.WaitForStatus( ServiceControllerStatus.Running, waitTo );

sc.Close();

PART IV: Putting it all together

The ServiceInstallerEx class is provided by the Verifide.ServiceUtils.dll library. Once this has been built, it is a trivial task to use the extension features as follows:

Step 1: Add an installer to the service project

After creating a Windows Service application in Visual Studio .NET, open the service in design view, right click, and select "Add Installer". This adds an installer to your project and places fully functional template code for the service installation. You will notice that there are two objects created for you. A ServiceProcessInstaller, and a ServiceInstaller. The ServiceProcessInstaller class controls the process properties such as the logon account (I suggest setting the account to LocalSystem unless otherwise required). The ServiceInstaller instance, on the other hand, controls properties such as the startup type (Automatic, Manual, Disabled) and dependencies on other services.

Step 2: Use ServiceInstallerEx instead of the ServiceInstaller instance

To use the extension class, you must first add a reference to the Verifide.ServiceUtils.dll through Solution Explorer. Then you should include the namespace with the using statement to use the un-elongated names for classes such as FailureAction and RecoverAction.

Edit the ProjectInstaller.cs file that was created in step 1. Change the code to use the Verifide.ServiceUtils.ServiceInstallerEx class instead of the System.ServiceProcess.ServiceInstaller class. This needs to be changed in two places. First in the declaration of the ServiceInstaller variable and second when the installer is instantiated in the "Component Designer generated code" region. Worth mentioning here is that all code in the "Component Designer generated code" region is dynamically generated. If you open the service installer in design mode and then view the code, you will notice that all your changes are lost. Therefore, do not do anything more in this region other than changing the type as required. (The design view generated code does not change the type.)

C#
public class ProjectInstaller : System.Configuration.Install.Installer
{

  private System.ServiceProcess.ServiceProcessInstaller 
                                    serviceProcessInstaller1;
  private Verifide.ServiceUtils.ServiceInstallerEx serviceInstaller1;
.
#region Component Designer generated code

  private void InitializeComponent()
  {
    this.serviceProcessInstaller1 = 
        new System.ServiceProcess.ServiceProcessInstaller();
    this.serviceInstaller1 = new Verifide.ServiceUtils.ServiceInstallerEx();

Step 3: There is no step three

Well, I lied, there is but it's easy. Add code to the constructor for the ProjectInstaller class to configure the options and you're done. Following is all that's left for the user. Of course, if you choose not to set any of the extension properties, you need not do anything else; since we inherited from the ServiceInstaller class, we're pretty transparent.

C#
public ProjectInstaller()
{
 InitializeComponent();

 serviceInstaller1.Description = "Look Smeagol! Its the Precious!";   
 serviceInstaller1.FailCountResetTime = 60*60*24*4;
 serviceInstaller1.FailRebootMsg = "Whitney Houston! We have a problem" ;

 serviceInstaller1.FailureActions.Add( 
   new FailureAction( RecoverAction.Restart,60000)  );
 serviceInstaller1.FailureActions.Add( 
   new FailureAction( RecoverAction.RunCommand, 2000 ) );
 serviceInstaller1.FailureActions.Add( 
   new FailureAction( RecoverAction.None, 3000 ) );

 serviceInstaller1.StartOnInstall = true;
  
}

Step 4: Install the service

From the bin directory of the service application you generate, install the service using the installutil.exe command. Run the following commands from a Visual Studio .NET command prompt (Start->Programs->Visual Studio->Tools->Command Prompt) to ensure the installutil program is in your path.

To install a service: installutil servicename.exe

To uninstall: installutil /u servicename.exe

Note that you must either specify the full path of the service exe, or you have to be in the directory where the service resides, to run the installutil program. Go to the Windows management console (Right click on My Computer->Manage->Services and Applications->Services). To view the recovery options, right click on the service in the list, select Properties, and then select the Recovery tab.

Part V: Conclusion

After having used .NET for over two years now, I have come to the conclusion that the platform surely makes a lot of tasks very easy. However, there is a lot of fine-grain functionality that is not exposed by the framework. For those of us that have programmed at, shall I dare to say, orthodox levels, these "advanced" features are nothing to fear. We've done them and most importantly are aware of them and the purpose they serve.

Services are an integral part of distributed and business platform development. Microsoft, in my opinion, has done a great job in simplifying the creation, yet more importantly the management of services. However, from sheer experience, the advances in Windows 2000 with the recovery options have personally blessed me with many nights of good sleep. I was quite disappointed that fault tolerance in services was not a priority in the development of the framework class libraries.

In implementing the solution, I chose to provide the simplest interface that would allow the developer to maintain the consistency with the FCL names and usages. It relies on the Win32 API to fill the void in the FCL. Aaah Win32, the ole high-performance, low-carb, low-fat, bread and butter that got me out of many a jam. Win32 interop is a great mechanism to get the fine grain control over the "advanced" features, however, must be used with caution. Microsoft will eventually obsolete this API (though they'll probably use it internally) and with it, the wealth of features that didn't make it to the .NET FCL will forever be gone.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President Verifide Technologies, Inc.
United States United States
Neil Baliga is the founder of Verifide Technologies, Inc. (www.verifide.com), an initiative for automated test systems for product verification used in manufacturing. He strongly believes that the value in software is in its simplicity. His experience includes UNIX, Win32 API, TCP/IP multithreaded servers, C#, C++ et. al, and Radio Frequency (RF) measurement science. He came across .NET in 2001 and has been in love with it ever since. He is an avid LA Lakers and Denver Broncos fan and loves to hang out with his dog 'Reboot'. He is extremely lucky to have the love and support of his beautiful wife Jyothi.

Comments and Discussions

 
GeneralValues in configfile Pin
Davilen1-Oct-07 22:41
Davilen1-Oct-07 22:41 
GeneralProperties visible in design environment Pin
cbumstead14-Sep-07 15:26
cbumstead14-Sep-07 15:26 
GeneralRe: Properties visible in design environment Pin
cjard6-Feb-08 6:27
cjard6-Feb-08 6:27 
QuestionA trouble about how to use failure actions Pin
iambright9-Sep-06 16:11
iambright9-Sep-06 16:11 
QuestionMultiple Service Pin
yamabiz12-Apr-06 14:33
yamabiz12-Apr-06 14:33 
QuestionStart at boot? Pin
John Boero3-Apr-06 7:55
John Boero3-Apr-06 7:55 
AnswerRe: Start at boot? Pin
Shaun Wilde3-Apr-06 8:11
Shaun Wilde3-Apr-06 8:11 
AnswerRe: Start at boot? Pin
John Boero3-Apr-06 8:14
John Boero3-Apr-06 8:14 
It's already automatic, but that doesn't start it at boot. It's just in some kind of "waiting" state to be activated by a call to StartService or opening the services manager.

-- modified at 14:42 Monday 3rd April, 2006

Hahaha :-> Ooops my thread was quitting right after the service started. It looked like I couldn't get it to run at boot. Thanks Shaun
QuestionSeconds or Milliseconds Pin
cphexad4-Feb-06 0:41
cphexad4-Feb-06 0:41 
AnswerRe: Seconds or Milliseconds Pin
Neil Baliga9-Feb-06 10:24
Neil Baliga9-Feb-06 10:24 
QuestionBut how to test!? Pin
Member 128921021-Oct-05 2:20
Member 128921021-Oct-05 2:20 
AnswerRe: But how to test!? Pin
Neil Baliga27-Oct-05 13:31
Neil Baliga27-Oct-05 13:31 
GeneralRe: But how to test!? Pin
cbumstead14-Sep-07 14:41
cbumstead14-Sep-07 14:41 
GeneralAuto-start after installation Pin
Morten Skille18-May-05 1:09
Morten Skille18-May-05 1:09 
GeneralRe: Auto-start after installation Pin
Morten Skille18-May-05 2:22
Morten Skille18-May-05 2:22 
QuestionPost-Back build error? Pin
_tbone23-Apr-04 5:35
_tbone23-Apr-04 5:35 
AnswerRe: Post-Back build error? Pin
Neil Baliga26-Apr-04 8:43
Neil Baliga26-Apr-04 8:43 
Generaloverrinding vs. event handling Pin
Paulo Morgado25-Feb-04 9:45
professionalPaulo Morgado25-Feb-04 9:45 
GeneralRe: overrinding vs. event handling Pin
Neil Baliga26-Feb-04 20:18
Neil Baliga26-Feb-04 20:18 
GeneralRe: overrinding vs. event handling Pin
Paulo Morgado29-Feb-04 0:38
professionalPaulo Morgado29-Feb-04 0:38 
GeneralRe: overrinding vs. event handling Pin
Neil Baliga1-Mar-04 8:12
Neil Baliga1-Mar-04 8:12 
GeneralRe: overrinding vs. event handling Pin
Paulo Morgado1-Mar-04 10:36
professionalPaulo Morgado1-Mar-04 10:36 
GeneralOops Pin
Neil Baliga24-Feb-04 7:44
Neil Baliga24-Feb-04 7:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.