Until now, you have been using the built-in Windows tools to view and manage Windows services. The .NET Framework also provides a set of classes that enable you to work with Windows services directly from your Visual Basic .NET application code. This can be very useful if you have created a Windows service that monitors and logs some system performance data, but you want it to run only while your application is running. You can start the service when your application starts up and stop it when your application closes. You can even add custom commands to your service and call them from application code.
In this section, you are going to learn how to use the ServiceController class. This is a .NET Framework class that has methods to programmatically control a Windows Service. You will create a Windows Forms application that can start and stop services. A sample application called ServiceControllerProject is included on the book’s CD and incorporates all the features covered in this section (see Figure 1.3). You might want to load the application code so that you can review it while you are reading this section.
Exercise 1.4 at the end of the chapter is designed to take you step-by-step through the features of the ServiceControllerProject demo application. Exercise 1.5 provides some examples that modify the CustomLogService that you created earlier in the chapter to support custom commands and for building a service controller application of your own to test them.
Figure 1.3: The ServiceControllerProject demo
When you instantiate a ServiceController object, you must supply two important pieces of information:
The service name that you want to control
The machine name that the service is running on
If you do not specify a machine name, the default is to look for the service on the local machine. Your project must include a reference to System.ServiceProcess.dll, and you should add an Imports statement for System.ServiceProcess as well. The ServiceController object can be instantiated as follows:
Dim servController = New _ ServiceController("CustomLogService")
In the preceding example, the service name is passed to the overloaded constructor method as a single string parameter. The ServiceName property can also be set independently, as shown here:
Dim servController as New ServiceController() ServController.ServiceName = "CustomLogService"
There are several important properties of the service that you might be interested in testing. Table 1.3 shows some of the properties of the ServiceController class. The listed properties map to the properties of the ServiceBase class discussed in the first part of this chapter.
Property Name | Description |
---|---|
CanPauseAndContinue | True if the service can be paused and continued. (Read-only) |
CanShutdown | True if the service should be notified when the system is shutting down. (Read-only.) |
CanStop | True if the service can be stopped after it has started. (Read-only.) |
DependentServices | Gets the set of services that depends on the service associated with this ServiceController instance. |
DisplayName | A friendly name for the service. |
MachineName | The name of the computer on which the service is running. |
ServiceName | Identifies the service that this instance of the ServiceController references. |
ServicesDependedOn | The set of services that this service depends on. |
ServiceType | One of the following: Win32OwnProcess, Win32ShareProcess (these are the types that can be created in Visual Studio .NET). Other system services might show a service type of Adapter, FileSystemDriver, InteractiveProcess, KernelDriver, or RecognizerDriver. |
Status | One of the following: StartPending, Running, StopPending, Stopped, PausePending, Paused, ContinuePending. |
Remember, you use the properties of the ServiceBase class when you are creating a Windows service. The CanStop, CanPauseAndContinue, and CanShutdown properties of the ServiceBase class enable you to set the behavior for your service. In the ServiceController class, these properties are read-only. The ServiceController instance can only test the property to see what was set when the service was created.
When you are working programmatically with a service, it is good practice to always test the service’s state before you try an operation. For example, before you try to issue a Pause command to a service, test the CanPauseAndContinue property to see whether Pause is a valid action for that particular service:
If servController.CanPauseAndContinue = True Then servController.Pause() End If
You also might want to test the current value of the Status property before issuing a command to change the status:
If servController.Status = _ ServiceControllerStatus.Paused Then servController.Continue() End If
The only valid settings for the Status property are defined by the ServiceControllerStatus enumeration, as Intellisense in Visual Studio .NET will show you (see Figure 1.4).
Figure 1.4: The ServiceControllerStatus enumeration
Table 1.4 lists the methods of the ServiceController class. These methods enable you to write code in a Visual Basic .NET application that can cause a Windows service application to start, stop, pause, or continue. Your code can also call custom commands and get other information about the service.
Method Name | Description |
---|---|
Close | Disconnects the ServiceController object from the service and releases any resources that were in use |
Continue | Resumes a service after a paused command |
ExecuteCommand | Executes a custom command on the service |
GetDevices | Gets a list of device driver services on a computer |
GetServices | Gets a list of services on a computer |
Pause | Pauses the service |
Refresh | Gets current property values |
Start | Starts the service |
Stop | Stops this service and any services that are dependent on this service |
WaitForStatus | Waits for the service to reach the specified status or for the request to time out |
The Start, Stop, Pause, and Continue methods are easy to understand. They work the same way in code that they work when you are issuing these commands through the Service Control Manager interface.
The Refresh method gets the current settings for the properties of the service that you are monitoring, without affecting the state of the service.
The GetServices and GetDevices methods populate an array of ServiceController objects, which in turn can access information about all the services installed on a computer (as shown in Listing 1.3). The GetDevices method gets those services that are of type KernelDriver or FileSystemDriver.
Listing 1.3 shows the procedure from the ServiceControllerProject demo that loads a ListBox control with the names of all services on the computer.
Listing 1.3: A Procedure to List All Services Running on the Local Computer
Private Sub btnGetServices_Click(ByVal sender _ As_System.Object, ByVal e As System.EventArgs) _ Handles btnGetServices.Click Dim servArray() As ServiceController Dim i As Integer servArray = ServiceController.GetServices() lstDisplay.Items.Clear() For i = 0 To servArray.Length - 1 lstDisplay.Items.Add(servArray(i).ServiceName) Next servArray = Nothing End Sub
The WaitForStatus method takes into consideration that sometimes a particular service might take a long time to start or not start at all. Also, StartPending, StopPending, PausePending, and ContinuePending will appear as the service’s status briefly, before they have completely reached a final state. You can test this with the ServiceControllerProject demo. After a service is stopped, click the Start button.
The display in the list box will show the status as StartPending. If you click the Get Properties button again a moment later, the display updates to show that the service now has a status of Running. If you select the check box labeled Wait Until Running, the code in the ServiceControllerProject demo will call the ServiceController object’s WaitForStatus method and the code will block until the target service achieves the specified status. The display does not update until the service’s status is Running. This is shown in the following code snippet:
If chkWait.Checked Then servController.WaitForStatus( _ ServiceControllerStatus.Running) End If
You can also call the WaitForStatus method by specifying two parameters: the status to wait for and a TimeSpan value, which indicates how long your code should wait before it times out and reports an error condition.
The ServiceController class offers a method that enables you to define truly customized functionality for your Windows service application. You have seen how to add code to standard methods that will fire in the normal cycle of events, as a Windows service application is started and stopped. The ServiceController class provides a means to call custom methods that you have designed for your Windows service application.
Let’s return to the source code for your Windows service application named CustomLogService. You will add another event procedure to the service and then recompile and reinstall it.
When you create custom functionality for a Windows service, calls to any of your procedures are handled inside the single Windows service event procedure named OnCustomCommand. Inside this procedure, you can use a conditional test or Case statement to break out one or more groups of code that will be executed as part of a given command. The OnCustomCommand method accepts an integer parameter that indicates which section of code should be executed for any specific call to the method. The integer parameter must be within the range of 128 and 256. Values below 128 are reserved for system commands. If the AutoLog property of the service is True, calls to OnCustomCommand will be noted in the Windows Application event log.
The procedure inside your Windows Service application will look like Listing 1.4.
Listing 1.4: The OnCustomCommand Procedure
Protected Overrides Sub OnCustomCommand( _ ByVal command As Integer) Select Case command Case 130 CustomEventLog.WriteEntry( _ "Command 130 successfully completed.") Case 140 CustomEventLog.WriteEntry( _ "Command 140 successfully completed.") Case 150 CustomEventLog.WriteEntry( _ "Command 150 successfully completed.") Case Else CustomEventLog.WriteEntry( _ "ERROR: Unrecognized command parameter!") End Select End Sub
For simplicity, your custom command does nothing more than write a log entry to verify that the command successfully completed. But that’s enough to test your code in the ServiceControllerProject demo.
After you have the code in the Windows service application, you can write a method in your ServiceController application that calls the ServiceController.ExecuteCommand method. The ServiceControllerProject demo has a simple user interface that calls the method and passes a user-selected integer parameter (see Figure 1.5). As you can see from Listing 1.4, the OnCustomCommand method will recognize three valid parameter values: 130, 140, and 150. If any other value is passed, an error message will be written to the custom event log.
Figure 1.5: Executing a custom command from the ServiceControllerProject demo
Listing 1.5 shows the code from the ServiceControllerProject demo that calls the Execute command method:
Listing 1.5: Executing a Custom Command
Private Sub btnCommand_Click(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles btnCommand.Click Dim commandNumber As Integer servController = New _ ServiceController("CustomLogService") Try commandNumber = CType(txtCommand.Text, Integer) servController.ExecuteCommand(commandNumber) MessageBox.Show("Command completed. " & _ "Check the Custom event log.") Catch ex As Exception MessageBox.Show("Invalid command number.") End Try End Sub
One important thing to remember about calling a custom command on a Windows service is that all error handling must be done within the Windows service application itself. In this simple example, our “error handling” consisted of writing an error message to the event log. In a real-world application, you will need to consider your error handling carefully. The error handling implemented in the ServiceController client application guards only against sending a nonnumeric value as a parameter to the call to ExecuteCommand.
In Exercise 1.4, you will load the ServiceControllerProject demo and try some of its features that were discussed in this section.
Exercise 1.4: Trying the ServiceController Demo Project
On the CD included with this book, you will find a Visual Basic .NET project titled ServiceControllerProject. Open this project in Visual Studio .NET.
If you have already created the CustomLogService (see Exercise 1.2), the ServiceControllerProject will immediately display information about the service when you first run it. If you do not have a service named CustomLogService installed, you will get an error message. If you get this message, type in the name of a valid service, such as ClipBook. Then click the Get Properties button.
Experiment with the Stop and Start buttons. You might also want to open the Service Control Manager and watch the service status changing there as well. You will need to refresh the display in the Service Control Manager each time you change the status by using the Visual Basic .NET application.
Note | Because the CanPauseAndContinue property of CustomLogService is set to False, the Pause and Continue buttons are disabled. |
Notice that when you stop and then start the CustomLogService, the status that is displayed is StartPending. If you click the Get Properties button again a few seconds later, you will see the status is now Running.
Select the Wait Until Running check box; then stop and start the service again. This time the ListBox display will not be updated until the service has been fully started and the status has reached Running.
Type in the name of a different service, such as EventLog, and view its properties. Remember, do not stop the system services or services you didn’t create (especially if you’re not sure what the service does); doing so can cause problems with your computer.
The Service Lists menu displays another form, where you can see a list of all the services installed on your computer. The GetDevices method shows all installed services that are device drivers.
Finally, the Execute Commands menu displays one more form. This form contains code to execute custom commands against CustomLogService. You can’t test this feature yet. In the next exercise, Exercise 1.5, you will modify CustomLogService to accept custom commands.
In Exercise 1.5, you will uninstall and modify the CustomLogService you created in Exercise 1.2. To uninstall the CustomLogService, you will be using the Windows Control Panel application Add/Remove Programs. While looking at the list of installed applications on your computer, you will see only the entry for the setup program that installs the service. You will not see an entry for the service itself.
Exercise 1.5: Uninstalling and Modifying CustomLogService
Start the Windows Control Panel application Add/Remove programs. Remove CustomLogSetup.
Verify that CustomLogService is no longer installed by checking the Service Control Manager. Right-click on Services (local) and choose Refresh.
Open the CustomLogService solution in Visual Studio .NET (it should contain both the service and setup projects).
Add the following method to CustomLogService.vb. Add this code directly after the OnStart and OnStop methods (refer to the following screen capture):
Protected Overrides Sub OnCustomCommand(ByVal command As Integer) Select Case command Case 130 CustomEventLog.WriteEntry( _ "Command 130 successfully completed.") Case 140 CustomEventLog.WriteEntry( _ "Command 140 successfully completed.") Case 150 CustomEventLog.WriteEntry( _ "Command 150 successfully completed.") Case Else CustomEventLog.WriteEntry( _ "ERROR: Unrecognized command parameter!") End Select End Sub
Save the solution. Right-click the CustomLogService project in the Solution Explorer and chose Build. Then right-click the CustomLogSetup project and choose Build.
Go to the Debug subdirectory under the CustomLogSetup project directory. Double-click the CustomLogSetup.msi file to install the revised version of the service.
Use the ServiceControllerProject demo that you used in Exercise 1.4 to verify that the CustomLogService is once again installed on your computer.
Start the CustomLogService.
Go to the Execute Commands form in the ServiceControllerProject demo and test execution of the custom commands. Try the valid parameter numbers 130, 140, and 150 and then try an invalid number, such as 155.
Open the Windows Event Viewer and look at the entries. Double-click an entry to display the Properties dialog box and view the message.