The Big Picture: From "Start" to "Running"

You press Start on a service. A moment later it says Running. What really happens in between? This section walks through the chain, step by step. It ties together everything from Sections 2 to 8.

┌────────────────────────────────────────────────────────────┐
│ 0. Someone asks: StartService (boot, services.msc, sc ...) │
└─────────────────────────────┬──────────────────────────────┘
                              ▼
┌────────────────────────────────────────────────────────────┐
│ 1. SCM (services.exe) reads the registry service entry     │
│    ImagePath, Type, ObjectName, DependOnService ...        │
└─────────────────────────────┬──────────────────────────────┘
                              ▼
┌────────────────────────────────────────────────────────────┐
│ 2. SCM starts dependencies, then the host process          │
│    (own .exe, or svchost.exe loading a ServiceDll)         │
└─────────────────────────────┬──────────────────────────────┘
                              ▼
┌────────────────────────────────────────────────────────────┐
│ 3. Process calls StartServiceCtrlDispatcher                │
│    SCM runs the service's ServiceMain                      │
└─────────────────────────────┬──────────────────────────────┘
                              ▼
┌────────────────────────────────────────────────────────────┐
│ 4. Service reports START_PENDING, then RUNNING             │
│    with SetServiceStatus; SCM updates its records          │
└────────────────────────────────────────────────────────────┘

The whole thing must finish in about 30 seconds, or the SCM reports error 1053 (Section 8).

9.1 SCM Reads Registry Service Entry

What it is: The first step. The SCM looks up the service in its database (Section 2.2) and decides how to start it.

Checks before anything starts:

CheckIf it fails
Does the service exist?Error 1060
Is it Disabled?Error 1058
Is the caller allowed to start it? (Section 6)Error 5, access denied
Is it already running?Error 1056, "an instance of the service is already running"
Are its dependencies running? (Section 4)SCM starts them first; if one fails, error 1068

Values it reads from HKLM\SYSTEM\CurrentControlSet\Services\<name>:

ValueUsed for
ImagePathWhich program file to launch, and with which options
TypeOwn process (0x10) or shared process (0x20)
ObjectNameWhich account to log on as (Section 5)
Start, DelayedAutostart, TriggersWhy and when it started (Section 3)
DependOnServiceStart order (Section 4)
FailureActionsWhat to do later if it crashes (Section 7)

Logging on. The SCM then gets the token (a Windows "ID card" listing the account and its powers) for the service's account. For custom accounts, it uses the stored password. If this fails: error 1069 (Section 8).

9.2 Executable/DLL Loaded (svchost.exe Hosting)

What it is: The SCM creates a new process for the service, using the account from step 1. What it launches depends on the service type.

Option A: Own process (Type = 0x10). ImagePath names the service's own .exe, for example C:\Windows\System32\spoolsv.exe for Print Spooler. That program is the service. It runs alone, so a crash affects only itself.

Option B: Shared process (Type = 0x20), hosted by svchost.exe. Many Windows services are not an .exe but a DLL (a code library: a file of functions that other programs load). A generic host program, svchost.exe (Service Host), loads the DLL.

The ImagePath then looks like:

C:\Windows\System32\svchost.exe -k netsvcs -p
PieceMeaning
svchost.exeThe generic host
-k netsvcsThe service group name. All services in the same group may share one svchost.exe
-pAn extra protection option used on many current services

Where is the DLL named? In the service's own Parameters sub-key, the value ServiceDll (for example %SystemRoot%\System32\...dll). svchost reads it and loads that DLL.

The list of which services belong to which group is stored in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost. Each value there is a group name (like netsvcs) and a list of service names.

Shared hosting                         Own process
┌──────────────────────────┐      ┌──────────────────────────┐
│ svchost.exe -k netsvcs   │      │ spoolsv.exe              │
│  ┌────────┐ ┌────────┐   │      │  (Print Spooler's own    │
│  │ DLL: A │ │ DLL: B │   │      │   program)               │
│  └────────┘ └────────┘   │      └──────────────────────────┘
└──────────────────────────┘
 one process, several services       one process, one service

Why share? In the past, to save memory: one process instead of 50. The price: if one DLL crashes the host, all services in it die.

Modern change. From Windows 10 version 1703, on PCs with enough RAM (about 3.5 GB or more), Windows runs most services in their own svchost.exe. That is why you see many svchost.exe entries. On low-memory PCs, grouping still happens.

See which service is in which process (read-only):

tasklist /svc /fi "imagename eq svchost.exe"

In Task Manager (Chapter 11), right-click a svchost.exe entry and choose Go to service(s).

9.3 Service Main Function Registered

What it is: Now the new process has to introduce itself to the SCM. A normal program starts at main. A service has an extra step: it must tell the SCM "I am a service. Here are the names of my services and the function that starts each."

The function names (from the Windows programming interface):

NamePlain meaning
StartServiceCtrlDispatcherThe process says "I'm a service host. Here is my table of service names and their start functions." This call does not return until all the services in the process have stopped
ServiceMainThe start function for one service. The SCM runs it in a new thread
RegisterServiceCtrlHandlerExThe service says "here is my control handler: the function to call when you want to stop or pause me"
Control handlerA function that receives messages like STOP, PAUSE, CONTINUE, SHUTDOWN

In the shared-host case, svchost does this work for you: it reads the DLL, finds its ServiceMain entry (the name can be set in the registry, ServiceMain is the default) and puts it in the table.

A service written as an .exe looks like this in simple pseudo-code:

main():
    table = [ ("MyService", ServiceMain) ]
    StartServiceCtrlDispatcher(table)     // blocks here, SCM takes over

ServiceMain():
    handler = RegisterServiceCtrlHandlerEx("MyService", ControlHandler)
    SetServiceStatus(START_PENDING)
    ... do start-up work (open files, start threads) ...
    SetServiceStatus(RUNNING)

The timing rule. After launching the process, the SCM waits for it to call StartServiceCtrlDispatcher. If this does not happen in about 30 seconds, the SCM gives up, kills the process and writes Event 7009; the user sees error 1053. This is also why an ordinary program turned into a service by a trick fails: it never calls the function, so the SCM thinks it hung.

Behind the scenes. The SCM and the service process talk over a private connection (a named pipe). When you press Stop, the SCM sends the STOP control down that pipe, and the service's control handler runs.

Important: ServiceMain must return quickly. Real work happens on other threads. If ServiceMain blocks forever, the service never reports RUNNING.

9.4 Status Reported Back to SCM

What it is: Throughout its life, the service reports its state to the SCM with SetServiceStatus. The SCM keeps this latest state and shows it to every tool.

The states:

StateMeaning
SERVICE_START_PENDING"I'm starting. Wait for me."
SERVICE_RUNNING"I'm ready and working."
SERVICE_PAUSE_PENDING / SERVICE_PAUSED / SERVICE_CONTINUE_PENDINGPausing, paused, resuming (only for services that support pause)
SERVICE_STOP_PENDING"I'm shutting down."
SERVICE_STOPPED"I've stopped."
        ┌─────────┐ start ┌───────────────┐ ready ┌─────────┐
        │ STOPPED │──────►│ START_PENDING │──────►│ RUNNING │
        └─────────┘       └───────────────┘       └────┬────┘
             ▲                                         │ stop
             │            ┌──────────────┐             ▼
             └────────────┤ STOP_PENDING │◄────────────┘
              stopped     └──────────────┘

Check points. A slow service can send repeated updates while still in a "pending" state, using two numbers: a checkpoint (which should go up each time) and a wait hint (how many more milliseconds it expects to need). This tells the SCM "I'm still working, don't give up." If the checkpoint stops increasing and the wait hint runs out, the SCM treats the service as stuck.

The "accepted controls" flags. In the same status report, the service says which controls it accepts: STOP, PAUSE_CONTINUE, SHUTDOWN, and others. That is why some services show Stop greyed out in services.msc: the service has not said it accepts STOP. Windows uses the flags to decide what buttons are active.

After RUNNING. The SCM now:

  1. Updates its records (visible via sc query).
  2. Writes Event 7036 ("entered the running state").
  3. Starts any dependent services waiting for this one (Section 4).
  4. Keeps watching the process. If it dies without reporting STOPPED, the SCM runs the recovery actions (Section 7) and logs 7034 or 7031.

See it in your own PC (read-only):

sc query Spooler

It prints STATE, and also WIN32_EXIT_CODE, CHECKPOINT and WAIT_HINT, the same fields the service reported.

Summary

StepWhoWhat
1SCMReads the registry entry, checks permissions, starts dependencies
2SCMStarts the host process under the service account: own .exe or svchost.exe -k group loading ServiceDll
3The processCalls StartServiceCtrlDispatcher; SCM runs ServiceMain; handler registered
4The serviceSetServiceStatus: START_PENDING, then RUNNING; later STOP_PENDING, STOPPED
Time limitSCMAbout 30 seconds to connect, else error 1053 / event 7009
AfterSCMLogs 7036, starts dependents, watches for crashes and applies recovery