Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Create Windows Services in the .Net Framework


May 2002/ Create Windows Services in the .Net Framework

One of the unsung heroes in the success of Microsoft 2000 (and Windows NT before it and Windows .Net Server to come) is the ability to run applications as services. Even more important than the ability to run services, is the ability to create them in a reasonably straightforward way. Of course, how reasonable the process has been depends upon how familiar you are with creating functions to be called by the operating system using C or C++. While there are exceptions, in general, Visual Basic programmers and those who are not willing to dig into MSDN need not apply.

With the new .Net Framework, the requirements for creating a Windows Service are suddenly a great deal less restrictive. Technically, .Net services (which will run on any NT/2000/.Net Server that has the .Net Framework installed) can be created in any .Net friendly language. The two most obvious candidates are Visual Basic .Net and C#. For this example, I will use C#, however the classes used with Visual Basic .Net are very similar and the assemblies included are identical, so no one familiar with Visual Basic .Net should have any difficulty translating the C# code into Visual Basic .Net. First though, a Windows Services primer.

Windows Services 101

You likely use many Windows Services without really thinking about them being services. If you use your NT/2000 machine as a workstation or as a server, either the Workstation or Server services, respectively, are running. There is a service that allows the event log to work, as well as services for plug and play components, networking, and task scheduling. In addition, many applications have components that run as services. For instance, Microsoft SQL Server runs as a service.

Services do not expose any user interface. All components used to create the service must be UI-less. This means that some ODBC drivers cannot be safely run as a service. If the service puts up a dialog box and waits for someone to click OK (when as a service, no one will ever see the dialog box, because it will not be visible on the user’s screen) you have a problem. If a service appears to have a user interface, it is likely that the user interface is really a helper program that interacts with the service. For instance, you can use SQL Server’s Enterprise Manager, but even if you run it on the server running SQL Server, the Enterprise Manager is not the same process that actually runs the database. While it is possible to allow the service to interact with the desktop, this is almost always a bad idea. I have created programs to interact with services via TCP/IP Sockets or Named Pipes, and have then exposed that functionality via a standard Win32 GUI program or by using a web interface where the web server itself interacts with the service and then renders HTML based upon the results of that interaction.

A Windows Service is essentially a standard Win32 Console Mode application that runs outside the context of any currently logged in user. Even if there is no user logged in, a service can run, presuming that its startup type was set to Automatic. While the Service does not run in the context of the currently logged in user, it must run within some security context, and this can be set as well. Figure 1 shows the screen that allows you to set the user context for the service. Services that do not interact with resources outside the current machine can use the Local System Account and run just fine. Services that interact with resources that are not on the local machine need to use some other user account that has authority on the machines with the resources that the service must control. A Windows Service must expose at least two functions:

ServiceMain is the entry point that actually runs the service. The work of a traditional Win32 Service is done within this procedure.

Handler or for Windows 2000 and later, HandlerEx. This is a function that is called to control the service. For instance, if you stop or pause a service using the Services Administrative option, Windows calls HandlerEx to tell the service that a request to stop or pause has arrived.

While these two methods are not terribly difficult to implement, before .Net they required a programmer proficient in C/C++ or able to interpret the MSDN documentation and implement the methods in some other language. Fortunately, .Net makes this far easier.

.Net Services

In the .Net Framework, it’s simple to create a service in any of the .Net languages. The only requirement is that your class derives from a particular class (System.ServiceProcess.ServiceBase). Note that ASP.Net also allows creation of a kind of service, called an XML Web Service. This is a totally different kind of service. It runs within IIS and is available via any SOAP compatible protocol. Unlike Windows Services, which are active whenever the service is running, XML Web Services are called (most often from another web server) and run only when explicitly called.

.Net Services in Visual Studio .Net

There are a couple of ways to create a .Net Service. As with all .Net programs, one option is to simply look at the documentation and use Notepad to create the program that will run the service. There is nothing magical about creating a service, so using Notepad is certainly an option.

Another way to create a .Net Service is to use Visual Studio .Net. The Visual Studio.Net team has endeavored to create a RAD (Rapid Application Development) environment for designers of service applications. While the effect is not quite perfect, Visual Studio.Net does make it easier to create a service that will both run and install correctly. I will work through the steps in Visual Studio, and then look at the resulting code to see exactly how it works. Note that unlike some previous Microsoft environments, there is no “Secret Sauce” or code that is invisible that magically allows an action to take place. While the designer hides some of the code as a convenience, there is not code running that is completely hidden from you.

RAD Service Development

The first step to creating a service using Visual Studio .Net is to start a new project. When you do this, a dialog as shown in Figure 2 will appear. From the list of project types, select Windows Service.

Figure 3 shows the screen once the service has been created. This is not exactly what I expected the first time I created a service in Visual Studio .Net. The first thing you should do from this screen is right click anywhere on the Service.cs [Design] page. The context menu will contain an option “Add Installer”. Select this option and ProjectInstaller.cs will be added to the project. On the design pane for this file, there will be two components. One will be called ServiceProcessInstaller1 and the other ServiceInstaller1. Normally I might change the names to something more meaningful, but will not do so for this example. When you click on ServiceInstaller1, the properties window will allow you to set various properties of the service. The most important are the ServiceName for the service and the StartType. The ServiceName is the name used to request a start of the service (and this name must be unique on a given system), and StartType controls how the service starts. There are three valid values for StartType:

  • Manual. This is the default, and this means that the service will start only when started explicitly, using the Services applet or by some program.
  • Automatic. This start type specifies that the service will start automatically when the machine is started. This is almost always the start type to select if you want a server to run all the time.
  • Disabled. This means that the service is disabled, will not start on start up, or if manually requested.

These are the same start types you can set within the services applet. The setting here controls how the service will be installed, but does not constrain the services from being set differently after install.

One other property of ServiceInstaller1 is ServicesDependedOn. This is an array of strings that contains the names of services this service depends upon. For instance, if you have a service that needs to connect to a resource on a remote machine, you might add Workstation to this list. Having done this, the service will not start until the Workstation service starts. One thing to note about these services that are depended upon is that the Service Control Manager tries to start the depended upon services, even if they are start type Manual. So, if you have a service that is intertwined with other services (Exchange might be a good example) if you wish to not automatically start the service, you might want to set the StartType to Disabled rather than Manual, unless you clearly understand the dependencies of these services.

ServiceProcessInstaller1 has several properties that are of interest. Account allows you to determine the security context the service will run under. Recall that services do not run under the security context of the logged in user, and can run even if no user is logged in. Every process needs to run within some security context, and so by setting Account you can specify what security context is used by the service. The options are:

  • User. The service runs in the context of the account of a standard user. The user name and password must be specified (in the ServiceProcessInstaller1 object), the properties for setting these are Username and Password.
  • LocalService. The service runs in the context of an account that provides extensive local privileges and presents the computer’s credentials to any remote server.
  • LocalSystem. The service runs in the context of an account that provides limited local privileges and presents the anonymous credentials to any remote server.
  • NetworkService. The service runs in the context of an account that provides limited local privileges and presents the computer’s credentials to any remote server.

LocalService and NetworkService are available only on Windows XP and the upcoming Windows .Net Server.

Getting into the Code Created By Visual Studio .Net

Once these simple properties are set, you will need to actually dive into the code. For any file that is opened in design mode (meaning that rather than code, you see components) you can select the View menu and then select Code to see the underlying code. Creating an example service is always a challenge. One of the key features about a service is that it does not present any user interface. Often I slave over a server application and in the end, there is really nothing for anyone to see; the service just works and does whatever work it should do without showing any evidence and saves a stream of entries in the event log. For this article, I created the SqlServiceMonitor service. This service checks if it can open a connection to a given database, and if not, checks the status of the service on the specified machine. Complete source code is available online at www.wd-mag.com.

By clicking on the design page for the main source module for SqlServiceMonitor and selecting the View/Code menu, you will see the code shown in Example 1.

All .Net Windows Services are created by inheriting from System.ServiceProcess.ServiceBase or some descendent class. Any useful service will override at least two methods: OnStart and OnStop. Looking at Example 1, the class derived from ServiceBase is called SqlMonitorService. At the top of the class are several variable declarations. Two important ones are:

 

        private bool m_IsRunning=false;
        private MonitorThread monitorThread = null;

The first is used as the underlying variable that is returned for a property that is called IsRunning. MonitorThread is a class defined at the end of this listing, and I declare an instance of it here. In theory, there could be multiple instances of this class so multiple SQL Servers could be monitored. More on this class later. OnStart must be overridden to actually cause the service to do something. OnStart should do its work and then exit as quickly as possible. The implementation of OnStart in Example 1 is fairly simple:

protected override void OnStart(string[] args)
{
    // Create the thread object, passing in the
    // monitorThread.workerThread method
    // using a ThreadStart delegate.
    Thread InstanceCaller = new
        Thread(new
        ThreadStart(
        monitorThread.workerThread));

    IsRunning=true;
    // Start the thread.
    InstanceCaller.Start();

}

Commonly services use threads to do their work. Details of .Net threading are beyond the scope of this article, but in brief, the OnStart creates a new Thread object, in doing so creates a ThreadStart object that accepts the entry point for the thread, in this case monitorThread.workerThread. .Net threading expects the entry point to be an instance variable method. Unlike traditional Win32 threading, there is no context pointer that is passed in to the newly created thread. Of course, since the thread is called on an instance method of a class, instance variables are available. Looking at the constructor for SqlMonitorService in Example 1, we see the following code:

monitorThread = new MonitorThread();
monitorThread.ServerName="Dell933";
monitorThread.DatabaseName="TEST";
monitorThread.SQLUser="sa";
monitorThread.SQLPassword="badpassword";
monitorThread.interval=120;
monitorThread.eventSource=
"SqlServiceMonitor";

Here the instance of MonitorThread is created, and various member variables are set. Note that the values set are assured to generate an error. (Note the literal “badpassword” passed as the password.)

A thread that is just created is not running. Calling Start on the thread object (called InstanceCaller in Example 1) starts the service. Starting the service causes the newly created thread to begin executing code in workerThread. Note the name of the method is not important; I commonly call the method workerThread, but this is certainly not required.

In addition to some instance variables (set in the previous snippet), monitorThread declares three methods: workerThread, onSuccess, and onFailure. Looking at workerThread, I declare several variables and then enter a while loop with IsRunning in the predicate. This is the IsRunning internal to monitorService that is set by the IsRunning property in the main service class. Services commonly use processing loops that do some unit of work, check the status of the service, and repeat. Inside this loop, it is important that we do not operate without any chance for some other process to operate. In this case, the work of the service requires relatively little actual work. Checking the status of an SQL Server is not heavy work, and so most of the time the service will be waiting. Generally, I want this service to wait perhaps two minutes (120 seconds - set by the interval instance data member of the workerThread object) between checking the service, and so the loops in workerThread are constructed so that there is no more than a second between the times that the service will check the status of the service. Alternatively, events could be used, and rather than checking the status of the service periodically, the workerThread could simply wait on the event, with interval seconds as the timeout value. Then if the event is fired, the service thread could immediately terminate, but if the timeout occurred, the service could then just perform its task. The thread ends when the method passed into the ThreadStart constructor returns.

I use exception handling because either opening the connection to the service or checking the status of the service can cause an exception. The connection string is set using the username, password, server, and database specified in the instance member variables. This is fairly standard ADO.Net stuff, so I won’t explain further. Checking the status of the service if the connection cannot be open is a bit more interesting. Unlike standard Win32 programs, .Net programs can easily check the status of a service. The following lines will generate a string containing the status of the service (or throw an exception):

 

    ServiceController sc=new
ServiceController("MSSQLSERVER",ServerName);
try
    {
        message="SQL Server Service status on "+
ServerName+" is "+
sc.Status.ToString();
    //... and so on...
    }

I simply create a ServiceController object passing in the name of the service (in this case, “MSSQLSERVER”) and the name of the server. Note that SQL Server 2000 can have multiple instances running with slightly different names. If you run multiple instances, or an instance with a different name, you should add support for setting the service name to look for.

Depending upon the outcome, success or failure, we call onSuccess or onFailure. The onFailure method is the most interesting, and onSuccess is very similar, so I will look at onFailure.

 

public void onFailure(string message)
{
    if (!EventLog.SourceExists(eventSource))
    {
            EventLog.CreateEventSource(
       eventSource,"Application");
    }
    EventLog MyLog=new EventLog();
    MyLog.Source=eventSource;
    MyLog.WriteEntry(message,
System.Diagnostics.EventLogEntryType.Error);
}

This code is deceptively simple, especially if you have tried to insert messages into the event log. I first call the static SourceExists method of EventLog, and if the service does not exist, I create the event source. Once I am certain the event source is created, I create an instance of EventLog and set the event source and the message. I use the overload of WriteEntry that allows me to set the type of the event log entry, in this case an error. Figure 4 shows entries in the event log for our service.

Example 2 shows ProjectInstaller.cs source listing. This class is required to properly install the service. Most code here is setting properties that have been set in the designer. The project must contain an instance of a System.Configuration.Install.Installer derived class. Once the project is compiled, you can then use installutil, a command line utility included with the .Net Framework to actually install the service. It has been my experience that installutil is not on the path, and so you need to move it someplace on the path, or add the folder that contains installutil to your path. (It is version dependent, as the version number is embedded in the path.)

When you run the service, after interval seconds, you should see entries in the event log for the service. You can also control the service, as shown in Figure 5. Note that the Pause button on the top is not enabled while my service is selected. This is because I have not set the CanPauseAndContinue property of ServiceBase to true and overridden OnPause and OnContinue.

Just the Beginning

This is just an introductory column on all that you can do with .Net services. It is not just that services are easier to create with .Net, but the other associated tasks, such as writing event log entries and controlling services, are also much easier. Even if you have never created a service before, if you need to have a task that runs without operator intervention, the .Net framework might make it easier than you imagined.


Douglas Reilly is the President of Access Microsystems Inc. He is also the author of Designing Microsoft ASP.NET Applications from Microsoft Press. Doug can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.