# Sunday, November 1, 2009

Back To Basics

I remember how excited I was in the early days of .Net when I discovered how easy it was to write a Windows Service. I probably wrote a half dozen services that first year. But I hadn't written one in years and (oddly) hadn't even heard anyone talking about writing a Windows service in as long.

Perhaps the reason one hears so little about Windows Services is because the way we write them has changed so little since .Net 1.0.

A Windows Service is not dependent on a particular user being logged in. In fact, a Windows Service can run if no one is currently logged onto the machine on which it is running. This makes a Windows Service inherently more secure than a Windows Forms application or a Console application because you can run it on a server, set credentials and log out of the console.

To create a Windows Service, open Visual Studio and select File | New Project. In the Project Types tree of the New Project dialog, expand either Visual C# or Visual Basic node and select the Windows node. In the Templates area of the dialog, select Windows Service. Set the Name, Location and Solution as you would any other project and click OK to create the project.

By default, a Windows service contains one class named "Service1". The name of this class isn't really important because it isn't called externally, so I always leave this default name. If you double-click this class in the Solution Explorer, it opens in a Design view. Select View | Code to switch to a code view of the class. Notice that the class inherits from ServiceBase and that it contains overrides of two methods: OnStart and OnStop.

As you might guess, OnStart contains code that runs when a service starts and OnStop contains code that runs when a service stops. I put setup code into OnStart, such as initializing variables that my service will need. I generally put very little code in the OnStop method, but this is where cleanup code goes.

Services are designed for long-running processes and are meant to stay in memory for a long time -sometimes months or years. Most of the services I've written use a timer object to periodically wake up and perform some check and respond if that check finds something. For example, you might check the contents of a folder every 10 minutes and, if an XML file is found in that folder, move it to a new location and parse it appropriately.

For example I  recently wanted a program that would check an error log every few minutes and automatically attempt to correct any errors found there. I had already written a class to read the error log, retry each error found and remove from the log any errors that were retried successfully. So my service only needed to call this class every 5 minutes. I used a timer class to do this. A partial code listing is shown below.

protected override void OnStart(string[] args)
{
    // Every 30 seconds, a timer will do some work
    timer1.Elapsed += new ElapsedEventHandler(timer1_Elapsed);
    timer1.Interval = 30000; 
    timer1.Enabled = true;
    timer1.Start();

}

protected override void OnStop()
{
    timer1.Enabled = false;
}

private void timer1_Elapsed(object sender, EventArgs e)
{
    // Wake up and perform some action.
    // [Cpde omitted]
}

I prefer to keep the code in the service to a minimum and abstract complex logic to a separate assembly. This makes testing and debugging easier. So the omitted code in the above example would call out to another assembly to do the hard work.

Installing a Service
In order to install a service, you will need to add an Installer class. Select Project | Add New Item; then select the Installer Class template. This class also opens in the designer by default. Select View | Code to see the code. The Installer class inherits from Installer, but you don't need to override any methods.

You can set some attributes of the service to make it easier to find. The Installer class's constructor is the place to do this. Instantiate a new ServiceInstaller and ServiceProcessInstaller, set properties of these objects, and add these objects to the Installer Class to affect the Windows Service when it is installed. Common properties that I like to set are

Class Property Description of property
ServiceInstaller ServiceInstallerServiceName The name of the service. This must match the ServiceName specified in WindowsService1.
ServiceInstaller DisplayName A name displayed in the Services Manager applet.
ServiceInstaller Description A description displayed in the Services Manager applet
ServiceProcessInstaller Account A built-in account under which the service will run.
ServiceProcessInstaller Username The name of the user account under which the service will run.
ServiceProcessInstaller Password The password of the user account under which the service will run.

For the ServiceProcessInstaller, you will set either the Account property or the UserName and Password properties. Typically, I set the Account property to System.ServiceProcess.ServiceAccount.LocalSystem, so that it can be installed. This account probably won't have sufficient privileges to accomplish what my code is trying to do, so someone will need to open the Services Manager and change this to a valid account. I could hard-code the Name and Password of an account that I know has sufficient privileges, but this ties my application too tightly to a single domain or server or organization. I would rather keep it flexible enough that it can run anywhere. And besides, the account under which a service runs is really a deployment issue, so others should be making these decisions and they should be forced to think about this at deployment time.

Below is sample code for the installer class

[RunInstaller(true)]
public partial class Installer1 : Installer
{
    public Installer1()
    {
        InitializeComponent();
        ServiceInstaller si = new ServiceInstaller();
        ServiceProcessInstaller spi = new ServiceProcessInstaller();

        si.ServiceName = "DGWinSvc"; // this must match the ServiceName specified in Service1.
        si.DisplayName = "DGWinSvc"; // this will be displayed in the Services Manager.
        si.Description = "A test service that takes some action every 30 seconds";
        this.Installers.Add(si);

        spi.Account = System.ServiceProcess.ServiceAccount.LocalSystem; // run under the system account.
        spi.Password = null;
        spi.Username = null;
        this.Installers.Add(spi);
    }
}

After your code is tested and compiled, you can deploy it to a server. Copy to a location on the server the service EXE and any associated DLLs, Config files or other objects required for it to run. The server must have the .Net framework installed. If it has the framework, it should have a program called InstallUtil.exe. You can find this program in the Windows folder under Microsoft.NET\Framework in the subfolder corresponding to the .Net CLR version under which you compiled the service. On my server, I found InstallUtil.exe in c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727. Open a command prompt, change to the location of InstallUtil.exe and run the following command
INSTALLUTIL <full path of Windows Service Executable>

You can later uninstall the service with the following command

INSTALLUTIL /u <full path of Windows Service Executable>

Now open the Services Manager applet (under Windows Administrative Tools) and refresh the list of services. You should see your service listed with the DisplayName you assigned in the Installer class. Right-click this service and select Properties to display the Service Properties dialog. Click the "Log On" tab, select "A particular user" and enter the name and password of the user under which this service should run. You may need to create this user. It should have permission to access all resources that the service needs to access. Click the OK button to save these changes and close the dialog.

Of course, you can also write a setup project to your solution if you want to automate the deployment process. This article does not cover that.

To start the service, right-click the service in the Services Manager and select Start.  You can use the same applet to stop the service. Right-click its name and select Stop. Alternatively, you can stop and start the service from the command line with the following commands

NET START <ServiceName>
NET STOP <ServiceName>

where <ServiceName> s the ServiceInstaller ServiceName property specified in our installer class. For our example, we would type. This is a useful if you want to script the starting and stopping of your service.

Windows Services are something that .Net got right very early and hasn't needed to change. Creating useful services is easy with the .Net framework.

You can download a simple Windows service at DGWinSvc.zip (28.05 KB).

 

Wednesday, December 21, 2011 6:23:45 AM (GMT Standard Time, UTC+00:00)
very good post
Comments are closed.