A Windows Service is an application that does not have a user interface. It commonly runs without human intervention and can be automatically started when the computer starts up. Examples of Windows Services are the Internet Information Server (IIS), Telnet, and FTP. Windows Services were formerly known as NT Services. In this article, I will illustrate how to create a Windows Service using Visual Studio .NET 2003.
In this article, I will develop a simple Windows Service that returns the current date and time. I will use the network example as illustrated in my earlier article. When this Windows Service is started, the server will start listening for all incoming network connections at port 500. Regardless of what the client sends, the Windows Service will return the current date and time.
Let's now build our Windows Service:
1. Launch Visual Studio .NET 2003 and create a New Project.
2. Select the Project Types (Visual Basic Projects) and the templates (Windows Service).
3. Name the project TimeService (see Figure 1).
|Figure 1. Creating a new Windows Service project|
4. Right-click on the design surface and select Properties.
5. Change the
ServiceName property to TimeService. Also, set the
property to True (see Figure 2). You want to be able
to pause and resume the Windows Service after you have started it.
|Figure 2. Naming the Windows Service|
6. Right-click on the design surface and select Add Installer (see Figure
|Figure 3. Adding the Installer|
7. Two installers will be added:
ServiceInstaller1 (see Figure 4). These two controls
are used by the
InstallUtil.exe utility when installing the Windows Service (more on this in the next section).
|Figure 4. Adding the two Installer controls|
8. Right-click on
ServiceProcessInstaller1 and select Properties.
Set the Account property to
9. Right-click on
ServiceInstaller1 and select Properties.
10. Finally, double-click on the design surface to reveal the two default methods,
OnStop() (see Figure
|Figure 5. The default
Now for the coding. First, import the following namespaces:
Imports System.Net.Sockets Imports System.Net Imports System.Text Imports System.Threading
You have also need to declare the following global variables:
Dim t1 As New Thread(AddressOf Listen) Dim canStopListening As Boolean = False Dim pause As Boolean = False Dim log As New System.Diagnostics.EventLog
I have a subroutine named
Listen() that repeatedly listens for incoming
network requests. When a client is connected, it will send the current
date and time. The global variables
the status of the loop.
Private Sub Listen() Const portNo As Integer = 500 Dim localAdd As System.Net.IPAddress = _ IPAddress.Parse("127.0.0.1") Dim listener As New TcpListener(localAdd, portNo) listener.Start() Do If Not pause Then Dim tcpClient As TcpClient = listener.AcceptTcpClient() Dim NWStream As NetworkStream = tcpClient.GetStream Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte '---read incoming stream Dim numBytesRead As Integer = _ NWStream.Read(bytesToRead, 0, _ CInt(tcpClient.ReceiveBufferSize)) '---write to event log log.WriteEntry("Received :" & _ Encoding.ASCII.GetString(bytesToRead, _ 0, numBytesRead) & " @ " & Now) '---write time back to client Dim time() As Byte = _ Encoding.ASCII.GetBytes(Now.ToString) NWStream.Write(time, 0, time.Length) tcpClient.Close() End If Loop Until canStopListening listener.Stop() End Sub
OnStart() event is invoked when the Windows Service is first started.
And in this event, I have spun off the
Listen() subroutine as a separate
thread. I then log a message indicating that the service has started,
using the event log. Using the event log is a good way to help debug
your Windows Service. Debugging Windows Services is not a trivial task
-- Windows Services run in the background and within the context of Services
Control Manager (and not within Visual Studio .NET) -- you cannot
debug Windows Services within Visual Studio .NET. Thus, a good way to
debug Windows Services is to use the event log for logging events.
Protected Overrides Sub OnStart(ByVal args() As String) t1.Start() Me.AutoLog = False If Not EventLog.SourceExists("Time Service Log") Then EventLog.CreateEventSource("Time Service Log", "MyLogFile") End If log.Source = "Time Service Log" log.WriteEntry("Service Started") End Sub
Note that you cannot have a infinite loop within the
as control needs to be returned to operating system when the service
is started. Hence I have spun off a thread (which runs continuously) to perform the actual reading
of incoming data.
The rest of the events to service are straightforward:
Protected Overrides Sub OnStop() ' Invoked when the service is stopped canStopListening = True log.WriteEntry("Service Stopped") End Sub Protected Overrides Sub OnPause() ' Invoked when the service is paused pause = True log.WriteEntry("Service Paused") End Sub Protected Overrides Sub OnContinue() ' Invoked when the service is continued pause = False log.WriteEntry("Service Continued") End Sub
Once the coding is done, you can build the Windows Service by clicking on Build->Build Solution.
Once the project is built, you need to install the Windows Service.
The .NET framework provides the
Installutil.exe utility. To install
the Windows Service that you have just built, launch the Visual Studio
.NET Command Prompt window:
1. Navigate to the directory containing the project files and type in the following:
C:\...\Windows Services\TimeService\bin>installutil TimeService.exe
2. If the installation is successful, you should see something like the following screen (Figure 6):
|Figure 6. Installing the TimeService Windows Service|
Note that if you need to make changes to your Windows Service after
it has been installed, you need to uninstall it first before you build
the new version. You would also need to stop the service, if it has
been started (more on this in the next section). To uninstall the installed
Windows Service, use the
/u option, like this:
installutil /u TimeService.exe
Once the service is successfully installed, you need to start the service. You can use the following commands in the Command Window:
C:\...\Windows Services\TimeService\bin>net start TimeService The TimeService service is starting. The TimeService service was started successfully.
Alternatively, you can also start the service using the Services utility found within the Administrative Tools grouping in Control Panel (see Figure 7).
|Figure 7. Locating the TimeService in the Services utility|
To test the TimeService, you will use the client example illustrated in my previous article. I have made some modifications here:
Imports System.Text Imports System.Net Imports System.Net.Sockets Module Module1 Sub Main() Const portNo = 500 Const textToSend = "anything" Dim tcpclient As New System.Net.Sockets.TcpClient tcpclient.Connect("127.0.0.1", portNo) Dim NWStream As NetworkStream = tcpclient.GetStream Dim bytesToSend As Byte() = Encoding.ASCII.GetBytes(textToSend) '---send the text Console.WriteLine("Sending : " & textToSend) NWStream.Write(bytesToSend, 0, bytesToSend.Length) '---read back the text Dim bytesToRead(tcpclient.ReceiveBufferSize) As Byte Dim numBytesRead = NWStream.Read(bytesToRead, 0, _ tcpclient.ReceiveBufferSize) Console.WriteLine("Received : " & _ Encoding.ASCII.GetString(bytesToRead, 0, _ numBytesRead)) Console.ReadLine() tcpclient.Close() End Sub End Module
Basically, the client will send a message to the server hosting the TimeService Windows Service (which is the local host, anyway) and read the date and time returned by the service. If your Windows Service is installed and started correctly, you should see the result shown in Figure 8 when you run the client application.
|Figure 8. Running the client|
During the lifetime of the TimeService Windows Service, it will log all sorts of information into the event log (as evident in our code). You can view the event log by going to Control Panel->Administrative Tools->Event Viewer.
Look under the MyLogFile item and you should see a list of event messages (see Figure 9):
|Figure 9. Viewing the event log|
Once a Windows Service is installed, you can control its state using
codes. In this section, I will illustrate controlling a Windows Service
ServiceController class. The
ServiceController class allows
a Windows Service to be started, stopped, paused, or continued.
1. Create a Windows application and name it WinAppServiceController.
2. On the toolbox, click on the Components tab and double-click on
(see Figure 10).
|Figure 10. Adding the
3. Change the
ServiceName properties of
to the machine name and the name of the Windows Service to control,
respectively (see Figure 11).
|Figure 11. Changing the
4. Add the following buttons to your Windows Form (see Figure 12):
|Figure 12. Creating the Windows application|
5. Key in the codes shown in bold below:
Private Sub Button1_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button1.Click ServiceController1.Start() ' for the Start button End Sub Private Sub Button2_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button2.Click ServiceController1.Pause() ' for the Pause button End Sub Private Sub Button3_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button3.Click ServiceController1.Stop() ' for the Stop button End Sub Private Sub Button4_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button4.Click ' for the Check Status button ServiceController1.Refresh() MsgBox(ServiceController1.Status.ToString) End Sub Private Sub Button9_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button9.Click ServiceController1.Continue() ' for the continue button End Sub
That's about it. Press F5 to test your application.
Note that some Windows Services do not have an
onPause() method. Therefore,
before a service is paused, it is advisable for you to check whether
it can be paused, or a runtime error will occur. You can do so using
If ServiceController1.CanPauseAndContinue Then ServiceController1.Pause() ' for the Pause button Else MsgBox("Service cannot be paused.") End If
Besides using the
ServiceController control from the toolbox, the
can also be invoked from code, as the following code shows:
Dim controller As New System.ServiceProcess.ServiceController("TimeService")
Besides controlling a single Windows Service, you can also retrieve the list of Windows Services running on your system. In this section, I will extend the Windows application built in the previous section to get a list of all of the Windows Services installed on your system and to be able to start, stop, or pause the services.
1. Using the application built earlier, extend the Windows form to incorporate the following Button, Label, and ListBox controls (see Figure 13).
|Figure 13. Extending the Windows application|
2. Type in the following code:
Private Sub Button6_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button6.Click '---for the Start button--- Dim controller As New _ System.ServiceProcess.ServiceController(ListBox1.SelectedItem) controller.Start() End Sub Private Sub Button7_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button7.Click '---for the Pause button--- Dim controller As New _ System.ServiceProcess.ServiceController(ListBox1.SelectedItem) If controller.CanPauseAndContinue Then controller.Pause() Else MsgBox("Service cannot be paused") End If End Sub Private Sub Button8_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button8.Click '---for the Stop button--- Dim controller As New _ System.ServiceProcess.ServiceController(ListBox1.SelectedItem) controller.Stop() End Sub Private Sub Button5_Click_1(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles Button5.Click '---for the Get Service button--- Dim controller As New System.ServiceProcess.ServiceController() Dim services() As System.ServiceProcess.ServiceController Dim i As Integer services = controller.GetServices() For i = 0 To services.Length - 1 Me.ListBox1.Items.Add(services(i).DisplayName) Next End Sub Private Sub ListBox1_SelectedIndexChanged_1(ByVal sender As _ System.Object, ByVal e As System.EventArgs) _ Handles ListBox1.SelectedIndexChanged '---when the listbox is clicked--- Dim controller As New _ System.ServiceProcess.ServiceController(ListBox1.SelectedItem) Label1.Text = ListBox1.SelectedItem & " - " & _ controller.Status.ToString End Sub
To test the application, press F5. You should see something like Figure 14. Click on the Start, Pause, and Stop buttons to change the state of the services.
|Figure 14. Getting the list of Windows Services running on the current system|
Writing Windows Services is now much easier using Visual Studio .NET. However, debugging Windows Services is still a pretty challenging task. For that, I suggest that you test out your code in a Windows application and ascertain that it works before porting it to a Windows Service application. Have fun!
Wei-Meng Lee (Microsoft MVP) http://weimenglee.blogspot.com is a technologist and founder of Developer Learning Solutions http://www.developerlearningsolutions.com, a technology company specializing in hands-on training on the latest Microsoft technologies.
Return to ONDotnet.com
Copyright © 2009 O'Reilly Media, Inc.