代码改变世界

The step-by-step guide of making a C# (or VB.NET) Windows 7 Trigger Start Service

2009-08-03 20:41  Jialiang  阅读(2246)  评论(3编辑  收藏  举报

Windows 7 introduces a new feature called Trigger-start Service. In this article, you will see a step-by-step guide of making a Windows 7 Trigger-start Service in Visual C# (or VB.NET).

Services and background processes have tremendous influence on the overall performance of the system. If we could just cut down on the total number of services, we would reduce the total power consumption and increase the overall stability of the system. The Windows 7 Service Control Manager has been extended so that a service can be automatically started and stopped when a specific system event, or trigger, occurs on the system. The new mechanism is called Service Trigger Event. A service can register to be started or stopped when a trigger event occurs. This eliminates the need for services to start when the system starts, or for services to poll or actively wait for an event; a service can start when it is needed, instead of starting automatically whether or not there is work to do. Examples of redefined trigger events include arrival of a device of a specified device interface class or availability of a particular firewall port. A service can also register for a custom trigger event generated by an Event Tracing for Windows (ETW) provider.

CSWin7TriggerStartService and VBWin7TriggerStartService samples in All-In-One Code Framework demonstrate how to make trigger-start Windows Service in Visual C# and VB.NET. The sample services are configured to start when a generic USB disk becomes available. It also shows how to trigger-start when the first IP address becomes available and trigger-stop when the last IP address becomes unavailable. The start and stop events are reported in Application event log.

Sample Prerequisite:

Windows Server 2008 R2 and Windows 7
Service trigger events are not supported until Windows Server 2008 R2 and Windows 7.

Deployment of the Sample Service

A. Setup

Installutil.exe CSWin7TriggerStartService.exe

It installs CSWin7TriggerStartService.exe into SCM as a Windows Service. If the current operating system support trigger-start service, the command prompt outputs "Configuring trigger-start service...", otherwise, it prints "The current system does not support trigger-start service.".

B. Cleanup

Installutil.exe /u CSWin7TriggerStartService.exe

It uninstalls CSWin7TriggerStartService from SCM.

Step-by-step Creation Guide of the Sample


A.
Creating an ordinary Windows Service project

Step1. In Visual Studio 2008, add a new Visual C# / Windows / Windows Service project named CSWin7TriggerStartService.

Step2. Rename the default Service1 to TriggerStartService. Open the service in designer and set the ServiceName property to be CSWin7TriggerStartService. Drag and drop an event log component from toolbox to the design view, and set its Log property to be Application, and its Source to be CSWin7TriggerStartService. The event log component will be used to log some application messages.

Step3. Right-click in the design view, and select Add Installer on the context menu. This creates the project installer components:

serviceProcessInstaller1
serviceInstaller1

Set the Account property of serviceProcessInstaller1 to be Local System, so the service will be run as Local System. Also rename serviceInstaller1 as triggerStartServiceInstaller, and make its ServiceName property CSWin7TriggerStartService. Keep everything else the default value.

B. Adding P/Invoke signatures for native APIs and structs related to SCM

Add a ServiceNative.cs file and define P/Invoke signatures for native APIs like ChangeServiceConfig2, QueryServiceConfig2 and the related structs and enumerations.

C. Configuring the service to trigger-start when a generic USB disk becomes available (or trigger-start when the first IP address becomes available and trigger-stop when the last IP address becomes unavailable.)

Services can be registered as trigger-start from the sc.exe command line utility (using the sc triggerinfo command and need to run the Command Shell as Administrator), or using the ChangeServiceConfig2 API programmatically. The service installer utility of .NET Windows Services (InstallUtil) does not support triggerinfo switch, so we do it programmatically. ProjectInstaller's AfterInstall event allows us to execute some codes after the serivce is installed. We are going to register the service as trigger-start in this event handler.

Step1. Add AfterInstall event handler for triggerStartServiceInstaller:

private void triggerStartServiceInstaller_AfterInstall(object sender, InstallEventArgs e)
{  }

 

Step2. Determine if the current operating system is Windows 7 and above.

Service trigger events are not supported until Windows Server 2008 R2 and Windows 7. Windows Server 2008 R2 and Windows 7 have major version 6 and minor version 1.

if (Environment.OSVersion.Version.Major > 6 ||

   (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 1))

{  }

 

Step3. Set service trigger start on USB arrival.

To modify service configuration programmatically, the Win32 API function ChangeServiceConfig2 must be called with the appropriate parameters.

1. Allocate a SERVICE_TRIGGER_SPECIFIC_DATA_ITEM structure
a) Set its dwDataType member to SERVICE_TRIGGER_DATA_TYPE_STRING
b) Set its cbData member to the length of the string L"USBSTOR\\GenDisk" in bytes
c) Set its pData member to that string.

2. Allocate a SERVICE_TRIGGER structure
a) Set its dwTriggerType member to SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL
b) Set its dwAction member to SERVICE_TRIGGER_ACTION_SERVICE_START
c) Set its pTriggerSubtype member to the address of the GUID_DEVINTERFACE_DISK GUID
d) Set its cDataItems member to 1 and its pDataItems member to the address of the structure allocated in the previous step.

3. Allocate a SERVICE_TRIGGER_INFO structure
a) Set its cTriggers member to 1 and its pTriggers member to the address of the structure allocated in the previous step.

4. Call the ChangeServiceConfig2 function with the SERVICE_CONFIG_TRIGGER_INFO information level and pass to it the address of the structure allocated in the previous step.

Please note that the above data structures are initially allocated on managed heap. We need to marshal them to the native memory by calling Marshal.AllocHGlobal and Marshal.StructureToPtr, and free the native memory after use (Marshal.FreeHGlobal). The complete code of setting service tigger start on USB arrival is:

using (ServiceController sc = new ServiceController(serviceName))

{

    // Marshal Guid struct GUID_DEVINTERFACE_DISK to native memory

    IntPtr pGuidUSBDevice = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));

    Marshal.StructureToPtr(GUID_DEVINTERFACE_DISK, pGuidUSBDevice, false);

 

    // Allocate and set the SERVICE_TRIGGER_SPECIFIC_DATA_ITEM structure

    SERVICE_TRIGGER_SPECIFIC_DATA_ITEM deviceData;

    deviceData.dwDataType = ServiceTriggerDataType.SERVICE_TRIGGER_DATA_TYPE_STRING;

    deviceData.cbData = (uint)(USBHardwareId.Length + 1) * 2;

    IntPtr pUSBHardwareId = Marshal.StringToHGlobalUni(USBHardwareId);

    deviceData.pData = pUSBHardwareId;

    // Marshal the SERVICE_TRIGGER_SPECIFIC_DATA_ITEM struct to native memory

    IntPtr pDeviceData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(

        SERVICE_TRIGGER_SPECIFIC_DATA_ITEM)));

    Marshal.StructureToPtr(deviceData, pDeviceData, false);

 

    // Allocate and set the SERVICE_TRIGGER structure

    SERVICE_TRIGGER serviceTrigger;

    serviceTrigger.dwTriggerType =

        ServiceTriggerType.SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL;

    serviceTrigger.dwAction =

        ServiceTriggerAction.SERVICE_TRIGGER_ACTION_SERVICE_START;

    serviceTrigger.pTriggerSubtype = pGuidUSBDevice;

    serviceTrigger.cDataItems = 1;

    serviceTrigger.pDataItems = pDeviceData;

    // Marshal the SERVICE_TRIGGER struct to native memory

    IntPtr pServiceTrigger = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(

        SERVICE_TRIGGER)));

    Marshal.StructureToPtr(serviceTrigger, pServiceTrigger, false);

 

    // Allocate and set the SERVICE_TRIGGER_INFO structure.

    SERVICE_TRIGGER_INFO serviceTriggerInfo = new SERVICE_TRIGGER_INFO();

    serviceTriggerInfo.cTriggers = 1;

    serviceTriggerInfo.pTriggers = pServiceTrigger;

    // Marshal the SERVICE_TRIGGER_INFO struct to native memory

    IntPtr pServiceTriggerInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(

        SERVICE_TRIGGER_INFO)));

    Marshal.StructureToPtr(serviceTriggerInfo, pServiceTriggerInfo, false);

 

    // Call ChangeServiceConfig2 with the SERVICE_CONFIG_TRIGGER_INFO level

    // and pass to it the address of the SERVICE_TRIGGER_INFO structure.

    bool bSuccess = ServiceNative.ChangeServiceConfig2(

        sc.ServiceHandle.DangerousGetHandle(),

        ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO, pServiceTriggerInfo);

    int errorCode = Marshal.GetLastWin32Error();

 

    // Clean up the native resources

    Marshal.FreeHGlobal(pGuidUSBDevice);

    Marshal.FreeHGlobal(pUSBHardwareId);

    Marshal.FreeHGlobal(pDeviceData);

    Marshal.FreeHGlobal(pServiceTrigger);

    Marshal.FreeHGlobal(pServiceTriggerInfo);

 

    // If the service failed to be set as trigger start, throw the error

    // returned by the ChangeServiceConfig2 function.

    if (!bSuccess)

    {

        Marshal.ThrowExceptionForHR(errorCode);

    }

}

 

Step4. Set service trigger start on IP address arrival.

The process is similar to setting service trigger start on USB arrival. You can find the complete code in ProjectInstaller.cs (or .vb).

D. Determinging if service trigger event is enabled inside the service

Unlike auto-start services which typically poll for interesting events using periodic timers, trigger-start services act when a trigger starts them and then deactivate (stop) themselves when there is no more work to perform.

Step1. Inside OnStart, check whether the service is configured to trigger-start using the IsTriggerStartService method. IsTriggerStartService calls QueryServiceConfig2 to query SERVICE_TRIGGER_INFO. If SERVICE_TRIGGER_INFO.cTriggers > 0, trigger-start is enabled.

if (IsTriggerStartService())

{

    this.eventLog1.WriteEntry("Service trigger events are enabled.");

 

    // Consider stopping the service

    //this.Stop();

}

else

{

    this.eventLog1.WriteEntry("Service trigger events are NOT enabled.");

 

    // Usually use a timer to poll the status

    // ...

}

 

 

How to Get the Samples

The CSWin7TriggerStartService and VBWin7TriggerStartService samples are available in All-In-One Code Framework (AIO). You can download the releases later than All-In-One Code Framework 2009-7-30, or download a changeset whose ID is larger than 26497. After the download, please open the AIO 2008 - Windows 7.sln solution file under the AIO\Visual Studio 2008 folder, and you can see both samples.

Feedback


If you have any feedback or questions about the samples, please feel free to post it to the discussion board or directly send it to us. Thanks.