Windows Services Programming 5



Receiving Events in a Service


A service that is a console application can register a console control handler to receive notification when a user logs off. However, there is no console event sent when an interactive user logs on. The system broadcasts device change events to all services. These events can be received by a service in a window procedure or in its service control handler. To specify which events your service should receive, use the RegisterDeviceNotification() function. Be sure to handle Plug and Play device events as quickly as possible. Otherwise, the system may become unresponsive. If your event handler is to perform an operation that may block execution (such as I/O), it is best to start another thread to perform the operation asynchronously. When a service calls RegisterDeviceNotification(), the service also specifies either a window handle or a service status handle. If a service specifies a window handle, the window procedure receives the notification events. If a service specifies its service status handle, its service control handler receives the notification events. Device notification handles returned by RegisterDeviceNotification() must be closed by calling the UnregisterDeviceNotification() function when they are no longer needed.




Multithreaded Services


The service control manager (SCM) controls a service by sending service control events to the service's control handler routine. The service must respond to control events in a timely manner so that the SCM can keep track of the state of the service. Also, the state of the service must match the description of its state that the SCM receives. Due to this communication mechanism between a service and the SCM, you must be careful when using multiple threads in a service. When a service is instructed to stop by the SCM, it must wait for all the threads to exit before reporting to the SCM that the service is stopped. Otherwise, the SCM can become confused about the state of the service and might fail to shut down correctly.

The SCM needs to be notified that the service is responding to the stop control event and that progress is being made in stopping the service. The SCM will assume the service is making progress if the service responds (through SetServiceStatus()) within the time (wait hint) specified in the previous call to SetServiceStatus, and the check point is updated to be greater than the checkpoint specified in the previous call to SetServiceStatus. If the service reports to the SCM that the service has stopped before all threads have exited, it is possible that the SCM will interpret this as a contradiction. This might result in a state where the service cannot be stopped or restarted.


Services and the Registry


A service should not access HKEY_CURRENT_USER or HKEY_CLASSES_ROOT, especially when impersonating a user. Instead, use the RegOpenCurrentUser() or RegOpenUserClassesRoot() function. If you attempt to access HKEY_CURRENT_USER or HKEY_CLASSES_ROOT from a service it may fail, or it may appear to work but there is an underlying leak that can lead to problems loading or unloading the user profile.


Services and Redirected Drives


A service (or any process running in a different security context) that must access a remote resource should use the Universal Naming Convention (UNC) name to access the resource. The service must have appropriate privileges to access the resource. If a server-side service uses an RPC connection, delegation must be enabled on the remote server. Drive letters are not global to the system. Each logon session receives its own set of drive letters from A to Z. Therefore, redirected drives cannot be shared between processes running under different user accounts. Moreover, a service (or any process running within its own logon session) cannot access the drive letters that were established within a different logon session. A service should not directly access local or network resources through mapped drive letters, nor should it call the net use command to map drive letters at run time. The net use command is not recommended for several reasons:


  1. Drive mappings exist across logon contexts, so if an application is running in the context of the LocalService account, then any other service running in that context may have access to the mapped drive.
  2. If the service provides explicit credentials to a net use command, those credentials might be inadvertently shared outside of the service boundaries. Instead, the service should use client impersonation to impersonate the user.
  3. Multiple services running in the same context may interfere with each other. If both services perform an explicit net use and provide the same credentials at the same time, one service will fail with an already connected error.


Additionally, a service should not use the Windows Networking Functions to manage mapped drive letters. Although the WNet functions may return successfully, the resulting behavior is not as intended. When the system establishes a redirected drive, it is stored on a per-user basis. Only the user is able to manage the redirected drive. The system keeps track of redirected drives based on the user's logon security identifier (SID). The logon SID is a unique identifier for the user's logon session. A single user can have multiple, simultaneous logon sessions on the system. If a service is configured to run under a user account, the system always creates a new logon session for the user and starts the service in that new logon session. Therefore, a service cannot manage the drive mappings established within the user's other sessions. Windows Server 2003:  On a computer that has multiple network interfaces (that is, a multihomed computer), delays up to 60 seconds may occur when using UNC paths to access files that are stored on a remote server message block (SMB) server.


Redirected Drives on Windows 2000


Drive letters are global to the system. All users on the system share one set of drive letters from A to Z. User do not get their own sets of drive letters. This means a user can access the redirected drives of another user as long as they have proper security access. Attempting to redirect a drive letter that is already in use results in an error. Although redirected drives are global to all users, only the user who established the mapping can manage it. If a user attempts to remove or query information on a redirected drive established by a different user, the API fails. If a user attempts to enumerate the list of redirected drives, the list contains only the redirected drives that were established by that user. Redirected drives of other users are not visible.

Windows Explorer can see all redirected drives because it calls the GetDriveType() function for each drive and displays an icon for each drive that it finds. Windows Explorer creates an icon for redirected drives created by all users because drive letters are global to the system. However, the interactive user cannot use Windows Explorer to disconnect the drive, because the drive was created within a different logon session. If a service that is running in the security context of the LocalSystem account establishes a drive mapping, only that service or another process running in the LocalSystem account can disconnect the drive. Note that all processes running in the LocalSystem account are running in the same logon session.



Service Trigger Events


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 predefined 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.

Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP/2000:  Service trigger events are not supported until Windows Server 2008 R2 and Windows 7.

A trigger consists of a trigger event type, a trigger event subtype, the action to be taken in response to the trigger event, and (for certain trigger event types) one or more trigger-specific data items. 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 binary data, a string, or a multistring. Strings must be Unicode; ANSI strings are not supported.

To register for trigger events, the service calls ChangeServiceConfig2() with SERVICE_CONFIG_TRIGGER_INFO and supplies a SERVICE_TRIGGER_INFO structure. The SERVICE_TRIGGER_INFO structure points to an array of SERVICE_TRIGGER structures, each specifying one trigger. The specified trigger action is taken if the trigger condition is true when the system starts, or if the trigger condition becomes true while the system is running. For example, if a service registers to be started when a particular device is available, the service is started when the system starts if the device is already plugged in to the computer; the service is started when the device arrives if the user plugs in the device while the system is running. If a trigger has trigger-specific data items, the trigger action is taken only if the data item that accompanies the trigger event matches one of the data items that the service specified with the trigger. Binary data matching is done by bitwise comparison. String matching is case-insensitive. If the data item is a multistring, all strings in the multistring must match. When a service is started in response to a trigger event, the service receives SERVICE_TRIGGER_STARTED_ARGUMENT as argv[1] in its ServiceMain() callback function. Argv[0] is always the short name of the service.

A service that registers to be started in response to a trigger event might stop itself after an idle time-out when the service has no work to do. A service that stops itself must be prepared to handle SERVICE_CONTROL_TRIGGEREVENT control requests that arrive while the service is stopping itself. The SCM sends a SERVICE_CONTROL_TRIGGEREVENT control request whenever a new trigger event occurs while the service is in the running state. To avoid losing trigger events, the service should return ERROR_SHUTDOWN_IN_PROGRESS for any SERVICE_CONTROL_TRIGGEREVENT control request that arrives while the service is transitioning from running to stopped. This instructs the SCM to queue trigger events and wait for the service to enter the stopped state. The SCM then takes the action associated with the queued trigger event, such as starting the service.

When the service is ready to handle trigger events again, it sets SERVICE_ACCEPT_TRIGGEREVENT in its controls-accepted mask in a call to SetServiceStatus(). This is usually done when the service calls SetServiceStatus() with SERVICE_RUNNING. The SCM then issues a SERVICE_CONTROL_TRIGGEREVENT request for each queued trigger event until the queue is empty.

A service that has dependent services running cannot be stopped in response to a trigger event. Trigger-start and trigger-stop requests are not guaranteed under low memory conditions. Use the QueryServiceConfig2() function to retrieve a service’s trigger event configuration. The SC tool (sc.exe) can be used to configure or query a service’s trigger events at the command prompt. Use the triggerinfo option to configure a service to start or stop in response to a trigger event. Use the qtriggerinfo option to query the trigger configuration of a service. The following example queries the trigger configuration 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.



C:\>sc qtriggerinfo w32time

[SC] QueryServiceConfig2 SUCCESS





          DOMAIN JOINED STATUS         : 1ce20aba-9851-4421-9430-1ddeb766e809 [DOMAIN JOINED]


          DOMAIN JOINED STATUS         : ddaf516e-58c2-4866-9574-c3b615d42ea1 [NOT DOMAIN JOINED]


The following example queries the trigger configuration of the table input service, which is configured to start when a HID device with the GUID {4d1e55b2-f16f-11cf-88cb-001111000030} and any of the specified HID device IDs arrives.


C:\>sc qtriggerinfo tabletinputservice

[SC] QueryServiceConfig2 SUCCESS


SERVICE_NAME: tabletinputservice



          DEVICE INTERFACE ARRIVAL     : 4d1e55b2-f16f-11cf-88cb-001111000030 [INTERFACE CLASS GUID]

            DATA                       : HID_DEVICE_UP:000D_U:0001

            DATA                       : HID_DEVICE_UP:000D_U:0002

            DATA                       : HID_DEVICE_UP:000D_U:0003

            DATA                       : HID_DEVICE_UP:000D_U:0004




< Windows Services 4 | Win32 Programming | Windows Services Win32 Programming | Windows Services 6 >