Skip to content

Add win-fix-registry-corruption - #159

Open
Marcus Ferreira (mvaferreira) wants to merge 3 commits into
Azure:mainfrom
mvaferreira:rsl-win-fix-registry-corruption
Open

Marcus Ferreira (mvaferreira) wants to merge 3 commits into
Azure:mainfrom
mvaferreira:rsl-win-fix-registry-corruption

Conversation

@mvaferreira

@mvaferreira Marcus Ferreira (mvaferreira) commented Sep 16, 2026

Copy link
Copy Markdown

What this adds

win-fix-registry-corruption — a single repair scenario, 982 lines, plus its own map.json entry.

Repairs corrupted or unloadable registry hives on an offline Windows disk.

The catalog entry a support engineer reads when choosing it:

Repairs corrupted, empty or unloadable registry hives behind stop error 0xC0000218 STATUS_CANNOT_LOAD_REGISTRY_FILE and stop error 0x74 BAD_SYSTEM_CONFIG_INFO. Validates every hive by loading a scratch copy, repairs structural damage in place with chkreg.exe, and for a hive it cannot repair prints the command to opt into a validated RegBack restore, which is performed only when allowRegBack=true - because RegBack is usually empty or stale on Windows 10 1803 and later, and restoring it rolls local account passwords, and a domain-joined machine account password, back to the time of the backup. Backs up every file it replaces. Pass detectOnly=true to report only, or hive to scope the run to a single hive. NOTE: use option --run-on-repair.

How it works

Runs against the broken OS disk attached to a rescue VM by "az vm repair create". Every hive is validated, only the damaged ones are repaired, and the result is re-validated. A healthy disk produces no writes at all.

Detection, per hive: 1. The hive file is missing. 2. The hive file is 0 bytes. 3. The hive file does not start with the 'regf' signature. 4. Windows itself cannot parse the hive. This is the authoritative check: a scratch copy of the hive and its transaction logs is loaded with reg.exe, so a hive that is merely dirty is recovered by log replay and correctly reported as healthy, and the file on the offline disk is never modified by the check. 5. chkreg.exe reports repairable structural damage in a hive that still loads.

Parameters

Parameter Effect
detectOnly "true" to report what would be changed and make no writes at all. Defaults to "false".
allowRegBack "true" to allow a RegBack restore for the hives chkreg could not repair in place. Defaults to "false". The periodic backup is disabled by default on Windows 10 1803 and later, so RegBack is usually empty or stale, and a restore rolls local account passwords, and on a domain joined VM the machine account password, back to the time of the backup. RegBack is therefore a last resort the operator opts into: when the periodic backup was enabled and RegBack holds a consistent set that covers the hives still damaged, the script says so and prints the command to opt in. In every other case it is skipped without comment.
hive Limit the run to a single hive: SYSTEM, SOFTWARE, SAM, SECURITY, DEFAULT or COMPONENTS. Defaults to "all".
windowsDrive Drive letter of the offline Windows installation, for example "F". Only needed when more than one Windows installation is attached and the automatically selected one is not the right one.

Conventions followed

  • Dot-sources .\src\windows\common\setup\init.ps1 and returns $STATUS_SUCCESS or $STATUS_ERROR.
  • Logging goes through the logger functions only; no Write-Host.
  • The detect summary is printed after the per-finding list, because az vm run-command keeps only the last 4096 characters of the output stream, so a summary printed first is the first thing a long run loses.
  • Evidence-driven: findings are gathered first and only what the evidence names is changed, so a healthy image produces no writes.
  • PSScriptAnalyzer 1.24.0: zero errors, one warning, PSUseShouldProcessForStateChangingFunctions on an internal helper function. The script is invoked non-interactively by az vm repair run, so -WhatIf/-Confirm on an internal function is unreachable; the reporting-only mode is the detectOnly parameter instead.

Testing

Exercised through the az vm repair run --preview product path against real Azure VMs: a lab VM is broken deliberately to create the fault, the repair is run against the attached disk from a rescue VM, and the VM is confirmed to boot and behave afterwards. Detection, restraint on a healthy image, and the repair itself are each checked.

Series

Second wave of four independent scenario PRs, after #149, #150, #151 and #152. The shared helpers
these scripts dot-source are already merged in #143, #146 and #147. This PR adds no helper files and
changes no existing scenario; its only existing-file change is appending this run-id to map.json,
inserted into whatever upstream carries now so every existing entry is preserved byte for byte.

Repairs corrupted or unloadable registry hives on an offline Windows disk.
- Repair-HiveInPlace validated the installed hive while its stale transaction
  logs were still beside it, and only moved them aside afterwards. reg.exe
  replays those logs on load, so the state that was validated was not the state
  the VM boots into. The logs are now moved first and the file is validated in
  the state it will be loaded in, and a failed validation puts both the hive and
  its logs back.
- Restore-HiveFromRegBack rolled the hive files back when a later hive failed but
  left the renamed transaction logs renamed, so a rollback did not return the
  disk to the state it was found in. Each move is now recorded per hive and undone
  with it.
- Test-RegBackMaintained trusted the exit code of reg load and then unloaded with
  a bare reg unload whose result was never checked, which silently leaked a
  mounted hive and a locked scratch file. Mount-OfflineHive cannot be pointed at
  a scratch copy, so the load stays direct but is now confirmed by key state, and
  the unload goes through Invoke-OfflineRegUnload, which retries while handles
  release and verifies the key is gone. The scratch file is only deleted once the
  unload is confirmed.
- Hive replacements now pass through Assert-OfflineTarget, the gate every other
  writer in the library calls, so a path outside the offline image is refused
  rather than written.
- The post-repair verification scan honoured the hive parameter, but the identity
  pair rule can restore SAM alongside SECURITY. A hive written outside the filter
  was never re-validated and the run reported clean. Get-AllFinding now takes a
  set of hives and the scan covers everything that was actually written. With
  hive=all the scope is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4446db8-dda7-4210-a934-5b9ae9e3fb15
@mvaferreira

Copy link
Copy Markdown
Author

Review findings addressed — win-fix-registry-corruption

A structured review was run against head 69601af4 and reported 0 Criticals and 5 Warnings. All 5
were confirmed against the source and all 5 resulted in code changes. Every change was then
validated end to end on throwaway Azure VMs through the real az vm repair path before this was
pushed.

Final tested head: 5c967e1 (+92 / −24)

Fixes implemented

ID Finding Change
W1 Transaction logs were renamed but never rolled back, and the hive was validated before the logs were moved aside — so a failure left the installation worse than it started Repair-HiveInPlace moves .LOG* aside before validating and restores both hive and logs if the repair fails. Restore-HiveFromRegBack records the moved logs per hive so each one can be rolled back individually
W2 Test-RegBackMaintained used raw reg.exe and never verified the unload, so a scratch copy could be left mounted the load is confirmed through Get-OfflineHiveKeyState, the unload goes through Invoke-OfflineRegUnload, and the scratch file is deleted only after the unload is confirmed
W3 finally did not release assigned drive letters finally now calls Clear-OfflineDriveLetter
W4 Offline writes bypassed Assert-OfflineTarget asserted before both hive-replacement paths
W5 A partner hive written outside the requested scope was excluded from the final verification -HiveFilter widened from [string] to [string[]], and the post-repair scan now covers hiveFilter ∪ restored ∪ repairedInPlace

Validation

Break → detect → repair → detect → az vm repair restore → confirm the guest boots, all through
the product path with --preview pointing at the candidate branch.

The break is deliberately harsher than typical real-world corruption: it damages three hives in
three different ways and deletes all six .LOG1/.LOG2 recovery logs, denying the transactional
recovery that normally resolves a torn write.

Hive Damage planted
SOFTWARE 64 KB of 0xDE written into the hive bins, header left intact
DEFAULT regf header signature zeroed
SECURITY truncated to 0 bytes
Scenario Image Generation Build Result
allowRegBack=true WindowsServer:2025-datacenter-g2 Gen2 / UEFI 26100 11/11 passed
default (allowRegBack=false) windows-11:win11-24h2-pro Gen2 / client 26100 correct refusal — see below

The RegBack run exercised every fix above, including the two that are unreachable by default:

Repaired 3 of 3 damaged hive(s). Repaired in place: SOFTWARE.
Restored from RegBack: SECURITY, DEFAULT, SAM.
Every replaced hive was saved next to itself with a .bak-<timestamp> suffix.

W5's partner-hive rule fired exactly as intended — SAM was undamaged, but was pulled into scope and
into the final verification to stay consistent with SECURITY:

Restoring SAM from RegBack as well, because the account and security hives are sealed together
and only work as a matched pair.

…and the cost was disclosed rather than hidden:

The account hives are being replaced with older copies. Local account passwords revert to their
state at backup time, and on a domain joined VM the machine account password reverts too, so the
domain trust may need to be repaired after the VM boots.

Post-repair detect reported "No registry hive corruption was found", and the guest booted.

The Windows 11 run is a deliberate negative case and is reported as a pass. With the default
allowRegBack=false, a 0-byte SECURITY hive and a signature-less DEFAULT have nothing recoverable
left on the disk. The script repaired SOFTWARE in place, refused to overstate the rest, and exited
ERROR:

Repaired 1 of 3 damaged hive(s). Repaired in place: SOFTWARE. 2 hive(s) are still damaged and
need a source image or a disk level repair.

That VM then did not boot — which is the honest outcome for that damage, and confirms the script
does not claim more than it achieved. It also confirms the advisory path adapts to the image:
WS2025 shipped with RegBack populated and periodic backup enabled, so detect offered it with a real
timestamp ("RegBack holds a usable backup from 2026-09-05 18:19 UTC covering SECURITY, DEFAULT"),
while the Windows 11 image had no RegBack directory at all and correctly stayed silent.

Provenance
  • Reviewed head: 69601af46f9524ee9c41d6bd80d9e09f278c5e9d
  • Final tested head: 5c967e1
  • Review prompt: PR-Review-Agent.md, SHA256 5451BA5C325F61E34FE63B9AF99322A17D70E55210CAF23F5CE27E0F839D8F70
  • Counts derived from the report tables: 0 Criticals, 5 Warnings (8 Info items not itemised here)
  • The original agent report is tied to head 69601af4 and is historical: the code has changed since.
  • Quality gates on the final head: PowerShell 7 and 5.1 parse clean, PSSAnalyzer at parity with the
    reviewed head, repository pre-commit hook passing, CRLF line endings preserved.

@mvaferreira

Copy link
Copy Markdown
Author

Additional validation — the recoverable case, on Gen1 / MBR

The run reported above is deliberately unrecoverable: it deletes the .LOG1/.LOG2 recovery logs,
which is what proves the script refuses honestly when there is nothing left to work with. That is
not, however, what most real corruption looks like. A third run was added to cover the common case.

Break: 64 KB of 0xDE written into the hive bins of SOFTWARE and DEFAULT, base blocks left
intact, every transaction log left exactly where Windows put it, and RegBack deliberately not
seeded — so the in-place chkreg path is the only route out.

Image Generation / layout Build Result
WindowsServer:2019-Datacenter Gen1 / MBR 17763 11/11 passed
RegBack: 5 file(s), 0 non-empty - left exactly as shipped, not seeded
SOFTWARE: overwrote 65536 bytes at offset 35127296 with 0xDE (file is 70254592 bytes)
DEFAULT:  overwrote 65536 bytes at offset 131072 with 0xDE (file is 262144 bytes)
Damaged 2 hive(s): SOFTWARE, DEFAULT. Kept 10 transaction log file(s).

Detect, with the default allowRegBack=false:

[FIXABLE] the SOFTWARE hive still loads but chkreg reports repairable structural damage, which
degrades installed software and OS settings and can bugcheck later

Detect only: found 1 damaged hive(s), 1 repairable in place. No changes were made.

Repair, then re-detect, then boot:

Repaired 1 of 1 damaged hive(s) in G:\Windows\System32\Config. Repaired in place: SOFTWARE.
No registry hive corruption was found in G:\Windows\System32\Config.
GUEST-VERDICT: booted as rslvm882fe2

Two things are worth calling out.

This is the run that actually exercises W1. With the logs present, the fixed
Repair-HiveInPlace has real work to do: it moves SOFTWARE.LOG1/.LOG2 aside before validating
the installed file, so the validation reflects the state the VM will boot into rather than a state
with stale logs still beside the hive. Under the previous ordering the file was validated with the
logs in place and only moved afterwards, so a pass there said nothing about what shipped. The guest
booted, which is the end-to-end confirmation.

Two hives were damaged and only one was reported, and that is the correct answer. DEFAULT is a
262,144-byte hive and the write landed at offset 131072, in space chkreg does not treat as live.
The script reported what it could actually substantiate — one hive, repairable in place — rather
than echoing the break. Post-repair detect was clean and the VM booted, so nothing was left behind.

Combined win-fix-registry-corruption coverage is now three runs across three images, both VM
generations and both partition layouts:

Scenario Image Gen / layout Build Outcome
Logs intact, in-place repair WS2019-Datacenter Gen1 / MBR 17763 repaired, booted
Logs deleted, allowRegBack=true WS2025-datacenter-g2 Gen2 / UEFI 26100 restored from RegBack, booted
Logs deleted, default win11-24h2-pro Gen2 / client 26100 partial repair, refused the rest

Azure#149 landed win-fix-firewall-service at the same map.json slot this entry uses, so git
saw two independent appends as one competing edit. Both entries are kept, upstream's
first so it holds the position it already has on main.

Catalog verified after the merge: 30 entries, no duplicate ids, the entry present.
win-fix-registry-corruption.ps1 is byte-identical to the lab-validated head 5c967e1.
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