ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Developing Windows Services

by Wei-Meng Lee
08/18/2003

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.

Developing the TimeService Windows Service

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.

Programming .NET Web Services

Related Reading

Programming .NET Web Services
By Alex Ferrara, Matthew MacDonald

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
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
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
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
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
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.

Installing the Service

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
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 

Starting the Service

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
Figure 7. Locating the TimeService in the Services utility

Testing the Service

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
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
Figure 9. Viewing the event log

Controlling Windows Services using the 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
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
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
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")

Retrieving the List of Windows Services Running on Your System

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
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
Figure 14. Getting the list of Windows Services running on the current system

Summary

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.