DevCity.NET - http://devcity.net
Windows Services for VB.NET Developers
http://devcity.net/Articles/190/1/article.aspx
Scott Rutherford
Scott Rutherford is a consultant and .NET developer who has developed Enterprise business applications in many fields including Restaurant, Market Research, Automotive, and Analytical Chemistry. He is an Microsoft Certified Professional with the .NET Platform. 
by Scott Rutherford
Published on 12/20/2005
 
Learn about Windows Services, how they work, and what they are for.  Build your first Windows Service and end up with a reusable template with which to create many more.  Tips for use, development, and deployment of a Windows Service.

Introduction

A Windows Service is an application that runs outside of any desktop session. It can run automatically when Windows starts up, before any users log in. Alternatively, it can be started up by some other app via the Windows Service Control Manager (SCM - pronounced "scum"). Not to be confused with a Web Service, which runs a particular protocol and serves up XML to remote machines, a Windows Service is a basic building block of the Windows operating system. An out-of-the-box install of Windows XP has more than 80 Windows Services installed (necessary?). Outside of a Microsoft environment, this type of program is commonly referred to as a ‘daemon’, or more accurately, a ‘dragon’ (which appeals far more to the author than any other terminology). It runs in a security context independent of any logged on user—configured in the Services Manager (right-click My Computer, select Manage, expand Services and Applications, then Services) where you’ll also find its start/stop controls.

Visual Studio .NET makes it very simple to create and install Windows Services: just select the Windows Service template when starting a new project. In this article you’ll see the (very little) code needed to make this service work and all the Administrative tasks you’ll need to perform to get it running. You’ll build a project called “Empty Service” that can be used as a template to build all kinds of Windows Services. You then just add your own business class to perform the activity you need.

A Windows Service exposes two main commands: Start and Stop. It begins work when it receives a Start command from the SCM. It is therefore in the OnStart method that you will put code to execute whatever work it is you want the service to perform. The Service can support other functions optionally, such as Pause and Continue, or Custom Commands. These too respond to controls received from the SCM. You want to avoid any desktop interaction since your service will run in a security session unattached to any particular user session (e.g. no message boxes, prompts, etc.).

As the Start command is only called once, the Service itself must implement a mechanism to periodically “wake the dragon”. The simplest way to do this is to drop a Timer component on the Service designer, and call its Start and Stop methods from the OnStart and OnStop methods of your Service. However, this gives you little control over process creation and you end up with many threads running at a time. This article shows you how to implement a single “worker” thread which executes in a loop only when the previous iteration is complete—hence program overlap is avoided.

 

Start the Project and Set Visual Properties (Visual Studio Designer)

To get started, you'll need to create a Visual Studio .NET Project for your new Service. Select File | New Project from Visual Studio, then select Windows Service from the appropriate templates window (Note: in VB Standard edition this template is not available. However, the code listed in this article will still compile) – the name of your project will become the name of your executable but not necessarily the service. Once the project is started, right-click anywhere in the designer and select “Add Installer”. This adds a Project Installer file to your project with two components called during Service installation to configure Registry keys that affect your Windows Service. You should set all of the following properties of the ServiceInstaller and ServiceProcessInstaller components:

    ServiceInstaller.ServiceName:
    This is the registered name of the service. This name is used to start and stop the service from the command line (e.g. NET START EmptyService).

    ServiceInstaller.DisplayName:
    This is the long name of the service that will appear in the “Name” column of the Windows Service Manager dialog.

    ServiceInstaller.StartType:
    This is the default state of the service. Disabled means the service will not start, manual means that it can be started, and automatic means that it will start when the OS boots up. This can be changed administratively in the Service Manager dialog.

    ServiceProcessInstaller.Account:
    This is account under whose security context the Service will run. This can be changed administratively on the “Log On” tab of the Service Manager dialog. Setting this property to “User” will cause a prompt for username/password when the service is installed (Alternatively, ServiceProcessInstaller.Username and Password properties can be set from the code-behind for the Project Installer).

There are several other properties you may consider setting at this point.

    ServiceInstaller.ServicesDependedOn:
    This requires other services to be running before this service will start, and causes this service to stop if they stop.

    Description
    This is only available with .NET Fx 2.0 (although a low-level hack is available), sets the full description displayed in the Service Manager dialog.

The following properties of the Service itself are available from the Designer as well:

    AutoLog:
    When set to True, the service will automatically report Start, Stop, Pause and Continue commands to the Windows Application Event Log (custom commands are also logged).

    CanStop:
    Some OS services do not accept stop commands, and for these CanStop is False. In most cases, you want to accept a Stop command. If you do not implement OnStop method (see below) the method of the base class will be used.

    CanPauseAndContinue:
    If you want to enable pausing for your service, you must set this property True and implement the OnPause/OnContinue methods (see below). If this property is False, these methods do not get called. Pausing should allow processing to stop without full take-down and cleanup of Service resources.

 

Coding the body

We are now ready to add programming to the service to make it do something.

    The Service Class (Code-behind)

    The Service class (e.g. EmptyService) will receive the Start and Stop commands for the service and must therefore implement OnStart() and OnStop(), which both override methods in the base ServiceProcess class. When you implement the more simple Service, with a Timer control, you would set the Enabled property of the Timer to True in OnStart() and set it to False in OnStop(). For our example, we will use these start/stop event handlers to create and destroy threads which will run the meat of our program.

    The next section of the article will discuss the contents of the Worker class. For now, we will create a private member of the Service class of type Worker. This is a singleton object that attaches to the main running program thread and performs work. In the OnStart() event handler, a new thread is created, with its start-point set to the Worker object’s main method (called “DoWork()”). Then the thread is started. In the OnStop() event handler, the worker object is torn down by first calling its StopWork() method and then freeing any extra objects used by the service.

    Windows Service processes are started by the SCM, and hence their working directory is that of the system, by default. An optional step here is to move the working directory of the service to its program directory. I always do this to simplify program configuration inputs, logging outputs, etc.

    You can also implement OnPause(), and OnContinue() here. Remember these will never get called if you do not set the CanPauseAndContinue property of the Service. These handlers have no arguments, and should look very similar to the OnStart()/OnStop(), except there should be no building or tearing down of objects.

    Custom commands are out of the scope of this article. However, the OnCustomCommand() event handler is available to extend your service to respond to custom events that you may have other programs execute via the SCM. Use this method to add other interactions to your service (http://msdn2.microsoft.com/en-us/library/system.serviceprocess.servicebase.oncustomcommand.aspx).

    [Visual Basic]
    Imports System.ServiceProcess
    Public Class EmptyService
        Inherits System.ServiceProcess.ServiceBase
        Private worker As New worker()
        Protected Overrides Sub OnStart(ByVal args() As String)
            System.IO.Directory.SetCurrentDirectory(GetMyDir)
            Dim wt As System.Threading.Thread
            Dim ts As System.Threading.ThreadStart
            ts = AddressOf worker.DoWork
            wt = New System.Threading.Thread(ts)
            wt.Start()
        End Sub
        Protected Overrides Sub OnStop()
            worker.StopWork()
        End Sub
        Function GetMyDir() As String
            Dim fi As System.IO.FileInfo
            Dim di As System.IO.DirectoryInfo
            Dim pc As System.Diagnostics.Process
            Try
                pc = System.Diagnostics.Process.GetCurrentProcess
                fi = New System.IO.FileInfo(pc.MainModule.FileName)
                di = fi.Directory
                GetMyDir = di.FullName
            Finally
                fi = Nothing
                di = Nothing
                pc = Nothing
            End Try
        End Function
    End Class
    

    The Worker Class

    The Worker class controls the thread in which all program processing will occur. It is called when a new thread is started by the Service class, and as mentioned previously, its entry point is the DoWork() method–this is where processing will begin. When DoWork() is called, it attaches to the current thread, gives it a name, and then starts a loop in which processing occurs. For this example, the only processing is to write an entry to the Windows Event Log, sleep, and then write another.

    [Visual Basic]
    Public Class Worker
        Private m_thMain As System.Threading.Thread
        Private m_booMustStop As Boolean = False
        Private m_rndGen As New Random(Now.Millisecond)
        Public Sub StopWork()
            m_booMustStop = True
            If Not m_thMain Is Nothing Then
                If Not m_thMain.Join(100) Then
                    m_thMain.Abort()
                End If
            End If
        End Sub
        Public Sub DoWork()
            m_thMain = System.Threading.Thread.CurrentThread
            Dim i As Integer = m_rndGen.Next
            m_thMain.Name = "Thread" & i.ToString
            While Not m_booMustStop
                System.Diagnostics.EventLog.WriteEntry("EmptyService", "Start work: " & m_thMain.Name)
                System.Threading.Thread.Sleep(10000)
                System.Diagnostics.EventLog.WriteEntry("EmptyService", "Finish work: " & m_thMain.Name)
            End While
        End Sub
    End Class
    

 

Deployment

Once your Service and Worker classes are complete, compile the project to get your service EXE.

To install the service on a PC you can use the .NET frameworks tool InstallUtil.exe, found in the frameworks system directory.

You can copy the code listed here into a BAT file for simple DOS installation.

To uninstall just put a –U flag after the InstallUtil.exe call
(i.e. %FIRSTPART%%DOTNETVER%%SECONDPART% -U %PROG%).

[DOS]
@echo off
SET PROG="C:\EmptyService.exe"
SET FIRSTPART=%WINDIR%"\Microsoft.NET\Framework\v"
SET SECONDPART="\InstallUtil.exe"
SET DOTNETVER=2.0.50727
  IF EXIST %FIRSTPART%%DOTNETVER%%SECONDPART% GOTO install
SET DOTNETVER=1.1.4322
  IF EXIST %FIRSTPART%%DOTNETVER%%SECONDPART% GOTO install
SET DOTNETVER=1.0.3705
  IF EXIST %FIRSTPART%%DOTNETVER%%SECONDPART% GOTO install
GOTO fail
:install
  ECHO Found .NET Framework version %DOTNETVER%
  ECHO Installing service %PROG%
  %FIRSTPART%%DOTNETVER%%SECONDPART% %PROG%
  GOTO end
:fail
  echo FAILURE -- Could not find .NET Framework install
:end
  ECHO DONE!!!
  Pause

Some deployment hints:
  • Elevate NTFS on the service EXE file to ensure it is not replaced by another similar named exe which will now run under the configured security context.
  • To avoid a reboot after install/uninstall unlock resources used by your service, which includes closing files, closing the Windows Service Manager, etc. Sometimes adding an IISRESET line to the install script is helpful.

Now your service is installed. Start it and Stop it from the Service Manager and use the Event Log Viewer to view its output.