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 utilise, so let's create a sample service. Visual Studio .NET makes it simple to create a service using the code of choice:
- 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.
- A Windows Service project is created with the default project name of WindowsService1 and a class name of Service1.
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);
}
}
}
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).
Just the beginning
You now have a basic Windows Service, but you still need to install and run it on a Windows computer. Also, the code will start, but what if you want or need it to run continuously based on a type of trigger?
I'll cover these topics in part 2, in which I'll show you how to install, run and schedule the service.
I covered the inner workings of a Windows Service developed with the .NET Framework. This included a somewhat rudimentary Windows Service application with both VB.NET and C#. This week, we'll install and execute the Windows Service. (Note: This story contains the complete code for part one and part two.)
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
} }
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.