back on july 3 i shipped a fix for a dumb bug in my windows optimizer. dry run mode is supposed to show you what the script would change without touching anything. mine was printing a line at the end of a dry run like "applied 9 fixes." it had written nothing. the number was made up.
quick context if you've never seen the tool: it's a powershell script that looks at a windows box, figures out if it's low, mid, or high end, and then applies a bunch of cleanup, privacy, and performance tweaks that make sense for that tier. because it edits the registry and pokes services, the -DryRun switch is kind of the whole trust story. if you can't run it dry and believe the preview, you're not going to run it for real. so a dry run that reports fake work is worse than a crash. a crash you notice.
the fix felt easy. each optimization function returns a count of what it changed, and the summary at the end adds those up. in dry run the functions were short-circuiting before they wrote to the registry, which is correct, but they were still bumping the counter on the way out. so i moved the increment so it only runs after an actual write. ran a dry run. summary said 0. shipped it. closed the issue.
then last week i was adding tests and ran a dry run scoped to just the performance module with -Only Performance. it said it applied 4 fixes. same bug, one module deep.
turns out my july 3 fix only really covered the modules that write through a shared helper. most of them call one function to set a registry value, and that's the exact spot where i moved the counter. the performance module doesn't always go through that helper. some of its work is tcp tuning, disabling nagle's algorithm, gpu scheduling, and a few of those run through their own paths with their own little tallies. the shared fix never touched them.
so the summary was correct for privacy, cleanup, services, all of it. and wrong for exactly one module. the one that happened to do the flashiest stuff.
here's the shape of the bug:
function Invoke-PerformanceOptimization {
param([switch]$DryRun)
$applied = 0
foreach ($tweak in $tcpTweaks) {
if ($DryRun) {
Write-Log "would set $($tweak.Name) = $($tweak.Value)"
$applied++ # counts a write that never happened
continue
}
Set-TcpParam @tweak
$applied++
}
return $applied
}
that $applied++ inside the dry run branch is the entire thing. it reads fine. it even looks symmetric, one increment per tweak either way. but in dry run there's no write under it, so every one of those is a lie the summary repeats back to you with confidence.
the fix is boring:
foreach ($tweak in $tcpTweaks) {
if ($DryRun) {
Write-Log "would set $($tweak.Name) = $($tweak.Value)"
continue # report it, don't count it
}
Set-TcpParam @tweak
$applied++
}
the part i actually changed my mind about was the test. my first instinct was a test that checks "dry run returns 0." but zero is a weird thing to assert. a function that does nothing also returns 0, so that test passes for the wrong reasons and it wouldn't catch a dry run that quietly writes one thing. so instead the pester test runs the module twice against a throwaway registry hive. once dry, once real. it asserts two things: the dry run leaves the hive byte for byte identical to before, and the set of changes it says it would make matches what the real run actually makes.
rough version:
It 'dry run predicts the real changes and writes nothing' {
$before = Snapshot-Registry $testKeys
$planned = Invoke-PerformanceOptimization -DryRun
$after = Snapshot-Registry $testKeys
$after | Should -Be $before # dry run touched nothing
$real = Invoke-PerformanceOptimization # real run, disposable hive
$planned | Should -Be $real # and it predicted the work
}
that catches both directions. over-counting, which was my bug, and under-doing, where a dry run silently writes.
the thing i took from this: a fix that goes through a shared helper feels like it covers everything. it covers everything that uses the helper. the one module that rolled its own path is exactly where it'll bite you, and it's usually the module doing the most work, because that custom path is why someone wrote it that way in the first place.
it's still not airtight. the test snapshots a small set of known keys, not the whole hive, so a rogue write to a key i didn't list would slip past. and the network tweaks that don't land in the registry aren't in the snapshot at all yet. for those i'm asserting the count and trusting it, which is the exact kind of trust that got me here. that's the next issue i've got open.
repo if you want to poke at the real modules: https://github.com/TiltedLunar123/Ultimate-Windows-System-Optimizer
powershell, runs on win 10/11, MIT, and the -DryRun on the performance section is one you can finally believe.
Top comments (0)