Windows Services

Windows services have long been a hot topic in Visual Basic programming circles. The preceding version of the language, Visual Basic 6, included no native support, forcing developers to resort to third-party tools or API wizardry. In the .NET platform, the mystique finally lifts; creating a Windows service is now as easy as creating any other type of application.

Windows services, of course, are long-running applications that have no visual interface and typically work in the background as soon as your computer is started. Services were first introduced with Windows NT and are mediated by the Windows Service Control Manager (SCM). You can start, stop, and configure services through the Computer Management administrative utility.

Services are used to manage everything from core operating system services (such as the distributed transaction coordinator) to long-running engines (such as Microsoft SQL Server and Microsoft Internet Information Services [IIS]). Windows services are ideal for server computers because they enable you to create components that run even while the computer is not logged in. (In fact, it's important to remember that even if a user is logged in, each Windows service runs under a specific fixed account, which is probably not the same as the logged-in user account.) This section shows how you can create a Windows service to use for a long-running task or as a component host.

The .NET support for Windows service applications originates from the System.ServiceProcess namespace. The types in this namespace serve three key roles:

  • They enable you to create Windows service applications (mainly by deriving a custom class from ServiceBase).

  • They enable you to install a Windows service (mainly by using ServiceInstaller and ServiceProcessInstaller).

  • They enable you to retrieve information about services installed on the system and programmatically start and stop them (by using ServiceController).

Creating a Windows Service

Visual Studio .NET programmers can start by creating a Windows service project. This creates a single class that inherits from ServiceBase. This class has the structure shown in Listing 7-9. I've revealed a portion of hidden designer code because it's conceptually important.

Listing 7-9 A basic Windows service
 Imports System.ServiceProcess Public Class Service1     Inherits System.ServiceProcess.ServiceBase     Public Sub New()         MyBase.New()         InitializeComponent()     End Sub     ' This is the main entry point for the process.     <MTAThread()> _     Shared Sub Main()              ' You could use the method below with an array         ' of ServiceBase instances to start multiple services in the         ' same process.         ServiceBase.Run(New Service1())     End Sub     Private Sub InitializeComponent()         ' (If you have configured Service1 properties at design-time,         ' the property setting code will be added here.)         Me.ServiceName = "TestService"     End Sub     Protected Overrides Sub OnStart(ByVal args() As String)         ' Add code here to start your service.         ' This method might start a timer or a separate thread         ' before it returns.     End Sub     Protected Overrides Sub OnStop()         ' Add code here to stop your service and release resources.     End Sub End Class 

When this application is started, the Main method runs first. The Main method uses the shared ServiceBase.Run and passes the new instance of your service. If you want to start multiple services in the same process so they can interact with each other but be stopped and started individually, you can use this method with an array of services, as shown here:

 Dim ServicesToRun() As ServiceBase ServicesToRun = New ServiceBase() {New Service1(), New Service2()} System.ServiceProcess.ServiceBase.Run(ServicesToRun) 

The ServiceBase.Run method doesn't actually start your service technically, it loads it into memory and provides it to the SCM so that it is ready to be executed. What happens next depends on how the service is configured in the SCM. The service itself might be started automatically when the computer boots up, for example, or manually when a user interacts with the Computer Management utility.

When the service is started, the SCM calls the OnStart method of your class. However, this method doesn't actually perform the work; instead, it schedules the work. If OnStart doesn't return after a reasonable amount of time (approximately 30 seconds), the start attempt is abandoned and the service is terminated. Therefore, you shouldn't perform any application-specific processing in the OnStart method. Instead, you need to use your OnStart method to set up a new thread or timer, which will then perform the real work. Similarly, when the service is stopped, the OnStop method is called. This is the point where you will release all in-memory objects and stop the timer or thread processing.

You have two simple tasks to complete to make your Windows service operable. First, you should set all the relevant ServiceBase properties. In Visual Studio .NET, you can set these properties using the Properties window in Design view (in which case the property setting code is added to the hidden Windows designer region). Alternatively, you can manually add the property set statements to the constructor. Table 7-1 lists the ServiceBase properties. Note that properties such as CanStop and CanShutdown indicate whether a specific feature will be provided to the SCM. If you code the required method but don't set the corresponding property, your code is ignored. Similarly, if you set a property to indicate that your service can perform something it can't, an exception is generated when the command is attempted.

Table 7-1. ServiceBase Properties

Property

Description

AutoLog

If this property is True, the service uses the Application event log to report command failures and a status message when it is started, stopped, paused, or continued. The name of the service is used as the log's EventLog.Source. Alternatively, you can disable this property and use your own custom logging, as discussed in Chapter 14.

CanHandlePowerEvent

A value of True indicates that your service class has overridden the OnPowerEvent method to respond to power changes (such as when the computer is suspended).

CanPauseAndContinue

A value of True indicates that your service class has overridden the OnPause and OnContinue methods and therefore supports pausing and continuing.

CanShutdown

A value of True indicates that your service class has overridden the OnShutdown method to respond when the computer is being powered off.

CanStop

A value of True indicates that your service class has overridden the OnStop method and therefore supports stopping. As a bare minimum, most services support this feature.

EventLog

Provides a convenient place to store an EventLog reference. You can use this log to write additional, custom log messages. Note that this log will not be used by the autologging feature.

ServiceName

The short name used to identify your service.

Next, you need to add the logic to the OnStart and OnStop methods. In Listing 7-10, this service just starts a thread that writes debug information. Note that you don't need to call the base OnStart or OnStop ServiceBase methods in your overridden methods because this happens automatically.

Listing 7-10 A test service with threading
 Imports System.ServiceProcess Imports System.Threading Public Class TestService     Inherits System.ServiceProcess.ServiceBase     Public Sub New()         MyBase.New()         InitializeComponent()     End Sub     <MTAThread()> _     Shared Sub Main()         ServiceBase.Run(New TestService())     End Sub     Private Sub InitializeComponent() 
         Me.ServiceName = "TestService"     End Sub     Private ServiceThread As Thread     Private StopThread As Boolean = False     Protected Overrides Sub OnStart(ByVal args() As String)         ServiceThread = New Thread(AddressOf DoWork)         ServiceThread.Start()     End Sub     Protected Overrides Sub OnStop()         ' We only have 30 seconds to act before the SCM takes matters         ' into its own hands.         ' Try to signal the thread to end nicely,         ' (and wait up to 20 seconds).         StopThread = True         ServiceThread.Join(TimeSpan.FromSeconds(20))         ' If the thread is still running, abort it.         If ServiceThread.ThreadState And _           ThreadState.Running = ThreadState.Running Then             ServiceThread.Abort()             ServiceThread.Join()         End If     End Sub     Private Sub DoWork()         Dim Counter As Integer         Do Until StopThread             Counter += 1             Debug.WriteLine("Now Starting Iteration #" & _               Counter.ToString())             Thread.Sleep(TimeSpan.FromSeconds(10))         Loop     End Sub End Class 

Alternatively, you can start a timer (using the System.Timers.Timer class), which fires at periodic intervals. A timer is best suited for a short, repeated task, whereas a thread can handle a long-running, continuous task that works through several stages. Listing 7-11 shows an example that uses a custom timer to perform a task.

Listing 7-11 A timer-based service
 Imports System.ServiceProcess Imports System.Timers Public Class TestService     Inherits System.ServiceProcess.ServiceBase     Public Sub New()         MyBase.New()         InitializeComponent()     End Sub     <MTAThread()> _     Shared Sub Main()         ServiceBase.Run(New TestService())     End Sub     Private Sub InitializeComponent()         Me.ServiceName = "TestService"     End Sub     Private WithEvents ServiceTimer As New Timer(10000)     Private Counter As Integer     Protected Overrides Sub OnStart(ByVal args() As String)         ServiceTimer.Start()     End Sub     Protected Overrides Sub OnStop()         ServiceTimer.Stop()     End Sub     Private Sub DoWork(ByVal sender As Object, _       ByVal e As ElapsedEventArgs) Handles ServiceTimer.Elapsed         Counter += 1         Debug.WriteLine("Now Starting Iteration #" & _           Counter.ToString())     End Sub End Class 

A third option is to not use a timer or thread but use some sort of event handler. For example, you can create a System.IO.FileSystemWatcher instance to monitor a directory. The OnStart method will connect the handler, and the OnStop method will disconnect it. Chapter 16 presents a case study that uses a long-running Windows service to perform directory monitoring.

Note

Most services support stopping and starting. When a service stops, any data held in form-level variables should be completely released and the OnStart method should perform the required initialization from scratch. If your service retains a significant amount of information, however, you might want to implement OnPause and OnContinue in addition to OnStart and OnStop. The pause method will then stop the timer or thread from processing, but it will retain all the state information. The continue method will then resume processing immediately, without requiring any initialization. This pattern isn't used in the example because no significant information is retained while the service is working.


Installing a Windows Service

Unfortunately, Windows service applications cannot be debugged inside Visual Studio .NET because they are controlled by the SCM. To test your service, you first need to create an installer.

The easiest approach is to let Visual Studio .NET perform some of the work for you. Just click your service code file, put it in Design view, and select the Add Installer link that displays in the Properties window (as shown in Figure 7-4).

Figure 7-4. Adding an installer in Visual Studio .NET

graphics/f07dp04.jpg

A new ProjectInstaller.vb file is added to your project. This file contains all the code required to install the service. This installer uses two installer components that are automatically added to the design-time view: Service­ProcessInstaller1 and ServiceInstaller1 (as shown in Figure 7-5).

Figure 7-5. The installer components

graphics/f07dp05.jpg

Taken together, these classes encapsulate the installation process. When included in a setup project, the Windows installer automatically calls the Install method of both classes. The classes then write the required Registry information.

Before continuing further, you might want to make two minor modifications:

  • The ServiceProcessInstaller provides an Account property. Set this to LocalSystem so that the service runs under a system account rather than the account of the currently logged-in user. (In this case, only an administrator can run the installer.) Alternatively, you can specify the username and password for a specific account.

  • The ServiceInstaller provides a StartType property, which is set to Manual by default. If you want to install this service and configure it to start automatically, set this property to Automatic.

You can also change these details later by modifying the configuration settings for the service in the Computer Management utility.

The project installer class is very simple and performs most of its work automatically using the functionality it gains from the Installer class in the System.Configuration.Install namespace (as shown in Listing 7-12).

Listing 7-12 A sample Windows service installer
 Imports System.Configuration.Install Imports System.ServiceProcess <RunInstaller(True)> Public Class ProjectInstaller     Inherits System.Configuration.Install.Installer     Public Sub New()         MyBase.New() 
         InitializeComponent()     End Sub     Friend ServiceProcessInstaller1 As ServiceProcessInstaller     Friend ServiceInstaller1 As ServiceInstaller     Private Sub InitializeComponent()         Me.ServiceProcessInstaller1 = New ServiceProcessInstaller()         Me.ServiceInstaller1 = New ServiceInstaller()         Me.ServiceProcessInstaller1.Account = _           ServiceAccount.LocalSystem         Me.ServiceInstaller1.ServiceName = "TestService"         ' Add the two installers.         Me.Installers.AddRange(New Installer() _          {Me.ServiceProcessInstaller1, Me.ServiceInstaller1})     End Sub End Class 

You can incorporate this installer into a custom setup project, or you can use the InstallUtil.exe utility included with Visual Studio .NET. To do so, build your project, browse to the Bin directory using a command-line window, and type the following instruction (where WindowsService1 is the name of your application):

 InstallUtil WindowsService1.exe 

The output for a successful install operation is shown here:

 Microsoft (R) .NET Framework Installation utility Version 1.0.3512.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Running a transacted installation. Beginning the Install phase of the installation. See the contents of the log file for the  e:\windowsservice1\bin\windowsservice1.exe assembly's progress. The file is located at e:\windowsservice1\bin\windowsservice1.InstallLog. Installing assembly 'e:\windowsservice1\bin\windowsservice1.exe'. Affected parameters are:    assemblypath = e:\windowsservice1\bin\windowsservice1.exe    logfile = e:\windowsservice1\bin\windowsservice1.InstallLog Installing service TestService... Service TestService has been successfully installed. Creating EventLog source TestService in log Application... The Install phase completed successfully, and the Commit phase is beginning. See the contents of the log file for the  e:\windowsservice1\bin\windowsservice1.exe assembly's progress. The file is located at e:\windowsservice1\bin\windowsservice1.InstallLog. Committing assembly 'e:\windowsservice1\bin\windowsservice1.exe'. Affected parameters are:    assemblypath = e:\windowsservice1\bin\windowsservice1.exe    logfile = e:\windowsservice1\bin\windowsservice1.InstallLog The Commit phase completed successfully. The transacted install has completed. 

You can now find and start the service using the Computer Management administrative tool (as shown in Figure 7-6).

Figure 7-6. The installed service

graphics/f07dp06.jpg

If you want to update the service, you need to recompile the executable, uninstall the existing service, and then reinstall the new service. To uninstall a service, just use the /u parameter with InstallUtil:

 InstallUtil WindowsService1.exe /u 

Debugging a Windows Service

Even though you can't run a Windows service in Visual Studio .NET, it is still possible to debug it with the IDE. You just need to attach to the service after it is already started. Visual Studio .NET then enables you to set breakpoints and single-step through the code.

To attach to a service, begin by loading the appropriate project into Visual Studio .NET. That way, the source code will already be available. Then make sure that the service is installed and currently started by the SCM.

Choose Tools | Debug Processes, and in the Processes window (shown in Figure 7-7) be sure to enable the Show System Processes check box if you are running your service under a system account; otherwise, it won't appear in the list. When you find the matching service, select it by clicking the Attach button.

Figure 7-7. Attaching the debugger to a service

graphics/f07dp07.jpg

In the Attach To Process window, choose to debug the code as a Common Language Runtime application (as shown in Figure 7-8). Then click OK.

Figure 7-8. Choosing the application type

graphics/f07dp08.jpg

You are now free to set breakpoints, pause execution, and create variable watches. If you have created the simple debugging service presented previously, some basic information will begin to appear in the Debug window (as shown in Figure 7-9).

Figure 7-9. Debugging the service

graphics/f07dp09.jpg

Note

The SCM provides only 30 seconds for you to set OnStart and OnStop breakpoints. If you set a breakpoint and allow the code to be paused for a longer period of time, the service is terminated.


Controlling Windows Services

You don't need to use an administrative tool to start and stop your services. In fact, you can interact directly with services using the ServiceController class. This is a useful technique if you want to create your own administrative tool that will monitor your custom services on various computers and that will enable you to manage them from a single location.

For example, you can create a ServiceController object bound to a specific service by specifying the service name in the constructor (or the service name and machine name, for a remote service).

 Dim TestService As New ServiceController("TestService") 

You are then free to query information from properties such as Status, CanStop, and ServiceType. You can also use methods to programmatically start, stop, pause, or continue the service:

 TestService.Stop() 

The ServiceController class also provides a shared GetServices method, which returns an array of ServiceController instances representing all the services on a single computer. You can use this technique to create a simple service manager, as shown in Figure 7-10.

Figure 7-10. A custom application for controlling services

graphics/f07dp10.jpg

This application simply fills a DataGrid with a list of services and enables the user to stop or start a selected service. Listing 7-13 shows the form code.

Listing 7-13 Managing active services
 Imports System.ServiceProcess Public Class ServiceManager     Inherits System.Windows.Forms.Form     ' (Windows designer code omitted.)     Friend WithEvents cmdGetServices As System.Windows.Forms.Button     Friend WithEvents gridServices As System.Windows.Forms.DataGrid     Friend WithEvents cmdStart As System.Windows.Forms.Button     Friend WithEvents cmdStop As System.Windows.Forms.Button     Private Sub cmdGetServices_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles cmdGetServices.Click         RefreshServices()     End Sub     Private Sub cmdStart_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles cmdStart.Click         If gridServices.CurrentRowIndex > 0 Then             Dim ServiceName = _               gridServices.Item(gridServices.CurrentRowIndex, 1)             Dim Service As New ServiceController(ServiceName)             Service.Start() 
             ' Refresh the display             RefreshServices()         End If     End Sub     Private Sub cmdStop_Click(ByVal sender As System.Object, _       ByVal e As System.EventArgs) Handles cmdStop.Click         If gridServices.CurrentRowIndex > 0 Then             Dim ServiceName = _               gridServices.Item(gridServices.CurrentRowIndex, 1)             Dim Service As New ServiceController(ServiceName)             Service.Stop()             ' Refresh the display             RefreshServices()         End If     End Sub     Private Sub RefreshServices()         gridServices.DataSource = ServiceController.GetServices()     End Sub End Class 

Note

Our example duplicates the basic functionality of the Computer Management utility. To make it truly useful, you can change it to work with a coordinator XML Web service that provides methods such as RegisterWindowsService and GetRegisteredWindowsServices. Your Windows service can call the RegisterWindowsService method when it first starts and then add information about the service and the computer name to a central database. The monitoring utility can call GetRegisteredWindowsServices to retrieve the full list of running services. A similar approach is developed in Chapter 11 with remote components.


Using a Windows Service for a Component Host

Now that you have a firm grasp of Windows service programming and how to interact with the SCM, you're ready to adapt the design to a component host. The code in Listing 7-14 is quite simple: The idea is that you start listening for client connections when OnStart is triggered, and you stop listening when OnStop is executed.

Listing 7-14 A Windows service component host
 Imports System.ServiceProcess Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Public Class DBComponentHost     Inherits System.ServiceProcess.ServiceBase     Public Sub New()         MyBase.New()         InitializeComponent()     End Sub     <MTAThread()> _     Shared Sub Main()         ServiceBase.Run(New TestService())     End Sub     Private Sub InitializeComponent()         Me.ServiceName = "DBComponentHost"     End Sub     ' Register the service.     Protected Overrides Sub OnStart(ByVal args() As String)         RemotingConfiguration.Configure("SimpleServer.exe.config")     End Sub     ' Remove all the listening channels.     Protected Overrides Sub OnStop()         Dim Channel As IChannel         For Each Channel In ChannelServices.RegisteredChannels             Try                 ChannelServices.UnregisterChannel(Channel)             Catch                 ' Ignore any channel errors.             End Try         Next     End Sub End Class 


Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

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