UGTS Document #29 - Last Modified: 8/29/2015 3:23 PM
Windows Services

A Service in Windows is any EXE or DLL which can be launched by the Service Control Manager (SCM) handled by services.exe, and meets the following requirements:
  • It has been installed into the registry to be launched by the SCM, either by using the CreateServices API, or through .NET's System.ServiceProcess.ServiceProcessInstaller and System.ServiceProcess.ServiceInstaller classes, or directly by writing the necessary information to the registry (unsupported and not generally recommended).

  • If the service is implemented as an EXE, it will be launched by the SCM with the command line options defined in the registry by the above step. If it is a DLL, it will be loaded into a svchost.exe process, and then the ServiceMain function will be called with the same command line options that would have been passed to an EXE.

  • When the EXE or DLL is invoked with the configured command line options, it will be run outside of the context of an interactive user account and window station, and must not require user input at any time. Note that this does not restrict an EXE to being a console or a GUI application, because both kinds of apps can choose or abstain from requiring user input. If a service were to ask for user input, it would appear to be stuck, because such prompts would be displayed on a separate windows station which cannot normally be seen.

  • When the EXE or DLL is invoked with the configured command line options, it should register itself with the SCM to start the service(s) it handles. If it is an EXE, it should call the StartServiceCtrlDispatcher API (ServiceBase.Run in .NET). This function will registers all the named services which are passed to it to the SCM and connect them to named pipes from the SCM to receive commands, and will create return pipes back to the SCM to send status. The function then waits for SCM commands, dispatching them to the specified handlers for each service. It only returns when all the services it handles are stopped by the SCM.

    If it is a DLL, it should instead only call RegisterServiceCtrlHandlerEx, SetServiceStatus, do minimal initialization of the service, and then return as soon as possible. Svchost.exe takes care of the rest of the communication required to receive commands from the SCM and send back status to the SCM.
SCM Configuration / Registry

The SCM stores its configuration mostly under the registry key:

HKLM\SYSTEM\CurrentControlSet\Services

This key holds all the parameters needed for the SCM to start every service which has been installed on the machine, except for account passwords, which are stored in protected storage.

Security / Authorization

Because the SCM database is stored in the HKLM section of the registry, administrative privileges are required to install a service. Services can run under a built-in account, such as LocalSystem, Local Service, or Network Service, or they can run under a specified local or domain user account. If a service runs under a built-in account, no password needs to be stored to launch the service (because these built-in accounts do not have passwords). However, if the service is configured to run under a user account, then the password for the account (including the domain name) will be stored in the registry in the protected storage area. This password is saved using the API call LsaStorePrivateData, and the data is stored in the LSA secrets key:

HKLM\SECURITY\Policy\Secrets

Access to this key is restricted to only the LocalSystem account via ACLs, and the SCM calls LogonUserEx when launching the service, indirectly getting the password saved here by using the 'logon as a service' logon type. The service process (EXE or svchost.exe) then runs with the 'logon as a service' logon type under the given user account.

Service Installation (.NET)

In .NET, a Windows service can be installed in a number of ways. Here are two of the ways:
  • installutil.exe - This program is redistributed with the .NET runtime, and you can launch it from the command line to install everything defined by the classes which define the RunInstaller attribute. The installutil.exe file can be found programmatically in the directory: Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory

  • ManagedInstallerClass.InstallHelper - Internally, all installutil.exe does is pass its command line options verbatim to this method, so really there is nothing that prevents you from calling this method directly in code. This is unsupported by Microsoft, but practically speaking if you are building your application against a particular version of the framework, it is guaranteed to be there and behave as expected.

If you use either of the above two methods, you'll need to create a class with the RunInstaller(True) attribute.  It can be a little hard to understand what this class is supposed to do - because it is not really supposed to do anything except to add Installer objects in the constructor to the Installers collection, each with the appropriate parameters to install whatever object should be installed.  The constructor will be invoked whether you are installing or uninstalling, so you should not use the constructor to prompt for user input.  You can, but the prompt will also come up at the uninstall, which will be confusing.

Another point to remember is that this RunInstaller class must be defined in the entry assembly for the service, and not in a different DLL.  This is because you must pass the path to the specific EXE / DLL which contains your class to the InstallHelper function, and this function will assume that this EXE / DLL is to be your entry point (this cannot be changed in the ServiceProcessInstaller class).