We introduced
Service 0 Isolation a few weeks ago as an application compatibility topic. It is only natural that we continue our conversation about services in the context of Windows 7. But this time, we will talk about some of the benefits to service optimization that are available in Windows 7. This post focuses on a new feature in Windows 7 called
Trigger Start Services. But before we jump into the API, let’s provide some background about services.
What Are Services?
A service is an integral mechanism built into Microsoft Windows operating systems. You can think of services as “special applications” that run with no regard to the current user context. Services are different from “regular” user applications because you can configure a service to run from the time a system starts up (boots) until it shuts down, without requiring an active user to be present – that is, services can run even though no users are logged on.
We like to think about services as running tasks for us in the background without interfering with user operations. Services on Windows are responsible for all kinds of background activity that do not involve the user, ranging from the Remote Procedure Call (RPC) service, through Printer Spoolers, to the Network Location Awareness service.
Over the years, Windows has grown and with it the number background services. But to be honest, background services in Windows are a pain – the operating system ships with a lot of them in the box. On top of that, ISVs and their applications add even more services, like software updates to name only one. With that said, some of these services are critical and are required during boot sequences, some are required later when a specific user logs on, while others don’t need to execute until they are called upon. Nonetheless, when you look at the currently running services, you see
a lot of services that really don’t need to run 24x7.
What’s Wrong with Services Running 24x7?
There are several issues with having services run 24x7:
First, why have something run (even in the background) when there is no need for it to run? Any running process (services included) uses valuable memory and CPU resources that could be used by other applications and services. If you total up all the services that are running at any given time, they add up to quite a lot of memory, handles, threads, and plenty of CPU usage. All of these “wasted” resources reduce the overall computer performance, decrease its responsiveness, and make users think their computers are sluggish and slow. Also, since most of the running services are configured as Auto-Start (start running upon system log-on), these services have an impact on the computer's boot time.
Second, these wasted resources, have a direct impact on power consumption. The more demands we place on the CPU, the more power our computer uses. This can be critical for laptops, and could reduce battery life from four hours to three hours.
Third, having non-productive software run all the time may lead to memory leaks and overall system instability. This can lead to application crashes and ultimately computer crashes.
Last, but not least, if a service is running 24x7, and this services is well known (any popular application might have one – like the PDF Reader), it provides a larger attack surface. A hacker might use the knowledge that a certain popular application installs a service that runs 24x7, and try to hack into that service to gain privileged access to the computer.
Given all of the above, it makes you wonder why so many developers configure their services to run all the time when there are other options. Even before Windows 7, there were several service start-up options:
- Disabled completely disables the service and prevents it and its dependencies from running—this means that the user must start the service manually from the Control Panel or the command line
- Manual starts a service as required (defined by dependencies to other services) or when called from an application using the relevant API as shown later in this post
- Automatic starts the services at system logon
- Automatic Delayed is a newer startup type introduced in Windows Vista that starts the service after the system has finished booting and after initial demanding operations have completed, so that the system boots up faster
Unfortunately, many ISVs (Microsoft included) still choose to configure their services to Automatic (or Automatic Delayed) because it is the easy solution for everyone. A service simply runs 24x7 and is always available, eliminating the need to check any dependencies or verify that the service is running.
There are many examples of existing services that can become more resource friendly and more secure by not running 24x7. For example, think of an update service that checks for new application updates. If the computer is not connected to a network and has no IP available, why should the update service run? It can't reach anywhere, so why run a program that does nothing? Think about a policy management service that is invoked when a group policy changes or when the computer joins or leaves a domain, but right now the computer is connected to my home network and again the service works in vain.
Introducing Windows 7 Trigger Start Services
The solution for the above problems is to move the service out of its “forever running state” into other types of background activity, such as scheduled tasks or trigger-start services. This post focuses on Windows 7 Trigger Start Services. Windows 7 Scheduled Tasks include a lot of valuable information that we will describe in another post.
Trigger-start services are new to Windows7. A trigger-start service is a regular service that you can configure to run (or stop running) only when it is triggered, that is, only when certain criteria and conditions that you define are met (for example, when the first network IP address becomes available, or when the last network IP is lost). Here is a list of the available triggers that you can use to configure the Start-Up mode of a given service:
- Device interface arrival or departure
- Joining or leaving a domain
- Opening or closing a firewall port
- Group policy change
- First IP address available/ last IP address leaving
- Custom event – Event Tracing for Windows (ETW)
The last item in the list represents the extendibility point. As a developer, you can configure any ETW event as a trigger for services, which gives you a very good tool to fine-tune your control over starting and stopping services from your application.
So what exactly is a trigger?
A trigger consists of:
- A trigger event type
- A trigger event subtype
- The action to be taken in response to the trigger event
- One or more trigger-specific data items (for certain trigger event types)
The subtype and the trigger-specific data items together specify the conditions for notifying the service of the event. The format of a data item depends on the trigger event type; a data item can be made up of binary data, a string, or a multistring.
Working with Trigger Start Services
Unfortunately, Windows 7 Services MMC UI does not include a graphical representation of the trigger start services. However, you have two options. You can still use the old and good sc.exe (Service Configuration command line tool), or you can use the WIN32
ChangeServiceConfig2 method to configure the service start option programmatically as demonstrated in this post.
Using SC.exe to Query Service Trigger Information
It's time to start have some fun. First, let’s start with just extracting some configuration information from a few services. The generic form for using the service configuration is:
sc [command] [service name] ...Where
server is optional and by default you work with the local computer:.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }
- command is the operation you wish to perform like querying trigger information
- service name is the name of the service you wish to work with
- options are the different values (options) you can pass to configure the service
Let’s start by querying a specific service for its trigger start configuration. To do so you need to launch a Windows Shell window:
- Open the start menu.
- Type CMD in the search box.
- Choose cmd.exe.
This will open a Windows Shell window. - Type sc qtriggerinfo w32time and press enter
This is how it should look:
As you can see, we queried the trigger information of the W32time service, which is configured to start when the computer is joined to a domain and stop when the computer leaves the domain.
Microsoft updated the sc.exe command-line tool for Windows 7 to support configuring and querying a service for supported triggers. Type sc triggerinfo in the Windows shell window and press enter. The result looks like the box below, and lists all the different triggers and how to configure a service to use trigger start services.
C:\>sc triggerinfoDESCRIPTION: Changes the trigger parameters of a service.USAGE: sc triggerinfo [service name] ...OPTIONS: start/device/UUID/HwId1/... start/custom/UUID/data0/.. stop/custom/UUID/data0/... start/strcustom/UUID/data0/.. stop/strcustom/UUID/data0/.. start/networkon stop/networkoff start/domainjoin stop/domainleave delete .csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }For example, to configure a service to start when the first IP address becomes available, all you need to do is type
sc triggerinfo [your service name] start/networkon, where “your service name” is replaced with the name of the service that you wish to configure.
Configuring Trigger Start Services Programmatically Using ChanceServiceConfig2
The more interesting aspect, from a developer's point of view, is writing services that are trigger aware and using code to configure a service. In Windows 7, you can use the
ChangeServiceConfig2 function to configure service trigger information and the
QueryServiceConfig2 function to query it.
Service trigger registration is performed by calling
ChangeServiceConfig2, passing SERVICE_CONFIG_TRIGGER_INFO for the
dwInfoLevel parameter, and providing the trigger registration information in a SERVICE_TRIGGER_INFO structure through the
lpInfo parameter. In addition, one or more trigger-specific data items can be specified. The following is an example of a service installer function that creates a USB device trigger for a service that is named
MyService:
define SERVICE_NAME L"MyService"
//set the device guid
static const GUID GUID_USBDevice = {
0x53f56307, 0xb6bf, 0x11d0,
{0x94, 0xf2, 0x00, 0xa0, 0xc9,
0x1e, 0xfb, 0x8b }};
BOOL _SetServiceToStartOnDeviceTrigger()
{
BOOL fResult = FALSE;
SC_HANDLE hScm = OpenSCManager(
NULL, //local machine
NULL, //active database
SC_MANAGER_CONNECT);
if(hScm != NULL)
{
SC_HANDLE hService = OpenService(
hScm,
SERVICE_NAME,
SERVICE_ALL_ACCESS);
If( hService != NULL)
{
LPCWSTR lpszDeviceString = L"USBSTOR\\GenDisk";
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData = {0};
deviceData.dwDataType = SERVICE_TRIGGER_DATA_TYPE_STRING;
deviceData.cbData =
(wcslen(lpszDeviceString)+1) * sizeof(WCHAR);
deviceData.pData = (PBYTE)lpszDeviceString;
SERVICE_TRIGGER st;
st.dwTriggerType =
SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;
st.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;
st.pTriggerSubtype = (GUID *) &GUID_USBDevice;
st.cDataItems = 1;
st.pDataItems = &deviceData;
SERVICE_TRIGGER_INFO sti;
sti.cTriggers = 1;
sti.pTriggers = &st;
sti.pReserved = 0;
fResult = ChangeServiceConfig2(
hService,
SERVICE_CONFIG_TRIGGER_INFO,
&sti);
}
CloseServiceHandle (hService);
}
CloseServiceHandle (hScm);
if(!fResult)
{
printf("Service trigger registration failed (%d)\n",
GetLastError());
}
return fResult;
}
Note: all services are controlled by the Service Control Manager (SCM), which we’ll touch on in a different post.
In the above code snippet, you can see that first we get a handle (hScm) to the SCM by calling
openSCManager. Next, we call
openService and pass the handle to the SCM- hscm,and the service name – SERVICE_NAME that we wish access. The last parameter, SERVICE_ALL_ACCESS, indicates that we have complete access to the services. Assuming we got a valid handle to the service, we now start to build the specific structure that we’ll soon use to configure the service.
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM defines the trigger event type. It contains trigger-specific data for the service trigger event. In our case we define the string that represents a USB gen disk arrival.
Next we define the SERVICE_TRIGGER structure, which represents a service trigger event. Note that this is where we define the trigger type (device arrival), the action (start the service), and the trigger sub type (the specific family of the USB device). Then we define one device that will trigger the service. Note that you can define an array of devices and their GUIDs. You should also note that we don’t want the service to be triggered upon just any USB device arrival like a mouse or a camera. We want the service to start only when a USB disk arrives.
Finally, we define the SERVICE_TRIGGER_INFO structure, which contains trigger event information for a service. This structure simply points to the SERVICE_TRIGGER struct that we defined previously, and the number of triggers that, in this case, is one.
Now we can call the ChanceServiceConfig2 function and pass the handle to the service we wish to configure, a SERVICE_CONFIG_TRIGGER_INFO parameter that indicates that we wish to configure the service trigger, and a null.
That is all there is to it. If we are successful, then our service will run after we insert a USB hard drive.
In the next post, we'll review how to write a simple implementation of a .NET service that we’ll program to start upon arrival of a USB generic disk.
You can learn about Windows 7 using the
Windows 7 Training Kit for Developers or by viewing Windows 7 videos on
Channel 9.
You can also get
hands-on experience for Windows 7 Trigger Start Services using the
Windows 7 Online training that is part of the
Channel 9 Learning Center