Windows Services and the Service Control Manager
How the Service Control Manager starts, stops, and supervises background processes, and how to configure and debug a service directly.
Every long-running background process on Windows that isn’t tied to a specific logged-in user — a database engine, a web server, an antivirus agent — is almost always registered as a service, supervised by a single system component: the Service Control Manager (SCM, services.exe). It’s Windows’ rough equivalent to systemd’s unit supervision or FreeBSD’s rc.d, with its own distinct configuration model.
Where service configuration actually lives
Every registered service has a corresponding key under the registry, which is the actual source of truth the SCM reads at boot and on demand:
HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Spooler"
Key values here include Start (boot / system / auto / manual / disabled, encoded as an integer), ImagePath (the executable, plus arguments), DependOnService (other services that must start first), and ObjectName (the account the service runs as).
Managing services: sc.exe and PowerShell
The classic command-line tool is sc.exe (Service Control), and modern administration typically uses PowerShell’s *-Service cmdlets instead, both ultimately talking to the same SCM API:
sc.exe query Spooler
Get-Service -Name Spooler | Select-Object Status, StartType, DependentServices
Start-Service Spooler
Stop-Service Spooler
Set-Service Spooler -StartupType Automatic
New-Service -Name "MyService" -BinaryPathName "C:\Apps\myservice.exe" -StartupType Automatic
Start types: when a service actually starts
Windows distinguishes several startup types, roughly analogous to a mix of systemd’s target-based ordering and enable/disable state: Boot/System start drivers extremely early (before most of the SCM’s own normal service-start sequence), Automatic starts at normal boot time, Automatic (Delayed Start) starts shortly after boot to avoid contending with more critical services for I/O and CPU during the busiest startup window, Manual only starts on explicit request or when another service/component starts it as a dependency, and Disabled can never start regardless of dependencies.
sc.exe config wuauserv start= delayed-auto
Dependencies: ordering without a full dependency-graph language
Unlike systemd’s rich After=/Requires=/Wants= vocabulary, the SCM’s dependency model is simpler: a service lists other services (or driver groups) it DependOnServices, and the SCM won’t start it until those are running. There’s no equivalent of systemd’s ordering-without-requiring (After= alone) — a listed dependency is always a hard requirement.
sc.exe qc Spooler
Service accounts and least privilege
A service’s ObjectName determines what it runs as — LocalSystem (full local privileges, avoid unless truly needed), LocalService/NetworkService (restricted built-in accounts), a Managed Service Account (an Active Directory-managed account with automatic password rotation, common for enterprise services needing network credentials), or a plain domain/local user account.
sc.exe config MyService obj= "NT AUTHORITY\LocalService"
Recovery actions: what happens when a service crashes
Each service has configurable recovery actions per failure — restart the service, run a program, or reboot the computer — settable per failure count (first failure, second failure, subsequent failures), which is the SCM’s equivalent to systemd’s Restart=/RestartSec=:
sc.exe failure Spooler reset= 86400 actions= restart/60000/restart/60000/run/1000
Debugging a service that won’t start
The Event Viewer’s System log is the first stop — the SCM logs a specific event (often Event ID 7000 or 7009) when a service fails to start, usually including the exact Win32 error code the service’s own startup routine returned:
Get-WinEvent -LogName System -FilterXPath "*[System[(EventID=7000 or EventID=7009 or EventID=7023)]]" -MaxEvents 10
Running the service’s executable directly from an elevated console, if it supports a debug/console mode, is often the fastest way to see the actual startup exception the Event Log’s summary line only hints at — many Windows services support a -debug or similar flag precisely for this.
Why so many services share one svchost.exe process
Look at a Task Manager process list and you’ll find a handful of svchost.exe processes, each hosting several unrelated-looking services simultaneously — Windows introduced this grouping in Windows 2000 specifically to avoid the memory overhead of every single service running as its own fully separate executable, since a great many built-in services are implemented as DLLs rather than standalone .exe files and need some host process to load into. The SCM decides which services share a host process based on grouping information in each service’s registry configuration (HKLM\SYSTEM\CurrentControlSet\Services\<Name> again, the same key that already holds Start and ImagePath), and historically this meant a handful of loosely-related services sharing fate: if one service in a shared svchost.exe process crashed hard enough to take the whole process down, every other service sharing that same host process went down with it.
Get-CimInstance Win32_Service | Where-Object ProcessId -eq 1234 | Select-Object Name, DisplayName
tasklist /svc /fi "PID eq 1234"
That shared-fate trade-off narrowed considerably in later Windows versions: starting with Windows 10 version 1703, machines with more than 3.5 GB of RAM run most services in their own individual svchost.exe process rather than grouped together by default, trading a modest amount of additional memory overhead for meaningfully better fault isolation and easier per-service debugging — a crashing service now more often takes down only itself, not a cluster of unrelated services that happened to share its host process.
Per-service SIDs: isolation beyond the account it runs as
A second, independent isolation mechanism arrived with Windows Vista: per-service SIDs. Before Vista, a service’s identity for ACL-checking purposes was entirely defined by whatever account it ran as — two services both configured to run as LocalSystem were, from an access-control standpoint, indistinguishable from each other, since both presented the exact same SID to any object they tried to access. Vista changed this by giving each service its own unique SID, derived deterministically from the service’s name, in addition to whatever SIDs its running account already carries:
sc.exe showsid Spooler
This lets a resource explicitly grant (or, more usefully, explicitly deny) access to one specific service by name, rather than to “whatever runs as LocalSystem” as an undifferentiated category — a service can be locked down to only touch the exact files, registry keys, and other resources it specifically needs, even while running under a broad built-in account it shares with many other unrelated services. This is what makes Windows Service Hardening (the umbrella term for these Vista-era service restrictions, which also includes write-restricted tokens and network firewall rules scoped per-service) meaningfully different from just “run as a less-privileged account” — it narrows access at the level of the individual service’s own identity, not merely the shared account multiple services might happen to run under.
The underlying design
Structurally, the SCM plays the same role systemd plays on Linux or launchd plays on macOS — a single, central process supervisor other tools query and command rather than each background daemon managing its own lifecycle independently. Its dependency model is intentionally simpler than systemd’s target/unit graph, but the core idea — declared dependencies, a single source of truth in a structured store (the registry, standing in for systemd’s unit files or launchd’s plists), and a uniform command surface (sc/Get-Service standing in for systemctl/launchctl) — is the same pattern every mainstream OS has converged on for supervising background work.
Related:
- PowerShell Remoting and WinRM: Managing Windows at Scale
- Understanding Windows Sessions and Session 0 Isolation
Sources: