Microsoft

Automate routine tasks with .NET's Windows Services

.NET Framework's Windows Services is an excellent application choice when working with administrative tasks or automating routine tasks. See how it allows you to program and schedule system tasks.

I know very few application developers who spend 100 percent of their time with actual programming work. We often have to devote a portion of our time to system maintenance, as well as the usual array of meetings. Find out how the .NET Framework's Windows Services (formerly known as NT services) allows you to program and schedule system tasks.

Welcome to the world of Windows Services

In the past, you had to be proficient in C++ to develop a Windows Service. Thankfully, the .NET platform opens the process to all .NET developers—albeit C#, VB.NET, J#, and so forth. These services can be automatically started when the computer boots, can be paused and restarted, and do not show any user interface.

You can create a service as a Microsoft Visual Studio .NET project, defining code within it that controls what commands are sent to the service and what actions should be taken when those commands are received. Commands sent to a service include starting, pausing, resuming, and stopping the service, and executing custom commands.

After you create and build the application, you can install it by running the command-line utility InstallUtil.exe and passing the path to the service's executable file, or by using Visual Studio's deployment features. You can then use the Services Control Manager to start, stop, pause, resume, and configure your service. Let's take a closer look at Windows Service classes.

Windows Service classes

You can create Windows Services with the help of the ServiceBase class in the System.ServiceProcess namespace. This class contains the following subset of events that interact with the Windows Service Control Manager (SCM):

  • OnContinue: Fires when a continue command is sent to the service by the SCM. It specifies actions to take when a service resumes normal functioning after being paused.
  • OnPause: Fires when a pause command is sent to the service by the SCM. It specifies actions to take when a service pauses.
  • OnPowerEvent: Fires when the computer's power status has changed. This applies to laptop computers when they go into suspended mode, which isn't the same as a system shutdown.
  • OnShutdown: Fires when the system is shutting down. It specifies what should happen immediately prior to the system shutting down.
  • OnStart: Fires when a start command is sent to the service by the SCM or when the operating system starts (for a service that starts automatically). It specifies actions to take when the service starts. An array of String objects is the event's lone parameter.
  • OnStop: Fires when a stop command is sent to the service by the SCM. It specifies actions to take when a service stops running.

Only the OnStart event accepts any type of parameter. The main starting point of a Windows Service execution is the Main method, which accepts no parameters. Now you're ready to create your own service.

Creating a Windows Service

At this point, you know which class and events to utilize, so let's create a sample service. Visual Studio .NET makes it simple to create a service using the code of choice:

  1. To create a new Windows Service, pick the Windows Service option from your Visual Studio Projects (choose appropriate language), give your service a name, and click OK.
  2. A Windows Service project is created with the default project name of WindowsService1 and a class name of Service1.

Now, you can add your own code to the skeleton Visual Studio .NET creates. The next C# code listing includes the code skeleton Visual Studio creates, along with custom code to write information to the Windows Event Log when the associated events are fired. In addition, a constructor (which is the same as class name), dispose, and the point of execution (i.e., the main method) are included as well:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
namespace WindowsService1 {
public class Service1 : System.ServiceProcess.ServiceBase {
private EventLog log = null;
private System.ComponentModel.Container components = null;
public Service1() {
InitializeComponent();
}
static void Main() {
System.ServiceProcess.ServiceBase[] ServicesToRun;
ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service1() };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
private void InitializeComponent() {
components = new System.ComponentModel.Container();
this.ServiceName = "Service1";
}
protected override void Dispose( bool disposing ) {
if( disposing ) {
if (components != null) {
components.Dispose();
} }
base.Dispose( disposing );
log.WriteEntry("Service dispose", EventLogEntryType.Information);
log.Close();
}
protected override void OnStart(string[] args) {
log = new EventLog("Builder.com");
log.WriteEntry("Service starting", EventLogEntryType.Information);
}
protected override void OnStop() {
log.WriteEntry("Service stopping.", EventLogEntryType.Information);
} } }

The equivalent VB.NET code follows:

imports System.Diagnostics
Imports System.ServiceProcess
Public Class Service1
Inherits System.ServiceProcess.ServiceBase
Private log As EventLog
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If (disposing) Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
log.WriteEntry("Service dispose", EventLogEntryType.Information)
log.Close()
MyBase.Dispose(disposing)
End Sub
<MTAThread()> _
Shared Sub Main()
Dim ServicesToRun() As System.ServiceProcess.ServiceBase
ServicesToRun = New System.ServiceProcess.ServiceBase() {New Service1}
System.ServiceProcess.ServiceBase.Run(ServicesToRun)
End Sub
Private components As System.ComponentModel.IContainer
Private Sub InitializeComponent()
components = New System.ComponentModel.Container
Me.ServiceName = "Service1"
End Sub
Protected Overrides Sub OnStart(ByVal args() As String)
log = New EventLog("Builder.com")
log.WriteEntry("Service Starting", EventLogEntryType.Information)
End Sub
Protected Overrides Sub OnStop()
log.WriteEntry("Service Stopping", EventLogEntryType.Information)
End Sub
End Class

Notice that the code execution entry point (Main method) creates a new instance of the ServiceBase class (an object array) and adds an instance of your class to it. The use of the array demonstrates the ability to run more than one process within the service (notice the thread marker within the VB.NET code).

Windows service installation

While console, Windows Form, and ASP.NET applications are easy to install by copying files, you must explicitly install Windows Service applications. Visual Studio .NET simplifies the installation process for Windows server applications. It displays a link in the Properties window of the Windows Service called Add Installer. When you select the link, the IDE adds the necessary installer classes to your project as part of a separate module.

This includes two classes: System.ServiceProcess.ServiceProcessInstaller and System.ServiceProcess.ServiceInstaller. Several properties may be set using the Properties window of each class. The most important property is Account within the ServiceProcessInstaller class. It specifies the Windows account under which the service runs (security context). The following options are available:

  • LocalService: Service has extensive local privileges and presents the computer's credentials to remote servers.
  • LocalSystem: Service has limited local privileges and presents anonymous credentials to remote servers.
  • NetworkService: Service has limited local privileges and presents the computer's credentials to remote servers.
  • User: A local or network account is specified. You may specify the necessary username and password via properties, or you may type them during installation. The Service uses the security context of the specified user account.

When an installer is added to the Service, the ProjectInstaller class is added. Defining properties via each class's Properties window results in an associated line of code in the corresponding ProjectInstaller class. The following code shows a sample installer for our C#-based Windows service with various properties defined, including the Account set to LocalService and ServiceName set to BuilderService.

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
namespace WindowsService1 {
[RunInstaller(true)]
public class ProjectInstaller : System.Configuration.Install.Installer {
public System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller testInstaller;
private System.ComponentModel.Container components = null;
public ProjectInstaller() {
InitializeComponent();
}
protected override void Dispose( bool disposing ) {
if(disposing) {
if(components != null) {
components.Dispose();
} }
base.Dispose( disposing );
}
#region Component Designer generated code
private void InitializeComponent() {
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.testInstaller = new System.ServiceProcess.ServiceInstaller();
this.serviceProcessInstaller1.Account =
System.ServiceProcess.ServiceAccount.LocalService;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
this.testInstaller.DisplayName = "Test Installer";
this.testInstaller.ServiceName = "BuilderService";
this.testInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
this.Installers.AddRange(
new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.testInstaller});
}
#endregion
} }

The corresponding VB.NET code follows:

Imports System.ComponentModel
Imports System.Configuration.Install
<RunInstaller(True)> Public Class ProjectInstaller
Inherits System.Configuration.Install.Installer
#Region " Component Designer generated code "
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
Private components As System.ComponentModel.IContainer
Public WithEvents ServiceProcessInstaller1 As System.ServiceProcess.ServiceProcessInstaller
Public WithEvents ServiceInstaller1 As System.ServiceProcess.ServiceInstaller
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.ServiceProcessInstaller1 = New System.ServiceProcess.ServiceProcessInstaller
Me.ServiceInstaller1 = New System.ServiceProcess.ServiceInstaller
Me.ServiceProcessInstaller1.Account =
System.ServiceProcess.ServiceAccount.LocalService
Me.ServiceProcessInstaller1.Password = Nothing
Me.ServiceProcessInstaller1.Username = Nothing
Me.ServiceInstaller1.DisplayName = "Test Service"
Me.ServiceInstaller1.ServiceName = "BuilderService"
Me.ServiceInstaller1.StartType =
System.ServiceProcess.ServiceStartMode.Automatic
Me.Installers.AddRange(New System.Configuration.Install.Installer()
{Me.ServiceProcessInstaller1, Me.ServiceInstaller1})
End Sub
#End Region
End Class

With the installers added to the project, you may now install the Windows Service and run it on the target computer. Another approach within our project is how the service is started. There are three options:

  • Manual: The user starts the Service.
  • Disabled: The Service isn't available for use.
  • Automatic: The Service starts automatically when the host computer starts.

In our example, I have the Service start automatically. With the Service properly compiled, we install it on the target computer via the Microsoft .NET Framework Installation utility (InstallUtil.exe) command-line tool. It allows you to easily install and uninstall our service. The compiled file (executable) or assembly is passed to it. Also, it provides the following command-line switches:

  • /LogFile=[filename] - Contains the log indicating installation success/failure. The default is the AssemblyName.InstallLog.
  • /LogToConsole=(true|false) - Signals whether console output is enabled.
  • /ShowCallStack - Signals whether call stack is displayed if an exception is encountered.
  • /u - Uninstalls the Service.

With our sample Service, the following line takes care of the installation:

InstallUtil WindowsService.exe

The line is run in the directory containing the assembly; otherwise, it would require the complete path to the assembly. After you install the new Service, it's located in the Services window of the Computer Management applet.

Triggering execution

Once you properly install the Service, it's triggered based upon the Service setting. Unfortunately, it's only executed when loaded or run by a user. You may augment this by adding a timer to the code, so the agent executes on a scheduled basis. The next VB.NET code listing shows our Service with a Timer object added. The timer is started when the Service starts, and it stops when the Service stops.

Imports System.Diagnostics
Imports System.ServiceProcess
Imports System.Timers
Public Class Service1
Inherits System.ServiceProcess.ServiceBase
Private log As EventLog
Private t As Timer
Public Sub New()
MyBase.New()
InitializeComponent()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
log.WriteEntry("Service dispose", EventLogEntryType.Information)
log.Close()
MyBase.Dispose(disposing)
End Sub
<MTAThread()> _
Shared Sub Main()
Dim ServicesToRun() As System.ServiceProcess.ServiceBase
ServicesToRun = New System.ServiceProcess.ServiceBase() {New Service1}
System.ServiceProcess.ServiceBase.Run(ServicesToRun)
End Sub
Private components As System.ComponentModel.IContainer
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container
Me.ServiceName = "Service1"
End Sub
Protected Overrides Sub OnStart(ByVal args() As String)
log = New EventLog("Builder.com")
log.WriteEntry("Service Starting", EventLogEntryType.Information)
t = New Timer(3600000)
AddHandler t.Elapsed, AddressOf TimerFired
With t
.AutoReset = True
.Enabled = True
.Start()
End With
End Sub
Protected Overrides Sub OnStop()
log.WriteEntry("Service Stopping", EventLogEntryType.Information)
t.Stop()
t.Dispose()
End Sub
Private Sub TimerFired(ByVal sender As Object, ByVal e As ElapsedEventArgs)
' Deal with firing
End Sub
End Class.

Use the right tool

A Windows Service is an excellent application choice when working with administrative tasks or automating routine tasks. Add it to your toolbox and use it when the situation arises.

TechRepublic's free .NET newsletter, delivered each Wednesday, contains useful tips and coding examples on topics such as Web services, ASP.NET, ADO.NET, and Visual Studio .NET. Automatically sign up today!

About Tony Patton

Tony Patton has worn many hats over his 15+ years in the IT industry while witnessing many technologies come and go. He currently focuses on .NET and Web Development while trying to grasp the many facets of supporting such technologies in a productio...

Editor's Picks