Creating a simple, reusable Windows Service (template code) - Part I
In this article, we'll see a way to create a simple Windows Service that can be reusable in that it can be used as the basis for any repeated operation that we would like to transform to a Windows Service.In other words, we can create our own assembly and then have the Windows Service call its entry point repeatedly over a period of time. By extending the code provided, you can even have multiple threads running at the same time.
Creating our service
First, go to Visual Studio -> New Project -> Windows Service
Visual Studio creates a very simple template for our Windows Service, that's far from working. Let's see what we can do:
First, we need a service name. That is the name by which our service will be known.
Let's go to the Region named "Component Desired Generated Code" and find the following function:
' NOTE: The following procedure is required by the Component Designer
' It can be modified using the Component Designer.
' Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
Me.ServiceName = "Service1"
End Sub
Okay, so you've got to change the string assigned to Me.ServiceName to something of your own, let's say "MyService". The name you will put here is the name you will be using with the NET START, NET STOP etc. commands from the command line.
I won't go into any further details about the generated code. I will only say that you can alter some other things as well like whether the service will have the right to pause or continue, and whether it can be shut down. To do this, add code to the InitializeComponent() function like this:
Me.CanPauseAndContinue = True
Me.CanShutdown = True
Defining basic members and properties
Okay. Before we continue, let's create a module with a global enumeration. We will use this as flags to keep track of our service's state later on. Don't hit me hard on the module stuff, of course you can incorporate this into your own code, no need to follow my approach here.
Here's the code:
Option Explicit On
Option Strict On
'/// <summary>
'/// Global definitions and settings.
'/// </summary>
Module mdlGlobals
'/// <summary>
'/// Service state enumeration
'/// </summary>
Public Enum ServiceModeEnum
MODE_STOP = 0
MODE_RUN = 1
MODE_PAUSE = 2
MODE_SHUTDOWN = 3
End Enum
End Module
Now we need to define a section where we will keep our status and other information. Lets's create a region in our original service code, like the following:.
#Region " Shared Attributes "
'/// <summary>
'/// Current service status.
'/// </summary>
Private Shared s_enumMode As mdlGlobals.ServiceModeEnum
'/// <summary>
'/// Master service thread.
'/// </summary>
Private Shared s_thrdMaster As Thread = New Thread(AddressOf ServiceMasterThread)
'/// <summary>
'/// Start-up arguments.
'/// </summary>
Private Shared s_astrArgs() As String
'/// <summary>
'/// Event log.
'/// </summary>
Private Shared s_logMain As EventLog
#End Region
Okay, let's see who is who here:
s_enumMode is used to keep track of our service status, using the ServiceModeEnum enumeration described above.
s_thrdMaster is essentially the functional part of our service. It is what will be executed when the service starts. As you see, this is the address of a thread, which means that we can make a delegate to a specific method and launch anything within that method.
s_astrArgs will be holding any start-up command-line arguments for our service. In this example, we won't have any arguments, but when you develop your own solution you probably will.
Finally, s_logMain is an EventLog object that will be used only for demonstration purposes in this example.
Extending Service Control methods
Now, as you can see, when you created the service Visual Studio created two methods in the service's code for you to implement. Here's the ORIGINAL code:
Protected Overrides Sub OnStart(ByVal args() As String)
' Add code here to start your service. This method should set things
' in motion so your service can do its work.
End Sub
Protected Overrides Sub OnStop()
' Add code here to perform any tear-down necessary to stop your service.
End Sub
What it says is "add code here" we will do that but not only that! Here we have only the two most basic overrides for our service behaviour. We need to have all other overrides like OnPause(), OnContinue(), OnShutDown(). Here is the changed code for that region:
#Region " Definition of Service Related Methods "
'/// <summary>
'/// Start the Service.
'/// </summary>
'/// <param name="args">Start-up arguments</param>
Protected Overrides Sub OnStart(ByVal args() As String)
Dim intI As Int32
'Store command args
If Not args Is Nothing Then
ReDim Me.s_astrArgs(args.Length - 1)
For intI = 0 To args.Length - 1
Me.s_astrArgs(intI) = args(intI)
Next
End If
'Start the Service
Me.ServiceStart()
End Sub
'/// <summary>
'/// Stop the Service.
'/// </summary>
Protected Overrides Sub OnStop()
'Stop the Service
Me.ServiceStop()
End Sub
'/// <summary>
'/// Pause the Service.
'/// </summary>
Protected Overrides Sub OnPause()
'Pause the Service
Me.ServicePause()
End Sub
'/// <summary>
'/// Restore the Service.
'/// </summary>
Protected Overrides Sub OnContinue()
'Resume the Service
Me.ServiceContinue()
End Sub
'/// <summary>
'/// Shutdown the Service.
'/// </summary>
Protected Overrides Sub OnShutdown()
'Shutdown the Service
Me.ServiceShutdown()
End Sub
#End Region
No need to go into much detail here. To keep things clear, the overrides call our own private methods (which you'll see below), one for each override. The only thing that we've added is the handling of the command-line arguments in the OnStart() sub which are put into the string array s_astrArgs for further use.
Do we need to have the overrides call our own private methods? The answer is, no. We could do all the work inside the overrides. But I decided to showcase it this way to make things a bit more clear. Where you will implement the actual functionality is really up to you.
Creating a Master Thread for our Service
Before we examine what our private methods for the service functionality do, let's see how we have defined the thread (remember s_thrdMaster?)
#Region " Threads "
'/// <summary>
'/// Master thread
'/// </summary>
Private Shared Sub ServiceMasterThread()
'Catch thread exceptions
Try
'Repeat while in RUN/PAUSE Mode
While s_enumMode = mdlGlobals.ServiceModeEnum.MODE_RUN Or _
s_enumMode = mdlGlobals.ServiceModeEnum.MODE_PAUSE
'If this is not a Pause mode
If s_enumMode <> mdlGlobals.ServiceModeEnum.MODE_PAUSE Then
'TODO: Do the job!
Thread.CurrentThread.Sleep(5000)
s_logMain.WriteEntry(String.Format("{0} Working... ", Now))
End If
'Sleep a bit
Thread.CurrentThread.Sleep(1000)
End While
Catch ex As ThreadAbortException
'An Abort request was received
'TODO: Handle error
Catch ex As Exception
'General error
'TODO: Handle error
End Try
End Sub
#End Region
This sub represents our main service thread. This thread will be running whenever the service starts and it will be stopping whenever the service stops.
Why a thread? Because, it's a service! Usually, a service does repeated tasks (well, ok, not all the time, but we won't attempt to cover all possible cases here). The point here is that you could possibly have a bunch of threads, each of which perform a different job. Our main thread is enough, though, to perform simple actions. What our Sub does here is to write an entry to the Event Log every 5 seconds. At the same time, it checks the state of the service (we'll see how we set that later) and decides whether it must do the stuff it's supposed to or just ignore it because the service's state has changed.
Read Part II