Download the code for this article
Whether you are developing at the enterprise level or for the smallest venues, IT is all about automation, finding ways to allow these infernal machines to do things better, faster, or in many cases, ‘at all’ than the human alternatives. Suppose, for example, that your company receives critical files by way of FTP, either from a mainframe FTP dump or via the internet from trading partners, in the Pre-.NET world RAD developers for the Microsoft Windows platform (OK, VB Developers) were limited to using either devices like the NT scheduler or launching background applications in the Startup folder, or worse yet, relying on human intervention to “see” certain conditions and react to them. The introduction of the .Net framework has introduced a new tool into the VB developer’s toolbox, Windows Services (of course, this is available to C# developers as well, and has been available to C++ developers for years). In all of the “hoopla” around Web services, windows services have been largely an unheralded feature of the .Net framework, but represent a real opportunity to get a lot of bang for your buck in development efforts.
It almost goes without saying that relying on human intervention is problematic at best (from sick days to vacation time, not to mention distractions and other “human factors”, this system is just not reliable). The Windows native NT Scheduler and other scheduler programs suffer from their own reliability issues, not to mention that they have to be explicitly launched and have to run in a specific user context, which requires maintaining significant overhead, just to support a few operations. Applications run in the Startup folder; well to begin with, they require someone to actually log in, and further, stay logged in, to start and run. In response to these challenges, Windows Services have the following benefits:
Of course, just being a “service” is of no intrinsic value, rather it is in the “task” that your service performs that any application gains value in your operation. Fortunately, the .Net framework provides several classes and objects that lend themselves to utilization by a service to accomplish useful functions. For our demonstration, we will look at the system.io.filewatcher and system.threading. (By no means do I wish to imply that the use of these assemblies is limited to use in services, only that they are very useful within services.) Additionally, we will go into a detailed look at how a service should be deployed and managed. Finally, because services, like any other application, generally require some level of debugging, we will cover how to attach the debugger to a running service.
As with any other application, there are several design “best” practices that should be followed to ensure a smooth running service. Services tend to fall into three categories: Watchers, Listeners, and Workers. Watchers and listeners tend to operate in a similar fashion; they only differ in the trigger mechanism. A watcher has a polling mechanism (either explicitly or implicitly) to watch for some external stimulus (like a file appearing in a directory), while a listener has a mechanism (such as a port listener) waiting for something external to act explicitly on that service to set it in motion (as IIS, through the World Wide Web Publishing Service, listens on Port 80 for a request for web pages).
Worker services are the heavy lifters which “do the work” that the service is built around. As a general rule design listener/watcher services to do as little as possible so that the service actually launches a separate worker process or service (as the World Wide Web Publishing Service hands off requests for ASP.NET pages to the ASP.NET worker process which in turn, does the work of building the httpResponse and sending it back to the requestor). This architecture accomplishes two important goals: first, better use of system resources, since the service is made more lightweight if it does not have the extra overhead of managing the (usually) more complex and larger worker routines which can be left out of active memory until they are truly “needed”. Second, improved availability. With both watchers and listeners, you don’t want to handle the same request more than once, but you also want to ensure that you can handle every request. Having a more lightweight service makes recycling the watcher/listener faster and more effective (when you review the code samples with this article you will notice that the first action after the watcher is triggered is to start a new watcher). Worker services should be reserved for those occasions and situations where both high availability and fast response time is critical (such as SQL server). These are generally services for which a physical server can and should be a dedicated device (again like SQL or Exchange).
| A note about the scenario: The focal point of this article is to show you how to build, debug, and deploy a Windows service, therefore we are using a fairly simple task, that of watching the files in a directory, as a catalyst to demonstrating the process of working with services. For more information on the system.io.filewatcher see MSDN. |
Scenario: A legacy mainframe system, connected by WAN, and not under our direct control, periodically does an FTP dump to a directory on our LAN. The files do not come in at a particular time or interval and each file needs to be handled separately and differently. The files do appear with a given naming pattern (filename + timestamp + .doc). The files we are interested in are:
The following goals are to be incorporated into the solution design, first we need to ensure Real-time processing; these files need to be processed as soon as they arrive so that the data and reports can be worked with by management in a timely manner. Second, we want a system that will require Minimal Human Intervention; these processes should be accomplished without the need for a staff member to have to constantly watch the download directory to see when the files arrive and have to react to their arrival. Next we want to make this economical by Using existing infrastructure; this process should not require the addition of new hardware or purchased software. (Of course, we are assuming you already own VS.NET and are running the .NET framework) and finally we want a solution that is Extensible; the solution needs to allow for new download management routines to be added to this system.
In order to accomplish the goals laid out we will build the following applications:
| Demo Note: As they are not directly relevant to this topic the wkrDaily, wkrReports, and wkrErrors executables will not be included in the download available with this article, instead, the service will send an entry to the application log saying what application it would have launched. The code to launch a separate worker process is present but remarked out. |
Creating your service project

will create the basic project for you by adding an AssemblyInfo.vb and Service1.vb
to your project. Now this is one of those (few) times when VS.Net helps you
“too much” because the service that is created in your project
is Service1.vb and a class, called (amazingly) Service1 is “pre-built”
for you. However, the Name of the service that will eventually appear in your
Service Control Manager is related to this and so it should be renamed. This
is accomplished with the following steps: Design Tip: Make sure that you have your “Task List” showing at this point as it will help you through this step. Once you change the name of the class, two errors will appear on the task list:
|
Imports System.ServiceProcess Public Class Service1 '(1a)Change to FTPwatch Here Inherits System.ServiceProcess.ServiceBase #Region " Component Designer generated code " Public Sub New() MyBase.New() ' This call is required by the Component Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call End Sub 'UserService overrides dispose to clean up the component list. Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub ' The main entry point for the process <MTAThread()> _ Shared Sub Main() Dim ServicesToRun() As System.ServiceProcess.ServiceBase ' More than one NT Service may run within the same process. To add ' another service to this process, change the following line to ' create a second service object. For example, ' ' ServicesToRun = New System.ServiceProcess.ServiceBase () {New Service1, New MySecondUserService} ' ServicesToRun = New System.ServiceProcess.ServiceBase () {New Service1} '(1b)Change to FTPwatch Here System.ServiceProcess.ServiceBase.Run(ServicesToRun) End Sub 'Required by the Component Designer Private components As System.ComponentModel.IContainer ' 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 = "FTPWatch" '(1c)Change to FTPwatch Here End Sub #End Region 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 End Class
Now let us next go back to the Property Box (which can be viewed while you have the designer open). There are several properties here which you should understand:
| Design Tip: Remember this point when we get to a discussion regarding the value of implementing multi-threading into a service, later in this article. |
While the default “service1.vb” file that is created for you in VS.NET only contains default handlers for “OnStart(ByVal args() As String)” and “OnStop()” Each of the properties listed above (except Autolog) expose one or default event handlers which can be added to your service, without the need for a “handles” statement. Since there are event handlers for all of these events in the System.ServiceProcess.ServiceBase class, event handlers for any of these events MUST be declared as Protected Overrides
| Design Tip: The base class event handlers are all “Subs” (void methods in C#) EXCEPT the OnPowerEvent handler which is a Boolean Function (or a Boolean Method in C#) |
For our purposes we will be setting “CanPauseAndContinue” & “CanShutdown” to True so that we can react to those events. But we will discuss those in further detail later. For now, all we need to do is change these properties and add the following subroutines to the FTPwatch class:
Protected Overrides Sub OnPause() End Sub Protected Overrides Sub OnContinue() End Sub Protected Overrides Sub OnShutDown() End Sub
Now that we have our class file in place let us examine its contents starting at the top. We start by Importing System.ServiceProcess. The System.ServiceProcess exposes the base functionality that is required to build services, in particular the ServiceProcess.ServiceBase class that our FTPwatcher class is derived from. In the Public Sub New the base class must be initialized first then the class must call the InitializeComponent() subroutine. Here, the code to set the properties we discussed earlier is executed as part of the service object creation.
A peculiarity that you will notice is that this object contains both a “Sub Main” and a “Sub New”. We have seen the path created when we create a new instance of the object is created. Now we will look at what happens when the object is executed via the Sub Main. The ServicesToRun array is created and then initialized with an object of the type of the local service. If you wished to run multiple services within the same process, you can add those types to the array at this point. The array is then passed into the Shared Run method of the ServiceBase class. This is the point at which the service is actually available; though the OnStart method has not been called, the service is functionally idle, waiting for the OnStart event to be invoked by the service control manager. Up to this point we have been looking at how the OS and the .NET framework put in motion the underlying pieces that make a service work. Now that these foundations are in place, it is here that we start to build our functionality into the service class.
Here is where good design practices become both very important and challenging. While we could simply start a new filesystemwatcher right here, this creates certain problems. The first is recycling the watcher, so that it will work for more than one instance (one file system event), until the whole service was restarted. The second is that we would have difficulty managing the pauses and continue commands. The third “challenge” here is one of resources: “How much system time do we want to dedicate to this activity?”
If, however, we create a new thread and put our worker process on that, and assign the priority of that thread to “low” (which is sufficient for our purposes, on “low” priority the watcher will “only” look at the specified directory several times per second) all of these issues become easily manageable. Since we need access to the thread throughout the application, we need to declare it in the general declarations for the class as Dim myThread As System.Threading.Thread. Then we create an instance of the thread in the OnStart event as myThread = New System.Threading.Thread(AddressOf StartMonitor), where StartMonitor is the subroutine which creates the fileSystemWatcher and sets it in motion. Similarly the FileSystemWatcher needs to be in the general declarations so that it can be managed across the scope of the class as well.
Private Sub Monitor()
myThread = New Thread(AddressOf StartMonitor)
With myThread
.Priority = ThreadPriority.Lowest
.Start()
End With
End Sub
'We delagate our thread to start here...
Private Sub StartMonitor()
myFSW = New FileSystemWatcher("C:\WatchMe") 'Of course you can
imagine dynamic ways to assign the target directory....
myLog.WriteEntry("Watching")
With myFSW
.IncludeSubdirectories = False
.EnableRaisingEvents = True
AddHandler .Created, AddressOf OnCreated
.WaitForChanged(WatcherChangeTypes.Created)
End With
End Sub
With these elements in place the next piece we need is the handler for the Created Event. First we need a means to manage the relationship between the Filename and the action to be taken. After parsing the filename, we will use the app.config file to fetch the program name that we need to execute.
Private Sub OnCreated(ByVal sender As Object, ByVal e As FileSystemEventArgs)
'start monitoring for the next file...
Monitor()
Dim FileName As String = DemoFileName(e.Name)
Dim ExecProg As String = ConfigurationSettings.AppSettings(FileName)
Dim LogMsg As String = e.Name & " created @ " & TimeStamp() & " " & vbCrLf & _
"Executing " & ExecProg & " on " & FileName
myLog.WriteEntry(LogMsg)
'This is the code you might use to execute the worker program.
'If ExecProg <> "" Then
' Call Shell(ExecProg)
'End If
'this Archives the specific file into a subdirectory called archive
File.Move(e.FullPath, e.FullPath.Replace(e.Name, "archive\" & e.Name.Replace(".dat", ".arc")))
'this ends the activites of this thread and leaves the new watcher running.
Thread.CurrentThread.Join()
End Sub
The Call Shell(ExecProg) piece (remarked out in this demo) shows you how to offload the heavy lifting to a separate application, allowing your service to return its attention to watching the file system for the next event.
| Demo Note: The demo code also includes a few minor functions that do not rise to the level of commentary. |
Services don't execute as normal processes do — instead, they are launched by the ServiceControlManager at system startup (if the service is flagged as "Automatic"). The SCM has to be told of the new service, and doing so is done either by going through a set of Win32 APIs to install the service, or by creating a VisualStudio.NET installer.
| Design Tip: When working with your code for your service I suggest building a console app to “pre-test” some of your subroutines and then copying those sub routines over to the service module. |
Debugging a service has traditionally been a tricky part of service development — because it doesn't run as a normal console application, and because a service is typically launched by the ServiceControlManager at system startup, under a least-privileged account, debugging the service in VisualStudio requires the developer to attach to the running process, and then start and stop the service as necessary (or do what actions are necessary to trigger the desired functionality). Fortunately, VisualStudio.NET makes this fairly simple.
To Debug the service
Now that we have designed, deployed and debugged our service it is ready to go into production. Of course all we have here is a very simple service, but with a little bit of tweaking it can be used to handle files arriving by FTP or any other means, in your environment. More importantly, what you have is a template for building other services that can automate processes on your network.
Windows Services represent a tremendous opportunity to create “fire-and-forget”
applications that can automate almost any process where human intervention is
time consuming, unreliable, and most importantly not required. Many key critical
parts of the Windows operating system, including but not limited to IIS and
ASP.NET, run as Services, for precisely the same reasons.