Windows Services Programming 4

 

 

 

 

 

Starting Services on Demand

 

The user can start a service with the Services control panel utility. The user can specify arguments for the service in the Start parameters field.

 

User can specify arguments for the service in the Start parameters field of the Windows Service property page

 

A service control program can start a service and specify its arguments with the StartService() function. When the service is started, the SCM performs the following steps:

 

  1. Retrieve the account information stored in the database.
  2. Log on the service account.
  3. Load the user profile.
  4. Create the service in the suspended state.
  5. Assign the logon token to the process.
  6. Allow the process to execute.

 

Service Record List

 

As each service entry is read from the database of installed services, the SCM creates a service record for the service. A service record includes:

 

  1. Service name
  2. Start type (auto-start or demand-start)
  3. Service status (see the SERVICE_STATUS structure)

 

a.       Type

b.      Current state

c.       Acceptable control codes

d.      Exit code

e.       Wait hint

 

  1. Pointer to dependency list

 

The user name and password of an account are specified at the time the service is installed. The SCM stores the user name in the registry and the password in a secure portion of the Local Security Authority (LSA). The system administrator can create accounts with passwords that never expire. Alternatively, the system administrator can create accounts with passwords that expire and manage the accounts by changing the passwords periodically. The SCM keeps two copies of a user account's password:

 

  1. A current password and
  2. A backup password

 

The password specified the first time the service is installed is stored as the current password and the backup password is not initialized. When the SCM attempts to run the service in the security context of the user account, it uses the current password. If the current password is used successfully, it is also saved as the backup password. If the password is modified with the ChangeServiceConfig() function, or the Services control panel utility, the new password is stored as the current password and the previous password is stored as the backup password. If the SCM attempts to start the service and the current password fails, then it uses the backup password. If the backup password is used successfully, it is saved as the current password. The SCM updates the service status when a service sends it status notifications using the SetServiceStatus() function. The SCM maintains the status of a driver service by querying the I/O system, instead of receiving status notifications, as it does from a service. A service can register additional type information by calling the SetServiceBits() function. The NetServerGetInfo() and NetServerEnum() functions obtain the supported service types.

 

SCM Handles

 

The SCM supports handle types to allow access to the following objects.

  1. The database of installed services.

  2. A service.

  3. The database lock.

 

An SCManager object represents the database of installed services. It is a container object that holds service objects. The OpenSCManager() function returns a handle to an SCManager object on a specified computer. This handle is used when installing, deleting, opening, and enumerating services and when locking the services database. A service object represents an installed service. The CreateService() and OpenService() functions return handles to installed services. The OpenSCManager(), CreateService(), and OpenService() functions can request different types of access to SCManager and service objects. The requested access is granted or denied depending on the access token of the calling process and the security descriptor associated with the SCManager or service object. The CloseServiceHandle() function closes handles to SCManager and service objects. When you no longer need these handles, be sure to close them.

 

Service Programs

 

A service program contains executable code for one or more services. A service created with the type SERVICE_WIN32_OWN_PROCESS contains the code for only one service. A service created with the type SERVICE_WIN32_SHARE_PROCESS contains code for more than one service, enabling them to share code. A service can be configured to execute in the context of a user account from either:

 

  1. The built-in (local),
  2. Primary, or
  3. Trusted domain

 

It can also be configured to run in a special service user account. The following sections describe the interface requirements of the service control manager (SCM) that a service program must include:

  1. Service Entry Point
  2. Service ServiceMain Function
  3. Service Control Handler Function

 

These sections do not apply to driver services. For interface requirements of driver services, see the Windows Driver Kit. Take note that, a service runs as a background process that can affect system performance, responsiveness, energy efficiency, and security.

 

Service Entry Point

 

Services are generally written as console applications. The entry point of a console application is its main function. The main function receives arguments from the imagePath value from the registry key for the service. When the SCM starts a service program, it waits for it to call the StartServiceCtrlDispatcher() function. Use the following guidelines.

 

  1. A service of type SERVICE_WIN32_OWN_PROCESS should call StartServiceCtrlDispatcher() immediately, from its main thread. You can perform any initialization after the service starts, as described in Service ServiceMain() Function.
  2. If the service type is SERVICE_WIN32_SHARE_PROCESS and there is common initialization for all services in the program, you can perform the initialization in the main thread before calling StartServiceCtrlDispatcher(), as long as it takes less than 30 seconds. Otherwise, you must create another thread to do the common initialization, while the main thread calls StartServiceCtrlDispatcher(). You should still perform any service-specific initialization after the service starts.

 

The StartServiceCtrlDispatcher() function takes a SERVICE_TABLE_ENTRY structure for each service contained in the process. Each structure specifies the service name and the entry point for the service. If StartServiceCtrlDispatcher() succeeds, the calling thread does not return until all running services in the process have entered the SERVICE_STOPPED state. The SCM sends control requests to this thread through a named pipe. The thread acts as a control dispatcher, performing the following tasks:

 

  1. Create a new thread to call the appropriate entry point when a new service is started.
  2. Call the appropriate handler function to handle service control requests.

 

Service ServiceMain Function

 

When a service control program requests that a new service run, the SCM starts the service and sends a start request to the control dispatcher. The control dispatcher creates a new thread to execute the ServiceMain() function for the service. The ServiceMain() function should perform the following tasks:

 

  1. Initialize all global variables.
  2. Call the RegisterServiceCtrlHandler() function immediately to register a Handler function to handle control requests for the service. The return value of RegisterServiceCtrlHandler() is a service status handle that will be used in calls to notify the SCM of the service status.
  3. Perform initialization. If the execution time of the initialization code is expected to be very short (less than one second), initialization can be performed directly in ServiceMain.

If the initialization time is expected to be longer than one second, call the SetServiceStatus() function, specifying the SERVICE_START_PENDING service state and a wait hint in the SERVICE_STATUS structure. If your service's initialization code performs tasks that are expected to take longer than the initial wait hint value, your code must call the SetServiceStatus function periodically (possibly with a revised wait hint) to indicate that progress is being made. Be sure to call SetServiceStatus() only if the initialization is making progress. Otherwise, the Service Control Manager can wait for your service to enter the SERVICE_RUNNING state assuming that your service is making progress and block other services from starting. Do not call SetServiceStatus() from a separate thread unless you are sure the thread performing the initialization is truly making progress.

  1. When initialization is complete, call SetServiceStatus() to set the service state to SERVICE_RUNNING.
  2. Perform the service tasks, or, if there are no pending tasks, return control to the caller. Any change in the service state warrants a call to SetServiceStatus() to report new status information.
  3. If an error occurs while the service is initializing or running, the service should call SetServiceStatus to set the service state to SERVICE_STOP_PENDING if cleanup will be lengthy. After cleanup is complete, call SetServiceStatus() to set the service state to SERVICE_STOPPED from the last thread to terminate. Be sure to set the dwServiceSpecificExitCode and dwWin32ExitCode members of the SERVICE_STATUS structure to identify the error.

 

Service Control Handler Function

 

Each service has a control handler, the Handler function, that is invoked by the control dispatcher when the service process receives a control request from a service control program. Therefore, this function executes in the context of the control dispatcher. A service calls the RegisterServiceCtrlHandler() or RegisterServiceCtrlHandlerEx() function to register its service control handler function. Whenever the service control handler is invoked, the service must call the SetServiceStatus() function to report its status to the SCM. This must be done regardless of whether the status changed. A service control program can send control requests using the ControlService() function. All services must accept and process the SERVICE_CONTROL_INTERROGATE control code. You can enable or disable acceptance of the other control codes by calling SetServiceStatus(). To receive the SERVICE_CONTROL_DEVICEEVENT control code, you must call the RegisterDeviceNotification() function. Services can also handle additional user-defined control codes.

If a service accepts the SERVICE_CONTROL_STOP control code, it must stop upon receipt, going to either the SERVICE_STOP_PENDING or SERVICE_STOPPED state. After the SCM sends this control code, it will not send other control codes.

For Windows XP/2000, if the service returns NO_ERROR and continues to run, it continues to receive control codes. This behavior changed starting with Windows Server 2003 and Windows XP with Service Pack 2 (SP2). The control handler must return within 30 seconds, or the SCM returns an error. If a service must do lengthy processing when the service is executing the control handler, it should create a secondary thread to perform the lengthy processing, and then return from the control handler. This prevents the service from tying up the control dispatcher. For example, when handling the stop request for a service that takes a long time, create another thread to handle the stop process. The control handler should simply call SetServiceStatus() with the SERVICE_STOP_PENDING message and return. When the user shuts down the system, all control handlers that have called SetServiceStatus() with the SERVICE_ACCEPT_PRESHUTDOWN control code receive the SERVICE_CONTROL_PRESHUTDOWN control code. The service control manager waits until the service stops or the specified pre-shutdown time-out value expires (this value can be set with the ChangeServiceConfig2() function). This control code should be used only in special circumstances, because a service that handles this notification blocks system shutdown until the service stops or the pre-shutdown time-out interval expires.

After the pre-shutdown notifications have been completed, all control handlers that have called SetServiceStatus() with the SERVICE_ACCEPT_SHUTDOWN control code receive the SERVICE_CONTROL_SHUTDOWN control code. They are notified in the order that they appear in the database of installed services. By default, a service has approximately 20 seconds to perform cleanup tasks before the system shuts down. After this time expires, system shutdown proceeds regardless of whether service shutdown is complete. Note that if the system is left in the shutdown state (not restarted or powered down), the service continues to run. If the service requires more time to cleanup, it sends STOP_PENDING status messages, along with a wait hint, so the service controller knows how long to wait before reporting to the system that service shutdown is complete. However, to prevent a service from stopping shutdown, there is a limit to how long the service controller waits. If the service is being shut down through the Services snap-in, the limit is 125 seconds. If the operating system is rebooting, the time limit is specified in the WaitToKillServiceTimeout value of the following registry key:

 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control

 

The HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control registry key

 

Important:  A service should not attempt to increase the time limit by modifying this value.

Customers require fast shutdown of the operating system. For example, if a computer running on UPS power cannot complete shutdown before the UPS runs out of power, data can be lost. Therefore, services should complete their cleanup tasks as quickly as possible. It is a good practice to minimize unsaved data by saving data on a regular basis, keeping track of the data that is saved to disk, and only saving your unsaved data on shutdown. Because the computer is being shut down, do not spend time releasing allocated memory or other system resources. If you need to notify a server that you are exiting, minimize the time spent waiting for a reply, because network problems could delay the shutdown of your service.

Take note that during service shutdown, the SCM does not take dependencies into consideration. The SCM enumerates the list of running services and sends the SERVICE_CONTROL_SHUTDOWN command. Therefore, a service may fail because another service it depends on has already stopped. To set the shutdown order of dependent services, use the SetProcessShutdownParameters() function. The SCM uses this function to give its handler 0x1E0 priority. The SCM sends SERVICE_CONTROL_SHUTDOWN notifications when its control handler is called and waits for the services to exit before returning from its control handler. These topics do not apply to driver services. For interface requirements of driver services, see the Windows Driver Kit. A service runs as a background process that can affect system performance, responsiveness, energy efficiency, and security. The following sections describe additional programming considerations for Windows Services as follows:

 

  1. Receiving Events in a Service
  2. Multithreaded Services
  3. Services and the Registry
  4. Services and Redirected Drives
  5. Service Trigger Events

 

Note that if the service functions as an RPC server, it should use dynamic endpoints and mutual authentication.

 

 

 

 

< Windows Services 3 | Win32 Programming | Windows Services Win32 Programming | Windows Services 5 >