Windows Services: New Base Classes in .NET Make Writing a Windows Service Easy
|This article assumes you're familiar with Visual Basic .NET and C#
|Level of Difficulty 1 2 3
|Browse the code for this article at Code Center: NET Services
|SUMMARY Windows services are applications that run outside of any particular user context in Windows NT, Windows 2000, or Windows XP. The creation of services used to require expert coding skills and generally required C or C++. Visual Studio .NET now makes it easy for you to create a Windows service, whether you're writing code in C++, C#, or Visual Basic. You can also write a Windows service in any other language that targets the common language runtime. This article walks you through the creation of a useful Windows service, then demonstrates how to install, test, and debug the service.
| f you're running Windows NT® or Windows® 2000, you're running at least a handful of Windows services (previously called Windows NT Services). Typically these services provide system-level support, including the system event log, task scheduler, and telephony. An important aspect of Windows services is that they can run without a user context, albeit only under Windows NT, Windows 2000, or Windows XP at this point. They don't require a user to be logged in in order to do their work, and they generally run in a higher-powered security mode than do most users.
Windows services are often started by Windows when you boot up, but can be stopped, paused, restarted, and shut down from the Services applet provided by Windows. In addition, you can manage Windows services programmatically, and Microsoft® .NET makes this easy, using the ServiceController component that's part of the .NET Framework.
Using Visual Studio® 6.0, writing a Windows service application was at best tricky. For C/C++ developers, the techniques were documented and manageable, but debugging was treacherous. With Visual Basic®, things were much worse. Without the help of some external tool (such as Desaware's NT Service Toolkit, which makes the process as easy as it's ever going to be in Visual Basic 6.0), creating a Windows service was next to impossible.
Visual Studio .NET changes all of this. Now, the language you choose is immaterial—the steps are the same whether you're creating a Windows service using Visual Basic, C#, or any other common language runtime (CLR)-supported language. Creating a Windows service in Visual Studio .NET is amazingly simple. If you follow the steps, you end up with a service, and debugging the service is much simpler than in previous versions.
This article focuses on creating a simple, somewhat useful Windows service which monitors file changes on a specific drive, or, with modification, on multiple drives. As you follow the steps to build this Windows service, I'll explain the EventLog and FileSystemWatcher components provided by the Microsoft .NET Framework. You'll see how to add an installer to your project, so that the InstallUtil program that comes with Visual Studio can install your service, and you'll see how to debug your service as well, taking advantage of the ServiceController component that is provided by the .NET Framework.
Creating a Windows Service in .NET
From a developer's perspective, a Windows service application exists as an executable file, although this file may contain more than one service. You determine the behavior of each service in your project by writing a class for each that inherits from the ServiceProcess.ServiceBase class (creating a Windows Service project takes care of most of these types of details for you), and adding code to handle the various methods provided by this class. Among other things, the service you create can provide code for the OnStart, OnStop, OnPause, OnContinue, and OnShutdown methods called by the Service Control Manager (SCM) as you interact with the service. None of these procedures is required, but you can use them to provide specific behavior in reaction to requests from the SCM's user interface, or from other services. (The Service Control Manager mentioned here is, by the way, not the same as the service COM uses to bootstrap objects, even though they share the same name.)
The service class you create can't install itself, however. You must also supply an Installer class that inherits from Configuration.Install.Installer. For each service in your project, this class creates one ServiceProcessInstaller object (which knows how to install the service with the SCM for you), and a ServiceInstaller object. The ServiceInstaller object writes information to your registry for you as needed for the installation—specifically, to a subkey under the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services key. (Working with Installers would require an entire article, so it will not be the focus here.)
Once you've created your service class with the appropriate functionality built in, you'll follow these steps to get it up and running:
This article covers each of these steps in some detail.
- Compile your project to create an EXE.
- Use the InstallUtil program that's part of the .NET platform to install your project (you should load it as a service).
- Use the Services applet from the Control Panel to start your service. By default, the project you created sets the start type to Manual.
- If you want to debug your service, now's the time to grab onto the process containing your service, and step through the code looking for problems.
To illustrate the process, I'll be creating a simple service that actually does something useful rather than using the example provided by Microsoft which writes "Hello, World" to your event log every second it runs. The service monitors file activity on the drive that you specify when you start the service. This service writes informational messages into your application's event log, indicating that you've changed an item in the file system underneath the path you specify.
This may sound tricky if you're used to Visual Basic because monitoring for file system changes in Visual Basic 6.0 required a reasonable knowledge of the Windows API. The .NET platform, however, provides the FileSystemWatcher component, which makes it easy to determine when users have modified, deleted, added, or moved files on a drive. All you have to do is place one of these components on the design surface for your service class, and you can react to events raised by the component when files are changed. It's even easier to take advantage of event logs—the EventLog class is provided by the .NET Framework as well, so you can just drop it and use it.
The sample files for this article, which can be found at the link at the top of this article, include the completed FileWatcher service, but if you want to create your own, follow these steps:
In attempting to add an installer, there are several ways you can go wrong. I spent five minutes trying before I realized that when the documentation told me to open the class in Design view, they were referring to opening the class's designer. I kept opening the code window, and never saw the Add Installer link. Make sure you open the Designer window, and you'll be all set.
- Start Visual Studio .NET, and create a new project. From the New Project dialog box, choose either a Visual Basic or Visual C#™ project, and select the Windows Service project type. Set the name for the project to FileWatcher, and place it in a convenient folder. (When entering code throughout this article, choose the appropriate code sample, C# or Visual Basic .NET, matching your project type. I've included the code for both languages—select the code for your project type, and enter that code.) Note that you don't have to use the Windows Service project template, but if you choose not to, you'll need to do a lot more work on your own. Selecting the project template sets up the appropriate inheritance, and includes the necessary components. The online documentation for Visual Studio describes how you can do this yourself.
- Modify the file name. Once Visual Studio has created the project, you'll find a class named Service1 within the project. In the Solution Explorer window, rename this file FileWatcher.cs or FileWatcher.vb, depending on the project language.
- Open the class in Design view. With the FileWatcher class selected, you can either press Shift+F7 or choose the View | Designer menu item.
- Modify the service properties. In the Properties window, set the service's properties, which are described in Figure 1. The three Boolean properties (CanPauseAndContinue, CanShutDown, and CanStop) control the behavior of the running service. The ServiceName property provides the name the system uses to refer to the service. Figure 2 shows the Properties window after making the appropriate changes. (You might also investigate the CanHandlePowerEvent, which indicates that the service handles the computer power status changes indicated in the PowerBroadcastStatus class. Modifying that property isn't really necessary in this example.)
Figure 2 Service Properties Window
- Add the FileSystemWatcher component. With the FileWatcher class still open in Design view, locate the Toolbox window (press Ctrl+Alt+X, or use the View | Toolbox menu item to ensure that it's visible). Find the Components tab, and then double-click on the FileSystemWatcher component to add an instance to the designer. Modify the component's name to be fsw.
- Set fsw's properties. Select the FileSystemWatcher component you just added and modify its properties to match those shown in Figure 3. Although you can accept the FileSystemWatcher object's default properties, you'll probably always want to set at least the Filter, Path, and IncludeSubdirectories properties. Figure 4 shows the FileSystemWatcher object's Properties window, once you've set all its properties.
Figure 4 FileSystemWatcher Properties
- Add startup code. Generally, you'll need code in your service's OnStart event that sets up your service and its data structures, and perhaps logs information to the system event log. In this case, your service will simply set fsw's Path property based on the arguments passed into the service, and will log the event to the event log. Use the View | Code menu item (or press F7) to load the class's module, find the existing OnStart procedure, and modify it so it looks like the code in Figure 5.
You can also write code to handle the OnContinue, OnCustomCommand, OnPause, OnShutdown, OnPowerEvent, and OnStop events of the service. This example doesn't need these events to be handled, but the event hooks are there for you.
- Add an Installer. Select the View | Designer menu item (or press Shift+F7). Make sure the Properties window is visible (use the View | Properties window menu item, or press F4), and click on the designer surface to make sure it's selected. Then select the Add Installer link at the bottom of the Properties window to add the installer classes. Figure 6 shows the designer and Properties window before you add the installer.
Figure 6 Add Installer
Once you've selected the Add Installer link, Visual Studio .NET creates a new project file named ProjectInstaller.cs (or .vb). Right now, this new file contains two components: ServiceProcessInstaller1 and ServiceInstaller1. If you investigate the properties of each, you'll see that the ServiceInstaller1 object includes a ServiceName property. This component installs your particular service. The other component, ServiceProcessInstaller1, has properties such as Account, Password, and UserName. This component takes care of interacting with the Windows SCM on behalf of your service. You may have more than one ServiceInstaller component in your project—if your project contains multiple classes that inherit from ServiceBase, for example—but it will only have a single instance of a ServiceProcessInstaller object.
Note that Visual Studio .NET sets the ServiceName property of the ServiceInstaller object when you add the ServiceInstaller object to your project. If you change the name of your service class, you'll need to manually change this property to match.
A full discussion of the Installer class is beyond the scope of this article, but if you intend to interact with the Windows Installer technology, you'll need to become familiar with the members of the Install class.
There are a few important things that you should note. The class that Visual Studio .NET creates for you, inheriting from System.Configuration.Install.Installer (named ProjectInstaller by default), must include the RunInstaller attribute, set to True, in order for the installation to be invoked when you install the assembly. If you remove this or set it to False, you service won't be installed. Like other classes that include a Designer window, this one provides an InitializeComponent method. The intent is that you won't modify this—make all your changes in the designer. Code you might add, however, would be in reaction to the various events raised by the ServiceProcessInstaller or ServiceInstaller classes, including Before/AfterCommit, Before/AfterInstall, Before/AfterRollback, and Before/AfterUninstall. These events give you a chance to modify the default behavior for the installer classes that you add to your projects.
In this example, you needn't modify the installer class at all. Simply accept all its default behaviors, and your service will install just fine. (You may also find it useful to set the ServicesDependedOn property, which contains an array of strings that lists by name the various services on which this service depends.)
By default, the ServiceInstaller class's StartType property is set to Manual. If you want your service to start automatically when you install it, select the ServiceInstaller1 component in ProjectInstaller's designer, and set the StartType property to Automatic.
In order for the installer to be able to install your service, you need to supply information about the account the service will run as. For this simple service, you can set the Account property of ServiceProcessInstaller1 to be LocalSystem—that way, you needn't supply any authentication information. However, if you don't set the Account property to LocalSystem, or you don't supply a valid UserName and Password property values, the installation setup will fail.
Adding FileSystemWatcher Event Code
The FileSystemWatcher component provides a number of events to which you can react in code, including Changed, Created, Deleted, and Renamed. In this example, you'll react to these events, writing information to the event log using the EventLog class. (Note that the event handlers for three of the events—Changed, Created, and Deleted—all use the same signature. The event procedure for the Renamed event uses a slightly different procedure signature.)
If you're creating a C# project, I suggest that you use the Events button (it has an image of a lightning bolt on it) on the Properties window toolbar to add your event handlers. If you do this, the designer will add the event handler plumbing for you. Select the event you want to add code for, and double-click on the event name. The designer will take you to the appropriate event handler stub, and will add the necessary code to hook up the event, as well.
Follow these steps to set up your code:
If you used the Events button on the Properties windows toolbar to create the event procedures, you won't need to add the code in the final step yourself—Visual Studio will have added it for you. If you're using Visual Basic .NET, you needn't worry about these event handlers, since Visual Basic .NET takes care of the hookups under the covers.
- Select the FileWatcher object in the Solution Explorer window, and then use View | Designer (Shift+F7) to open the designer window.
- Select the FileSystemWatcher component on the FileWatcher class designer.
- Add code for the Changed event. In reaction to the Changed event, you'll write the type of change, plus the name of the file or folder, to the event log. In the code window, select fsw from the left dropdown list at the top of the window, and then select Changed from the right-hand list. Modify the event procedure so it looks like the code in Figure 7.
- Add code for the Created event. In reaction to the Created event, write the name of the new file or path to the event log, as you can see in Figure 8.
- Add code for the Deleted event. In reaction to the Deleted event, write the name of the deleted file or path to the event log, as you can see in Figure 9.
- Add code for the Renamed event, shown in Figure 10, which writes an event log entry indicating the old and new names.
- If you're creating a Visual Basic .NET project, you're all finished. If you're creating a C# project, however, you'll need to manually hook up the event handlers for the event procedures you just added. (Visual Basic .NET provides this internal plumbing for you—C# does not.) Therefore, if you're creating a C# project, follow this final step: in the FileWatcher.cs module, find the InitializeComponent procedure. Ensure that the lines of code shown in Figure 11 appear before the call to the EndInit method of the fsw object. These lines add event handlers for the procedures you've created.
Testing Your Service
Unlike many applications you'll create using Visual Studio .NET, you cannot simply run a Windows service from within the development environment. You must build the executable, install it, and test it as a running service. (Debugging a service is another story altogether, which I'll cover in the next section.) Follow these steps to test your Windows service:
- Use the File | Save All menu item, or press Ctrl+Shift+S, to save your entire project.
- Choose the Build | Build menu item (or press Ctrl+Shift+B) to build your service's executable file.
- Find the executable. Use the Visual Studio .NET Command Prompt item from the Start menu. It's installed as a subitem of the Visual Studio .NET Tools item to open a Windows command prompt. (If you don't follow these steps, you won't have your MS-DOS path set correctly.) Change to the folder where your project is stored. Navigate to the bin\debug folder for C# projects, or the bin folder for Visual Basic projects, and locate the FileWatcher.exe file you've just created.
- Install your service. Run the InstallUtil tool that comes with Visual Studio, using the following command line:
Figure 12 shows the output of running the InstallUtil utility. InstallUtil runs a transactioned install. If anything fails as part of the install, all changes are rolled back. Unless you set the Account or Password and UserName properties of the ServiceProcessInstaller object, this installation will fail. If you want to view all the options available for this installation utility, run InstallUtil /?
Figure 12 InstallUtil Output
- Start the service. Unless you modified the ServiceInstaller1 StartType property, you'll need to start your service. Bring up the Windows service manager. (The exact steps depend on your operating system. In Windows 2000, you can use the Administrative Tools applet in Control Panel and select the Services item.) In the Services tool, find the FileWatcher service and double-click it to load the properties dialog box for the service. If you want to specify a path other than C:\ for the FileWatcher class, you can do that in the Start Parameters textbox. When you're ready, press Start to start the service.
- Investigate the Startup events. Open the Event Viewer (again, the steps vary depending on your operating system). Select the Application log, and note that you should find two entries in the event log already. The first, sent from your service's OnStart event procedure, contains the text "FileWatchService starting. There are 0 args. Watch path is 'C:\'." (The text will be different if you specified an argument when you started the service.) The second, sent automatically (because you set the service's AutoLog property to True), indicates that the service started successfully.
- Test the service. Open Windows Explorer, navigate to the folder your service is "watching," and make some changes. Rename, delete, or modify files. Switch back to the event viewer, and you should see information about your changes logged there.
- Remove the service. When you're done, use the Services dialog box to stop your service. Run InstallUtil with its /u parameter to uninstall your service. You'll also need to shut down the Windows Services applet to completely remove your service from memory.
Debugging Your Service
At some point, you're likely to want to single-step through the code you've written in your service application. Although you can debug services much as you debug other applications, you cannot simply press F5 to start running the service from within the Visual Studio development environment, as you can with other projects.
Because you must compile and install a service before running it, debugging becomes a little more complex than for normal applications. Visual Studio .NET makes this process as easy as possible. Once your service is running, you can grab onto the running process and debug your code. To test this out, follow these steps:
If you've gotten this far, you're most likely very happy to see that you can debug services so easily, but the nagging question about how you debug your service's OnStart event handler remains. Because you can't start debugging unless the process is running, and the OnStart event procedure runs when you start the service, how can you debug this code? One easy solution is to embed calls to EventLog.WriteEntry in the code, as pioneer coders might have done before the advent of adequate debugging tools.
- Reinstall and start your service, as described in the previous section. (You must start the service running, or you won't be able to debug it.)
- In Visual Studio, with your project loaded, select the Debug | Processes menu item, displaying the Processes dialog box.
- Select the Show system processes checkbox so that the Available Processes list includes running services.
- In the Available Processes list, select the Filewatcher.exe entry, and then click Attach. On the Attach to Process dialog box, make sure that the Common Language Runtime option is selected, then select OK to accept. Then you can close the Processes dialog box.
- Set a breakpoint in your code at the location you'd like to test. In this case, set a breakpoint on one of the event procedures you've created within the FileWatcher class.
- Trigger the breakpoint by taking the necessary action within the file system. For example, if you set a breakpoint in the Created event procedure, creating a new folder in the "watched" folder should bring you back to your breakpoint. At this point, you can single-step through the code, as you would with any other application. Press F5 to continue running when you're finished with your testing.
- When you're finished debugging, detach the debugger from the running process using the Debug | Processes menu. Select the FileWatcher project in the list of debugged processes, then click the Detach button that's to the right of the list. Dismiss the dialog box.
- Use the Debug | Stop Debugging menu item to end the debugging session.
Single-stepping though your startup code, however, will take a bit more effort. The .NET SDK explains that the OnStart method must return within 30 seconds, or the SCM will time out. If you must start the service and attach to it in order to debug, how do you grab onto the process before the Start event has completed?
One solution is to cheat—that is, insert a pause into the OnStart procedure, using code like this (I'll leave it to anyone using Visual Basic to remove the semicolon when inserting the following code into their class):
This code causes the thread to sleep for 25 seconds. This is long enough for you to dig through the Debug | Processes menu item, find the process you want to debug, and attach to it, but not so long that the SCM times out. Once you've added this code, you can step through your OnStart procedure.
If you've dutifully followed the steps outlined in this walkthrough, you've successfully created a simple Windows service. You can use the information here as the basis for further experimentation with the creation and deployment of Windows services. There's much more to this topic, however, that you may want to dig into. You might also want to investigate the following tasks:
As with most aspects of .NET, as soon as you dig in a little, you find that there's much more to the topic than you might have imagined. Because .NET makes it so simple for you to create, install, and test your Windows services, you have power you could only dream of with previous tools.
- Managing the service's role—this example service assumed it was running with administrator privileges.
- Creating multithreaded services.
- Providing interaction with the Windows Installer, using the ProjectInstaller class.
- Adding a user interface to your service.
- Having your service process custom commands.
- Using the ServiceController object to manage service behavior.
- Using the EventLog object to manage reading and writing entries in the event log.
- Using properties of the FileSystemWatcher object to gain finer control over files and folders that you're watching.
|For related articles see:
Write a Simple Service Application
Introduction to Windows Service Applications
|Ken Getz is a senior consultant with MCW Technologies and splits his time between programming, writing, and training. Ken is coauthor of ASP.NET Jumpstart (Sams, 2002), Access 2002 Developer's Handbook (Sybex, 2001), and VBA Developer's Handbook, 2nd Edition (Sybex, 2001). Reach him at email@example.com.