Reference

Command line reference

Every command, every switch and every exit code of bws, version 0.5.0. bws --help is the authoritative one and is longer - this page is the same surface, laid out to be read.

The commands

Eleven commands. snapshot is a noun and takes a word after it. tool is a noun too, and on its own it lists the tools.

bws list [--query TEXT] [--signatures] [--memory] [--required-by] [--follow-network] [--json] [--timing]
bws show NAME [--full] [--follow-network] [--json] [--timing]
bws stop|start|restart NAME [--dry-run] [--dependents] [--timeout SECONDS] [--json] [--timing]
bws kill NAME [--force] [--restart] [--dry-run] [--dependents] [--timeout SECONDS] [--json] [--timing]
bws start-type NAME automatic|delayed|manual|disabled [--stop] [--dry-run] [--json] [--timing]
bws snapshot create [FILE] [--note TEXT] [--follow-network] [--force] [--json] [--timing]
bws snapshot diff EARLIER LATER [--exit-code] [--json] [--timing]
bws snapshot diff EARLIER --live [--exit-code] [--json] [--timing]
bws history [NAME] [--json] [--timing]
bws tool
bws tool fix
bws tool fix NAME [--dry-run] [--timeout SECONDS] [--json] [--timing]
bws license [--components]
bws --help
bws --version

A mistyped verb is offered the one you probably meant, and the run ends with code 2 rather than doing something close to what you asked.

$ bws lst
There is no command lst. Did you mean list?

What each verb does

list and show

list is the whole machine, narrowed by --query. show is one entry with everything known about it - the signature, the privileges, the security descriptor and the memory, all of them read every time, because over one entry that costs about sixty milliseconds where over the whole machine it costs a second. Fields that are genuinely empty are left out unless you pass --full. A field nobody could read is printed either way, because leaving one out would look like an answer.

stop, start and restart

Each builds a plan and carries it out. --dry-run prints the plan and changes nothing, --dependents puts the services that would break into it as steps of their own, and --timeout says how long one step may go without progress. Drivers are refused rather than attempted, because stopping a kernel driver is often not reversible without a restart.

kill - for a service that will not stop

It asks the service to stop and ends the process behind it only if that does not work, so an entry that stops on its own is never ended. Ending a process takes every other service living in that process with it, whether or not they stopped first, and the preview names them and names the process by number. --force skips the polite step and the preview then shows one step instead of two. --restart brings everything back afterwards.

Windows spells this idea differently. --force on Stop-Service means "even if something depends on it", which is what --dependents does here. That is why this is a verb of its own rather than a switch on stop.

start-type - a setting, not a move

It says what the manager will do with an entry at the next boot, and without --stop it moves nothing: an entry that is running keeps running, one that is stopped stays stopped, and the plan says so. disabled is the one worth pausing over, because it stops the manager starting the entry at all, including on demand for something else that needs it. delayed is automatic, started a little after the other automatic entries - the word start:delayed asks about in a query - and Windows refuses it for an entry in a load order group, which the plan says before anything happens. Each of the four writes whether the entry starts late, as sc.exe does. --stop, beside disabled only, stops the entry in the same plan once the setting is written.

snapshot create and snapshot diff

create always reads signatures and hashes, because a snapshot is kept and compared later and one without them would compare against one with them as though the machine had changed. diff says what changed going from the first file to the second, or from a file to this machine with --live. The snapshot page has the format and the reasoning.

history - who changed it, from the System log

Start type changes and installations out of the System log, newest first, with the account Windows recorded. The first line says how far back the log reaches and whether it was cleared, because a history is only as long as the log. With a name it is that service's start type changes - installations are listed without a name only, because Windows records an installation under the display name and never the service name. An account is named where this machine can name it on its own and shown as its SID otherwise, and start types come in the words Windows wrote.

tool - ready-made answers to common problems

The same tools the window shows on its Tools tab. bws tool lists them and bws tool fix lists the quick fixes: printer, sound, indexing and search, each a restart of one Windows service together with what depends on it, chosen because Microsoft's own troubleshooting pages suggest it. bws tool fix printer is exactly bws restart Spooler --dependents - the same plan, the same output and the same exit codes - and like every command here that changes something it does not ask first, so --dry-run is the way to look before it runs.

license - what is inside the file you downloaded

It reads nothing at all - no service manager, no disk, no network - and answers out of a register compiled into the executable. --components turns the notice into every component with its version, its licence and where it came from. The same set the bill of materials published beside the download carries, because both are rendered from one register, and the reason it is worth having: the program is one self-contained file, so there is nothing beside it for a scanner to read.

Examples

The plans below were checked with --dry-run against the program this page describes. A command that changes something does not ask first, so --dry-run is the way to look before it runs. Spooler stands for any service - put the name of yours in its place.

Find and read

bws list                                       every service and driver on this machine
bws list --query "start:auto !status:running"  what should be up and is not
bws list --query "status:running" --memory     what each running entry's process is using
bws show Spooler                               everything known about one entry
bws show Spooler --full                        the fields that are genuinely empty as well

Look first, then change

bws restart Spooler --dry-run                the plan, and nothing else
bws restart Spooler --dependents             restart it together with what depends on it
bws stop Spooler --dependents --timeout 120  allow two minutes per step without progress
bws start-type WSearch delayed --dry-run     start it a little after the other automatic entries
bws start-type Spooler disabled --stop       disable it and stop it in one plan

A service that will not stop

bws kill Spooler --dry-run          the process, and every service living in it
bws kill Spooler --restart          end the process only if a stop fails, then bring it all back
bws kill Spooler --force --dry-run  one step instead of two: the process ended at once

Snapshots and drift

bws snapshot create before.json --note "before the patch"  freeze the machine, with a note kept inside
bws snapshot diff before.json after.json                   two files - two days, or two machines
bws snapshot diff before.json --live                       the file against this machine as it is now
bws snapshot diff before.json --live --exit-code           the same, ending with 5 when the configuration drifted

Who changed it

bws history          every start type change and installation the System log holds
bws history Spooler  the start type changes of one service
bws history --json   the same as a document, with log and changes

Quick fixes

bws tool                      the tools there are
bws tool fix                  the quick fixes, and the service behind each one
bws tool fix sound --dry-run  the plan for restarting the two audio services
bws tool fix printer          restart the Print Spooler with what depends on it

What is inside the file

bws license --components  every component, its version, its licence and where it came from
bws --version             the version of this executable

In a PowerShell script

# the entries that should be up and are not, as objects
bws list --query "start:auto !status:running" --json | ConvertFrom-Json | Select-Object serviceName, displayName, status

# the unsigned files, into a spreadsheet
bws list --query "signed:no" --json | ConvertFrom-Json | Select-Object serviceName, binaryPath | Export-Csv unsigned.csv -NoTypeInformation

# what one service needs
(bws show Spooler --json | ConvertFrom-Json).dependsOn

# a nightly check that branches on the exit code
bws snapshot diff C:\baselines\web01.json --live --exit-code > $null
switch ($LASTEXITCODE) {
  0 { 'no configuration drift' }
  5 { 'the configuration drifted' }
  default { "no clean answer, exit code $LASTEXITCODE" }
}

In cmd and a batch file

rem a second command only when the first did not end cleanly
bws restart Spooler || echo the restart did not end cleanly

rem in a batch file with delayed expansion turned on, the ! needs a caret
bws list --query "start:auto ^!status:running"

Switches

18 of them. The third column is the verbs each one belongs to - a switch passed to a verb that has no use for it is an error with that list in it, rather than a flag quietly ignored.

SwitchWhat it doesWhere it works
--queryNarrow the listing with the query language.list
--signaturesRead who signed each binary and whether Windows trusts it. Several seconds over a whole machine, so it is off unless asked - and a query about signatures turns it on by itself.list
--memoryRead what each running entry's process is using. Off by default because it is a measurement rather than a setting: it is different a second later.list
--required-byRead which entries break if one is stopped, asked of Windows directly rather than worked out from what everything declares. A call per entry, so off unless asked. show and snapshot create read it every time.list
--follow-networkLet the tool reach off this machine at all - a launch path living on somebody else's share, and a certificate this machine does not already hold. Off by default for safety: one unreachable share costs twenty-one seconds and authenticates as whoever ran it, and fetching a certificate is a connection to a third party in the middle of an ordinary listing. Without it a signature is checked against what is here, and a result that might have been caused by not looking is reported as unread rather than as a verdict.list, show, snapshot create
--forceOn kill, end the process straight away without asking politely - the preview then shows one step instead of two. On snapshot create, replace a snapshot that is already there - a file that is not a snapshot is never replaced.snapshot create, kill
--restartOn kill, bring the entry back once the process is gone, along with everything that shared it.kill
--stopOn start-type, and only beside disabled, stop the entry in the same plan once the setting is written. A startup type changes the next boot and nothing now, so without it a running entry set to disabled keeps running.start-type
--fullOn show, print the fields that are genuinely empty as well. A field nobody could read is printed either way.show
--jsonThe same document, machine readable, on standard output.list, show, stop, start, restart, start-type, snapshot create, snapshot diff, kill, history, tool fix NAME
--noteWhat the snapshot was taken for, kept inside the file.snapshot create
--exit-codeEnd with code 5 when the configuration differs - an entry added or removed, or one set up differently. Running state alone does not count. Off by default, so a script that only wants the differences printed is not tripped by finding some.snapshot diff
--liveCompare the file against this machine as it is now, rather than against a second file.snapshot diff
--timingHow long each part of the read took, on standard error.list, show, stop, start, restart, start-type, snapshot create, snapshot diff, kill, history, tool fix NAME
--dry-runPrint the plan and change nothing. It is the same plan an execution runs - there is no second code path for the real thing.stop, start, restart, start-type, kill, tool fix NAME
--dependentsPut the services that would break into the plan as steps of their own.stop, restart, kill
--timeoutHow long one step may go without progress before it is given up on, counted again every time the service reports progress. Sixty seconds unless you say otherwise. A service that keeps reporting progress is watched for as long as it takes. Running out is the end of watching rather than a failure, and the report says where the entry was left.stop, start, restart, kill, tool fix NAME
--componentsOn license, turn the notice into every component inside this executable with its version, its licence and where it came from. The same set the bill of materials published beside the download carries, because both are rendered from one register.license

Exit codes

6 endings, and every one of them is a different thing for a script to do. Data goes to standard output and everything else to standard error, so bws list --json | jq works and a warning never lands in your JSON.

Every JSON document is plain ASCII: a letter outside it is written as a six character escape that every JSON reader turns back into the letter, so bws list --json > file.json is valid UTF-8 and | ConvertFrom-Json reads it right whatever code page the console is set to. In the text a person reads, a character that would move the terminal rather than print - a tab, a line break, an escape sequence, a direction override - is shown as its code point, <U+001B>.

-- ends the switches, so a service whose name begins with a dash can still be named: bws show -- -odd. The command lines the tool hands you are written for PowerShell: a name with a space goes in double quotes, which cmd reads the same way, and a name PowerShell would expand inside them, such as MSSQL$SQLEXPRESS, goes in single quotes.

CodeMeaning
0Done, and everything arrived where it was going.
1The tool failed - it could not do what was asked, for a reason that is about the tool rather than about the plan.
2The command line was wrong. A mistyped command is offered the one you probably meant.
3The plan was good, it ran, and something in it did not get where it was going - the manager refused, or the session had no rights to it.
4Somebody stopped the run by hand. Non-zero even when every step still arrived, so a wrapper does not treat an interrupted run as clean.
5A comparison ran and found the configuration drifted - an entry added or removed, or one set up differently. Running state alone is not drift. Only with --exit-code, because drift is what this tool is for finding, and finding it is not a failure.

Ctrl+C has three levels, each saying what the next one costs. The first stops going forward and still puts back what was taken, the second leaves things as they are and still prints the report, the third ends the process. A run stopped by hand ends with 4 even when every step still arrived.

Closing the console window counts as the first Ctrl+C. Windows gives a program five seconds after that, and in them this one stops going forward and puts back what it took down. Signing out and shutting down end it without any warning it could act on, and a dropped remote session may too.

In a scheduled task

The shape this tool was built for: freeze the machine when it is known good, then ask every night whether it still is.

# the day the machine goes into service
bws snapshot create C:\baselines\web01.json --note "after the build"

# every night, from a scheduled task
bws snapshot diff C:\baselines\web01.json --live --exit-code --json > C:\logs\drift.json
#   0 - no configuration drift
#   5 - the configuration drifted, and drift.json says what
#   3 - it ran and could not read everything, so the answer is partial

The window is the other half of the same engine: BetterWindowsServices-win-x64.zip holds it, and the download page says what each archive is.

The window's argument

BetterWindowsServices.exe takes one argument meant for a person: --catalogue opens a developer's sheet showing every component of the window in every state, and reads nothing from your machine. A second one, --carry, is how a window hands what it was showing to the copy of itself it restarts as administrator. Nobody types it, and anything in it that window could not have written is refused.