sc Command in Windows: Query and Control Services from CMD

How to use sc.exe to query, configure and control Windows services from the command line: start types, recovery actions, dependencies and remote servers.

Windows services are managed by the Service Control Manager, and sc.exe is the built-in client that talks to it directly. It reads and writes the same configuration that services.msc shows in a GUI: start type, binary path, logon account, dependencies and recovery actions – on the local machine or on a remote server.

Most admins only ever use sc query and sc stop. That leaves the useful half untouched. sc qc is the fastest way to see why the same service behaves differently on two servers, and sc failure is the only built-in way to script service recovery actions – PowerShell has no cmdlet for it even today.

This article covers the subcommands worth knowing, the exact spacing rule that makes half of all sc commands fail silently, and the parsing tricks that actually survive real output. Killing a service process that refuses to stop is covered separately in tasklist and taskkill.

Applies to: Windows 10 / 11, Windows Server 2016 / 2019 / 2022


Quick answer

The four commands that cover most day-to-day work. Run them from an elevated prompt – querying works unelevated, changing anything does not.

rem current state and PID of a single service
sc queryex Spooler

rem the real configuration - start type, binary, logon account, dependencies
sc qc Spooler

rem change start type - note the space after the equals sign, it is mandatory
sc config Spooler start= demand

rem stop and start
sc stop Spooler
sc start Spooler
Warning: The equals sign belongs to the option name and a space is required after it. start= demand works. start=demand prints the usage screen and changes nothing – it does not report an error.

What sc.exe does

sc.exe is a command-line client for the Service Control Manager. It does not edit the registry directly – it calls the same API that services.msc uses, so changes take effect immediately and are recorded the same way. Every subcommand accepts an optional server name in UNC form as the first argument, before the subcommand.

rem the server name goes BEFORE the subcommand, not after it
sc \\SRV-PROD-01 query Spooler

The subcommands that matter in practice:

SubcommandWhat it returns or changesNeeds elevation
queryState of one service, or all active servicesNo
queryexSame as query plus PID and FLAGSNo
qcConfiguration: start type, binary path, logon account, dependenciesNo
qfailureCurrent recovery actionsNo
qdescriptionThe description text shown in services.mscNo
getkeynameService key name from a display nameNo
enumdependServices that depend on this oneNo
configChanges start type, binary path, account, dependenciesYes
failureSets recovery actions after a crashYes
start / stopSends START / STOP control requestsYes
create / deleteRegisters or removes a serviceYes
Note: sc query defaults to state= active. A stopped service is simply absent from the list until you add state= all. This is the single most common reason an enumeration looks incomplete.

Practical examples

1. The service name is not what services.msc shows you

The problem: The Services console lists “Print Spooler”, so you run sc stop "Print Spooler" and get [SC] OpenService FAILED 1060. The service exists, but SCM does not know it by that name.

The solution: sc getkeyname translates a display name into the key name every other subcommand expects. Quote the display name if it contains spaces – it is not case sensitive.

rem display name in, registry key name out
sc getkeyname "print spooler"

rem now the key name works everywhere else
sc query Spooler
[SC] GetServiceKeyName SUCCESS   Name = Spooler
Common mistake: Error 1060 reads as “service does not exist” and sends people looking for a missing installation. Nine times out of ten the service is there and the display name was used instead of the key name.

2. Same service, two servers, different behaviour

The problem: A vendor agent starts on SRV-PROD-01 and fails on SRV-PROD-02. Both show the same display name and the same version in services.msc.

The solution: sc qc prints the four fields that actually differ in practice – START_TYPE, BINARY_PATH_NAME, DEPENDENCIES and SERVICE_START_NAME. Run it against both servers and diff the output.

rem local
sc qc Spooler

rem remote - server name comes first, before the subcommand
sc \\SRV-PROD-01 qc Spooler

Expected output. SERVICE_START_NAME is the logon account – a service that runs as a domain account on one box and LocalSystem on another will behave differently no matter how identical the binary is.

[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: Spooler
        TYPE               : 110  WIN32_OWN_PROCESS (interactive)
        START_TYPE         : 2   AUTO_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : C:\Windows\System32\spoolsv.exe
        LOAD_ORDER_GROUP   : SpoolerGroup
        TAG                : 0
        DISPLAY_NAME       : Print Spooler
        DEPENDENCIES       : RPCSS
                           : http
        SERVICE_START_NAME : LocalSystem

To pull a single field into a script, split on colon and space and take the remainder of the line. Splitting on colon alone breaks the path at the drive letter.

rem tokens=1* keeps everything after the first token, so C:\ survives intact
rem delims is a colon followed by a space - both are treated as separators
for /f "tokens=1* delims=: " %a in ('sc qc Spooler ^| findstr /c:"BINARY_PATH_NAME"') do @echo %b
Result: C:\Windows\System32\spoolsv.exe – the full path, not truncated at the drive letter. Inside a batch file double the percent signs: %%a and %%b.

3. Change the start type without opening services.msc

The problem: A monitoring agent is set to start automatically on a test box and keeps generating alerts nobody wants. You need it off at boot but still startable by hand.

The solution: sc config writes the start type straight into SCM. The accepted values are boot, system, auto, demand, disabled and delayed-auto. “Manual” in the GUI is demand on the command line.

rem demand is what the GUI calls Manual - startable, but not at boot
sc config Spooler start= demand

rem delayed-auto starts after the auto-start services, which cuts boot contention
sc config Spooler start= delayed-auto

rem always read it back - config reports SUCCESS even when nothing useful changed
sc qc Spooler | findstr /c:"START_TYPE"

Put it back when the test window is over. Any article that changes a start type should show the way back.

rem restore the Windows default for Spooler
sc config Spooler start= auto
sc start Spooler
Warning: sc config changes configuration only. It does not stop or start anything. After start= disabled the service keeps running until the next stop or reboot, which is why “I disabled it” and “it is still running” are both true at the same time.

4. Make a flaky service restart itself

The problem: A vendor service dies roughly once a week at 03:00 and nobody notices until the morning. The GUI Recovery tab can fix it, but not across forty servers.

The solution: sc failure sets up to three actions, used on the first, second and third failure. Each action is followed by its delay in milliseconds and actions are separated by forward slashes. reset= is the error-free period in seconds after which the failure counter goes back to zero.

rem restart after 60s, restart again after 60s, then run a command on the third failure
rem reset= 86400 means the counter clears after a full day without a crash
sc failure Spooler reset= 86400 actions= restart/60000/restart/60000/run/1000

rem read the configuration back - this is the only way to confirm it took
sc qfailure Spooler

A command to run on the third failure is set separately with command=. For a batch file, call it through cmd.exe explicitly – SCM will not run a .bat on its own.

rem cmd.exe /c is required - SCM launches the binary, not the shell
sc failure Spooler reset= 86400 command= "cmd.exe /c C:\bat\notify-spooler.bat" actions= restart/60000/restart/60000/run/1000

Clearing the recovery actions again uses an empty actions= value.

rem back to "Take No Action" on every failure
sc failure Spooler reset= 0 actions= ""
Warning: reset= and actions= require each other. Sending one without the other fails. Also note that recovery actions only fire when the service terminates unexpectedly – a clean sc stop is not a failure and triggers nothing.

5. Know what falls over before you stop it

The problem: You need to restart a service on a production server during a short window. Stopping it takes down two other services that depend on it, and you find out from the ticket queue.

The solution: sc enumdepend lists everything that cannot run without the target service. Run it first, then use sc queryex to confirm the process actually went away.

rem what breaks if this service stops
sc enumdepend Spooler

rem 1024-byte default buffer is often too small on core services
rem the error message tells you the exact size to pass as the last argument
sc enumdepend rpcss 6971

Then confirm the state and the PID. queryex adds the PID and FLAGS lines that plain query leaves out.

SERVICE_NAME: Spooler
        TYPE               : 110  WIN32_OWN_PROCESS (interactive)
        STATE              : 4  RUNNING
                                (STOPPABLE, PAUSABLE, ACCEPTS_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 3184
        FLAGS              :

To capture just the PID for a follow-up command, the PID line has exactly three whitespace-separated tokens – the label, the colon and the number.

rem tokens=3 lands on the number because the colon is its own token
for /f "tokens=3" %a in ('sc queryex Spooler ^| findstr /c:"PID"') do @echo %a
Note: A PID of 0 means the service is not running. If the process refuses to die after sc stop, see tasklist and taskkill – but stop through SCM first, or the Service Control Manager keeps reporting the service as running.

Hidden gems

findstr treats spaces as OR, not as a phrase

This is the single biggest source of wrong output when filtering sc results. findstr "SERVICE_NAME STOPPED" matches any line containing either word, so a filter meant to find stopped services returns every service name as well. Use /c: for a literal string.

rem WRONG - matches every SERVICE_NAME line and every STOPPED line
sc query state= all | findstr "SERVICE_NAME STOPPED"

rem RIGHT - /c: makes the whole argument one literal search string
sc query state= all | findstr /c:"1  STOPPED"

Never split BINARY_PATH_NAME on a colon alone

for /f "tokens=2 delims=:" looks like the obvious way to grab the value after the label. On the binary path it returns C – the drive letter’s own colon ends the token. Use tokens=1* delims=": " as shown in example 2, or move to PowerShell for anything more than one field.

sc delete only marks a running service for deletion

If the service is running, or any process still holds an open handle to it, sc delete returns SUCCESS but the service stays in SCM and in services.msc until every handle closes – frequently until reboot. Stop the service first, then close services.msc, then delete.

sc query sees drivers that services.msc never shows

The Services console only lists Win32 services. type= driver enumerates kernel and file-system drivers from the same SCM database, which is the fastest way to confirm whether a filter driver is loaded.

rem kernel and file system drivers - invisible in services.msc
sc query type= driver

rem drivers in a specific load-order group
sc query type= driver group= NDIS
Note: type= can legitimately appear twice in one sc query. The first occurrence selects what to enumerate (driver, service, all), the second selects the service or driver type (own, share, interact, kernel, filesys and so on). It is not a typo in the documentation.

PowerShell equivalents

PowerShell covers querying and start types cleanly. It does not cover recovery actions at all – there is still no built-in cmdlet for what sc failure does.

The direct replacement for sc qc. Since PowerShell 6 the ServiceController object carries BinaryPathName, UserName, StartupType and DelayedAutoStart, so one line replaces reading the whole sc qc block.

# StartType alone reports Automatic for delayed-auto services -
# DelayedAutoStart is the property that tells them apart
Get-Service -Name Spooler |
    Select-Object Name, DisplayName, Status, StartType, DelayedAutoStart, UserName, BinaryPathName

The equivalent of sc config start=. Set-Service accepts AutomaticDelayedStart directly, which is cleaner than the delayed-auto spelling in CMD.

# Manual in the GUI, demand in sc.exe, Manual again in PowerShell
Set-Service -Name Spooler -StartupType Manual

# read it back
Get-Service Spooler | Select-Object Name, StartType, Status

For the PID and the exact command line, CIM is more reliable than parsing sc queryex output. Win32_Service.PathName includes any arguments the binary was registered with, which BINARY_PATH_NAME also shows but is harder to split.

# StartMode returns Auto for both plain and delayed automatic services -
# check DelayedAutoStart separately if the distinction matters
Get-CimInstance Win32_Service -Filter "Name='Spooler'" |
    Select-Object Name, State, StartMode, DelayedAutoStart, ProcessId, StartName, PathName
Warning: Get-Service -ComputerName was removed in PowerShell 6 and is not coming back. For remote queries either use Invoke-Command -ComputerName SRV-PROD-01 { Get-Service Spooler }, or stay with sc \\SRV-PROD-01 query Spooler, which still works from any CMD prompt with no remoting configured.

Where this matters

  • Post-patch validation – after a maintenance window, sc \\SRV-PROD-01 query across a server list confirms in seconds which services did not come back up.
  • Server build driftsc qc against a known-good reference server exposes a wrong logon account or a changed binary path faster than any GUI comparison.
  • Unattended recoverysc failure across a fleet turns a nightly crash into a self-healing restart instead of a morning incident.
  • Change windowssc enumdepend before a restart turns an unplanned outage into a planned one with the dependent services listed in the change ticket.
  • Security reviewsc qc shows every service still running under a domain account instead of a managed service account, which is the first thing an auditor asks for.
  • Server Core and rescue prompts – there is no services.msc on Server Core and none in a recovery console. sc.exe is what remains.

Tips and limitations

  • Querying needs no elevation. config, failure, start, stop, create and delete all do – without it you get [SC] OpenService FAILED 5: Access is denied.
  • In PowerShell, sc is an alias for Set-Content. Always type sc.exe in a PowerShell window, or you will silently create a file instead of touching a service.
  • Error 1060 is “service does not exist” – usually a display name used where a key name was required. Error 1072 means the service is already marked for deletion.
  • sc stop returns as soon as the STOP request is accepted, not when the service has stopped. Poll sc query if a script depends on the service actually being down.
  • Not every service accepts changes to its failure options – services that run as part of a shared service set often refuse.
  • Remote use goes over RPC on the SMB named pipe, so it needs TCP 445 reachable and local admin rights on the target. It does not need WinRM, which is why sc \\SRV-PROD-01 still works on hosts where PowerShell remoting was never enabled.
  • Do not use sc delete on built-in roles and features such as DHCP, DNS or IIS. Remove them through Server Manager instead.

Official documentation


Related tools

  • Event Log Analyzer – service crashes and recovery actions are logged as System events 7000, 7009, 7031 and 7034; paste the log and read them without opening Event Viewer.
  • Port Checker – when sc query says RUNNING but nothing answers, confirm whether the service is actually listening on its port.

Related guides