Getting Started

Build Your First .NET Windows Service

Once limited to the domain of C++ and MFC programmers, Windows Services can now be a key part of your .NET applications' infrastructure. Learn how to create a Windows Service application to provide monitoring for your .NET apps.

Technology Toolbox: VB.NET

Once limited to the domain of C++ and MFC programmers, Windows Services can now be used as a key part of your .NET applications' infrastructure. Much like moving database-level code into a database tier, you can implement a core set of application services (such as database connection pooling, event logging, and auditing) and deploy them as a Windows Service—available to all of your .NET applications without bloating them with redundant code. It's a good idea to implement common functionality across applications as a Windows Service for those features where an application requests a "service" (such as retrieving messages or getting a connection to a database). Otherwise, you might want to put common functionality code into a namespace in the global assembly cache (GAC).

In this article, I'll introduce you to creating a Windows Service with VS.NET 2003. I'll show you how to create a simple monitoring service for all of your .NET desktop apps. For purposes of this article, I'll define monitoring as the ability to identify critical security events (such as gates opening and closing, fire alarms activating, sprinkler systems activating, and so on) across all security applications. The monitoring service will provide all applications with the ability to signal these events and the potential to log these events (outside of the Windows event log, which sometimes can be a security requirement). The service will also provide the ability for a desktop application to communicate with it.

A Windows Service is a process that runs unattended and without a user interface. Windows Services are available only in Windows NT, Windows 2000, Windows XP, and Windows Server 2003. Windows Services run in their own process space and can be configured to start up during the OS boot process. Other desktop applications can interact with them, if you configure them that way.

You can accomplish monitoring in two ways. First, you can have the service poll each application. Unfortunately, a lot of processor time is spent polling while no monitoring activity is happening. Second, you can use an event model, where each application registers with the monitoring service and then sends monitoring events as they happen. I'll show you how to use the event model in this article.

Create a Windows Service
Start by creating a Windows Service project. Select File | New Project, then select the Windows Service template (in this example, use AppMonitor as the service name). Notice that VS.NET creates a new project with a single VB.NET file called AppMonitor.vb. Double-click on this file to bring up a designer view (see Figure 1).

Notice that you can set several properties for the service project. For example, you can tell Windows that this service will respond to shutdown, pause, and continue, and other events. You can also tell Windows to log service events such as stop and start in the Windows Event Log by setting the AutoLog property to True. Figure 1 shows the Properties page for the AppMonitor service project.

Now take a look at the code for the service by clicking on the "click here to switch to code view" link. The first thing you'll notice is that the AppMonitor class inherits from the System.ServiceProcess.ServiceBase class. This base class holds all of the events and properties necessary to define and interact with the service. VS.NET 2003 generates code for the Main subroutine, which runs when the service first loads (not runs) during the OS boot process:

<MTAThread()> _
   Shared Sub Main()
   ' This is the main thread that gets 
   ' loaded when all the services
   ' listed in the service directory get 
   ' instantiated.
   Dim ServicesToRun() As _
      System.ServiceProcess.ServiceBase

   ' Setup the service to run
   ServicesToRun = New _
      System.ServiceProcess.ServiceBase() _
      {New AppMonitorService}
      System.ServiceProcess.ServiceBase.Run( _
      ServicesToRun)
End Sub

The ServicesToRun object contains an array of service classes that you should run under the same process space (thereby conserving system resources), but can still be managed independently as services. This service array is passed to the ServiceBase.Run() method, which looks at the services and starts up any service whose Startup property is set to Automatic.

Beware that when creating your first service, the service class is named Service1 automatically. Even if you rename the service class, you still must change the line of code manually from this:

ServicesToRun = New _
   System.ServiceProcess.ServiceBase() _
   {New Service1}
   System.ServiceProcess.ServiceBase.Run( _
      ServicesToRun)

to this:

ServicesToRun = New _
   System.ServiceProcess.ServiceBase() _
   {New AppMonitorService}
   System.ServiceProcess.ServiceBase.Run( _
      ServicesToRun)

You can hook into several Windows Service events by overriding the base class routines for these events—there's no way in VS.NET 2003 to auto-generate these overridden events (see Listing 1 to view the code for the overridden service events in the AppMonitor service). Because the procedures are overridden, they need to match the procedure names and arguments exactly in order to compile correctly.

In the example, you respond to all of the available Windows Service events by defining overriding procedures for all possible events. In addition to start, stop, pause, and continue events, notice that there's also a custom command procedure:

Protected Overrides Sub OnCustomCommand( _
   ByVal iCommand As Integer)

This procedure allows an external application to send an integer command to the service while it's running. This procedure cannot return a value, because its intended use was for primitive one-way IPC communications between a process and a service. In the AppMonitor service example, you can use OnCustomCommand to provide a way for applications to send monitoring events to the AppMonitor service. From there, merely log the command to a test log file to keep things simple. You could easily expand this for use in a production environment—for example, you could store monitors in a database or send the messages to a message queue.

You can also send parameters to a Windows Service on startup. Notice that the OnStart routine takes an array of strings as an argument:

Protected Overrides Sub OnStart( _
   ByVal args() As String)

Use a parameter in the AppMonitor example to specify the name and location of the log file. If the service is started without an argument, simply use a file in the root directory for logging. To send a parameter to a service, use the Services MMC snap-in, select properties of the service, and enter the parameter in the Start Parameters textbox (see Figure 2).

Install the Service
You need to be able to install a service now that you've created it. Add an installer to the AppMonitor project by going to the designer view of AppMonitor and clicking on the Add Installer link on the property tab (see Figure 1).

Clicking on this link adds a ProjectInstaller.vb module to the project, which contains two new components in the designer (ServiceProcessInstaller and ServiceInstaller). The ServiceProcessInstaller component for a Windows Service project holds the account type (LocalSystem, LocalUser, User, and so on) and credentials that the service will run under. The ServiceInstaller component holds information written to the Registry (HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services) about the service. This includes the name, display name, list of services depended on, and startup type (automatic, manual, or disabled—manual is the default). If your project contains multiple services, each service should have a separate ServiceInstaller component. You can also use the install and uninstall events of this component to perform additional installation work for your service.

Once these components are added and the project is compiled, the RunInstaller attribute is set to True. Only those classes with this attribute set to True will be installed when running the installer (see Listing A to view the auto-generated code and events that you can hook into within the ProjectInstaller.vb module).

You can install the service by going to the \bin directory underneath the AppMonitor project directory and running the InstallUtil program. InstallUtil is a .NET Framework utility that runs the .NET installer on the target executable (the .NET Framework directory should be part of the path by default after installing the .NET Framework). Use this command to install the AppMonitor service:

InstallUtil AppMonitor.exe

You can also use InstallUtil to uninstall the service, as long as it is stopped:

InstallUtil AppMonitor.exe /u

If you've loaded multiple versions of the .NET Framework, you need to use the framework version of InstallUtil that matches the .NET Framework you compile to. You can find InstallUtil.exe in the framework directory:

%WINDIR%\Microsoft.NET\Framework\vx.y.zzzz

Take a look at the command window result of running InstallUtil on the AppMonitor project. InstallUtil also logs this information in a file called <service_name>.InstallLog.

One last thing to note about compiling and installing Windows Services is that if you install the service from the project's \bin directory, you won't be able to recompile the service project while the service is running, because Windows will have the service executable locked.

Create a Service Wrapper Class
Now that you've created and installed the service, it's time to start work on interfacing the desktop client applications with the AppMonitor service. The easiest way to do this is by implementing the OnCustomCommand event within the service, then having each application send monitoring commands to the service (which I'll discuss in a moment). A Windows Service can only receive integer values and cannot return values. A poorly documented user service command restriction says that service custom user commands must be in the range of 128-255. Windows reserves values under 128 for system-level commands. You'll get a vague Win32 exception if you attempt to send a custom service command out of this range:

Cannot control service 
   <service_name> on machine ?.?

The easiest way to provide an interface with the AppMonitor service is to create a wrapper class that all of the desktop applications can use. The purpose of the wrapper class is to obfuscate the details of how and where the monitoring service is implemented. It also makes it easier to change the way the monitoring service is designed, without affecting any of the client applications. Create a MonitorWrapper class that contains enumerations for the various monitoring command codes, as well as a single method (MonitorThis()) that external applications can use to send monitoring commands (see Listing 2).

Notice that you need to reference the ServiceProcess namespace in order to access Windows Service classes:

Imports System.ServiceProcess

You can create a reference to the ServiceProcess.dll component by going to Add Reference under the References folder of the Solution Explorer for the project (see Figure 3).

Once the ServiceProcess component is referenced, you can now access the ServiceController class, which is the key to working with Windows Services. You can query and control the service, and set almost all its properties through the ServiceController object. As you can see in the MonitorThis() method, the first thing you need to do is get an instance of the ServiceController object associated with the AppMonitor service. If it exists, the service's status is examined to make sure that it's running before you send a command to the service using the ExecuteCommand() method.

Now that you've written the MonitorWrapper class, it's time to deploy it for use by other desktop applications. Normally, you would deploy it to the GAC, but for purposes of simplicity in this article, let's create a project reference to it from a sample desktop application (SecurityController).

Create a Sample App to be Monitored
The SecurityController project is a simple .NET WinForms desktop application that we'll use as a proof of concept for the AppMonitor monitoring service. This mythical security control application references the MonitorWrapper class created earlier to send service commands associated with security events. In addition, the application also includes buttons to start and stop the AppMonitor service to show how to control a Windows Service through the ServiceController object (see Listing 3).

As you can see, commands are sent to the AppMonitor service through the MonitorThis() method (in the MonitorWrapper class). You can also add start and stop buttons and code to start and stop the AppMonitor service. As discussed earlier, you control the service through the ServiceController object. The only difference between sending a command to the service and changing a service's status is that you need to start and stop a service asynchronously.

You could start a service by calling the Start() method of the ServiceController object. However, this doesn't guarantee that the service will start. After calling Start(), you need to make a call to the WaitForStatus() method, which waits for a specified time span (or indefinitely, if no time span is specified) for the status to change to the desired state. If the WaitForStatus() method times out, an exception is thrown, which needs to be caught. This same logic also goes for stopping, pausing, or resuming a service.

Debugging a Windows Service is just like debugging any external process. Within the IDE, you can attach to a running Windows Service by using the Tools | Debug Process command. Bring up the code within the IDE, set your breakpoints, and debug as you would a normal application. Keep in mind that you're debugging a "live" process when debugging a Windows Service. If you hit a breakpoint, the service stops on the machine and doesn't respond to any other events. If you close the debugger/IDE without resuming the process, the service reverts to a stopped state and could result in instability within the service.

Now that you've deployed a simple monitoring service, you can see how you can extend this model easily. The fact is, you probably wouldn't use this kind of monitoring design in a production environment because it relies on file I/O and simplistic integer service commands. A more elegant (but complex) design would be to spin off a separate thread within the OnStart() method of the service. This thread would initialize a message queue for application monitoring commands. You could then revise the MonitorWrapper to communicate with the message queue instead of executing an integer command on the AppMonitor service itself. After the new Windows Service is ready to deploy, you could compile, reversion, and deploy a new MonitorWrapper class to the GAC to run side by side with the original version. You could give the new Windows Service a version-unique name so it could run side by side with the old AppMonitor service as well. You would merely need to recompile desktop applications with a reference to the new MonitorWrapper class in order to take advantage of the new monitoring design.

You now have the basics for developing Windows Services in .NET. As you can see, VS.NET 2003 makes it simple to create and deploy services, and the ability to hook into service events gives you a strong foundation for building even the most complex Windows Service in any .NET language. You can extend this simple architecture easily to make it more robust for your high-capacity production environment. This is a Getting Started column, so I'll shelve how to extend the AppMonitor service using threads and MSMQ for a future article. Stay tuned for this AppMonitor service upgrade, which will combine the use of some of the .NET technologies that I've written about in VSM over the past six months (including threads and MSMQ) with Windows Services, to provide an exciting monitoring architecture.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube