Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,40 @@ is not part of this repository.

### Changed

- **Breaking for scripts reading `--json`:** every word-valued field in the JSON of a plan and of a
comparison is now spelled the way the listing and the snapshot spell theirs - `"outcome":
"Succeeded"` beside `"status": "Running"`, where one result used to say `"succeeded"` beside
`"Running"`. Field names are unchanged. What changed:

| Field | Before | Now |
|---|---|---|
| `action` | `stop`, `start`, `restart`, `setStartType`, `forceStop`, `forceRestart` | `Stop`, `Start`, `Restart`, `SetStartType`, `ForceStop`, `ForceRestart` |
| `operation` | `stop`, `start`, `setStartType`, `terminate` | `Stop`, `Start`, `SetStartType`, `Terminate` |
| `reason` | `requested`, `cascade`, `sharesTheProcess`, `restore`, `escalation` | `Requested`, `Cascade`, `SharesTheProcess`, `Restore`, `Escalation` |
| `outcome` | `succeeded`, `failed`, `timedOut`, `skipped` | `Succeeded`, `Failed`, `TimedOut`, `Skipped` |
| `skippedBecause` | `alreadyThere`, `earlierStepFailed`, `cancelled`, `processStays`, `nothingToPutBack` | `AlreadyThere`, `EarlierStepFailed`, `Cancelled`, `ProcessStays`, `NothingToPutBack` |
| `kind` of a warning | `cascade`, `dependentsInTheWay`, `sharedProcess` and the rest | `Cascade`, `DependentsInTheWay`, `SharedProcess` and the rest - the first letter raised on every one |
| `group` in `snapshot diff --json` | `configuration`, `runningState` | `Configuration`, `RunningState` |

PowerShell compares text without case by default, so `$step.outcome -eq 'succeeded'` keeps
working. A comparison that is case-sensitive - `-ceq`, `jq`, most other languages - needs the new
spelling.
- `snapshot diff --exit-code` ends with 5 only when the configuration differs: an entry added or
removed, or one set up differently. A service that only stopped or started by itself between the
two snapshots is still reported, under "Changed" and marked as running state, and no longer makes
a nightly check fail. `"differs"` in `--json` answers the same way.
- Per-user service copies - the ones Windows makes for each signed-in session, named like
`CDPUserSvc_1036d1` - are left out of a comparison on both sides and counted in one line at the
top, and in `"instancesLeftOut"` in `--json`. They come and go with the people signed in, so they
used to appear as added and removed every time. The template they are made from is still compared.
- A snapshot now records Windows down to the monthly update (`operatingSystemVersion`, for example
`10.0.26200.9550`) and the language the service manager names things in (`namesLanguage`). The
comparison says when the two were taken on different updates, when they were taken by different
accounts, and when the names are in different languages - and then leaves display names and
descriptions out, instead of reporting every translated one as changed. Snapshots are now schema
version 5. Files written by 0.3.0 (version 4) are still read and compared, with a line saying they
do not record the update or the language. A file written by this version is refused by 0.3.0.

- The warning that Windows starts a service again once its process is ended now says when, beside
every name: "Spooler (5 s later)", or "W32Time (60 s or 120 s later)" when the recovery actions
name several delays - which of them applies depends on how often the service has failed, and
Expand Down Expand Up @@ -111,7 +145,7 @@ is not part of this repository.
those only if the entry did not stop. A service sharing the process that refused to stop used to
make the plan skip the entry's own polite stop and end the process at once, and when the entry
would have stopped on its own, the others had been stopped for nothing. In the JSON of a run, the
steps not needed are reported with `"skippedBecause": "processStays"`, and the run still counts as
steps not needed are reported with `"skippedBecause": "ProcessStays"`, and the run still counts as
completed.
- The preview of a force stop names a critical service arriving with `--dependents`, a service
sharing the process that does not accept a stop, and one that is disabled and could not be
Expand Down Expand Up @@ -146,27 +180,33 @@ is not part of this repository.
- The preview of a force stop says when Windows will start a service living in the process again by
itself once the process is ended, when it will run a program named in a service's recovery
actions, and when a service has a recovery action of a kind the tool cannot name. In the JSON of a
plan these are the warnings `recoveryRestarts`, `recoveryRunsProgram` and `recoveryUnnamed`. Until
plan these are the warnings `RecoveryRestarts`, `RecoveryRunsProgram` and `RecoveryUnnamed`. Until
now `bws kill` reported such a service stopped while Windows was already starting it again.
- A force stop whose service Windows starts again at once is reported straight away as the process
ended and the service running again, with its new process, instead of after the whole limit as
having run out of time. In the JSON of a run that step is `"outcome": "failed"` with `errorCode` 0.
having run out of time. In the JSON of a run that step is `"outcome": "Failed"` with `errorCode` 0.
- Just before the process is ended, a force stop looks at it once more. If a service has started
inside it since the preview, or a running service outside it has started to depend on something
inside it, the process is not ended and the step says why.
- Stop pressed before the first step of a restart, or Ctrl+C, no longer starts a service that was
not running. A run now starts again only what it stopped itself: after an interruption, after a
stop that was refused, and for a dependent somebody else stopped between the preview and the
run, the step says "not started, this run never stopped it" and `skippedBecause` in the JSON of
a run is `nothingToPutBack`. The same goes for a restart of a whole selection interrupted half
a run is `NothingToPutBack`. The same goes for a restart of a whole selection interrupted half
way. A service stopped by somebody else after the preview of its own restart is left stopped,
and the run says it did not end where the plan wanted it.
- Restarting a service that is not running only starts it, from the window and with
`bws restart`, and the preview says so - one step and the warning "is not running, so
restarting it only starts it" (`restartOnlyStarts` in the JSON). Until now the preview showed a
restarting it only starts it" (`RestartOnlyStarts` in the JSON). Until now the preview showed a
stop and a start. A disabled service that is not running is no longer refused with a sentence
about stopping it - the plan warns that Windows will refuse to start it, the same as a plan to
start it.
- `snapshot diff` on a snapshot with an empty name in an entry's list of fields nobody read says
which entry is damaged and ends with code 2, instead of "Object reference not set to an instance
of an object." and code 1.
- `snapshot create` given a folder instead of a file name says so at once and ends with code 2. It
used to verify every signature on the machine first and then answer "Access to the path is
denied."

## [0.3.0] - 2026-09-25

Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ bws stop Winmgmt --dry-run --dependents what stopping it would take do
bws kill Spooler --dry-run what ending its process would take with it
bws start-type Spooler manual --dry-run what taking it off automatic would do
bws snapshot create before.json freeze the machine before a change
bws snapshot diff before.json --live --exit-code what has changed since, 5 if anything has
bws snapshot diff before.json --live --exit-code what has changed since, 5 if the setup has
```

A preview is the plan, printed:
Expand Down Expand Up @@ -237,8 +237,11 @@ session had no rights to it, comes out as *not compared* under its entry, and th
those entries, so a diff that says *nothing changed* means nothing changed in what both sides
could see.

`--exit-code` ends with `5` when anything differs, so a scheduled task can take a snapshot at
deployment and page somebody the first night the machine drifts. Compare two files instead of a
`--exit-code` ends with `5` when the configuration differs - an entry added or removed, or one set
up differently - so a scheduled task can take a snapshot at deployment and page somebody the first
night the machine drifts. A service that only stopped or started by itself is reported and does
not count, and neither do the per-user copies Windows makes for each signed-in session: those are
left out of the comparison and counted in a line of their own. Compare two files instead of a
file and the machine to answer *what did the Tuesday patch do*, or *why does staging differ from
production* - take one on each and diff them anywhere.

Expand Down
4 changes: 2 additions & 2 deletions site/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"exit.usage": "The command line was wrong. A mistyped command is offered the one you probably meant.",
"exit.incomplete": "The 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.",
"exit.interrupted": "Somebody stopped the run by hand. Non-zero even when every step still arrived, so a wrapper does not treat an interrupted run as clean.",
"exit.differences": "A comparison ran and found differences. Only with <code>--exit-code</code>, because drift is what this tool is for finding, and finding it is not a failure.",
"exit.differences": "A 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 <code>--exit-code</code>, because drift is what this tool is for finding, and finding it is not a failure.",

"switch.query": "Narrow the listing with the query language.",
"switch.signatures": "Read 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.",
Expand All @@ -52,7 +52,7 @@
"switch.components": "On <code>license</code>, 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.",
"switch.json": "The same document, machine readable, on standard output.",
"switch.note": "What the snapshot was taken for, kept inside the file.",
"switch.exit-code": "End with code 5 when anything differs. Off by default, so a script that only wants the differences printed is not tripped by finding some.",
"switch.exit-code": "End 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.",
"switch.live": "Compare the file against this machine as it is now, rather than against a second file.",
"switch.timing": "How long each part of the read took, on standard error.",
"switch.dry-run": "Print the plan and change nothing. It is the same plan an execution runs - there is no second code path for the real thing.",
Expand Down
4 changes: 2 additions & 2 deletions site/i18n/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"exit.usage": "Wiersz poleceń był zły. Przy literówce w komendzie program podpowiada tę, o którą prawdopodobnie chodziło.",
"exit.incomplete": "Plan był dobry, wykonał się, a coś w nim nie dotarło tam, gdzie miało - menedżer odmówił albo sesja nie miała do tego praw.",
"exit.interrupted": "Ktoś zatrzymał przebieg ręcznie. Niezerowy nawet wtedy, gdy każdy krok i tak dotarł, żeby skrypt nadrzędny nie potraktował przerwanego przebiegu jako czystego.",
"exit.differences": "Porównanie wykonało się i znalazło różnice. Tylko z <code>--exit-code</code>, bo dryf jest tym, czego to narzędzie szuka, a znalezienie go nie jest porażką.",
"exit.differences": "Porównanie wykonało się i znalazło dryf konfiguracji - wpis doszedł albo zniknął, albo jest ustawiony inaczej. Sam stan pracy nie jest dryfem. Tylko z <code>--exit-code</code>, bo dryf jest tym, czego to narzędzie szuka, a znalezienie go nie jest porażką.",

"switch.query": "Zawęża listę językiem zapytań.",
"switch.signatures": "Czyta, kto podpisał każdy plik i czy Windows temu podpisowi ufa. Kilka sekund na całej maszynie, więc jest wyłączone, dopóki nie poprosisz - a zapytanie o podpisy włącza to samo.",
Expand All @@ -52,7 +52,7 @@
"switch.components": "Przy <code>license</code> zamienia notę w pełną listę: każdy składnik wewnątrz tego pliku wykonywalnego, z wersją, licencją i adresem źródeł. Ten sam zestaw, który niesie spis składników publikowany obok pobrania, bo oba powstają z jednego rejestru.",
"switch.json": "Ten sam dokument, czytelny dla maszyny, na wyjściu standardowym.",
"switch.note": "Po co snapshot został zrobiony - zapisane w środku pliku.",
"switch.exit-code": "Kończy kodem 5, gdy cokolwiek się różni. Domyślnie wyłączone, żeby skrypt, który chce tylko zobaczyć różnice, nie wywracał się na tym, że je znalazł.",
"switch.exit-code": "Kończy kodem 5, gdy różni się konfiguracja - wpis doszedł albo zniknął, albo jest ustawiony inaczej. Sam stan pracy się nie liczy. Domyślnie wyłączone, żeby skrypt, który chce tylko zobaczyć różnice, nie wywracał się na tym, że je znalazł.",
"switch.live": "Porównuje plik z tą maszyną w jej obecnym stanie, zamiast z drugim plikiem.",
"switch.timing": "Ile trwała każda część odczytu, na wyjściu błędów.",
"switch.dry-run": "Drukuje plan i nie zmienia niczego. To ten sam plan, który wykonuje się przy wykonaniu - nie ma drugiej ścieżki kodu dla wersji prawdziwej.",
Expand Down
4 changes: 2 additions & 2 deletions site/pages/audit-windows-services/en.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ <h2>The nightly shape</h2>

<span class="c"># every night, from a scheduled task</span>
<span class="k">bws snapshot diff C:\baselines\web01.json --live --exit-code --json &gt; C:\logs\drift.json</span>
<span class="c"># 0 - nothing differs
# 5 - something differs, and drift.json says what
<span class="c"># 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</span></code></pre>
<p>Exit code <code>5</code> is what makes this an audit rather than a report nobody reads: the task is silent until the machine drifts, and the night it does, somebody is paged.</p>
<p class="note"><strong>Take the baseline from an elevated session.</strong> Without administrator rights the manager lists fewer entries and refuses more of what it lists. The snapshot records whether the account that took it was an administrator, so the comparison can say that rather than reporting entries as removed that nobody removed - but a baseline missing half the machine is still a baseline missing half the machine.</p>
Expand Down
4 changes: 2 additions & 2 deletions site/pages/audit-windows-services/pl.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ <h2>Nocny kształt</h2>

<span class="c"># co noc, z zadania harmonogramu</span>
<span class="k">bws snapshot diff C:\baselines\web01.json --live --exit-code --json &gt; C:\logs\drift.json</span>
<span class="c"># 0 - nic się nie różni
# 5 - coś się różni, a drift.json mówi co
<span class="c"># 0 - konfiguracja się nie rozjechała
# 5 - konfiguracja się rozjechała, a drift.json mówi co
# 3 - wykonało się i nie wszystko dało się odczytać, więc odpowiedź jest częściowa</span></code></pre>
<p>Kod wyjścia <code>5</code> jest tym, co robi z tego audyt, a nie raport, którego nikt nie czyta: zadanie milczy, dopóki maszyna się nie rozjedzie, a tej nocy, w której się rozjedzie, ktoś zostaje wezwany.</p>
<p class="note"><strong>Punkt odniesienia rób z sesji elewowanej.</strong> Bez praw administratora menedżer wymienia mniej wpisów i odmawia częściej. Snapshot zapisuje, czy konto, które go zrobiło, było administratorem, więc porównanie może to powiedzieć, zamiast zgłaszać jako usunięte wpisy, których nikt nie usunął - ale punkt odniesienia bez połowy maszyny nadal jest punktem odniesienia bez połowy maszyny.</p>
Expand Down
4 changes: 2 additions & 2 deletions site/pages/cli-reference/en.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ <h2>In a scheduled task</h2>

<span class="c"># every night, from a scheduled task</span>
<span class="k">bws snapshot diff C:\baselines\web01.json --live --exit-code --json &gt; C:\logs\drift.json</span>
<span class="c"># 0 - nothing differs
# 5 - something differs, and drift.json says what
<span class="c"># 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</span></code></pre>
<p>The window is the other half of the same engine: <code>{{archive_window}}</code> holds it, and <a href="/download/">the download page</a> says what each archive is.</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions site/pages/cli-reference/pl.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ <h2>W zadaniu harmonogramu</h2>

<span class="c"># co noc, z zadania harmonogramu</span>
<span class="k">bws snapshot diff C:\baselines\web01.json --live --exit-code --json &gt; C:\logs\drift.json</span>
<span class="c"># 0 - nic się nie różni
# 5 - coś się różni, a drift.json mówi co
<span class="c"># 0 - konfiguracja się nie rozjechała
# 5 - konfiguracja się rozjechała, a drift.json mówi co
# 3 - wykonało się i nie wszystko dało się odczytać, więc odpowiedź jest częściowa</span></code></pre>
<p>Okno jest drugą połową tego samego silnika: niesie je <code>{{archive_window}}</code>, a <a href="/pl/pobieranie/">strona pobierania</a> mówi, co jest w którym archiwum.</p>
</div>
Expand Down
4 changes: 2 additions & 2 deletions site/pages/home/en.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ <h2>What changed since yesterday</h2>
<ul class="points points-ok">
<li><strong>Configuration and running state are reported apart.</strong> Two snapshots taken a day apart differ in what happens to be running and almost none of it is drift - the start type that changed is the line to read.</li>
<li><strong>What could not be compared is listed, not hidden.</strong> A field one snapshot never read comes out as <em>not compared</em>, so a diff that says <em>nothing changed</em> means nothing changed in what both sides could see.</li>
<li><strong><code>--exit-code</code> ends with <code>5</code> when anything differs</strong>, so a scheduled task can take a snapshot at deployment and page somebody the first night the machine drifts.</li>
<li><strong><code>--exit-code</code> ends with <code>5</code> when the configuration differs</strong>, so a scheduled task can take a snapshot at deployment and page somebody the first night the machine drifts.</li>
<li><strong>Two machines, two files.</strong> Take a snapshot on staging and one on production, and diff them anywhere.</li>
</ul>
</div>
Expand Down Expand Up @@ -167,7 +167,7 @@ <h2>From the command line</h2>
<span class="k">bws kill Spooler --dry-run</span> <span class="c">what ending its process would take with it</span>
<span class="k">bws start-type Spooler manual --dry-run</span> <span class="c">what taking it off automatic would do</span>
<span class="k">bws snapshot create before.json</span> <span class="c">freeze the machine before a change</span>
<span class="k">bws snapshot diff before.json --live --exit-code</span> <span class="c">what has changed since, 5 if anything has</span></code></pre>
<span class="k">bws snapshot diff before.json --live --exit-code</span> <span class="c">what has changed since, 5 if the setup has</span></code></pre>
<a class="more" href="/cli-reference/">Every command, switch and exit code</a>
</div>
</section>
Expand Down
Loading
Loading