Starting and Controlling a Service

[Previous] [Next]

As I mentioned earlier, many services ship with a client-side application that allows administrators to start, stop, pause, continue, and otherwise control a service. Writing such a service control program is very easy. Here's how one works: The program first opens the SCM on the desired machine by calling OpenSCManager using the SC_MANAGER_CONNECT access right. Then the program calls OpenService to open the service you want to control by using the desired combination of SERVICE_START, SERVICE_STOP, SERVICE_PAUSE_CONTINUE, SERVICE_USER_DEFINED_CONTROL, and SERVICE_INTERROGATE access rights. After a service is opened, calling StartService can start it:

BOOL StartService(    SC_HANDLE hService,    DWORD     dwArgc,    PCTSTR*   pszArgv); 

The hService parameter identifies the opened service, and the dwArgc and pszArgv parameters indicate the set of arguments that you wish to pass to the service's ServiceMain function. Most services don't use these parameters, so 0 and NULL are usually passed for the last two arguments. Remember that starting a service can cause several services to start if the service you're starting depends on other services or groups. Here are some of the main reasons StartService can fail:

  • The handle returned from OpenService doesn't have SERVICE_START access.
  • The service's executable file is not in the location specified in the directory.
  • The service is already running, disabled, or marked for deletion.
  • The SCM's database is locked. (I'll talk more about this later in this chapter.)
  • The service depends on another service that doesn't exist or failed to start.
  • The user account for the service could not be validated.
  • The service didn't respond to the start request in a timely fashion.

Note that the StartService function returns as soon as the service's primary thread is created, so the service might not be ready to process control codes or handle client requests by the time StartService returns. Also, a service must not call StartService while it is initializing, or a deadlock (lasting 80 seconds) will occur. The problem is that the SCM locks the SCM database while starting a service, preventing another service from starting.

Once the service is running, you can call ControlService to send controls to it:

 BOOL ControlService(    SC_HANDLE       hService,    DWORD           dwControl,    SERVICE_STATUS* pss); 

Again, the hService parameter identifies the opened service that you wish to control. The dwControl parameter indicates what you wish the service to do and can be any one of the following values:

  • SERVICE_CONTROL_STOP
  • SERVICE_CONTROL_PAUSE
  • SERVICE_CONTROL_CONTINUE
  • SERVICE_CONTROL_INTERROGATE
  • SERVICE_CONTROL_PARAMCHANGE

Notice that these codes are the same codes that your HandlerEx function receives (as discussed in Chapter 3). In addition to these values, you can send a user-defined code ranging from 128 through 255. Note that ControlService fails if you pass a value of SERVICE_CONTROL_SHUTDOWN; only the system can send this code to a service's handler function.

ControlService's last parameter, pss, must point to a SERVICE_STATUS structure. The function will initialize the members of this structure to report the service's last-reported status information. You can examine this information after ControlService returns to see how the service is doing. Here are some of the main reasons ControlService can fail:

  • The handle returned from OpenService doesn't have the proper access.
  • The service can't be stopped because other services depend on it. In this case, your application must stop the dependent services first.
  • The control code is not valid or is not acceptable to the service. Remember from Chapter 3 that a service sets the SERVICE_STATUS structure's dwControlsAccepted member when it calls SetServiceStatus.
  • The control code can't be sent to the service because the service is reporting SERVICE_STOPPED, SERVICE_START_PENDING, or SERVICE_STOP_PENDING.
  • The service is not running.
  • The service has not returned from its HandlerEx function in a timely fashion (within 30 seconds).

Certainly, if the service's handler function processes the call, you would expect the returned SERVICE_STATUS structure to be initialized properly. But what do you think the contents of the SERVICE_STATUS structure will be if you attempt to send a SERVICE_CONTROL_INTERROGATE control to a stopped service? Well, you'll be pleased to know that Microsoft has enhanced the ControlService function so that it returns a valid SERVICE_STATUS structure if the function fails with an error code of ERROR_INVALID_SERVICE_CONTROL, ERROR_SERVICE_CANNOT_ACCEPT_CTRL, or ERROR_SERVICE_NOT_ACTIVE. The following code shows how to stop a service:

 void StopService(PCTSTR pszInternalName) {    // Open the SCM and the desired service    SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);    SC_HANDLE hService = OpenService(hSCM, pszInternalName,       SERVICE_STOP | SERVICE_QUERY_STATUS);    // Tell the service to stop    SERVICE_STATUS ss;    ControlService(hService, SERVICE_CONTROL_STOP, &ss);    // Wait up to 15 seconds for the service to stop    WaitForServiceState(hService, SERVICE_STOPPED, &ss, 15000);    // Close the service and the SCM    CloseServiceHandle(hService);    CloseServiceHandle(hSCM); } 

NOTE
For clarity, the preceding code does not have any error checking. OpenSCManager, OpenService, and ControlService can fail for many reasons. Please add the proper error checking when adding code like this into your own application.

You'll notice that StopService calls the WaitForServiceState function. The WaitForServiceState function is not a Windows function but rather a function I wrote, and it demonstrates how to handle the polling of a service's state properly. Consider the following scenario. Using the Services snap-in, you initiate a stop request for a service, causing the SCM to notify the selected service that it should stop. The service should respond by calling SetServiceStatus, with the SERVICE_STATUS structure's dwCurrentState member set to SERVICE_STOP_PENDING. However, the service has not stopped yet, so the Services snap-in doesn't update its user interface to reflect that the service has stopped. Unfortunately, the system does not provide a way for an application to be notified of service state changes, so an SCP must periodically poll the service to determine when its state has changed. The WaitForServiceState function handles this polling.

 BOOL WaitForServiceState(SC_HANDLE hService, DWORD dwDesiredState,    SERVICE_STATUS* pss, DWORD dwMilliseconds) {    BOOL  fServiceOk = TRUE;    BOOL  fFirstTime = TRUE; // Don't compare state/checkpoint the                             // first time    DWORD dwLastState = 0, dwLastCheckPoint = 0;    DWORD dwTimeout = GetTickCount() + dwMilliseconds;    // Loop until service reaches desired state, error occurs, or timeout    for (;;) {       // Get current state of service       fServiceOk = ::QueryServiceStatus(hService, pss);       // If we can't query the service, we're done       if (!fServiceOk) break;       // If the service reaches the desired state, we're done       if (pss->dwCurrentState == dwDesiredState) break;       // If timeout, we're done       if ((dwMilliseconds != INFINITE) && (dwTimeout < GetTickCount())){          fServiceOk = FALSE;          SetLastError(ERROR_TIMEOUT);          break;       }       // If first time, save service's state/checkpoint       if (fFirstTime) {          dwLastState      = pss->dwCurrentState;          dwLastCheckPoint = pss->dwCheckPoint;          fFirstTime       = FALSE;       } else {          // If not first time and state changed, save state/checkpoint          if (dwLastState != pss->dwCurrentState) {             dwLastState      = pss->dwCurrentState;             dwLastCheckPoint = pss->dwCheckPoint;          } else {             // State hasn't changed; make sure checkpoint isn't              // decreasing             if (pss->dwCheckPoint >= dwLastCheckPoint) {                // Good checkpoint; save it                dwLastCheckPoint = pss->dwCheckPoint;             } else {                // Bad checkpoint, service failed, we're done!                fServiceOk = FALSE;                 break;             }          }       }       // We're not done; wait the specified period of time       // Poll 1/10 of the wait hint       DWORD dwWaitHint = pss->dwWaitHint / 10;       // At most once a second       if (dwWaitHint <  1000) dwWaitHint = 1000;       // At least every 10 seconds       if (dwWaitHint > 10000) dwWaitHint = 10000;       Sleep(dwWaitHint);    }    // Note: The last SERVICE_STATUS is returned to the caller so    // that the caller can check the service state and error codes    return(fServiceOk); } 

We all know that polling is a horrible thing to do because it wastes precious CPU cycles, but we really have no choice in this case. Fortunately, the situation is not as bad as you think because the SERVICE_STATUS structure contains the dwWaitHint member. When a service calls SetServiceStatus, the dwWaitHint member must indicate how many milliseconds the program that is sending the control code should wait before polling the service's status again.

The service control program should also examine the checkpoint returned from the service during the polling process to make sure that it never decreases. If a service returns a smaller checkpoint value, the service control program should assume that the service has failed.

You'll notice that the WaitForServiceState function calls QueryServiceStatus:

 BOOL QueryServiceStatus(    SC_HANDLE       hService,     SERVICE_STATUS* pss); 

QueryServiceStatus asks the SCM to return the service's last cached state information (set when the service last called SetServiceStatus). Calling QueryServiceStatus is similar to calling ControlService and passing the SERVICE_CONTROL_INTERROGATE code, but calling ControlService with SERVICE_CONTROL_INTERROGATE sends an action request to the service to update the current state information. Another difference between calling QueryServiceStatus and calling ControlService is that QueryServiceStatus always returns in a timely fashion, whereas ControlService might return failure if the service has stopped responding. If a service's handler function is busy when you send an interrogate code to it, the service might not be able to respond for a while, which will cause your call to ControlService to wait (possibly for 30 seconds). Of course, the downside to using QueryServiceStatus is that the SCM's cached data might not accurately reflect the most up-to-date state of the service. Now that you know the trade-offs, you can query a service's state using whichever method works best in your situation.

In addition to the QueryServiceStatus function, Microsoft recently added the new QueryServiceStatusEx function:

 BOOL QueryServiceStatusEx(    SC_HANDLE      hService,    SC_STATUS_TYPE InfoLevel,    PBYTE          pbBuffer,    DWORD          cbBufSize,    PDWORD         pdwBytesNeeded); 

This function queries a service's status and initializes the new SERVICE_STATUS_PROCESS structure:

typedef struct _SERVICE_STATUS_PROCESS {    DWORD  dwServiceType;    DWORD  dwCurrentState;    DWORD  dwControlsAccepted;    DWORD  dwWin32ExitCode;    DWORD  dwServiceSpecificExitCode;    DWORD  dwCheckPoint;    DWORD  dwWaitHint;    DWORD  dwProcessId;    DWORD  dwServiceFlags; } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS;  

This structure is identical to the SERVICE_STATUS structure except that it has two additional members at the end: dwProcessId and dwServiceFlags. The dwProcessId member indicates the ID of the process that contains the service, and dwServiceFlags indicates some additional information about the service. If dwServiceFlags contains SERVICE_RUNS_IN_SYSTEM_PROCESS (the only flag currently defined), the service is running in a system process such as Services.exe or LSASS.exe. You should never attempt to kill services running in a system process since the process itself is an integral component of the operating system.



Programming Server-Side Applications for Microsoft Windows 2000
Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Programming)
ISBN: 0735607532
EAN: 2147483647
Year: 2000
Pages: 126

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net