Chapter 33. Windows Services

Modern, multitasking operating systems often need to run applications that operate in the background and that are independent of the user who is logged in. From Windows NT to Windows Vista, such applications are called Windows Services (formerly known as NT Services). The tasks carried out by Windows Services are typically long-running tasks and have little or no direct interaction with a user (so they don't usually have user interfaces). Such applications may be started when the computer is booted and often continue to run until the computer is shut down.

This chapter covers the following:

  • The characteristics of a Windows Service

  • How to interact with a Windows Service using Visual Studio 2008 and the management applets in the Windows Control Panel

  • How to create, install, and communicate with a Windows Service using Visual Basic

  • How to debug a Windows Service from within Visual Studio 2008

As VB6 did not offer direct support for the creation of Windows Services, you might be unfamiliar with such applications. To help you understand the variety of such applications, this chapter examines some scenarios for which a Windows Service application is a good solution.

Example Windows Services

Microsoft SQL Server, Exchange Server, Internet Information Server (IIS), and antivirus software all use Windows Services to perform tasks in response to events that occur on the system overall. Only a background service, or Windows Service, that runs no matter which user is logged in, could perform such operations. For example, consider these potential Windows Services:

  • A file watcher — Suppose you are running an FTP server that enables users to place files in a particular directory. You could use a Windows Service to monitor and process files within that directory as they arrive. The service runs in the background and detects when files are changed or added within the directory, and then extracts information from these files in order to process orders, or update address and billing information. You will see an example of such a Windows Service later in this chapter.

  • An automated stock price reporter — You could build a system that extracts stock prices from a Web service or website and then e-mails the information to users. You could set thresholds such that an e-mail is sent only when the stock price reaches a certain price. This Windows Service can be automated to extract the information every 10 minutes, every 10 seconds, or whatever you choose. Because a Windows Service can contain any logic that does not require a user interface, you have a lot of flexibility in constructing such applications.

  • Microsoft Transaction Server (MTS) — Part of COM+ Services in Windows 2000 and later, this is an object broker that manages instances of components. It is used regularly by professional developers. This service runs constantly in the background and manages components as soon as the computer is booted, just like IIS or Exchange Server.

Characteristics of a Windows Service

To properly design and develop a Windows Service, it is important to understand how it differs from a typical Windows program. Here are the most important characteristics of a Windows Service:

  • It can start before a user logs on. The system maintains a list of Windows Services, which can be set to start at boot time. Services can also be installed such that they require a manual startup and will not start at bootup.

  • It can run under a different account from that of the current user. Most Windows Services provide functionality that needs to be running all the time, and some load before a user logs on, so they cannot depend on a user being logged on to run.

  • It has its own process. It does not run in the process of a program communicating with it (Chapter 29 has more information on processes).

  • It typically has no user interface. This is because the service may be running under a different account from that of the current user, or the service may start at bootup, which means that calls to put up a user interface might fail because they are out of context (it's possible to create a Windows Service with a user interface, but Visual Basic 2008 can't be used to do it; you will learn why later).

  • It requires a special installation procedure; just clicking on a compiled EXE will not run it. The program must run in a special context in the operating system, and a specific installation process is required to do the configuration necessary for a Windows Service to be run in this special context.

  • It works with a Service Control Manager (discussed shortly). The Service Control Manager is required to provide an interface to the Windows Service. External programs that want to communicate with a Windows Service (for example, to start or stop the service) must go through the Service Control Manager. The Service Control Manager is an operating-system-level program, but it has a user interface that can be used to start and stop services, and this interface can be accessed through the Computer Management section of the Control Panel.

Interacting with Windows Services

You can view the services that are used on your computer by opening the Service Control Manager user interface. To do so in Windows 2000, select Administrative Tools

Interacting with Windows Services
Figure 33-1

Figure 33.1. Figure 33-1

The Status column indicates the current state of the service. If this column is blank, then the service has not been started since the last time the computer was booted. Other possible values for Status are Started, Stopped, and Paused. You can access additional settings and details concerning a service by double-clicking it.

When a service is started, it automatically logs into the system using either a user or a system account:

  • The user account is a regular NT account that allows the program to interact with the system — in essence, the service impersonates a user.

  • The system account is not associated with a particular user.

The Service Control Manager shown in Figure 33-1 is part of the operating system (OS), which is what supports Windows Services; it is not a part of the .NET Framework. Any service run by the OS is exposed through the Service Control Manager, regardless of how the service was created or installed. You can also interact with Windows Services via the Server Explorer in Visual Studio 2008. You will see this technique later.

Creating a Windows Service

Prior to the release of the .NET Framework, most Windows Services were created with C++. Third-party toolkits were available to enable Windows Services to be created in VB6 and earlier, but deployment problems and threading issues meant that few developers took this route.

In .NET, the functionality needed to interface to the operating system is wrapped up in the .NET Framework classes, so any .NET-compliant language can now be used to create a Windows Service.

The .NET Framework classes for Windows Services

Several base classes are needed to create a Windows Service:

  • System.ServiceProcess.ServiceBase — Provides the base class for the Windows Service. The class containing the logic that will run in the service inherits from ServiceBase. A single executable can contain more than one service, but each service in the executable is a separate class that inherits from ServiceBase.

  • System.Configuration.Install.Installer — This is a generic class that performs the installation chores for a variety of components. One class in a Windows Service process must inherit and extend Installer in order to provide the interface necessary to install the service under the various Windows operating systems.

Each class that inherits from Installer needs to contain an instance of each of the following classes:

  • System.ServiceProcess.ServiceProcessInstaller — This class contains the information needed to install a .NET executable that contains Windows Services (that is, an executable that contains classes that inherit from ServiceBase). The .NET installation utility for Windows Services (InstallUtil.exe, discussed later) calls this class to get the information it needs to perform the installation.

  • System.ServiceProcess.ServiceInstaller — This class also interacts with the InstallUtil.exe installation program. Whereas ServiceProcessInstaller contains information needed to install the executable as a whole, ServiceInstaller contains information on a specific service in the executable. If an executable contains more than one service, then an instance of ServiceInstaller is needed for each one.

For most Windows Services you develop, you can let Visual Studio 2008 take care of Installer, ServiceProcessInstaller, and ServiceInstaller. You just need to set a few properties. The class you should thoroughly understand is ServiceBase, as this is the class that contains the functionality of a Windows Service and therefore must inherit from it.

The ServiceBase Class

ServiceBase contains several useful properties and methods, but initially it is more important to understand the events of ServiceBase. Most of these events are fired by the Service Control Manager when the state of the service is changed. The most important events are as follows:

Event

How and When the Event Is Used

OnStart

Occurs when the service is started. This is where the initialization logic for a service is usually placed.

OnStop

Occurs when the service is stopped. Cleanup and shutdown logic are generally placed here.

OnPause

Occurs when the service is paused. Any logic required to suspend operations during a pause goes here.

OnContinue

Occurs when a service continues after being paused

OnShutdown

Occurs when the operating system is being shut down

OnSessionChange

Occurs when a change event is received from a Terminal Session service. This method was new in .NET Framework 2.0.

OnPowerEvent

Occurs when the system's power management software causes a change in the power status of the system. This is typically used to change the behavior of a service when a system is going in or out of a "suspended" power mode. This is more frequent with end users who are working on laptops.

OnCustomCommand

Occurs when an external program has told the Service Control Manager that it wants to send a command to the service. The operation of this event is covered in "Communicating with the Service."

The events used most frequently are OnStart, OnStop, and OnCustomCommand. The OnStart and OnStop events are used in almost every Windows Service written in Visual Basic, and the OnCustomCommand is used when any special configuration of the service needs to be done while the service is running.

All of these are Protected events, so they are only available to classes that inherit from ServiceBase. Because of the restricted context in which it runs, a Windows Service component that inherits from ServiceBase often lacks a public interface. While you can add public properties and methods to such a component, they are of limited use, because outside programs cannot obtain an object reference to running a Windows Service component.

To be active as a Windows Service, an instance of ServiceBase must be started via the shared Run method of the ServiceBase class. However, normally you don't have to write code to do this because the template code generated by Visual Studio 2008 places the correct code in the Main subroutine of the project for you.

The most commonly used property of ServiceBase is the AutoLog property. This Boolean property is set to True by default. If True, then the Windows Service automatically logs the Start, Stop, Pause, and Continue events to an Event Log. The Event Log used is the Application Event Log and the Source in the log entries is taken from the name of the Windows Service. This automatic event logging is stopped by setting the AutoLog property to False.

The following File Watcher example goes into more detail about the automatic logging capabilities in a Windows Service, and about Event Logs in general.

Installation-Oriented Classes

The Installer, ServiceProcessInstaller, and ServiceInstaller classes are quite simple to build and use if you are employing Visual Studio 2008. After you create your Windows Service project, Visual Studio 2008 will create a class file called Service1.vb for you. To add the Installer, ServiceProcessInstaller, and ServiceInstaller classes to your project, simply right-click the design surface of this ServiceBase class, Service1.vb, and select Add Installer. This creates the code framework necessary to use them.

The Installer class (named ProjectInstaller.vb by default in a Windows Service project) generally needs no interaction at all — it is ready to use when created by Visual Studio 2008. However, it may be appropriate to change some properties of the ServiceProcessInstaller and ServiceInstaller classes. You can do this by simply highlighting these objects on the design surface and changing their properties directly in the Properties window of Visual Studio 2008. The properties that are typically modified for ServiceProcessInstaller include the following:

  • Account — This specifies the type of account under which the entire service application will run. Different settings give the services in the application different levels of privilege on the local system. For simplicity, this chapter uses the highest level of privilege, LocalSystem, for most of the examples. If this property is set to User (which is the default), then you must supply a username and password, and that user's account is used to determine privileges for the service. If there is any possibility that a service could access system resources that should be "out of bounds," then using the User setting to restrict privileges is a good idea. Besides LocalSystem and User, other possible settings for the Account property include NetworkService and LocalService.

  • Username — If Account is set to User, then this property specifies the user account to use in determining the privileges the system will have and how it interacts with other computers on the network. If this property is left blank, then it is requested when the service is installed.

  • Password — This property indicates the password to access the user account specified in the Username property. If the password is left blank, then it is requested when the service is installed.

  • HelpText — This specifies information about the service that will be displayed in certain installation options.

If the Account property is set to User, then it is good practice to set up a special user account for the service, rather than rely on some existing account intended for a live user. The special account can be set up with exactly the appropriate privileges for the service. This way, it is not as vulnerable to having its password or its privileges inadvertently changed in a way that would cause problems in running the service.

For the ServiceInstaller class, the properties you might change include the following:

  • DisplayName — The name of the service displayed in the Service Manager or the Server Explorer can be different from the class name and the executable name if desired, though it is better to make this name the same as the class name for the service.

  • StartType — This specifies how the service is started. The default is Manual, which means you must start the service yourself, as it will not start automatically after the system boots. If you want the service to always start when the system starts, then change this property to Automatic. The Service Manager can be used to override the StartType setting.

  • ServiceName — The name of the service that this ServiceInstaller handles during installation. If you changed the class name of the service after using the Add Installer option, then you would need to change this property to correspond to the new name for the service.

ServiceProcessInstaller and ServiceInstaller are used as necessary during the installation process, so there is no need to understand or manipulate the methods of these.

Multiple Services within One Executable

It is possible to place more than one class that inherits from ServiceBase in a single Windows Service executable. Each such class then allows for a separate service that can be started, stopped, and so on, independently of the other services in the executable.

If a Windows Service executable contains more than one service, then it must contain one ServiceInstaller for each service. Each ServiceInstaller is configured with the information used for its associated service, such as the displayed name and the start type (automatic or manual). However, the executable still needs only one ServiceProcessInstaller, which works for all the services in the executable. It is configured with the account information that is used for all the services in the executable.

The ServiceController Class

Another important .NET Framework class used with Windows Services is System.ServiceProcess.ServiceController. This class is not used when constructing a service. It is used by external applications to communicate with a running service, enabling operations such as starting and stopping the service. The ServiceController class is described in detail in "Communicating with the Service."

Other types of Windows Services

The ServiceBase and ServiceController classes can be used to create typical Windows Services that work with high-level system resources such as the file system or performance counters. However, some Windows Services need to interact at a deeper level. For example, a service may work at the kernel level, fulfilling functions such as that of a device driver.

Presently, the .NET Framework classes for Windows Services cannot be used to create such lower-level services, which rules out both VB and C# as tools to create them. C++ is typically the tool of choice for these types of services. If the .NET version of C++ is used, the code for such services would typically run in unmanaged mode.

Another type of service that cannot be created with the .NET Framework classes is one that interacts with the Windows desktop. Again, C++ is the preferred tool for such services.

You'll look at the types of services that are possible during the discussion of the ServiceType property of the ServiceController class, in "Communicating with the Service."

Creating a Windows Service in Visual Basic

Now it is time to create and use a Windows Service with Visual Basic, using the previously discussed .NET Framework classes. These tasks are demonstrated later in a detailed example. Here is a high-level description of the necessary tasks:

  1. Create a new project of the type Windows Service. By default, the service will be in a module named Service1.vb, but it can be renamed, like any other .NET module. (The class automatically placed in Service1.vb is named Service1 by default, and it inherits from ServiceBase.)

  2. Place any logic needed to run when the service is started in the OnStart event of the service class. You can find the code listing for the Service1.vb file by double-clicking this file's design surface.

  3. Add any additional logic that the service needs to carry out its operation. Logic can be placed in the class for the service, or in any other class module in the project. Such logic is typically called via some event that is generated by the operating system and passed to the service, such as a file changing in a directory, or a timer tick.

  4. Add an installer to the project. This module provides the interface to the Windows operating system to install the module as a Windows Service. The installer is a class that inherits from System.Configuration.Install.Installer, and it contains instances of the ServiceProcessInstaller and ServiceInstaller classes.

  5. Set the properties of the installer modules as necessary. The most common settings needed are the account under which the service will run and the name the service will display in the Service Control Manager.

  6. Build the project. This results in an EXE file. For example, if the service were named WindowsService1, then the executable file would be named WindowsService1.exe.

  7. Install the Windows Service with a command-line utility named InstallUtil.exe. (As previously mentioned, a service cannot be started by just running the EXE file.)

  8. Start the Windows Service with the Service Control Manager (available via the Control Panel

    Creating a Windows Service in Visual Basic

You can also start a service from the command console if the proper paths to .NET are set. The command is NET START <servicename>. Note that the <servicename> used in this command is the name of the service, not the name of the executable in which the service resides. Depending on the configuration of your system, a service started with any of the aforementioned methods will sometimes fail, resulting in an error message indicating that the service did not start in a timely fashion. This may be because the .NET libraries and other initialization tasks did not finish fast enough to suit the Service Control Manager. If this happens, attempt to start the service again; it usually succeeds the second time.

Steps 2 through 5 can be done in a different order. It doesn't matter whether the installer is added and configured before or after the logic that does the processing for the service is added.

At this point, a service is installed and running. The Service Manager or the Server Explorer can stop the service, or it will be automatically stopped when the system is shut down. The command to stop the service in a command console is NET STOP <servicename>.

The service does not automatically start the next time the system is booted unless it is configured for that. This can be done by setting the StartType property for the service to Automatic when developing the service, or it can be done in the Service Manager. Right-clicking the service in the Service Manager provides access to this capability.

This process is superficially similar to doing most other Visual Basic projects. There are a few important differences, however:

  • You cannot debug the project in the environment as you normally would any other Visual Basic program. The service must be installed and started before it can be debugged. It is also necessary to attach to the process for the service to do debugging. Details about this are included in "Debugging the Service."

  • Even though the result of the development is an EXE, you should not include any message boxes or other visual elements in the code. The Windows Service executable is more like a component library in that sense, and should not have a visual interface. If you include visual elements such as message boxes, the results can vary. In some cases, the UI code will have no effect. In other cases, the service may hang when attempting to write to the user interface.

  • Finally, be especially careful to handle all errors within the program. The program is not running in a user context, so a runtime error has no place to report itself visually. Handle all errors with structured exception handling, and use an Event Log or other offline means to record and communicate runtime errors.

Creating a Counter Monitor Service

To illustrate the outlined steps, the following example creates a simple service that checks the value of a performance counter, and when the value of the counter exceeds a certain value, the service beeps every three seconds. This is a good example for stepping through the process of creating, installing, and starting a Windows Service. It contains very little logic, and you can easily tell when it is working.

In the first phase of the example, you create a service that always beeps. Then, in the second phase, you add logic to monitor the performance counter and only beep when the counter exceeds a specific value:

  1. Start a new Windows Service project using Visual Studio 2008. Name the project CounterMonitor.

  2. In the Solution Explorer, rename Service1.vb to CounterMonitor.vb.

  3. Click the design surface for CounterMonitor.vb. In the Properties window, change the ServiceName property from Service1 to CounterMonitor (the Name property changes the name of the class on which the service is based, while the ServiceName property changes the name of the service as known to the Service Control Manager).

  4. Right-click the project for the service and select Properties. You will then be presented with the CounterMonitor Property Pages as one of the paged tabs directly in Visual Studio. From the Application tab, set the Application Type drop-down list to Windows Service if necessary (it should already be set to this), and from the drop-down list named Startup Object, make sure CounterMonitor is selected (see Figure 33-2).

  5. Go back to the CounterMonitor.vb file's Design view and open the Visual Studio 2008 Toolbox. Open the Components (not the Windows Forms) node in the Toolbox. Drag a Timer control from the Toolbox onto the CounterMonitor design surface. It will appear on the design surface with the name Timer1. It is very important that you grab the correct Timer object from the Toolbox. Visual Studio 2008 does not have the Timer object for this example in the Toolbox, whereas earlier versions of Visual Studio before VS 2005 did. To ensure that you have the correct Timer object, right-click on the Toolbox and select Choose Items from the menu. In the .NET Framework Components tab of the Choose Toolbox Items dialog, scroll down until you see a couple of Timer objects. In the list are a couple of Timer objects from the System.Windows.Forms namespace, but you want to instead choose the Timer object that is part of the System.Timers namespace. This is the Timer object that you work with for the rest of this example.

    Figure 33-2

    Figure 33.2. Figure 33-2

  6. In the Properties window for Timer1, change the Interval property to a value of 3000 (that's 3,000 milliseconds, which causes the timer to fire every three seconds).

  7. Go to the code for CounterMonitor.vb. Inside the OnStart event handler (which is already created for you in the code), enter the following:

    Timer1.Enabled = True
  8. In the OnStop event for the class, enter the following:

    Timer1.Enabled = False
  9. Create an Elapsed event for the timer by highlighting Timer1 in the left-hand drop-down box at the top of the code editor window. Select the Elapsed event in the right-hand drop-down box from the Code view of the file.

  10. In the Elapsed event, place the following line:

    Beep()
  11. Now add an installer to the project. Go back to the design surface for CounterMonitor and right-click it. Select Add Installer. A new file called ProjectInstaller1.vb is created and added to the project. The ProjectInstaller1.vb file has two components added to its design surface: ServiceProcessInstaller1 and ServiceInstaller1, as shown in Figure 33-3.

    Figure 33-3

    Figure 33.3. Figure 33-3

  12. On the ProjectInstaller.vb design surface, highlight the ServiceProcessInstaller1 control. In its Properties window, change the Account property to LocalSystem.

  13. Highlight the ServiceInstaller1 control. In its Properties window, type in CounterMonitor as the value of the DisplayName property.

  14. Now build the project by right-clicking on the solution and selecting Build from the menu. An EXE named CounterMonitor.exe will be created for the service.

Installing the service

Now you are ready to install the service. The utility for doing this, InstallUtil.exe, must be run from a command line. It is located in the .NET utilities directory, found at C:WINNTMicrosoft.NETFrameworkv2.0.50727 on Windows 2000 and NT systems, or C:WindowsMicrosoft.NETFrameworkv2.0.50727 on Windows XP, Windows Vista, Windows Server 2003, and Windows Server 2008.

You can easily access this utility (and all the other .NET utilities in that directory) using an option from the Programs menu that is installed with Visual Studio 2008. Choose Microsoft Visual Studio 2008

Installing the service
InstallUtil CounterMonitor.exe

Check the messages generated by InstallUtil.exe to ensure that the installation of the service was successful. The utility generates several lines of information; if successful, the last two lines are as follows:

The Commit phase completed successfully.

The transacted install has completed.

If these two lines do not appear, then you need to read all the information generated by the utility to find out why the install didn't work. Reasons might include a bad pathname for the executable, or trying to install the service when it is already installed (it must be uninstalled before it can be reinstalled), as described later.

Starting the service

Later in this chapter, you will create your own "control panel" screen to start and stop the service. For now, to test the new Windows Service, you will use the Server Explorer in Visual Studio 2008. Open the Server Explorer in Visual Studio 2008 and expand the Services node. The resulting screen is shown in Figure 33-4.

Figure 33-4

Figure 33.4. Figure 33-4

If the CounterMonitor service does not appear in the list, then the installation failed. Try the installation again and check the error messages. Right-click the CounterMonitor service and select the Start menu option. You will hear the service beep every three seconds. You can stop the service by right-clicking it again and selecting the Stop menu option.

You can also use the Service Control Manager built into Windows to start the CounterMonitor service, shown in Figure 33-5.

Figure 33-5

Figure 33.5. Figure 33-5

Start CounterMonitor by right-clicking it and selecting Start or by clicking the Start link. As before, you will hear your computer beep every three seconds. Stop the service by right-clicking CounterMonitor and selecting Stop or by clicking the Stop link. Note that if you already started the service via the Server Explorer (as described earlier), then it will be in a started state when you access the Service Control Manager program.

Uninstalling the service

Uninstalling the service is very similar to installing it. The service must be in a stopped state before it can be uninstalled, but the uninstall operation will attempt to stop the service if it is running. The uninstall operation is done in the same command window (with the Visual Studio 2008 Command Prompt) as the install operation, and the command used is the same as the one for installation, except that the option /u is included just before the name of the service. Remember that you need to navigate to C:Users[ user ]DocumentsVisual Studio 2008ProjectsCounterMonitorProjectsCounterMonitorCounterMonitorobjDebug to run this command:

InstallUtil.exe /u CounterMonitor.exe

You can tell that the uninstall was successful if the information displayed by the utility contains the following line:

Service CounterMonitor was successfully removed from the system.

If the uninstall is not successful, read the rest of the information to determine why. Besides typing in the wrong pathname, another common reason for failure is trying to uninstall a service that is in a running state and could not be stopped in a timely fashion.

Once you have uninstalled CounterMonitor, it will no longer show up in the list of available services to start and stop (at least, after a refresh it will not).

Note

A Windows Service must be uninstalled and reinstalled every time you make changes to it. You should uninstall CounterMonitor now because you are about to add new capabilities to it.

Monitoring a Performance Counter

Performance counters are a system-level function of Windows. They are used to track usage of system resources. Performance counters can be expressed as counts (number of times a Web page was hit), percentages (how much disk space is left), or other types of information. Many counters are automatically maintained by the operating system, but applications can create and manage their own performance counters.

To demonstrate how services can interact with system-level functionality, you will add to the CounterMonitor the capability to monitor a particular performance counter, and only beep when the performance counter exceeds a certain value.

Performance counters can be monitored by a user with the Performance Monitor. A variety of performance counters are built into the operating system, providing access to information such as the number of threads currently active on the system, or the number of documents in a print queue. Any of these, and any custom performance counters, can be graphed in the Performance Monitor.

Creating a performance counter

This example creates a performance counter named ServiceCounter. Then you will change CounterMonitor to check that counter and only beep when its value is over 5. To test it, you will also create a small Windows Forms application that increments and decrements the counter.

Performance counters are typically accessed in Visual Studio 2008 through the Server Explorer tab. To see the available performance counters, open the Server Explorer, shown in Figure 33-6.

To see the categories of performance counters, click the plus sign next to the Performance Counters option in the Server Explorer. Several dozen categories will be shown. You can look at the counters in any particular category by clicking the plus sign next to the category.

You can also create new categories and new counters. For this example, you need to create a new category for the counter called Service Counters. To do that, right-click the Performance Counters option in the Server Explorer and select the Create New Category option. In the resulting Performance Counter Builder dialog box (shown in Figure 33-7), enter the name of the category as Service Counters, and create a new counter by clicking the New button and entering TestCounter for the name. Once that is complete, click the OK button. Visual Studio 2008 will then create a new category called Service Counters that contains a single performance counter called TestCounter.

Figure 33-6

Figure 33.6. Figure 33-6

Integrating the counter into the service

Using a performance counter in the CounterMonitor service you created earlier is straightforward. Open the CounterMonitor project and go to the design surface for CounterMonitor. Then open the Server Explorer so that it shows the TestCounter performance counter you created. Click TestCounter from within the Server Explorer and drag it onto the CounterMonitor.vb design surface.

A new visual control named PerformanceCounter1 will appear on the page's design surface, ready for use. Change the logic in the Elapsed event for Timer1 as shown here:

If PerformanceCounter1.RawValue > mnMaxValue Then
    Beep()
End If
Figure 33-7

Figure 33.7. Figure 33-7

The RawValue property being used in this code fetches the unformatted value of the counter. For counters that track whole numbers (such as the number of times a Web page is hit), the RawValue property is normally used to get the value of the counter for testing or display. Some other types of counters use a NextValue method to get a formatted value. See the CounterType property of the PerformanceCounter class for more information on the types of performance counters available.

Next, put this statement in the code module just under the first line of the CounterMonitor class:

Dim mnMaxValue As Integer = 5

This creates the mnMaxValue as a Private variable. Now build the service again, install it as before, and start the service. It should not beep at this point because the value in the performance counter is zero. You can leave the counter running, because you will now create a program to change the value in the performance counter, thereby making the service begin beeping.

Changing the value in the performance counter

To manipulate the performance counter, you will build a small forms-based application. Close the CounterMonitor solution in Visual Studio and start a new Windows Application Project named CounterTest. Place two buttons on Form1 and change their properties as shown in the following table:

Name

Text

BtnIncrement

Increment Counter

BtnDecrement

Decrement Counter

Then, open the Server Explorer and drag the TestCounter performance counter onto the form itself, just as you did earlier with the CounterMonitor project. As with all nonvisible components from the Toolbox, the counter will appear in the component tray (just under the form), rather than on the form's design surface.

The PerformanceCounter1 control for CounterTest needs one property change. Set the ReadOnly property of the control to False. This enables the application to manipulate the counter. (This change was unnecessary for the CounterMonitor Windows Service project because that project only reads the value of the performance counter and does not change it.)

Now double-click btnIncrement to get to its click event. Place the following code in the event:

PerformanceCounter1.Increment()

Double-click the btnDecrement to get to its click event. Place the following code in the event:

PerformanceCounter1.Decrement()

Build and run the program and click the Increment button six times. If the CounterMonitor service is running, then on the sixth click it will begin beeping because the value in the counter has exceeded five. Then click the Decrement button a couple of times, and the beeping will stop.

If you want to monitor the current value of the counter, select Start

Changing the value in the performance counter

Communicating with the Service

Up to this point, you have learned how to do the following:

  • Create a Windows Service using Visual Basic

  • Start and stop a service with the Server Explorer in Visual Studio 2008 or the Service Control Manager from the Control Panel

  • Make a service work with a system-level function such as a performance counter

If these procedures are sufficient to start, stop, and check on the service through the Server Explorer or the Service Control Manager, and there is no need for any other communication with the service, then this is all you have to do. However, it is often helpful to create a specialized application to manipulate your service. This application will typically be able to start and stop a service, and check on its status. The application may also need to communicate with the service to change its configuration. Such an application is often referred to as a control panel for the service, even though it does not necessarily reside in the operating system's Control Panel. A commonly used example of such an application is the SQL Server Service Manager, whose icon appears in the tray on the taskbar (normally in the lower-right section of the screen) if you have SQL Server installed.

Such an application needs a way to communicate with the service. The .NET Framework base class that is used for such communication is ServiceController. It is in the System.ServiceProcess namespace. You need to add a reference to System.ServiceProcess.dll (which contains this namespace) before a project can use the ServiceController class.

The ServiceController class provides an interface to the Service Control Manager, which coordinates all communication with Windows Services. However, you do not have to know anything about the Service Control Manager to use the ServiceController class. You just manipulate the properties and methods of the ServiceController class, and any necessary communication with the Service Control Manager is accomplished on your behalf behind the scenes.

It is a good idea to use exactly one instance of the ServiceController class for each service you are controlling. Multiple instances of ServiceController that are communicating with the same service can have timing conflicts. Typically, that means using a module-level object variable to hold the reference to the active ServiceController, and instantiating the ServiceController during the initialization logic for the application. The following example uses this technique.

The ServiceController class

The constructor for the ServiceController requires the name of the Windows Service with which it will be communicating. This is the same name that was placed in the ServiceName property of the class that defined the service. You will see how to instantiate the ServiceController class shortly.

The ServiceController class has several members that are useful in manipulating services. Here are the most important methods, followed by another table of the most important properties:

Method

Purpose

Start

A method to start the service

Stop

A method to stop the service

Refresh

A method to ensure that the ServiceController object contains the latest state of the service (needed because the service might be manipulated from another program)

ExecuteCommand

A method used to send a custom command to the service. This method is covered later in the section "Custom Commands."

Here are the most important properties:

Property

Purpose

CanStop

A property indicating whether the service can be stopped

ServiceName

A property containing the name of the associated service

Status

An enumerated property that indicates whether a service is stopped, started, in the process of being started, and so on. The ToString method on this property is useful for getting the status in a string form for text messages. The possible values of the enumeration are as follows:

 

ContinuePending — The service is attempting to continue.

 

Paused — The service is paused.

 

PausePending — The service is attempting to go into a paused state.

 

Running — The service is running.

 

StartPending — The service is starting.

 

Stopped — The service is not running.

 

StopPending — The service is stopping.

ServiceType

A property that indicates the type of service. The result is an enumerated value. The enumerations are as follows:

 

Win32OwnProcess — The service uses its own process (this is the default for a service created in .NET).

 

Win32ShareProcess — The service shares a process with another service (this advanced capability is not covered here).

 

Adapter, FileSystemDriver, InteractiveProcess, KernelDriver, RecognizerDriver — These are low-level service types that cannot be created with Visual Basic because the ServiceBase class does not support them. However, the value of the ServiceType property may still have these values for services created with other tools.

Integrating a ServiceController into the example

To manipulate the service, you will enhance the CounterTest program created earlier. Here are step-by-step instructions to do that:

  1. Add three new buttons to the CounterTest form, with the following names and text labels:

    Name

    Text

    BtnCheckStatus

    Check Status

    BtnStartService

    Start Service

    BtnStopService

    Stop Service

  2. Add a reference to the DLL that contains the ServiceController class: Select Project

    Integrating a ServiceController into the example
  3. Add this line at the top of the code for Form1:

    Imports System.ServiceProcess
  4. As discussed, the project needs only one instance of the ServiceController class. Create a module-level object reference to a ServiceController class by adding the following line of code within the Form1 class:

    Dim myController As ServiceController
  5. Create a Form Load event in Form1, and place the following line of code in it to instantiate the ServiceController class:

    myController = New ServiceController("CounterMonitor")

You now have a ServiceController class named myController that you can use to manipulate the CounterMonitor Windows Service. In the click event for btnCheckStatus, place the following code:

Dim sStatus As String
myController.Refresh()
sStatus = myController.Status.ToString

MsgBox(myController.ServiceName & " is in state: " & sStatus)

In the click event for btnStartService, place this code:

Try
    myController.Start()
Catch exp As Exception
    MsgBox("Could not start service or the service is already running")
End Try

In the click event for btnStopService, place this code:

If myController.CanStop Then
    myController.Stop()
Else
    MsgBox("Service cannot be stopped or the service is already stopped")
End If

Run and test the program. The service may already be running because of one of your previous tests. Make sure the performance counter is high enough to make the service beep, and then test starting and stopping the service.

More about ServiceController

ServiceController classes can be created for any Windows Service, not just those created in .NET. For example, you could instantiate a ServiceController class that was associated with the Windows Service for Internet Information Server (IIS) and use it to start, pause, and stop IIS. The code would look just like the code used earlier for the application that controlled the CounterMonitor service. The only difference is that the name of the service would need to be changed in the line that instantiates the ServiceController (step 5).

Keep in mind that the ServiceController is not communicating directly with the service. It is working through the Service Control Manager. That means the requests from the Service Controller to start, stop, or pause a service do not behave synchronously. As soon as the ServiceController has passed the request to the ServicesControlManager, it continues to execute its own code without waiting for the Service Control Manager to pass on the request, or for the service to act on the request.

Custom Commands

Some services need additional operations besides starting and stopping. For example, for the CounterMonitor Windows Service, you might want to set the threshold value of the performance counter that causes the service to begin beeping, or you might want to change the interval between beeps.

With most components, you would implement such functionality through a public interface. That is, you would put public properties and methods on the component. However, you cannot do this with a Windows Service because it has no public interface that you can access from outside the service.

To deal with this need, the interface for a Windows Service contains a special event called OnCustomCommand. The event arguments include a numeric code that can serve as a command sent to the Windows Service. The code can be any number in the range 128 to 255. (The numbers under 128 are reserved for use by the operating system.)

To fire the event and send a custom command to a service, the ExecuteCommand method of the ServiceController is used. The ExecuteCommand method takes the numeric code that needs to be sent to the service as a parameter. When this method is accessed, the ServiceController class tells the Service Control Manager to fire the OnCustomCommand event in the service, and to pass it the numeric code.

The next example demonstrates this process in action. Suppose you want to be able to change the interval between beeps for the CounterMonitor service. You cannot directly send the beep interval that you want, but you can pick various values of the interval, and associate a custom command numeric code with each.

For example, assume you want to be able to set intervals of 1 second, 3 seconds (the default), or 10 seconds. You could set up the following correspondence:

Custom Command Numeric Code

Beep Interval

201

One second (1,000 milliseconds)

203

Three seconds (3,000 milliseconds)

210

Ten seconds (10,000 milliseconds)

The correspondences in the table are completely arbitrary. You could use any codes between 128 and 255 to associate with the beep intervals. These were chosen because they are easy to remember.

First, you need to change the CounterMonitor service so that it is able to accept the custom commands for the beep interval. To do that, first make sure the CounterMonitor service is uninstalled from any previous installs. Then open the Visual Studio 2008 project for the CounterMonitor service.

Create an OnCustomCommand event in the service: Open the code window for CounterMonitor.vb and type Protected Overrides OnCustomCommand. By this point, IntelliSense will kick in and you can press the Tab key to autocomplete the shell event. Notice how it only accepts a single Integer as a parameter:

Protected Overrides Sub OnCustomCommand(ByVal command As Integer)

End Sub

In the OnCustomCommand event, place the following code:

Timer1.Enabled = False
Select Case command
    Case 201
        Timer1.Interval = 1000
    Case 203
        Timer1.Interval = 3000
    Case 210
        Timer1.Interval = 10000
End Select
Timer1.Enabled = True

Build the countermonitor service, reinstall it, and start it.

Now you can enhance the CounterTest application created earlier to set the interval. To enable users to pick the interval, you will use radio buttons. On the CounterTest program Form1 (which currently contains five buttons), place three radio buttons. Set their text labels as follows:

RadioButton1 - 1 second
RadioButton2 - 3 seconds
RadioButton3 - 10 seconds

Place a button directly under these option buttons. Name it btnSetInterval and set its text to Set Interval. In the click event for this button, place the following code:

Dim nIntervalCommand As Integer = 203
If RadioButton1.Checked Then
    nIntervalCommand = 201
End If
If RadioButton2.Checked Then
    nIntervalCommand = 203
End If
If RadioButton3.Checked Then
    nIntervalCommand = 210
End If
myController.ExecuteCommand(nIntervalCommand)

At this point, Form1 should look something like the screen shown in Figure 33-8.

Figure 33-8

Figure 33.8. Figure 33-8

Start the CounterTest control program and test the capability to change the beep interval. Make sure the performance counter is high enough that the CounterMonitor service beeps, and remember that every time you stop and restart the service, it resets the beep interval to three seconds.

Passing Strings to a Service

Because the OnCustomCommand event only takes numeric codes as input parameters, you cannot directly pass strings to the service. For example, if you wanted to reconfigure a directory name for a service, you could not just send the directory name over. Instead, it would be necessary to place the information to be passed to the service in a file in some known location on disk. Then a custom command for the service could instruct it to look at the standard file location and read the information in the file. What the service did with the contents of the file would, of course, be customized for the service.

Creating a File Watcher

Now let's step through another example to illustrate what a Windows Service can do and how to construct one. You will build a service that monitors a particular directory and reacts when a new or changed file is placed in the directory. The example Windows Service application waits for those files, extracts information from them, and then logs an event to a system log to record the file change.

As before, create a Windows Service from the built-in template named Windows Service in the New Project screen. Name the new project FileWatcherService and click OK. This creates a new service class called Service1.vb. Rename this to FileWatcherService.vb. Right-click the design surface, select Properties, and set the ServiceName property to FileWatcherService.

As in the first example, set the application type to Windows Service and reset the project's start object to FileWatcherService. All of this is illustrated earlier in this chapter.

Writing events using an Event Log

The way to ensure that the service is doing its job is by having it write events to a system Event Log. Event Logs are available under the Windows operating system. As with many other system-level features, the use of Event Logs is simplified in .NET because a .NET Framework base class does most of the work for you.

There are three Event Logs on the system: Application, Security, and System. Normally, your applications should only write to the Application log. A property of a log entry called Source identifies the application writing the message. This property does not have to be the same as the executable name of the application, but it is often given that name to make it easy to identify the source of the message.

You can look at the events in the Event Log by using the Event Viewer. Select Control Panel

Writing events using an Event Log

It was mentioned earlier in the chapter that the AutoLog property of the ServiceBase class determines whether the service automatically writes events to the Application log. The AutoLog property instructs the service to use the Application Event Log to report command failures, as well as information for OnStart, OnStop, OnPause, and OnContinue events on the service. What is actually logged to the Event Log is an entry indicating whether the service started successfully and stopped successfully, and any errors that might have occurred. If you look in the Application Event Log now, these events are logged for the CounterMonitor Windows Service that you created and ran earlier in the chapter.

You can turn off Event Log reporting by setting the AutoLog property to False in the Properties window for the service, but leave it set to True for this example. That means some events will be logged automatically (without you including any code for them). Then, you add some code to the service to log additional events not covered by the AutoLog property. First, though, you need to implement a file monitoring control in the project.

Creating a FileSystemWatcher

For performance reasons, you should do all of your work on a separate thread to your main application thread. You want to leave your main application free to accept any requests from the user or the operating system. You can do this by using some of the different components that create their own threads when they are launched. The Timer component and the FileSystemWatcher component are two examples. When the Timer component fires its Elapsed event, a thread is spawned and any code placed within that event will work on that newly created thread. The same thing happens when the events for the FileSystemWatcher component fire.

You can learn more about threading in .NET in Chapter 26.

The FileSystemWatcher Component

The FileSystemWatcher component is used to monitor a particular directory. The component implements Created, Changed, Deleted, and Renamed events, which are fired when files are placed in the directory, changed, deleted, or renamed, respectively.

The operation that takes place when one of these events is fired is determined by the application developer. Most often, logic is included to read and process the new or changed files. However, you are just going to write a message to a log file.

To implement the component in the project, drag and drop a FileSystemWatcher control from the Components tab of the Toolbox onto the designer surface of FileWatcherService.vb. This control is automatically called FileSystemWatcher1.

The EnableRaisingEvents Property

The FileSystemWatcher control should not generate any events until the service is initialized and ready to handle them. To prevent this, set the EnableRaisingEvents property to False. This prevents the control from firing any events. You will enable it during the OnStart event in the service. These events fired by the FileSystemWatcher are controlled using the NotifyFilter property, discussed later.

The Path Property

The path that you want to monitor is the TEMP directory on the C: drive, so set the Path property to C:TEMP (be sure to confirm that there is a TEMP directory on your C: drive). Of course, this path can be changed to monitor any directory depending on your system, including any network or removable drives.

The NotifyFilter Property

You only want to monitor when a file is freshly created or the last modified value of a file has changed. To do this, set the NotifyFilter property to FileName, LastWrite. You could also watch for other changes such as attributes, security, size, and directory name changes as well, just by changing the NotifyFilter property. Note that you can specify multiple changes to monitor by including a comma-separated list.

The Filter Property

The types of files that you will look for are text files, so set the Filter property to .txt. Note that if you were going to watch for all file types, then the value of the Filter property would be set to *.*.

The IncludeSubdirectories Property

If you wanted to watch subdirectories, you would set the IncludeSubdirectories property to True. This example leaves it as False, which is the default value. Figure 33-9 shows how the properties should be set.

Figure 33-9

Figure 33.9. Figure 33-9

Adding FileSystemWatcher Code to OnStart and OnStop

Now that some properties are set, let's add some code to the OnStart event. You want to start the FileSystemWatcher1 component so it will start triggering events when files are created or copied into the directory you are monitoring, so set the EnableRaisingEvents property to True:

Protected Overrides Sub OnStart(ByVal args() As String)
    ' Start monitoring for files
    FileSystemWatcher1.EnableRaisingEvents = True
End Sub

After the file monitoring properties are initialized, you are ready to start the monitoring. When the service stops, you need to stop the file monitoring process. Add the following code to the OnStop event:

Protected Overrides Sub OnStop()
    ' Stop monitoring for files
     FileSystemWatcher1.EnableRaisingEvents = False
End Sub

The EventLog Component

Now you are ready to place an EventLog component in the service to facilitate logging of events. Drag and drop an EventLog control from the Components tab of the Toolbox onto the designer surface of FileWatcherService.vb. This control is automatically called EventLog1.

Set the Log property for Eventlog1 to Application, and set the Source property to FileWatcherService.

The Created Event

Next, you will place some logic in the Created event of the FileSystemWatcher component to log when a file has been created. This event fires when a file has been placed or created in the directory that you are monitoring. It fires because the information last modified on the file has changed.

Select FileSystemWatcher1 from the Class Name drop-down list and select Created from the Method Name drop-down list. The Created event will be added to your code. Add code to the Created event as follows:

Public Sub FileSystemWatcher1_Created(ByVal sender As Object, _
           ByVal e As System.IO.FileSystemEventArgs) _
           Handles FileSystemWatcher1.Created

     Dim sMessage As String
     sMessage = "File created in directory - file name is " + e.Name
     EventLog1.WriteEntry(sMessage)
End Sub

Notice that the event argument's object (the object named "e" in the event parameters) includes a property called Name. This property holds the name of the file that generated the event.

At this point, you could add the other events for FileSystemWatcher (Changed, Deleted, Renamed) in a similar way and create corresponding log messages for those events. To keep the example simple, you will just use the Created event in this service.

You need to add an Installer class to this project to install the application. This is done as it was in the CounterMonitor example, by right-clicking the design surface for the service and selecting Add Installer or by clicking the Add Installer link in the Properties window of Visual Studio 2008. Don't forget to change the Account property to LocalSystem, or set it to User and fill in the Username and Password properties.

As before, you must install the service using InstallUtil.exe. Then, start it with the Server Explorer or the Service Manager. Upon successful compilation of these steps, you will get a message logged for any file with a .txt extension that you copy or create in the monitored directory. After dropping some sample text files into the monitored directory, you can use the Event Viewer to ensure that the events are present.

Figure 33-10 shows the Event Viewer with several example messages created by the service. If you right-click one of the events for FileWatcherService, you will see a detail screen. Notice that the message corresponds to the Event Log message you constructed in the Created event of the FileSystemWatcher control in the service, as shown in Figure 33-11.

Figure 33-10

Figure 33.10. Figure 33-10

Debugging the Service

Because a service must be run from within the context of the Service Control Manager, rather than from within Visual Studio 2008, debugging a service is not as straightforward as debugging other Visual Studio 2008 application types. To debug a service, you must start the service and then attach a debugger to the process in which it is running. You can then debug the application using all of the standard debugging functionality of Visual Studio 2008.

Figure 33-11

Figure 33.11. Figure 33-11

Note

Don't attach to a process unless you know what the process is and understand the consequences of attaching to and possibly killing that process.

To avoid going through this extra effort, you may want to test most of the code in your service in a standard Windows Forms application. This test-bed application can have the same components (FileSystemWatchers, EventLogs, Timers, and so on) as the Windows Service, and thus will be able to run the same logic in events. Once you have checked out the logic in this context, you can just copy and paste it into a Windows Service application.

However, sometimes the service itself needs to be debugged directly, so it is important to understand how to attach to the service's process and do direct debugging. You can only debug a service when it is running. When you attach the debugger to the service, you are interrupting it. The service is suspended for a short period while you attach to it. It is also interrupted when you place breakpoints and step through your code.

Attaching to the service's process enables you to debug most, but not all, of the service's code. For instance, because the service has already been started, you cannot debug the code in the service's OnStart method this way, or the code in the Main method that is used to load the service. To debug the OnStart event or any of the Visual Studio 2008 designer code, you have to add a dummy service and start that service first. In the dummy service, you would create an instance of the service that you want to debug. You can place some code in a Timer object and create the new instance of the object that you want to debug after 30 seconds or so. Allow enough time to attach to the debugger before the new instance is created. Meanwhile, place breakpoints in your startup code to debug those events, if desired.

Follow these steps to debug a service:

  1. Install the service.

  2. Start the service, either from the Service Control Manager, from Server Explorer, or from code.

  3. In Visual Studio 2008, load the solution for the service. Then select Attach to Process from the Debug menu. The Attach to Process dialog box appears (see Figure 33-12).

    Figure 33-12

    Figure 33.12. Figure 33-12

  4. For a Windows Service, the desired process to attach to is not a foreground process; be sure to check the check box next to the "Show processes from all users" option.

  5. In the Available Processes section, click the process indicated by the executable name for the service, and then click Attach.

  6. You can now debug your process. Place a breakpoint in the code for the service at the place you want to debug. Cause the code in the service to execute (by placing a file in a monitored directory, for example).

  7. When finished, select Stop Debugging from the Debug menu.

Let's go through an actual scenario, using your earlier CounterMonitor example. Bring up both the CounterMonitor project and the CounterTest project in separate instances of the Visual Studio 2008 IDE. Make sure that the CounterMonitor service has been started. It is best if you hear it beeping — that way you know it is working. If necessary, increment the performance counter to make it beep.

In the CounterMonitor project, select Debug

Figure 33-12

You will then get a dialog box asking you what program types you are interested in debugging. Because you are working solely within .NET, check the box next to Common Language Runtime and leave the rest unchecked. Click the OK button, and then click the Close button on the Processes dialog box. You are now attached to the process running CounterMonitor in the background.

Place a breakpoint on the first line of the OnCustomCommand event:

Timer1.Enabled = False

Now you are ready to check debugging. Bring up the CounterTest program and start it. Press one of the radio buttons to change the beep interval. You will hear the beeping stop because CounterMonitor.exe has entered debugging mode. Switch back to the CounterMonitor project. The cursor will be on the breakpoint line in OnCustomCommand. You can use the normal commands at this point to step through the code.

Summary

This chapter presented a general overview of what a Windows Service is and how to create one with Visual Basic. The techniques in this chapter can be used for many different types of background service, including the following:

  • Automatically moving statistical files from a database server to a web server

  • Pushing general files across computers and platforms

  • A watchdog timer to ensure that a connection is always available

  • An application to move and process FTP files, or indeed files received from any source

While Visual Basic cannot be used to create every type of Windows Service, it is effective at creating many of the most useful ones. The .NET Framework classes for Windows Services make this creation relatively straightforward. The designers generate much of the routine code needed, enabling you, as a developer, to concentrate on the code that is specific to your particular Windows Service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset