Table of Contents
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
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.
Now if you look into the service, you would have noticed that you are running the service throughout the day without running a single
line of statement. I mean if the computer doesnt connect to the internet, every time the Ping to server fails and
hence the loop is going on without anything to do. In such a scenario, Start triggers comes into play.
In this article you will learn how to configure the Service Triggers, so that it enables and disables your service
according to your need.
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.
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.
Before we start creating the actual service let see what are the main components of a Trigger.
Triggers consists of :
- 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
Now lets start configuring service triggers:
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.
The options that you might use are :
- 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.
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
{
public ServiceTriggerType dwTriggerType;
public ServiceTriggerAction dwAction;
public IntPtr pTriggerSubtype;
public uint cDataItems;
public IntPtr pDataItems;
}
public struct SERVICE_TRIGGER_INFO
{
public uint cTriggers;
public IntPtr pTriggers;
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 in pTriggerSubtype
as
interface guid that arrives
SERVICE_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 be DOMAIN_JOIN_GUID
(1ce20aba-9851-4421-9430-1ddeb766e809) or
DOMAIN_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. The pTriggerSubtype
should be
specified with FIREWALL_PORT_OPEN_GUID
(b7569e07-8421-4ee0-ad10-86915afdad09) or FIREWALL_PORT_CLOSE_GUID
(a144ed38-8e12-4de4-9d96-e64740b1a524). You can use pDataItems
to
define port, protocol or executable path.
SERVICE_TRIGGER_TYPE_GROUP_POLICY
(5) : This event triggered when Machine Group policy is modified. pTriggerSubtype
can have MACHINE_POLICY_PRESENT_GUID
(659FCAE6-5BDB-4DA9-B1FF-CA2A178D46E0) or USER_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))
{
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;
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();
Marshal.FreeHGlobal(GuidIpAddressArrivalPointer);
Marshal.FreeHGlobal(GuidIpAddressRemovalPointer);
Marshal.FreeHGlobal(pServiceTriggers);
Marshal.FreeHGlobal(ServiceTriggerInfoPointer);
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.
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)
{
return false;
}
IntPtr pBuffer = Marshal.AllocHGlobal(outBytes);
try
{
if (ServiceNative.QueryServiceConfig2(sc.ServiceHandle.DangerousGetHandle(),
ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, pBuffer,
outBytes, out outBytes)!= 0)
{
return false;
}
SERVICE_TRIGGER_INFO sti = (SERVICE_TRIGGER_INFO)
Marshal.PtrToStructure(pBuffer, typeof(SERVICE_TRIGGER_INFO));
return (sti.cTriggers > 0);
}
catch{}
finally
{
if (pBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(pBuffer);
}
}
}}
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
.
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.
As InstallUtil
which installs .NET services does not support TriggerInfo
switch, we did using ChangeServiceConfig2
API method defined in advapi32.dll
ChangeServiceConfig2
requires a pointer to ServiceTriggerInfo
structure.
- The
SERVICE_TRIGGER_INFO
should be assigned to SERVICE_TRIGGER
objects each of which points to the
service events that we want to configure.
SERVICE_TRIGGER
configures the type of each event.
- 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.
4 Jan 2010 : Initial Post
11 Jan 2010 : Added the use of QueryServiceConfig2 to query for Trigger Service
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.