Adding a Service to the SCM s Database

[Previous] [Next]

One of the most common reasons to manipulate the SCM database is to add a service. To add a service, you must call OpenSCManager, specifying the SC_MANAGER_CREATE_SERVICE access, and then call CreateService:

 SC_HANDLE CreateService(    SC_HANDLE hSCManager,    PCTSTR    pszServiceName,    // Internal, programmatic string name    PCTSTR    pszDisplayName,    DWORD     dwDesiredAccess,    DWORD     dwServiceType,    DWORD     dwStartType,    DWORD     dwErrorControl,    PCTSTR    pszPathName,    PCTSTR    pszLoadOrderGroup,    PDWORD    pdwTagId,          // Always 0 for services    PCTSTR    pszDependencies,   // Double zero-terminated string    PCTSTR    pszUserName,    PCTSTR    pszUserPswd); 

As you can see, CreateService requires quite a few parameters (13 to be exact). The hSCManager parameter is the handle returned from OpenSCManager. The next two parameters, pszServiceName and pszDisplayName, indicate the name of the service. Services have an internal name for use by programmers and a display name that is shown to users. The internal name, identified by pszServiceName, is used by the SCM to store the service information inside the registry. For example, the Logical Disk Manager service has the internal name "dmserver," and its service information can be found under the following registry key:

 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\dmserver 

CreateService's dwDesiredAccess parameter is useful because it tells the SCM what you intend to do with the newly installed service after CreateService returns a handle to it (so you can manipulate the service right away). If you are only installing a service and do not intend to manipulate it after it is installed, simply pass 0 for dwDesiredAccess, and then immediately close the handle returned from CreateService by calling CloseServiceHandle. Table 4-2 shows the access rights you can specify for dwDesiredAccess when you use CreateService.

Table 4-2. Access right values for CreateService's dwDesiredAccess parameter that specify access to the service added to the SCM database

Access Rights Description
SERVICE_START Enables calling of StartServiceto start the service.
SERVICE_STOP Enables calling of ControlService to stop the service.
SERVICE_PAUSE_CONTINUE Enables calling of ControlService to pause and continue the service. This access also allows changing a service's parameters.
SERVICE_INTERROGATE Enables calling of ControlServiceto ask the service to report its status immediately.
SERVICE_USER_DEFINED_CONTROL Enables calling of ControlService to specify a user-defined control code.
SERVICE_QUERY_STATUS Enables calling of QueryServiceStatus(Ex) functions to ask the service control manager about the status of the service.
SERVICE_ENUMERATE_DEPENDENTS Enables calling of EnumDependentServices to enumerate all the services dependent on the service.
SERVICE_CHANGE_CONFIG Enables calling of ChangeServiceConfig(2) to change the service configuration.
SERVICE_QUERY_CONFIG Enables calling of QueryServiceConfig(2) to query the service configuration.
DELETE Enables calling of DeleteServiceto delete the service.

The CreateService function does not have a parameter that accepts a pointer to a SECURITY_ATTRIBUTES structure. So when a new service is installed in the SCM's database, the SCM sets default security on the service. You can alter these security settings using the QueryServiceObjectSecurity and SetServiceObjectSecurity functions. These are the SCM's access settings for the service's default security:

  • Administrators and System Operators have
    SERVICE_CHANGE_CONFIG,
    SERVICE_ENUMERATE_DEPENDENTS,
    SERVICE_INTERROGATE, SERVICE_PAUSE_CONTINUE,
    SERVICE_QUERY_CONFIG, SERVICE_QUERY_STATUS,
    SERVICE_START, SERVICE_STOP,
    SERVICE_USER_DEFINED_CONTROL, READ_CONTROL,
    WRITE_OWNER, WRITE_DAC, and DELETE access to the service.
  • LocalSystem has SERVICE_ENUMERATE_DEPENDENTS,
    SERVICE_INTERROGATE, SERVICE_PAUSE_CONTINUE,
    SERVICE_QUERY_CONFIG, SERVICE_QUERY_STATUS,
    SERVICE_START, SERVICE_STOP,
    SERVICE_USER_DEFINED_CONTROL, and
    READ_CONTROL access to the service.
  • Authenticated users have
    SERVICE_ENUMERATE_DEPENDENTS,
    SERVICE_INTERROGATE, SERVICE_QUERY_CONFIG,
    SERVICE_QUERY_STATUS,
    SERVICE_USER_DEFINED_CONTROL, and
    READ_CONTROL access to the service.
  • On Windows 2000 Professional and Windows 2000 Server, Power
    Users have SERVICE_QUERY_CONFIG,
    SERVICE_QUERY_STATUS,
    SERVICE_ENUMERATE_DEPENDENTS,
    SERVICE_INTERROGATE, SERVICE_START, SERVICE_STOP,
    SERVICE_PAUSE_CONTINUE,
    SERVICE_USER_DEFINED_CONTROL, and
    READ_CONTROL access to the service.

The dwServiceType parameter tells the system whether the executable file contains one or multiple services. Pass SERVICE_WIN32_OWN_PROCESS when the executable implements a single service, or SERVICE_WIN32_SHARE_PROCESS when the executable implements two or more services. You can also combine the SERVICE_INTERACTIVE_PROCESS flag with either SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS if you want the services in a process to interact with the user's desktop.

NOTE
For SERVICE_WIN32_SHARE_PROCESS service executables, if one service requires the SERVICE_INTERACTIVE_PROCESS flag, all services must use this flag. When the system starts the first service, the setting of that service determines whether the whole process is allowed to interact with the desktop.

The dwStartType parameter tells the system when the service should be started. A value of SERVICE_AUTO_START instructs the SCM to start the service when the machine boots. A value of SERVICE_DEMAND_START tells the system that the service should not be started when the machine boots; an administrator can start the service manually. In addition, SERVICE_DEMAND_START specifies that the service is a demand-start service, which tells the SCM to automatically start the service if the administrator attempts to start a service that depends on it. I'll talk more about service dependencies shortly. A value of SERVICE_DISABLED prevents the system from starting the service at all.

A service is a very important part of the system, so the system needs to know what it should do if the service fails to start. This instruction is the job of the dwErrorControl parameter. Passing a value of SERVICE_ERROR_IGNORE or SERVICE_ERROR_NORMAL tells the system to log the service's error in the system's event log and continue starting the system. The difference between these two codes is that SERVICE_ERROR_NORMAL has the system display a message box notifying the user that the service failed to start. Services that are demand-started should always specify SERVICE_ERROR_IGNORE.

The values of SERVICE_ERROR_SEVERE and SERVICE_ERROR_CRITICAL tell the system to abort startup when the service fails to start. When a service fails and one of these codes is specified, the system logs the error in the system's event log and then reboots automatically using the last-known good configuration. If the system is booting the last-known good configuration and a service with an error control of SERVICE_ERROR_SEVERE fails to start, the system continues to boot. If a service with an error control of SERVICE_ERROR_CRITICAL fails to start, the system will also abort the booting of the last-known good configuration.

CreateService's pszPathName parameter identifies the full pathname of the executable that contains the service or services. Many service files are installed in the \WINNT\System32 directory, but you can place a service executable anywhere in the file system.

Now we get to the issue of service dependencies. Loosely speaking, a service is like part of the operating system, and many services will not work properly unless they know that other parts of the system are up and running first. When the system boots, it follows an algorithm that dictates the order in which services should start. Microsoft has divided the system services into a set of predefined groups, listed here:

  • System Reserved
  • File system (CD-ROM and NTFS file system support)
  • Boot Bus Extender
  • Event log (event log support)
  • System Bus Extender (PCMCIA support)
  • Streams Drivers
  • SCSI miniport (SCSI device drivers)
  • NDIS Wrapper
  • Port
  • PNP_TDI (NetBT and TCP/IP support)
  • Primary disk (floppy/hard drives)
  • NDIS (network support)
  • SCSI class (SCSI drives)
  • TDI (AFD networking support and DHCP)
  • SCSI CDROM class (CD-ROM drives)
  • NetBIOSGroup (NetBIOS support)
  • Filter (CD audio)
  • PlugPlay
  • Boot file system (fast FAT drive access)
  • SpoolerGroup (print spooling support)
  • Base (system beep)
  • NetDDEGroup (network DDE support)
  • Pointer Port (mouse support)
  • Parallel arbitrator (parallel port support)
  • Keyboard Port (keyboard support)
  • Extended base (modem, serial, and parallel support)
  • Pointer Class (more mouse support)
  • RemoteValidation (net logon support)
  • Keyboard Class (more keyboard support)
  • PCI Configuration
  • Video Init (video support)
  • MS Transactions
  • Video (video chip support)
  • Video Save (more video support)

You can also find this list in the registry under the following registry subkey:

 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceGroupOrder 

As the system boots, it iterates through the list, loading any device drivers and services that are part of each group. For example, the system loads all the device drivers and services that are part of the System Reserved group before loading the device drivers and services that are part of the SCSI miniport group.

When you add a service to the SCM's database, you can assign it to be in one of the predefined groups in the preceding list by passing the name of the group in CreateService's pszLoadOrderGroup parameter. Usually, your services don't need to load early in the system's boot cycle and should load after all the grouped devices and services have started to run. To make your service load after all the more system-critical device drivers and services, simply pass NULL for the pszLoadOrderGroup parameter.

If you are adding a device driver to the SCM (as opposed to a service), you can get even greater granularity in establishing the driver's start time by specifying a tag ID. Services cannot take advantage of this additional granularity and must always pass NULL to CreateService's pdwTagId parameter. If you're interested in device drivers, the Platform SDK documentation and DDK documentation discuss the pdwTagId parameter as well as two additional start options (SERVICE_BOOT_START and SERVICE_SYSTEM_START).

In addition to telling the SCM that your service is part of a particular load-order group, you can tell the SCM that your service requires certain other services and groups to be running before your service can run. For example, the Computer Browser service requires that the Workstation and Server services be up and running before it can work properly, and the ClipBook service requires that the Network DDE service be running.

Specifying which services your service depends on is typically much more useful than indicating that your service is part of a group. You use CreateService's pszDependencies parameter to tell the SCM's database which services you depend on. If your service has no dependencies, simply pass NULL for this parameter.

The pszDependencies parameter is a little unusual because you must pass the address of a double zero-terminated array of zero-separated names. In other words, pszDependencies must point to a block of memory that contains a set of zero-terminated strings with an extra zero character at the end of the buffer.

So to create a service that is dependent on the Workstation service (like the Alerter service), you would set pszDependencies (as shown in the following code) before passing it to CreateService:

 // The buffer below ends with two zero characters PCTSTR pszDependencies = TEXT("LanmanWorkstation\0"); CreateService(..., pszDependencies, ...); 

And to create a service that is dependent on the Workstation and Remote Procedure Call (RPC) services (such as the Messenger service), you would set pszDependencies like this:

 // The buffer below separates the strings with a zero character // and ends with two zero characters PCTSTR pszDependencies = TEXT("LanmanWorkstation\0RpcSs\0"); CreateService(..., pszDependencies, ...); 

A service can also be dependent on a group rather than on a single service, but this is very uncommon. Dependency on a load-order group means that the service can run if at least one member of the group is running after an attempt has been made to start all members of the group. To specify a group in the pszDependencies buffer, you must precede the group name with the special SC_GROUP_IDENTIFIER character, defined in WinSvc.h as follows:

 #define SC_GROUP_IDENTIFIERW           L'+' #define SC_GROUP_IDENTIFIERA           '+' #ifdef UNICODE #define SC_GROUP_IDENTIFIER            SC_GROUP_IDENTIFIERW #else  #define SC_GROUP_IDENTIFIER            SC_GROUP_IDENTIFIERA #endif 

Therefore, to create a service that is dependent on the Workstation service and the TDI group, you would set pszDependencies like this:

 // The buffer below specifies two dependencies: the Workstation service // and the TDI group (a group because of the '+' before TDI) PCTSTR pszDependencies = TEXT("LanmanWorkstation\0+TDI\0"); CreateService(..., pszDependencies, ...); 

When setting the pszDependencies value, you can specify as many services and groups as you like. Just remember to place a zero character between each service or group, to put a plus sign in front of all group names, and to place a terminating zero character just before the closing quote.

This brings us now to CreateService's final two parameters: pszUserName and pszUserPswd. These parameters allow you to specify under which user account the service is to run. To have the service run under the LocalSystem account (the most common case), pass NULL for these two parameters. If you want the service to run under a specific user account, pass an account name in the form of DomainName\UserName for the pszUserName parameter, and pass the user account's password for the pszUserPswd parameter.

NOTE
Interactive services must be configured to run under the LocalSystem account. CreateService fails to add the service to the SCM's database if you attempt to add an interactive service with a non-LocalSystem account.

If CreateService is successful in adding the service to the SCM's database, a non-NULL handle is returned. This handle is required by other functions to manipulate the service. Be sure you pass this handle to CloseServiceHandle when you're finished using it. If CreateService fails, it returns NULL, and a call to GetLastError returns a value indicating the reason for failure. Here are the most common reasons CreateService can fail:

  • The handle returned from OpenSCManager doesn't have SC_MANAGER_CREATE_SERVICE access.
  • The new service specifies a circular dependency.
  • A service with the same display name already exists.
  • The specified service name is invalid.
  • A parameter is invalid.
  • The specified user account does not exist.

I always write my service executables so that they can install themselves. In my (w)main or (w)WinMain function, I call a function such as ServiceInstall (shown in the next code snippet) if "-install" is passed as a command-line argument. The TimeService sample service presented in the last chapter demonstrates this technique.

 void ServiceInstall(PCTSTR pszInternalName, PCTSTR pszDisplayName,    DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl) {    // Open the SCM database to add a service    SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);    // Get the full pathname of our service's executable    char szModulePathname[_MAX_PATH];    GetModuleFileName(NULL, szModulePathname, sizeof(szModulePathname));    // Add this service to the SCM database    SC_HANDLE hService = CreateService(       hSCM, pszInternalName, pszDisplayName, 0, dwServiceType,       dwStartType, dwErrorControl, szModulePathname,       NULL, NULL, NULL, NULL, NULL);    // Close the newly created service and the SCM    CloseServiceHandle(hService);    CloseServiceHandle(hSCM); } 

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



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