Windows 7 Trigger-Start Services

February 25, 2009

2 comments

I’m interrupting our scheduled series on the Windows 7 taskbar to talk about something entirely different: Trigger-start services.  (Don’t worry, we will resume the exhaustive overview of all taskbar features fairly soon.)

Over the years, as Windows has become more complicated, more and more background services were added to the system.  Componentization and new features are the two primary forces behind this current.  Hardware and software manufacturers have jumped the opportunity – webcams, IM clients, MP3 players and photo editing applications all seem to have a burning desire and need for a background service doing something.

And these “somethings” accumulate.  The user might not know or even care that he has 90 services running on the system, but he sure will feel their impact even on modern hardware.  The more background services in the system, the longer the boot and shutdown times, one of the main latency issues plaguing the Windows OS.  The more background services in the system, the more resources they consume – memory utilization, I/O and CPU, as well as the adverse effect on power consumption.  The more background services in the system, the wider the security attack surface becomes, especially considering that many services run with the highest system credentials (the LocalSystem built-in account).

The only solution for this stack of problems is moving code out of permanently-running services and into other hosts of background activity – scheduled tasks and trigger-start services.  Scheduled tasks have been available in previous versions of Windows, but their true blossom was in Windows Vista, when a multitude of core OS functionality was moved into tasks, which we won’t be discussing within the scope of this post.

image

Trigger-start services are a brand new mechanism present in Windows 7.  A trigger-start service is not always running – it is triggered by one of a set of triggers, ranging from the availability of a network IP address and all the way to the arrival of a specific hardware device.  When the service completes its work, or when another trigger occurs, it typically terminates until the start trigger happens again.

Examples might include an antivirus on-demand scan service which scans removable disk drives as they are plugged into the system; a media player service which automatically starts when a media device is detected; a policy management service that is invoked when group policy changes or when the computer joins or leaves a domain; – and I’m sure that you could come up with a few great examples of your own.

Configuring a service to trigger-start is not trivial, considering that the Windows 7 Services MMC UI does not have any graphical knobs for this task.  However, the good old sc.exe utility is always there to the rescue, and there’s also the hardcore way of doing this through code with ChangeServiceConfig2.

Making a service trigger-start with sc.exe

So the first way is relatively straight-forward.  Querying a service for its triggers is only a single line away (within an elevated command prompt):

sc qtriggerinfo <SERVICENAME>

Configuring a service to trigger-start when the computer acquires its first IP address is similarly easy:

sc triggerinfo <SERVICENAME> start/networkon

More complicated configuration options, such as starting the service upon arrival of a specific hardware device, will all become clear if you just run sc triggerinfo on the command line and read all it has to tell you.

Making a service trigger-start from code

From code, hmm.  Well, we will have to use ChangeServiceConfig2 which I mentioned above.  A purely managed wrapper is possible, but due to the nature of these APIs it’s much easier to work with them from C++, so C++/CLI is the way to go if you need these features in a managed application.

The following C++/CLI code determines whether the specified service is configured to trigger-start, without querying its specific triggers:

bool ServiceControl::IsServiceTriggerStart(System::String^ serviceName)

{

    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager == NULL)

    {

        throw Marshal::GetExceptionForHR(GetLastError());

    }

 

    marshal_context context;

 

    SC_HANDLE hService = OpenService(hSCManager,

        context.marshal_as<LPCWSTR>(serviceName), SERVICE_ALL_ACCESS);

    if (hService == NULL)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    DWORD cbBytesNeeded = (DWORD)-1;

    QueryServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, NULL, 0, &cbBytesNeeded);

    if (cbBytesNeeded == (DWORD)-1)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

    PBYTE lpBuffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbBytesNeeded);

 

    if (QueryServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, lpBuffer, cbBytesNeeded, &cbBytesNeeded) == FALSE)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        HeapFree(GetProcessHeap(), 0, lpBuffer);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    PSERVICE_TRIGGER_INFO pTriggerInfo = (PSERVICE_TRIGGER_INFO)lpBuffer;

    BOOL bTriggerStart = pTriggerInfo->cTriggers > 0;

 

    HeapFree(GetProcessHeap(), 0, lpBuffer);

 

    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

 

    return bTriggerStart;

}

The following C++/CLI code configures the specified service to trigger-start when a USB storage device (e.g. a disk on key) is inserted into the system:

static const GUID GUID_USBDevice = {

    0x53f56307, 0xb6bf, 0x11d0, {0×94, 0xf2, 0×00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b }};

 

void ServiceControl::SetServiceTriggerStartOnUSBArrival(System::String^ serviceName)

{

    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (hSCManager == NULL)

    {

        throw Marshal::GetExceptionForHR(GetLastError());

    }

 

    marshal_context context;

 

    SC_HANDLE hService = OpenService(hSCManager,

        context.marshal_as<LPCWSTR>(serviceName), SERVICE_ALL_ACCESS);

    if (hService == NULL)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    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 serviceTrigger = {0};

    serviceTrigger.dwTriggerType = SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;

    serviceTrigger.dwAction = SERVICE_TRIGGER_ACTION_SERVICE_START;

    serviceTrigger.pTriggerSubtype = (GUID*)&GUID_USBDevice;

    serviceTrigger.cDataItems = 1;

    serviceTrigger.pDataItems = &deviceData;

 

    SERVICE_TRIGGER_INFO serviceTriggerInfo = {0};

    serviceTriggerInfo.cTriggers = 1;

    serviceTriggerInfo.pTriggers = &serviceTrigger;

 

    if (ChangeServiceConfig2(hService, SERVICE_CONFIG_TRIGGER_INFO, &serviceTriggerInfo) == FALSE)

    {

        DWORD dwLastError = GetLastError();

        CloseServiceHandle(hService);

        CloseServiceHandle(hSCManager);

        throw Marshal::GetExceptionForHR(dwLastError);

    }

 

    CloseServiceHandle(hService);

    CloseServiceHandle(hSCManager);

}

Summary

Clearly, developing trigger-start services might be more difficult than a resident process that runs all the time from boot to shutdown.  However, the positive effect on the user’s environment from the reliability, security, performance and power consumption perspectives often outweighs this minor inconvenience.  If you’re developing a background service for Windows today, see if you can make it a scheduled task or a trigger-start service instead.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

2 comments

  1. Vijay SanthanamMarch 1, 2009 ב 3:29 PM

    it’s a shame there isn’t a managed API for this.

    Reply
  2. Core Technologies ConsultingDecember 3, 2009 ב 11:25 PM

    Hi. We have recently released “Service Trigger Editor”, a free GUI utility for adding/editing/viewing Service Triggers:
    http://www.coretechnologies.com/products/ServiceTriggerEditor/

    For those who want to explore Trigger-Start services, it is a good alternative to using the SC.exe command (or writing your own code).

    Reply