Skip to content

Add win-fix-boot-partition - #158

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

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

Conversation

@mvaferreira

Copy link
Copy Markdown

What this adds

win-fix-boot-partition — a single repair scenario, 2106 lines, plus its own map.json entry.

Repairs the system partition of an offline Windows disk: the partition the firmware reads before Windows exists, and the boot sectors inside it. Targets a damaged or missing system partition, not the contents of the BCD store.

The catalog entry a support engineer reads when choosing it:

Repairs the system partition itself, the layer below the BCD store - a deleted EFI System Partition or System Reserved partition, an unformatted ESP, a missing 0x55AA boot signature, zeroed or NTLDR era bootstrap code, no Active partition or more than one, and a stale BPB HiddenSectors field that gives 'A disk read error occurred' after a partition move or restore while chkdsk and sfc report nothing. Pass detectOnly=true to report only, or revert=true to restore the sectors a previous run backed up; an EFI System Partition created for a Generation 2 VM is not removed by a revert. 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". It answers the question "can the firmware reach a boot partition on this disk at all", which is the layer below win-fix-bcd. If there is no system partition, or its boot sectors are damaged, no amount of BCD repair helps, because the firmware never gets far enough to read the store.

The script repairs in two tiers, and which tier runs is decided by the evidence:

Tier 1, sectors. The system partition exists but the boot sectors are wrong: a missing 0x55AA
signature, zeroed bootstrap code, a stale BPB HiddenSectors field, or an Active flag that is
missing or set on more than one partition. Each is corrected on its own. This is Gen1/BIOS
only, because UEFI reads a file from a FAT32 partition and never executes a boot sector.

Parameters

Parameter Effect
detectOnly "true" to report the findings and make no writes at all. Defaults to "false".
revert "true" to write back the boot sectors this script backed up on its last run, using the manifest it wrote. Defaults to "false". See the notes for what revert does and does not cover.
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, 4 warnings, all PSUseShouldProcessForStateChangingFunctions on internal helper functions. The script is invoked non-interactively by az vm repair run, so -WhatIf/-Confirm on an internal function are 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.

Marcus Ferreira and others added 2 commits September 16, 2026 09:02
Repairs the system partition of an offline Windows disk: the partition the firmware reads before Windows exists, and the boot sectors inside it. Targets a damaged or missing system partition, not the contents of the BCD store.
- Invoke-SectorRepair assembled the sector 0 write from \.MbrRaw, which the
  detect pass captured before the Set-Partition calls at the top of the same
  function moved the Active flag. Write-RawDiskSector writes the whole 512 bytes,
  partition table included, so the stale buffer put the old table back and undid
  the Active flag repair that had just been reported as successful. Sector 0 is
  now re-read immediately before the buffer is built, and the repair is abandoned
  rather than guessed at if that read fails.
- Get-PartitionFilesystemFinding read State.FileSystem without checking whether
  the boot sector had been read at all. A failed read leaves it 'Unknown', which
  is not FAT, so a transient I/O or sharing error on a perfectly good EFI System
  Partition would have it reformatted. An unread boot sector is now a distinct
  non-repairable finding.
- The revert manifest is written before the first write instead of after the last
  one. Several repair paths return early on failure, and a disk that has been
  modified with no manifest beside it cannot be reverted at all.
- Get-UnreadablePartitionTable is the one write that happens before the offline
  disk has been identified. It now skips the rescue VM's own system, boot and
  clustered disks, and the caller refuses to act when more than one disk
  qualifies instead of writing to each in turn.
- Added the missing finally: release the drive letters Get-OfflineWindowsDisk
  assigned and flush the log buffer on the failure paths too.
- Replaced references to run ids that do not exist in the library. The filesystem
  scenario is win-chkdsk-fs-corruption; the boot configuration script is named
  descriptively because its run id depends on the library version installed.

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-boot-partition

A structured review was run against head 8759d95f and reported 1 Critical and 5 Warnings. All 6
were confirmed against the source and all 6 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: 844a508 (+74 / −15)

Fixes implemented

ID Finding Change
C1 The sector-0 write used a stale in-memory MbrRaw, silently undoing the Set-Partition -IsActive repair performed moments earlier in the same run sector 0 is re-read fresh with Read-RawDiskSector immediately before the bootstrap write, and the repair aborts if that read throws
W1 A failed VBR read was treated as an unformatted ESP, sending a readable-but-unlucky partition to Format-Volume a null VbrRaw now raises EspUnreadable instead of triggering a format
W2 No finally: drive letters were never released finally added
W3 Revert manifest written after the sector repair's early return the manifest is written before the first write
W4 Dangling run-id references (win-fix-bcd, win-fix-file-system) win-fix-file-system corrected to win-chkdsk-fs-corruption
W5 Preflight RAW-disk sector write ran before offline binding with no exclusions Get-UnreadablePartitionTable now skips IsSystem/IsBoot/IsClustered disks, and the caller refuses when more than one disk qualifies

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. Three scenarios, deliberately
spanning both partition layouts and both VM generations:

Scenario Image Generation / layout Build Result
Active flag cleared WindowsServer:2016-Datacenter Gen1 / MBR 14393 11/11 passed
C1 differential WindowsServer:2022-Datacenter Gen1 / MBR 20348 11/11 passed
ESP bootstrap zeroed WindowsServer:2022-datacenter-azure-edition Gen2 / GPT 20348 11/11 passed

The C1 differential deserves a note, because the first scenario did not actually reach the
defect. Clearing the Active flag alone makes the repair take the partition path and return before
it ever writes sector 0. A combined break was needed — clear the Active flag and zero the MBR
bootstrap — to force both repairs in one run. That produced the exact ordering the fix exists for:

Set the Active flag on partition 1
Prepared the Windows MBR bootstrap ... partition table at offset 446 is unchanged
Sector 0 written and read back identical

Under the previous code the stale clone would have replayed the partition table captured before
the Active flag was set, reverting it. Post-repair detect was clean and the VM booted.

The Gen2/GPT run exercised the destructive ESP path end to end and confirmed two of the fixes in
its log — the manifest is written before the format, and the disk is re-read after the partition
change:

Wrote the revert manifest to F:\win-fix-boot-partition-revert.json.
Formatted the EFI System Partition (G:) as FAT32.
Running: bcdboot F:\Windows /s G: /f UEFI
  bcdboot: Boot files successfully created.
Restored the EFI System Partition type on partition 3.
Re-reading the disk after the partition change, so the boot sector repair works from the
partition table that is there now rather than the one read before it.

Post-repair detect reported the ESP back as FAT32 and "the system partition and its boot sectors
are consistent with this disk"
; the guest booted.

Provenance
  • Reviewed head: 8759d95fbd27553252bb96027e4d085dfc69086f
  • Final tested head: 844a508
  • Review prompt: PR-Review-Agent.md, SHA256 5451BA5C325F61E34FE63B9AF99322A17D70E55210CAF23F5CE27E0F839D8F70
  • Counts derived from the report tables: 1 Critical, 5 Warnings (6 Info items not itemised here)
  • The original agent report is tied to head 8759d95f 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.

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