 .NET 1.1Creating a Windows Service
Windows services are background processes that usually have no direct interaction with the user interface. This article explains how to create a windows service with an example that monitors the file system for changes using a FileSystemWatcher object.
Windows Services
Windows services were introduced in Windows NT under the name, "NT Services". A service is simply a long-running executable program that runs in the background in its own Windows session. Services are therefore ideal for monitoring applications and for reacting to events. For example, in this article we will create a service that reacts to the creation, modification and deletion of files using the FileSystemWatcher class. Services can also be used for scheduled processes using timer events, though consideration should be given to using the standard scheduling tools provided by Windows in these cases.
Services do not usually include any graphical user interface elements. Although it is possible to display dialog boxes and other windows from a correctly configured service, this is usually inappropriate. One of the primary considerations is that services do not require an interactive user to be logged on to Windows. In fact, if a service is set to start automatically, it may be executing before the log on screen is displayed. For these reasons, direct user input is usually not included in a service and output is generally logged to the event log, a file or a database.
Service Accounts
As a service does not require that an interactive user be logged on, the service itself logs on to Windows separately. This allows a service to use a different account to that of any interactive user of the computer, potentially with different privileges. The user credentials for the service are first supplied when it is installed. The account may be any of the following:
- Standard User. Any user name and password may be provided to assign that user's permissions to the service. This option is usually used during development rather than for production systems. One problem is that if the user changes their password, the service will need to be reconfigured to enable it to log on.
- Local Service. The built-in local service account is specific to the local computer and has reduced privileges. This account presents anonymous credentials to any remote computer, restricting its scope for using network resources. This account is ideal for services that require limited or no access to network resources.
- Network Service. The network service account is built into Windows. It provides reduced privileges to the local computer. The network service account is does provide access to remote computers. This account is ideal for services that require substantial access to network resources.
- Local System. This built-in account provides full access to the local computer's resources, including control of the directory service if installed on a domain controller. This account should be used with care as, if used maliciously, it has the capability of accessing the entire domain.
Microsoft Management Console
Windows services are installed differently than normal applications. Whereas a standard executable program may have a shortcut in the Start menu or on the desktop, a service is controlled using the Services snap-in for the Microsoft Management Console (MMC). This is accessed using the Services option from the Administrative Tools section of the Control Panel.
In the Services window, the full list of installed services is displayed. These can be started and stopped or paused and resumed using the tools of the snap-in. By modifying a service's properties, it can be made to start automatically when the operating system is booted, manually by a user, or it can be disabled altogether. The service's user account and password may also be configured using this tool.
A full description of the Services snap-in is beyond the scope of this article. However, the basics of starting and stopping a service will be used later in the article once the example service is installed.
Creating a Service
In this section of this article we will create a Windows service using C# and Microsoft Visual Studio. The edition of Visual Studio is important, as we will be using the Windows Service template project. In some editions, such as Visual C# Express, this template is not available. This does not prevent you from creating a service in Visual C# Express or another, non-Microsoft development environment. However, the process is more complicated.
The service that we will create will perform a very simple function. On starting, a FileSystemWatcher object will be created and set to monitor the contents of a folder. Whenever a change is detected due to the creation, deletion, modification or renaming of a file in the folder, the update will be logged in the event log. In a real-world application, the service would probably be modified to perform some activity using the files. In this case, adding such functionality would only complicate the examples.
Creating the Project
To begin, we need to create a project in Visual Studio using the Windows Service template. Start Visual Studio and specify that you want to create a new project. You should see the option and icon for creating a Windows Service project. Select this option and name the project "FileMonitorService".
Once the new project is created, you will see that it contains a class file named "Service1". This is the class that contains the basic functionality of the service. If you double-click the file you will see that, rather than viewing the code immediately, a design window is displayed. In a similar manner to when creating Windows forms applications or components, you can drag components from the toolbox into this designer and use them with your service.
To view the code in the Service1 class, you can click the "click here to switch to code view" link on the designer's surface or right-click the file in the Solution Explorer and choose "View Code". You will see that the class file contains some methods. The actual code will vary according to the version of Visual Studio that you are using. However, it will include the following members:
- Constructor. A constructor will be defined and will make a call to InitializeComponent. This is required to enable the components that you add to the service using the designer.
- OnStart. This method is called when the service is started, either manually via the MMC or automatically when the operating system is loaded.
- OnStop. This method is called when the service is stopped.
You should also note that the Service1 class inherits from ServiceBase. This base class contains the methods required to correctly operate a Windows service, including OnStart and OnStop, which are overridden in the code.
Service1 is not a good name for the class. Rename the class file from "Service1.cs" to "Monitor.cs" and change the class definition to match the following. NB: If the current definition includes the "partial" keyword, leave this in place and just change the name of the class.
public class Monitor : ServiceBase
Depending upon the version of Visual Studio that you are using, the base class may have been fully qualified before we changed it. To ensure that the service will still compile, add the following using statement to the top of the code if it is not already present:
using System.ServiceProcess;
The service will still not be able to be compiled at this point, as the name of the constructor must match the name of the class. Modify the declaration of the constructor as follows, then check that the code will compile.
public Monitor()
NB: At this point you may find that the code does not compile because there is a reference to the Service1 class in the Main method. If this is the case, simply change the name from "Service1" to "Monitor" and try compiling again.
ServicesToRun = new ServiceBase[] { new Monitor() };
Initialising the Service
Any initialisation that is required would usually be added in the constructor, following the call to InitializeComponent. In the case of our simple example service, the only initialisation required is to ensure that an event source exists so that we can log events successfully. However, as the service may execute under a user account with limited privileges, it may not be possible to create the event source within the service. Additionally, on some operating systems it is not possible to create an event source unless the program is running as an administrator.
A typical solution to this problem is to create a separate program to create the event source and run this as an administrator before the service is used. This is explained in the article, "Logging Messages in Event Logs". If you do not wish to read that article now, simply create a new console application, add the following code to the Main method and run the program once.
string eventSource = "File Monitor Service";
System.Diagnostics.EventLog.CreateEventSource(eventSource, "Application");
NB: If you are using Microsoft Vista or a more recent operating system, you will need to compile the program and run it as an administrator.
Logging Events
Each time an event happens within the service, we will log a message in the event log. This logging will occur when the service is started, stopped, paused or resumed and when the FileSystemWatcher object captures a file change event. To simplify the code, we will create a single method that logs all events. First, ensure that the following using statement appears at the top of the code in Monitor.cs:
using System.Diagnostics;
Once the using statement is present, add the following method to the Monitor class. This method accepts a message as a string and adds it to the event log using the event source created earlier.
void LogEvent(string message)
{
string eventSource = "File Monitor Service";
EventLog.WriteEntry(eventSource, message);
}
Adding the OnStart Method
When a service is started, the OnStart method is called. This is where we will initialise our FileSystemWatcher object and set it to monitor a folder for changes. The code for this is quite simple and has already been explained in the article, "Detecting File Changes with FileSystemWatcher", so I will not describe it in detail again.
As the FileSystemWatcher object must exist beyond the scope of the OnStart method, we will begin by declaring it as a class-level variable. Add the following declaration within the class but outside of any method definition:
private FileSystemWatcher _watcher;
You may also need to add an extra using statement to the code to provide access to the FileSystemWatcher class:
using System.IO;
We can now initialise the FileSystemWatcher within the OnStart method. Much of this code could have been included in the constructor. However, I have included it in OnStart so that we can dispose of the object when stopping the service and recreate it each time the service is restarted. On completion of this step, we will also log an event.
Modify the OnStart method as follows to create the object, attach its events and begin monitoring.
protected override void OnStart(string[] args)
{
_watcher = new FileSystemWatcher();
_watcher.Path = @"c:\watched";
_watcher.Changed += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Created += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Deleted += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Renamed += new RenamedEventHandler(LogFileSystemRenaming);
_watcher.Error += new ErrorEventHandler(LogBufferError);
_watcher.EnableRaisingEvents = true;
LogEvent("Monitoring Started");
}
NB: Note that the Path property of the _watcher object is set to "c:\watched". This is the folder that will be monitored. If you wish to use this folder, create it now. If not, change the Path property to that of an existing folder to monitor for changes.
Adding the Logging Methods
The OnStart method creates a new FileSystemWatcher object and connects five of its events to three methods. These methods will use the information from the event arguments passed to them to create event log entries for file changes or log buffer overflow errors. To define these methods, add the following to the class:
void LogFileSystemChanges(object sender, FileSystemEventArgs e)
{
string log = string.Format("{0} | {1}", e.FullPath, e.ChangeType);
LogEvent(log);
}
void LogFileSystemRenaming(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}", e.FullPath, e.OldName);
LogEvent(log);
}
void LogBufferError(object sender, ErrorEventArgs e)
{
LogEvent("Buffer limit exceeded");
}
Adding the OnStop Method
When a service is stopped, the OnStop method is called. In the sample project, we will use this method to disable and dispose of the FileSystemWatcher object, freeing the resources it uses. We will also send a message to the event log. To do so, change the OnStop method as follows:
protected override void OnStop()
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
LogEvent("Monitoring Stopped");
}
Adding OnPause and OnContinue Methods
Often a Windows Service has the ability to be paused and resumed. This is useful if the start-up process for a service is slow or uses many resources. Although this is not true for our example, we will add pause and continue functionality for demonstration purposes.
When a new service is created, it does not support pausing and resuming by default. These options must be configured within the project before compiling. The simplest method to achieve this is to return to the designer for the service and set its properties.
Double-click the Monitor.cs file in the Solution Explorer to view the designer surface. If you cannot see its properties, right-click the grey background of the designer and choose Properties from the menu that appears. You should see a set of properties that determine which actions the service can use. To enable pausing and resuming, set the value of the CanPauseAndContinue property to True.
You should also see a property named, "AutoLog". When set to True, this property indicates that standard messages should be logged in response to the service events, such as starting and stopping. As we are logging our own messages for these activities, set the AutoLog property value to False. We can also set a friendlier name for the service by changing the ServiceName property to "File Monitor".
We can now add the code to react to pausing and continuing execution of the service. We do this by overriding methods from the ServiceBase class. Add the following to methods to the Monitor class:
protected override void OnPause()
{
_watcher.EnableRaisingEvents = false;
LogEvent("Monitoring Paused");
}
protected override void OnContinue()
{
_watcher.EnableRaisingEvents = true;
LogEvent("Monitoring Resumed");
}
Other Events
Windows services can react to two additional events, each with its own enabling property and overridable method. The OnShutdown method is called when the computer is being shut down completely. The OnPowerEvent method captures changes to the computer's power status, such as the machine being suspended. For the example service we will not implement these methods.
Adding an Installer
The basic code for the Windows service is now complete. However, at the moment it will not be possible to install the service so that it can be configured using the Microsoft Management Console. In order to permit this, we need to add installer components to the service. This can be achieved simply using Visual Studio.
Double-click the Monitor.cs file to redisplay the designer for the service. Right-click the grey background and select "Add Installer..." from the context-sensitive menu that appears. This will add two components to the designer; a ServiceInstaller and a ServiceProcessInstaller.
The two components allow the configuration of the service to be modified. We will start by changing the service process installer. Click the component named "serviceProcessInstaller1" to select it and view its properties. The important property is Account. This allows you to define the default account that will be used by the service when it is running. This can be a user account or a system account. If a user account is selected, a password will be required during installation. In this case we want the service to run with limited privileges so select "LocalService" from the drop-down list.
The service installer component can be used to set general information relating to the service. This information is registered on installation and appears in the Services window of the MMC.
Select the serviceInstaller1 component. Change the DisplayName property to "File Monitor Demonstration Service".
Installing the Service
With the code complete and the installer added, we can now install the service. First, compile the service. This will generate the executable file that will be installed.
The service will access the watched folder and the executable files of the service itself using the user account specified earlier. To ensure that this will be possible, check the security permissions of this folder and the service's files to ensure that they can be read by the local service account.
Services are installed using a command-line program named "InstallUtil". This utility is installed as a part of the .NET framework. The easiest way to obtain access to it is using the Visual Studio command prompt tool. This is a special version of the command prompt utility that has predefined paths configured to simplify using tools such as InstallUtil.
Open a new Visual Studio command prompt by selecting the following Start Menu options. Start, All Programs, Visual Studio, Visual Studio Tools, Visual Studio Command Prompt. NB: If you have the option, right-click the icon and run the program as an administrator. If you do not have this option, you must be an administrator to successfully complete the install.
Within the command prompt window, change the current folder to that of the compiled executable for the service. You can now install the service by executing the following command:
installutil FileMonitorService.exe
If the process is successful, open the Services utility from the Administrative Tools section of the Control Panel. The list of services should now include a new item named "File Monitor Demonstration Service". This is the descriptive name we specified earlier.
Starting the Service
You can now test the service. Using the Services window, try starting the service. You should find that a new event is logged and can be viewed using the Event Viewer. Try creating, modifying, renaming and deleting files to see further events logged. You can also pause the service to cease logging events temporarily, then resume to continue logging.
Uninstalling the Service
Once you have finished with the service, you will probably want to uninstall it. First, ensure the service is stopped. You can then remove it from the Services window by executing the following command at the Visual Studio command prompt:
installutil -u FileMonitorService.exe
|