Windows 7 Trigger Start Service






4.98/5 (30 votes)
Implement services that start/stop automatically to some events like Device attached, Network Availability, Firewall port modified, Domain join, Group Policy change etc
- Download WindowsTriggerService_Src.zip - 50.3 KB
- Download WindowsTriggerServiceDemo_Installer.zip - 380.46 KB
Table of Contents
- Introduction
- Introduction to Service Triggers
- Types of Service Events
- Implementation
- Using TriggerInfo option of sc.exe
- Using ChangeServiceConfig2 API
- Query a Service Using QueryServiceConfig2
- To Summarize
- Steps to Test the Application
- References
- History
- Conclusion
Introduction
When the word multitasking comes into mind, we always think of foreground processes that is opened in the taskbar. But this is not true. There are two types of tasks that runs on every computer. First is foreground Tasks, and another is background tasks. Foreground tasks are the process which have user interfaces so that user can easily communicate with the process easily using the application. On the other hand, Background processes are generally hidden processes that runs in our machine and invokes specific requirement of the system to enhance the user experience. Windows Service is the most common way of developing a background process in Windows Environment.
There are lots of services that we all the time while we are logged into windows. To name a few:- Server: It is the main service that enables you to share files, printers etc over network.
- Shell Hardware Detection: It invokes the hardware detection wizard automatically when new hardware is attached with your computer.
- Network Connections : Invokes Network Connection Folder for network management. Etc...
There are lots of services there in our windows environment which works in background, polls throughout the session with few active long running threads.This results in slower performance, larger number of useless process running, killing up a portion of CPU, and hence slow performance of the overall user experience.
If you want to know more about windows 7 usage other than Service Triggers just visit :
Windows 7 : New Features Explained Using .NET
Introduction to Service Triggers
Windows 7 (Later with Windows Server 2008) puts forth a new concept to start services based on some events. The background services once configured
to the computer will automatically start itself when the event occurs to the system. Thus eliminates the concept of running
background services infinitely. Thus we will have lesser number of background threads running on the system and also lesser amount of memory is
blocked, and hence better performance of Foreground process. Removing background threads also reduces the overall
power consumption of the system as well.
To elaborate the scenario, lets say you are about to build a service that gets some updates from the
server only when the computer is connected to the Internet. So what will be your steps :
- Create a service and override the OnStart event of the Service do an Infinite loop and check Whether network is available.
- Ping the Server if service is available or not.
- If service is available do the respective updates, otherwise poll it again and do the same job over and over.
In this article you will learn how to configure the Service Triggers, so that it enables and disables your service according to your need.
Types of Service Events
By the type of Service Events I mean the types of events that currently the API supports that you can use in your application.- Device Join : This trigger is fired when any device is joined to the system. Say printer is attached, USB device is installed like (Pen Drive, Bluetooth Dongle etc)
- Domain Join : This trigger is fired when the computer is attached to a Domain. Generally we track this event to do specific tasks which you want to execute when computer domain server is changed.
- Firewall Event : Say you have exempted one port in the Firewall. This event will be triggered when any change in firewall settings is made.
- Group Policy Change : Group policies defines the actual restrictions to the system that windows will impose during the session is on progress. If certain Group policies are modified, this event gets triggered automatically.
- Network IP Availability : This event is triggered when the first IP address on the TCP/IP networking stack becomes available to the system. You might look into the events like FirstIPAvailable, or LastIPAvailable and run /stop your service accordingly.
- Custom Event : You can also define your own custom events which is generated by Event Tracing of windows.
Implementation
To implement service triggers in your application you have two choices:
- Configure using Command Tool sc.exe (Service Configuration Command Line) together with TriggerInfo switch.
- Use ChangeServiceConfig2 API to configure the service programmatically.
- A Trigger Event Type
- A Trigger Event Sub Type
- The Action that you want to take in response to the trigger
- Trigger specific Data Items that you need to pass. The Data depends on the actual used Event type
Using TriggerInfo option of sc.exe
Windows 7 and Windows 2008 introduced a new option to Service Configuration tool sc.exe to configure and query
services that have supported triggers associated with it. Open CMD and type sc triggerinfo
you will see
all the options that are available to handle trigger info.
Usage:
sc <server> triggerinfo [service Name] <option1> <option2>...
The server is optional which defaults to Local Computer.
- start/device/UUID/HwId1/.. : Specifies a start of a service when specific device of UUID (ClassID) and HwId1, HwId2 ... are attached.
- start/custom/UUID/data0/.. : Specifies a start of a custom service provided by Event Tracer based on hexadecimal data passed.
- stop/custom/UUID/data0/.. : Stops the custom ETW service
- start/strcustom/UUID/data0/.. : Specifies a start of a custom service when data is passed as string
- stop/strcustom/UUID/data0/.. : Stops the custom string based custom service
- start/networkon :Determines the start of a service when first IP is available
- stop/networkoff : Stops the service when last IP is unavailable
- start/domainjoin : Starts the service when domain is joined
- stop/domainleave : Stops the service when Domain is left
- start/portopen/parameter : Configures the service to start when a specified port is opened through the firewall. You can mention the parameter with ;(semicolon) delemeter. viz. portnumber;protocolname;imagepath;servicename
- stop/portclose/parameter : Stops the service when port is unavailable.
- start/machinepolicy : Starts a service when machine policy is modified.
- start/userpolicy : Starts a service when user group policy is modified
Therefore to configure a service that will start automatically when the system is attached to a Domain server you can just
execute:
sc triggerinfo myservice start/domainjoin
The service "myservice" will automatically configured to start when domain is joined to the local computer.
To query if the service is configured properly as Triggered Start you can use qtriggerinfo
switch with the servicename in sc.exe.
Usage:
sc <server> qtriggerinfo [service Name] <buffer size>
Thus if you call
sc qtriggerinfo myservice

It will return you the info regarding how the service is configured to Trigger start when domain is joined.
Using ChangeServiceConfig2 API
Services can also be configured to trigger by invoking ChangeServiceConfig2
API method with SERVICE_CONFIG_TRIGGER_INFO
.
The SERVICE_TRIGGER_INFO
structure points to an array of SERVICE_TRIGGER
structures, each specifying one trigger event.
The structure of SERVICE_TRIGGER
and SERVICE_TRIGGER_INFO
looks like:
public struct SERVICE_TRIGGER { /// The trigger event type. public ServiceTriggerType dwTriggerType; /// The action to take when the specified trigger event occurs. public ServiceTriggerAction dwAction; /// Points to a GUID that identifies the trigger event subtype. The value /// of this member depends on the value of the dwTriggerType member. public IntPtr pTriggerSubtype; /// The number of SERVICE_TRIGGER_SPECIFIC_DATA_ITEM structures in the /// array pointed to by pDataItems. public uint cDataItems; /// A pointer to an array of SERVICE_TRIGGER_SPECIFIC_DATA_ITEM /// structures that contain trigger-specific data. public IntPtr pDataItems; } public struct SERVICE_TRIGGER_INFO { /// The number of triggers in the array of SERVICE_TRIGGER structures /// pointed to by the pTriggers member. public uint cTriggers; /// A pointer to an array of SERVICE_TRIGGER structures that specify the /// trigger events for the service. public IntPtr pTriggers; /// This member is reserved and must be NULL. public IntPtr pReserved; }
The structure is predefined and should not be changed. Now to call the API we will declare the API methods and corresponding structures. We will use the
ServiceNative.cs provided by Microsoft which you can find in the sample code associated with this application.
It uses advapi32.dll
to P/Invoke native methods likeChangeServiceConfig2
, QueryServiceConfig2
etc
and also defines related structs and enumerations like SERVICE_TRIGGER
, SERVICE_TRIGGER_INFO
, SERVICE_TRIGGER_SPECIFIC_DATA_ITEM
etc.
You can call ChangeServiceConfig2
to configure the service and hence check if the service is properly installed or not using QueryServiceConfig2
.
SERVICE_TRIGGER
contains few important important members. dwTriggerType
defines the event type that you are about to define
for which the service is going to react. The value of dwTriggerType
can be :
SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL
(1): The event is triggered when a specific device type defined inpTriggerSubtype
as interface guid that arrivesSERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY
(2) : The event is triggered when first Network IP is available and vice versa.SERVICE_TRIGGER_TYPE_DOMAIN_JOIN
(3) : The event is triggered when system is joined to a Domain.pTriggerSubtype
should beDOMAIN_JOIN_GUID
(1ce20aba-9851-4421-9430-1ddeb766e809) orDOMAIN_LEAVE_GUID
(ddaf516e-58c2-4866-9574-c3b615d42ea1).SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT
(4) : This event is triggred when firwall specific port is opened or 60 seconds after the port is blocked. ThepTriggerSubtype
should be specified withFIREWALL_PORT_OPEN_GUID
(b7569e07-8421-4ee0-ad10-86915afdad09) orFIREWALL_PORT_CLOSE_GUID
(a144ed38-8e12-4de4-9d96-e64740b1a524). You can usepDataItems
to define port, protocol or executable path.SERVICE_TRIGGER_TYPE_GROUP_POLICY
(5) : This event triggered when Machine Group policy is modified.pTriggerSubtype
can haveMACHINE_POLICY_PRESENT_GUID
(659FCAE6-5BDB-4DA9-B1FF-CA2A178D46E0) orUSER_POLICY_PRESENT_GUID
(54FB46C8-F089-464C-B1FD-59D1B62C3B50).SERVICE_TRIGGER_TYPE_CUSTOM
(20) : This event is triggrred by Event Tracer.pTriggerSubtype
could be used to define event provider's GUID.
The dwAction
defines the action that we are going to take when the event is triggered. dwAction can have two values :
SERVICE_TRIGGER_ACTION_SERVICE_START
: Specified to start the service.-
SERVICE_TRIGGER_ACTION_SERVICE_STOP
: Specified to stop the service.
Now lets create a service that will detect if network is connected and based on which It will produce a log entry(to make it the most simple) in text file. We can check the log file and see our custom message for testing. In your original application, the corresponding code will be executed on these events.
To write log we use :
private void WriteLog(string message) { using (FileStream fs = new FileStream( Environment.GetFolderPath(Environment.SpecialFolder.System), FileMode.Append, FileAccess.Write)) { using (StreamWriter sw = new StreamWriter(fs)) { sw.WriteLine(DateTime.Now.ToString() + "\t" + message); sw.Close(); } fs.Close(); } }
This function will write the message with DateTime
to the system folder. From the OnStart
and OnStop
event of the service
we wrote some silly messages to detect actually what is happenning.
Finally we need to configure the service by calling ChangeServiceConfig2
after the service gets installed in the machine.
To do this the best candidate should be the AfterInstall
event of the ProjectInstaller. The event occurs Just after the service is
installed and thus it is the most right place to do this.
Right click on the Designer view of the and add Installer.
A project installer will be added In the project installer. The project installer will have two objects created within it.
Lets rename the ServiceInstaller1
to triggerStartServiceInstaller
(You can leave it as it is) and change the
ServiceName
to WindowsTriggerService
.
Open the code window and write the code below inside the AfterInstall of the Project installer.
if (Environment.OSVersion.Version >= new Version(6, 1)) { //Means we find TriggerInfo supported Operating system this.IpArrivalTriggerConfigure(triggerStartServiceInstaller.ServiceName); } else { throw new NotSupportedException(); }
You should note that Trigger start services are introduced in Windows 7 and you can use it in Windows 2008 R2. Thus we need to avoid installing it in lower version of windows.
IpArrivalTriggerCapture
is my own function that
is used to configure the service. triggerStartServiceInstaller
represents the serviceInstaller.
You need to configure the Serviceinstaller 's servicename property to your own service.
To create the configuration section first, we need to Marshall the structs that points to the Network FirstIP arrival GUID and LastIP Removal GUID to its native pointers. Lets allocate a pointer and Marshall the structure to a pointer using the following code :
private IntPtr GetGUIDStructToPointer(Guid structure) { IntPtr pointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))); Marshal.StructureToPtr(structure, pointer, false); return pointer; }
Marshall.AllocHGlobal
is used to allocate memory pointer where we can marshall the structure. The pointer is returned.
private SERVICE_TRIGGER AllocateServiceTrigger(ServiceTriggerAction action, ServiceTriggerType type, IntPtr subType) { SERVICE_TRIGGER st = new SERVICE_TRIGGER(); st.dwTriggerType = type; st.dwAction = action; st.pTriggerSubtype = subType; }
We use AllocateServiceTrigger
to get the SERVICE_TRIGGER
. We define ServiceTriggerAction
, ServiceTriggerType
and its SubType
.
The Device Data can also be configured here using SERVICE_TRIGGER_SPECIFIC_DATA_ITEM
to pDataItems member, but it is not required in current context.
private IntPtr GetServiceTriggerInfo(IntPtr serviceTriggers, uint no_of_trigger) { SERVICE_TRIGGER_INFO serviceTriggerInfo = new SERVICE_TRIGGER_INFO(); serviceTriggerInfo.cTriggers = no_of_trigger; serviceTriggerInfo.pTriggers = serviceTriggers; // Marshal the SERVICE_TRIGGER_INFO struct to native memory IntPtr ServiceTriggerInfoPointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof( SERVICE_TRIGGER_INFO))); Marshal.StructureToPtr(serviceTriggerInfo, ServiceTriggerInfoPointer, false); return ServiceTriggerInfoPointer; }
In this method I have allocated SERVICE_TRIGGER_INFO
structure, marshall it to the memory and return back the pointer to the base location.
Finally lets look into the IPArrivalTriggerConfigure
method :
private bool IpArrivalTriggerConfigure(string serviceName) { Guid NETWORK_MANAGER_FIRST_IP_ADDRESS_ARRIVAL_GUID = new Guid("4F27F2DE-14E2-430B-A549-7CD48CBC8245"); Guid NETWORK_MANAGER_LAST_IP_ADDRESS_REMOVAL_GUID = new Guid("CC4BA62A-162E-4648-847A-B6BDF993E335"); using (ServiceController sc = new ServiceController(serviceName)) { try { IntPtr GuidIpAddressArrivalPointer = this.GetGUIDStructToPointer( NETWORK_MANAGER_FIRST_IP_ADDRESS_ARRIVAL_GUID); IntPtr GuidIpAddressRemovalPointer = this.GetGUIDStructToPointer( NETWORK_MANAGER_LAST_IP_ADDRESS_REMOVAL_GUID); SERVICE_TRIGGER serviceTrigger1 = this.AllocateServiceTrigger( ServiceTriggerAction.SERVICE_TRIGGER_ACTION_SERVICE_START, ServiceTriggerType.SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, GuidIpAddressArrivalPointer); SERVICE_TRIGGER serviceTrigger2 = this.AllocateServiceTrigger( ServiceTriggerAction.SERVICE_TRIGGER_ACTION_SERVICE_STOP, ServiceTriggerType.SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, GuidIpAddressRemovalPointer); IntPtr pServiceTriggers = Marshal.AllocHGlobal( Marshal.SizeOf(typeof(SERVICE_TRIGGER)) * 2); Marshal.StructureToPtr(serviceTrigger1, pServiceTriggers, false); Marshal.StructureToPtr(serviceTrigger2, new IntPtr((long)pServiceTriggers + Marshal.SizeOf(typeof(SERVICE_TRIGGER))), false); IntPtr ServiceTriggerInfoPointer = this.GetServiceTriggerInfo(pServiceTriggers, 2); bool bSuccess = ServiceNative.ChangeServiceConfig2( sc.ServiceHandle.DangerousGetHandle(), ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, ServiceTriggerInfoPointer); int errorCode = Marshal.GetLastWin32Error(); // Clean up the native resources Marshal.FreeHGlobal(GuidIpAddressArrivalPointer); Marshal.FreeHGlobal(GuidIpAddressRemovalPointer); Marshal.FreeHGlobal(pServiceTriggers); Marshal.FreeHGlobal(ServiceTriggerInfoPointer); // If the service failed to be set as trigger start, throw the error // returned by the ChangeServiceConfig2 function. if (!bSuccess) { Marshal.ThrowExceptionForHR(errorCode); return false; } else return true; } catch { return false; } } }
In the above code we first create pointers to the respective GUIDs to invoke the NETWORK_MANAGER_FIRST_IP_ADDRESS_ARRIVAL_GUID
. The pointer is used
to AlloacateServiceTrigger object. As we are registering the service to start and stop both, we have to create two object of SERVICE_TRIGGER.
We Allocate an array of SERVICE_TRIGGER
and get the pointer to the structure.
Finally we create a pointer to the SERVICE_TRIGGER_INFO
using GetServiceTriggerInfo
method.
The API ChangeServiceConfig2 is called to configure the Service with ServiceTriggerInfoPointer.
bool bSuccess = ServiceNative.ChangeServiceConfig2( sc.ServiceHandle.DangerousGetHandle(), ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, ServiceTriggerInfoPointer);The first argument is Service Handle pointer. We will get the ServicePointer from ServiceController.ServiceHandle, we pass
SERVICE_CONFIG_TRIGGER_INFO
as second argument as it determines that the Service we are now going to
configure is ServiceTrigger. And finally ServiceTriggerInfoPointer
is the pointer to configure the service as ServiceTrigger.Query a Service Using QueryServiceConfig2
Now let us look on how we can call QueryServiceConfig2
to determine if the service is already registered to Start Trigger Service. Lets look at the code below :
private bool IsQueryService(string serviceName) { using (ServiceController sc = new ServiceController(serviceName)) { int outBytes = -1; ServiceNative.QueryServiceConfig2( sc.ServiceHandle.DangerousGetHandle(), ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, IntPtr.Zero, 0, out outBytes); if (outBytes == -1) { // Failed to allocate bytes return false; } IntPtr pBuffer = Marshal.AllocHGlobal(outBytes); try { if (ServiceNative.QueryServiceConfig2(sc.ServiceHandle.DangerousGetHandle(), ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, pBuffer, outBytes, out outBytes)!= 0) { //win32 Exception occurred. return false; } SERVICE_TRIGGER_INFO sti = (SERVICE_TRIGGER_INFO) Marshal.PtrToStructure(pBuffer, typeof(SERVICE_TRIGGER_INFO)); return (sti.cTriggers > 0); } catch{} finally { // Free the memory if (pBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(pBuffer); } } }// end using }
If you inspect the above method, I have called the API QueryServiceConfig2
two times, the first call is made with null pointer. This call will fail as we are not passing any service handler to it and this is basically to determine how many bytes needed to allocate for the current system to query the service.
For the next call, which will give you the actual result we first allocate a buffer with the same size as retrieved from the first call with Null Pointer.
ServiceNative.QueryServiceConfig2(sc.ServiceHandle.DangerousGetHandle(), ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, pBuffer, outBytes, out outBytes)
We pass the Service handle to the API as first argument. The second argument is the Trigger Info Configuration(0x00000008) which is already defined in the ServiceNative. pBuffer
is the allocated Buffer pointer with the outBytes
.
The API will return 1 if the Trigger is found, otherwise returns 0.
To get the actual TriggerInfo object we Marshall the pBuffer
to SERVICE_TRIGGER_INFO
as below :
SERVICE_TRIGGER_INFO sti = (SERVICE_TRIGGER_INFO) Marshal.PtrToStructure(pBuffer, typeof(SERVICE_TRIGGER_INFO));
Thus we find the information of the Trigger info easily using API QueryServiceConfig2
.
To Summarize
Before we conclude let us Summarize the concept :
- There are two option to Configure a Service to Trigger Start, First using sc.exe with
Triggerinfo
attribute, and another programmatically. AsInstallUtil
which installs .NET services does not supportTriggerInfo
switch, we did usingChangeServiceConfig2
API method defined inadvapi32.dll
ChangeServiceConfig2
requires a pointer toServiceTriggerInfo
structure.- The
SERVICE_TRIGGER_INFO
should be assigned toSERVICE_TRIGGER
objects each of which points to the service events that we want to configure. SERVICE_TRIGGER
configures the type of each event.
Steps to Test the Application
- Install the application.
You can either use
Installutil.exe windowstriggerservice.exe
, (if current windows supports Trigger Start service, the command prompt will print "Configuring trigger-start service") or you can use Setup Application. After you install you can inspect if the service console if the service is installed properly or not. You can see the service listed in the Services.msc applet.
- Check in Windows\System32 folder and check the file TriggerServiceLog.txt is present.
- Now to check even further, open Command Prompt and type
sc qtriggerinfo WindowsTriggerService
You can see if the Appropriate Triggers are installed as shown in the figure - Now, Open Network properties and Disable Network.
- Inspect the TriggerServiceLog.txt file to see if there is any changes made. You will find a message "Service Stopped as LastIP removed"
- Finally, Enable the Network again and inspect the file. You will see that the trigger is started automatically again.
References
- Service Trigger Events
- MSDN Article on TriggerStart Service
- Understanding Windows Trigger Start Service
History
4 Jan 2010 : Initial Post
11 Jan 2010 : Added the use of QueryServiceConfig2 to query for Trigger Service
Conclusion
I have tried to build the sample application as simple as possible to ensure that even a newbie could understand
what is required to do.
Thanks for Reading, also love to see your feedback.