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.
|
Related Reading Programming .NET Web Services |
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 CanPauseAndContinue
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
3).
|
| Figure 3. Adding the Installer |
7. Two installers will be added: ServiceProcessInstaller1
and 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 LocalSystem.
9. Right-click on ServiceInstaller1 and select Properties.
Set the StartType to Manual.
10. Finally, double-click on the design surface to reveal the two default
methods, OnStart() and OnStop() (see Figure
5).
|
Figure 5. The default OnStart() and OnStop() methods |
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 canStopListening and pause control
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
|
The 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 OnStart() event,
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 |
|
ServiceController Class Once a Windows Service is installed, you can control its state using
codes. In this section, I will illustrate controlling a Windows Service
using the 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 ServiceController
(see Figure 10).
|
Figure 10. Adding the ServiceController control |
3. Change the MachineName and ServiceName properties of ServiceController1
to the machine name and the name of the Windows Service to control,
respectively (see Figure 11).
|
Figure 11. Changing the MachineName and ServiceName properties |
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
the CanPauseAndContinue property.
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 ServiceController
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.