Skip to content

Force a GC before unloading the target user hive - #61

Merged
BiosSystem merged 1 commit into
masterfrom
fix/hive-unload-gc
Sep 18, 2026
Merged

BiosSystem merged 1 commit into
masterfrom
fix/hive-unload-gc

Conversation

@BiosSystem

Copy link
Copy Markdown
Owner

The bug

Invoke-WithTargetUserHive (Scripts/Helpers/UserHiveHelpers.ps1) mounts a target user's NTUSER.DAT, runs the caller's work against it, then unloads in a finally. The apply, verification (Test-WinnowCustomFeatureState under -Sysprep/-User), and restore-to-another-user paths all read or write the mounted hive through the registry provider / Microsoft.Win32.RegistryKey, which leaves handles open in our process. reg unload fails while any handle into the hive is open (exit 1), and the old code only Write-Warninged — leaving the hive mounted, which can lock the target profile until reboot and break the next per-user run.

No garbage collection was performed anywhere before the unload (git grep for GC]::Collect returned nothing), which is the documented remedy for offline-hive scripts.

The fix

Route the unload through Dismount-WinnowTargetUserHive, which:

  • forces [System.GC]::Collect() + WaitForPendingFinalizers() to release the lingering RegistryKey handles,
  • unloads, and retries once after a second collect and a short pause,
  • warns only if it still cannot unload.

The actual reg unload is behind Invoke-WinnowRegHiveUnload so the retry logic is unit-testable (reg.exe is native and cannot be mocked).

Validation

  • Tests\Invoke-StaticValidation.ps1 -RequirePSScriptAnalyzer clean (126 files).
  • New Tests/Unit/Test-UserHiveHelpers.ps1 (Pester 5.7.1): 3/3 — unloads first try, retries-then-succeeds, warns-and-fails-after-two. Wired into CI.
  • Standalone rebuilt (embeds the fixed helper) and parses.

The live offline-hive load/unload cycle is only reachable under -Sysprep/-User on a real machine; the retry logic is covered by the unit tests above.

Invoke-WithTargetUserHive loaded a user's NTUSER.DAT, ran the apply,
verification, or restore work against it, then called reg unload. But that
work opens .NET RegistryKey handles into the hive through the registry
provider, and reg unload fails while any handle is still open. The old code
just warned and moved on, leaving the hive mounted, which can lock the target
profile until reboot and break the next per-user run.

Route the unload through Dismount-WinnowTargetUserHive, which forces a garbage
collection to release the lingering handles, unloads, and retries once after a
second collect. It only warns if the hive still will not unload. The unload
itself is behind Invoke-WinnowRegHiveUnload so the retry logic is unit-tested
(reg is native and cannot be mocked directly).
@BiosSystem
BiosSystem merged commit 009ff89 into master Sep 18, 2026
1 check passed
@BiosSystem
BiosSystem deleted the fix/hive-unload-gc branch September 18, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant