Windows Services Made Simpler, Part 1
In Windows a service is a process whose lifetime is managed by the system rather than by a user. MSDN describes a service as a long running process and often times this is true. Services tend to start before a user logs in (or shortly thereafter) and run until the system shuts down. This is unlike traditional applications that start up after a user logs in and terminates when they log out. While a service looks a lot like a standard application they behave quite a bit differently. In this article I’ll discuss some of the issues around creating a service and ways to make it easier to write and debug one.
Services vs. Programs
One of the first questions that you should ask yourself before deciding to write a service is whether you actually need one or not. Services and normal programs can solve the same problems and each has strengths and weaknesses that should be carefully evaluated before making a decision. Here’s a quick overview of each approach.
- Easy to write and debug
- No special permissions needed to run
- Can run on demand
- Can run on login
- Can be scheduled to run periodically via Task Scheduler
- Responsible for its own lifetime
- Can have a UI
- Has the same access rights as the user running the application
- Harder to write and debug
- Requires administrative rights to start and stop
- Runs based upon the configured options (automatically, manually, etc)
- Can run without any user logged in
- Cannot have a UI
- Must be installed
- Runs until stopped by system, crashes or shutdown
- In general will have more rights to the system then a normal user
In general you should only be looking at writing a service if one or more of the following requirements are needed.
- You need to run a program without requiring a user to log in
- You do not need any UI
- The program needs to run continuously or frequently in order to do work
- The program needs to perform administrative work that a normal user cannot do
The lifetime of a service is completely different than a normal program. The entire lifetime of the service is managed by the Service Control Manager (SCM). A service doesn’t start itself nor does a user start it by clicking on it. The SCM is responsible for telling a service when to start and when to stop. To help keep the system stable the SCM puts restrictions on how long a service can take to do this. Every service in the system is considered to be in one of several different states:
- Pending one of the above states
MSDN has a good chart on the state transitions. The simplified flow is as follows:
- SCM receives a request to start a service so it finds the service information in the registry
- SCM starts the service process if it is not yet running
- SCM moves the service to Start Pending and sends the Start command to the service
- SCM waits for the command to complete (default is 30 seconds)
- If command returns then the SCM moves the service to Started otherwise it fails the call
Stopping the service follows a similar flow:
- SCM receives a request to stop a service
- SCM moves the service to Stop Pending and sends the Stop command to the service
- SCM waits for the command to complete
- If command returns then the SCM moves the service to Stopped otherwise it fails the call
While running a service may be paused and resumed as well. The service must opt into the feature but it allows a service to be paused without stopping it from running.
Service Process vs. Instance
When discussing the lifetime a distinction between the process and instance was made. A single service process can host multiple service instances. This is similar to how a single web site can host multiple web applications. While the entire process is shared by all the services, the SCM sees each service instance as a separate service that can be controlled. This is useful for grouping related services together. Windows itself ships a lot of its services this way (svchost.exe). Because of this separation, a service must not do any work until it is formally started. The use of global objects and statics must be limited since a service may not actually ever run. In managed code a single assembly is created to store all the service instances. Each service instance is a separate class that implements the per-service logic. When the process is started an instance of each service class is created. It is therefore important that the service constructor not do any work since the service may never actually be started. All work in a service must be deferred until the service is explicitly started. We will discuss how to implement the service class later. One of the side effects, and restrictions, of this lifetime management is that it is difficult to debug any code in a service before it is actually started. Once a service is started you can attach the debugger to it. It is therefore in your best interest to limit any complex interactions until after the service starts.
Service Security and UI
Let’s go ahead and get this out of the way now – services CAN NOT have a UI. It doesn’t really make sense anyway. A service is generally run without a user logging in so no one would see it anyway. If you have a requirement for a UI then you will either not write a service or you will need to write a service to do the work and a separate, normal program that displays a UI. You’ll have to implement some sort of IPC for them to communicate. If you think you’ve found a situation where it makes sense, you haven’t so don’t bother using a service. If you do try to display a UI then no one will see it. Starting with Vista all services run in a separate Windows session and desktop. Any UI displayed by a service will be displayed on this separate desktop that normal users cannot see nor switch to. Hence you’ll deadlock your service. In older versions of Windows it was possible to have a UI but this causes more problems then it solves. Starting with Vista there is a new service that monitors this alternate desktop. If it detects a UI on the desktop then it displays an unfriendly message to the current user allowing them to temporarily switch over to the desktop to dismiss the UI. This service is strictly there for backwards compatibility. It is turned off by default and should not be relied upon for new services.
In order to run a service it must be installed. There is no way around this. Services must be installed such that the SCM is aware of them. Installing a service requires administrative privileges. There is no way around that either. A service is considered to be a security sensitive program and therefore should not be taken lightly. There are several different approaches to installing a service.
- sc.exe – The service control program that ships with Windows allows you to install, remove and control services. It is the command line interface to the SCM. It is implementation agnostic so you probably shouldn’t use it for managed services.
- InstallUtil.exe – This is the .NET installer program and it supports services. For this tool to work you have to create installers within your service but this is straightforward so there is no reason not to. The other issue with this tool is that it is not in the system path so you have to find it in the .NET directory on the machine (default is: %systemroot%Microsoft.NETFramework<version>). Note that there is also an x64 version if you need it.
- Installation program – All commercial installation programs support installing services. If you already have an installation program for your application then adding the service should be no problem.
Now that we’ve gone over the highlights for a service we’re now ready to look at creating a service in managed code. Next time I’ll provide an example service and identify ways to make it easier to write, debug and manage.
Download the Demo Code
Download the Library Code