Writing a Windows Service

I l @ ve RuBoard

You can use the classes in the System.ServiceProcess namespace to create new non “device driver services. (If you want to create a device driver service, you must resort to the Microsoft Windows Driver Development Kit [DDK].) When you develop a service, you must also create a service installer that can be used to configure the service and create the appropriate Windows Registry keys. In this section, we'll create a simple service called CakeService. This service will act as a remoting server that hosts the CakeUtils.CakeInfo class that we developed in Chapter 11. The CakeInfo class exposes the FeedsHowMany method, and we'll test the service by creating a remoting client that invokes this method in the CakeService service.

The Structure of a Service Application

As we mentioned earlier, a Windows service can be hosted in its own executable program, or the same program can host a number of services. The host program is executed by the Service Control Manager (SCM). The SCM is a part of the Windows operating system that handles requests to start services. Such requests are made when Windows boots as well as through tools that exploit the Windows Service APIs (such as the Services console and the ServiceForm application shown earlier). The SCM also sends other signals (such as Stop , Pause , and Continue ) to a service when directed by Windows Service API calls.

Using the same program to contain multiple services is useful if those services have a lot of common functionality and maybe need to share some information. For example, the World Wide Web Publishing Service and the IIS Admin service are obviously related , and are both implemented by the Inetinfo.exe program. In our example, the CakeService service will be hosted in its own executable.

Visual Studio .NET supplies a Windows Service template that you can use to get a running start. However, this template is not available in every edition of Visual Studio .NET (it is omitted from the Standard edition, for example), so we'll show you how to create a new service from scratch.

The CakeService class (which is available in CakeService.jsl in the CakeService project) was created using the Console Application template in Visual Studio .NET. To make a class into a service, it must extend the System.ServiceProcess.ServiceBase class. (You must also add a reference to the System.ServiceProcess.dll assembly.) The ServiceBase class provides a default implementation of a Windows service, and all you have to do is override its methods and properties and provide your own functionality where needed. At an absolute minimum, you should provide a default constructor in which you initialize the properties of the service and an OnStart method that will be executed when the service is started. You must also provide a main method that creates and runs the service (as explained shortly).

The CakeService constructor is executed when the process hosting the service starts running and creates an instance of the CakeService class. You should always invoke the ServiceBase constructor and then set the ServiceName property of the service. You can set other properties that determine how the service will respond to various events (see Table 15-2), but you should not perform any service-specific application logic in the constructor. Instead, you should use the OnStart method, for a couple of reasons:

  • If the service shares its host executable with other services, the host process will commence running when the first service that it hosts starts. The host process will invoke the constructor for every service it contains (as shown later). This might be useful if all the services need access to some shared data structures, but it can also waste resources. In contrast, the OnStart method is invoked only when the service is sent the Start signal.

  • If the service is stopped , the host process will not be terminated if it contains other services that are still running. If the service is restarted, the constructor will therefore not be executed again ”although OnStart will be.

Notice the corresponding implications for finalization . If your service relies on a finalizer to free resources, the finalizer will not be executed until after the last service hosted by the executable stops and the host process can terminate. If you need to release any resources when a service stops, you should override the OnStop method.

The properties that you can set for a service and the methods you should implement are listed in Table 15-2.

Table 15-2. ServiceBase Properties

Property

Description

ServiceName

This is the name of the service; it is used to identify the service to the SCM when the service starts running. It should be the same as that recorded in the Windows Registry.

CanPauseAndContinue

This is a Boolean property that indicates whether the service supports pausing and resuming. If you set this property to true , you should implement the OnPause and OnContinue methods. OnPause releases any noncritical resources used by the service, and OnContinue reacquires them.

CanStop

This is a Boolean property that specifies that the service can be stopped once it is running. If you set this property to true you should consider overriding the OnStop method to perform any tidying up required by the service.

CanShutdown

This is a Boolean property that indicates that the service should be notified if it is running when the operating system shuts down. You implement the processing to be performed in the OnShutdown method. The service will not be sent a Stop signal, so much of the processing specified by the OnStop method should be performed by OnShutdown as well.

CanHandlePowerEvent

This is a Boolean property that specifies whether the service should be alerted if the computer power status changes (if the computer goes into hibernation mode, for example). You should implement the OnPowerEvent method to handle the alert. OnPowerEvent takes a System.ServiceProcess.PowerBroadcastStatus parameter indicating the reason for the change of power state.

AutoLog

This is a Boolean property. If you set it to true , every Start , Stop , Pause , and Continue signal sent to the service will be automatically recorded in the Windows Application event log. If you want to record events in a different event log, you should set AutoLog to false and programmatically write messages to your selected event log in the OnStart , OnStop , OnPause , and OnContinue methods.

EventLog

This is a read-only property that returns a handle to the Windows Application event log used for recording events when AutoLog is true . The value is returned as a System.Diagnostics.EventLog object. If you want to add messages to the log, you can call the WriteEntry method of the EventLog object that's returned.

The constructor for the CakeService class is shown below. It sets the ServiceName property to " Cake Service" , indicates that the service can be stopped by an administrator, and enables auto-logging:

 publicclassCakeServiceextendsServiceBase { publicCakeService() { //CalltheServiceBaseconstructorfirst super(); //ThenameoftheservicethatappearsintheRegistry this.set_ServiceName("CakeService"); //Allowanadministratortostop(andrestart)theservice this.set_CanStop(true); //ReportStartandStopeventstotheWindowseventlog this.set_AutoLog(true); } } 

The OnStart method registers a remoting channel listening at port 6000 and configures the CakeInfo class in the CakeUtils assembly as a server-activated , singleton object that's accessed through the URL "CakeInfo.rem". (If you need to refresh your memory about how this works, see Chapter 11.)

 protectedvoidOnStart(String[]args) { //Registerachannel ChannelServices.RegisterChannel(newTcpChannel(6000)); //RegistertheCakeInfoclass RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("CakeUtils.CakeInfo,CakeUtils"), "CakeInfo.rem", WellKnownObjectMode.Singleton); } 

You might be interested to observe that OnStart takes a String array as a parameter. When an administrator configures a service using its Properties dialog box from the Services console in Control Panel, she can specify a set of space-delimited strings to be passed to the service, as shown in Figure 15-2. Any values specified will be packaged up into a string array and passed to the OnStart method.

Figure 15-2. The Properties dialog box for a service

There is one major caveat with the OnStart method. The Windows operating system will allow your service up to 30 seconds to start. If the service has not started by the end of 30 seconds, Windows will assume that the service has frozen and will shut it down. Therefore, you must make sure that any processing you perform in OnStart will take less than 30 seconds to complete.

The CakeService can be stopped, but it has no OnStopped method because there is nothing for the OnStopped method to do in this example. (When the process running the service stops, all remote access will cease automatically because the remote objects will disappear.) Therefore, the default implementation will suffice!

The main method of the CakeService class is executed by the SCM when the first service in the host application is started. The main method should call the static Run method of the ServiceBase class to create and run the service:

 ServiceBase.Run(newCakeService()); 

The Run method takes an object that implements a service and arranges for the OnStart method to be called. The Run method is overloaded. If the host program implements several services, they should all be instantiated and added to an array of ServiceBase objects. This array should then be passed as the parameter to run. For example

 ServiceBase[]services=newServiceBase[4]; services[0]=newCakeService(); services[1]=newAnotherService(); services[2]=newYetAnotherService(); services[3]=newAFinalService(); ServiceBase.Run(services); 

Only the service being started will have its OnStart method invoked at this time, but as other services are sent Start signals their OnStart methods will be executed as well.

The complete CakeService class can be downloaded as a part of the CakeService project.

Understanding Installer Classes

Before you can use a service, it must be installed. To install a service, you can define an installer class. You can then use the InstallUtil utility supplied with the .NET Framework SDK to invoke the installer class and install your service. The InstallUtil tool takes an assembly and will attempt to install all components it contains that are tagged with System.ComponentModel.RunInstallerAttribute . You can use the CakeServiceInstaller class (shown later) to install the CakeService service. (It is supplied as part of the CakeService project.)

An installer class extends the System.Configuration.Install.Installer class. (You must add a reference to the System.Configuration.Install.dll assembly.) The Installer class provides a basic installation framework, and you override properties, methods, and events as required. An Installer object is associated with a single component. (The term component is used very loosely here; in the CakeService example, the CakeService itself is one component and the host process that executes it is actually another. This is because each of these items has its own set of properties that can be configured independently, as you'll see.) Each component to be installed will need an associated Installer object. If a component comprises many subcomponents, each will have its own associated Installer object. The Installer class contains the Installers property, which is a collection that you populate with Installer objects for configuring these subcomponents. (You can think of the data structure defined by the Installer class and the Installers property as a tree of installers.) The InstallUtil tool invokes the Install method of the top-level Installer object, which installs the component and recursively installs each of the subcomponents by iterating through the Installers property and executing the Install method of each item found.

You can subscribe to various events (such as BeforeInstall and AfterInstall ) that are fired as installation progresses if you need to monitor progress. If an exception occurs during installation, the install process might roll back (firing BeforeRollback and AfterRollback events). If the installation is successful, it can be committed, and the Committing and Committed events will be fired. The Installer class also provides the Uninstall method together with the BeforeUninstall and AfterUninstall events.

Creating a New Installer

Much of the time, the default installation methods defined by the Installer class will be adequate; all you need to do for the CakeServiceInstaller class is to populate the Installers property with installers for the service and its host executable, and you can let InstallUtil get on with it. The best place to set the Installers property is the default constructor. ( InstallUtil will instantiate your installer class using the default constructor.)

The System.ServiceProcess namespace contains two specialized Installer subclasses for installing services and their host processes. These classes are System.ServiceProcess.ServiceInstaller and System.ServiceProcess.ServiceProcessInstaller , respectively. To install a service, you typically create a single ServiceProcessInstaller object (that describes the service host executable) and as many ServiceInstallers as there are services in the host executable. You should set the properties of the ServiceInstaller object as required by your service. Table 15-3 describes the available properties.

Table 15-3. ServiceInstaller Properties

Property

Description

ServiceName

This is the name of the service. It must be the same as the ServiceName property that was specified when the service was created.

DisplayName

This is the friendly name of the service.

StartType

This is a value from the System.ServiceProcess.ServiceStartMode enumeration. The possible values you can specify are Automatic (which indicates that the service should be started when Windows boots), Manual (which indicates that the service must be started manually by an administrator), and Disabled (which indicates that the services cannot be started at all).

ServicesDependedOn

This is a String array containing the ServiceName values for each service that this service depends on. When this service starts, the SCM will start any services in this list that are not already running.

The CakeService example includes only a single service, so the installer will require only a single ServiceInstaller object. The following code (which is taken from the constructor of the CakeServiceInstaller class) creates a ServiceInstaller object and populates the StartType , ServiceName , and DisplayName properties. The CakeService service does not depend on any other services. At the end of the constructor, the ServiceInstaller object is added to the Installers property.

 privateServiceInstallerserviceInstaller=null; publicCakeServiceInstaller() { serviceInstaller=newServiceInstaller(); serviceInstaller.set_StartType(ServiceStartMode.Manual); serviceInstaller.set_ServiceName("CakeService"); serviceInstaller.set_DisplayName("RemotingCakeSizeServer"); this.get_Installers().Add(serviceInstaller); } 

In a similar vein, you should also create and set the properties of a Service ­ProcessInstaller object. This object defines security information that describes the security account the service should use to execute. Table 15-4 describes the available properties.

Table 15-4. ServiceProcessInstaller Properties

Property

Description

Account

The type of account the service should run as. You specify a value from the System.ServiceProcess.ServiceAccount enumeration. The values in this enumeration are LocalService , LocalSystem , NetworkService , and User . (These options will be described in detail later.) If the Account type is set to User , you can also supply values for the Username and Password properties to identify the account to use. (If you don't, you'll be prompted for this information when the service is installed by InstallUtil .)

Username

The name of the account the service should run as when the Account property is set to User .

Password

The password of the account the service should run as when the Account property is set to User .

HelpText

A text description of the requirements for any user account that executes the service.

The following code shows the parts of the CakeServiceInstaller constructor that relate to the ServiceProcessInstaller for the CakeService service:

 privateServiceProcessInstallerprocessInstaller=null; publicCakeServiceInstaller() { processInstaller=newServiceProcessInstaller(); processInstaller.set_Account(ServiceAccount.LocalService); this.get_Installers().Add(processInstaller); } 

Selecting an Appropriate Security Account

The Account property of the ServiceProcessInstaller class determines security context and hence the privileges that are available to your service. (When a client uses a service, the code executed by the service runs using the security context specified for the service and does not impersonate the client.) It is important that you select the appropriate type of account for your service. If you select an ordinary user account and your service requires access to privileged system resources, the service will not execute correctly. However, it is also unwise ”for security reasons ”to run a service using an account that has too many privileges. For example, if you configure a service to run using an administrator account (or LocalSystem) and the service opens a command window (to execute a command file, for example), a user could abort the command file (by pressing Ctrl+C) and then have access to a command window that has Administrator privileges.

Each of the options in the System.ServiceProcess.ServiceAccount enumeration provides a different level of security.

  • LocalSystem

    This is arguably the most powerful option (with the exception of certain user accounts). The LocalSystem account is a member of the Administrators group on the host computer, so it has access to the entire system. However, it does not have privileged network or domain access ”it can use only those remote resources that permit anonymous access.

  • LocalService

    This option causes the service to execute as the NT AUTHORITY\LocalService account. This account is available only on Windows XP and later. (InstallUtil cannot install the service if you use this option on Windows 2000.) The account has minimal privileges on the local machine by default ”it can only access resources on the local machine that it has been granted access to or to which members of the Everyone and Authenticated Users groups have been granted access. It can use only the network resources that allow anonymous access.

  • NetworkService

    This option causes the service to execute as the NT AUTHORITY\NetworkService account. This is also available only on Windows XP and later. Like LocalService, the NetworkService account has no local special privileges. The NetworkService account is typically used by services that require network access ”it is configured to present the computer's credentials when it attempts to use a remote resource. As long as the computer has been granted access, the NetworkService account will be able to use the remote resource.

  • User

    This option allows you to run the service in the security context of a specific user account. You supply the identity of the account in the Username and Password properties of the ServiceProcessInstaller object. You can use any valid account, and the service will be able to access all the resources available to that account. This might not be wise ”a user account can be disabled or removed, which will prevent the service from starting, or the account might be granted additional privileges (by being added to the Administrators group, for example). This can open a potential hole in your system security.

Adding a Service Description

If you examine a service using the Services console, you'll see that a service can have a text description. For some reason, neither the ServiceBase class nor the ServiceInstaller class actually provides the description as a property. The System.ServiceProcess namespace does contain the ServiceProcessDescriptionAttribute class, which looks promising at first glance. But unfortunately , it supplies a description for a service only when it is deployed as a component and used in a visual designer (such as the Windows Forms editor in Visual Studio .NET, when the description appears in the Properties window).

You can still set the description for a service, but you must insert it directly into the Windows Registry when the component is installed. The service description should be inserted in the Registry at the key HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\<ServiceName> , where <ServiceName> is the name of the service. You can add a text value labeled as Description to the service key. It would be inconvenient to expect an administrator to perform this task manually after the service has been installed, so you should instead override the Install method of the Installer class and perform the operation there. The Install method for the CakeServiceInstaller class is shown here:

 importMicrosoft.Win32.*; publicvoidInstall(IDictionarystateSaver) { super.Install(stateSaver); RegistryKeyservice=Registry.LocalMachine.OpenSubKey ("System").OpenSubKey("CurrentControlSet").OpenSubKey ("Services").OpenSubKey(serviceInstaller.get_ServiceName(),true); service.SetValue("Description", "ServicethatprovidesremotingaccesstotheCakeSizeServer"); } 

Invoke the Install method of the parent class (which will cause the Install method of all the Installer objects in the Installers collection to be called) and add any other functionality you require. In this case, the Install method opens the key for the CakeService and sets the Description value. The RegistryKey and Registry classes are available in the Microsoft.Win32 namespace.

Installing and Testing the Service

If you build the CakeService project (which comprises the CakeService and CakeServiceInstaller classes), the compiler will generate the assembly CakeService.exe. You can then execute InstallUtil to install the service as shown here:

 C:\>InstallUtilCakeService.exe Microsoft(R).NETFrameworkInstallationutilityVersion1.0.3705.0 Copyright(C)MicrosoftCorporation1998-2001.Allrightsreserved. Runningatransactedinstallation. BeginningtheInstallphaseoftheinstallation. Seethecontentsofthelogfileforthec:\temp\chapter15\cakeservice\ bin\debug\cakeservice.exeassembly'sprogress. Thefileislocatedatc:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog. Installingassembly'c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.exe'. Affectedparametersare: assemblypath=c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe logfile=c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog InstallingserviceCakeService... ServiceCakeServicehasbeensuccessfullyinstalled. CreatingEventLogsourceCakeServiceinlogApplication... TheInstallphasecompletedsuccessfully,andtheCommitphaseis beginning. Seethecontentsofthelogfileforthec:\temp\chapter15\cakeservice\ bin\debug\cakeservice.exeassembly'sprogress. Thefileislocatedatc:\temp\chapter15\cakeservice\bin\debug\cakeservice.InstallLog. Committingassembly'c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe'. Affectedparametersare: assemblypath=c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe logfile=c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog TheCommitphasecompletedsuccessfully. Thetransactedinstallhascompleted. 

A record of the installation progress will be written to the log files cakeservice.InstallLog and cakeservice.InstallState. The service will appear in the Services console (see Figure 15-3), and you can start and stop the service in the same way you would any other service.

Figure 15-3. The Remoting Cake Size Server in the Services console

To test the service, start it and then execute the CakeClient program supplied in the CakeClient project. (This is a copy of the server-activated cake client program from Chapter 11.) The main method of the CakeClient class calls on the CakeService service to create a CakeInfo object, and then it invokes the FeedsHowMany method, passing details describing a 14-inch hexagonal fruit cake. If all is well, you should be informed that the cake will feed 31 people, as shown in Figure 15-4.

Figure 15-4. The output of the CakeClient program

Uninstalling a Service

You can uninstall a service using InstallUtil, specifying the /u flag:

 C:\>InstallUtil/uCakeService.exe 

The InstallUtil program will call the Uninstall method of all classes in the specified assembly that are tagged with RunInstallerAttribute . The default Uninstall method of the Installer class is usually adequate and will arrange for the Uninstall method of all the installers held in the Installers property to be run. If you've overridden the Install method, you might also need to override Uninstall and undo any custom changes made during installation. In the case of the CakeService service, this is not necessary because the CakeService keys are automatically removed from the Registry when the service is uninstalled , taking the Description value with them.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net