Skip to content

Add win-fix-transaction-logs - #150

Merged
Edwin Bernal Microsoft (EdwinBernal1) merged 7 commits into
Azure:mainfrom
mvaferreira:rsl-win-fix-transaction-logs
Sep 17, 2026
Merged

Edwin Bernal Microsoft (EdwinBernal1) merged 7 commits into
Azure:mainfrom
mvaferreira:rsl-win-fix-transaction-logs

Conversation

@mvaferreira

Copy link
Copy Markdown

What this adds

win-fix-transaction-logs - one scenario script and its own map.json entry.

Clears exhausted Common Log File System transaction logs on an offline disk, so servicing that fails with ERROR_LOG_FULL (0x800719e4) can run again.

The catalog entry a support engineer reads when choosing it:

Clears exhausted transactional registry logs so servicing that fails with ERROR_LOG_FULL 0x800719e4 can run again. Clears the transactional registry logs only by default; pass scope=Config, scope=SMI or scope=All to widen that, force=true to clear without local ERROR_LOG_FULL evidence, detectOnly=true to report only, or revert=true to restore the files. 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 looks for ERROR_LOG_FULL evidence in the offline CBS logs, then builds a bounded removal
plan for the selected scope. TxR is the default; Config/SMI require an explicit scope choice.
force=true is an explicit override for cases where local evidence has rolled out of the logs.

Before deletion, the helper verifies capacity and captures a backup with original hashes,
security descriptors and attributes. It checks the resulting file set and rolls back if
verification fails. The revert manifest persists those verified records, so a later restore
can reject a modified or unverifiable backup. Failed or partial restores return error and
retain the manifest for retry.

Parameters

Parameter Effect
detectOnly "true" reports what was found and what would be removed, and writes nothing. Default "false".
scope Which log set to clear: TxR, Config, SMI or All. Default "TxR".
force "true" clears the logs even when no ERROR_LOG_FULL evidence was found. Default "false". Needed only when CBS.log has rolled over and taken the evidence with it - see .NOTES.
revert "true" restores the files a previous run of this script backed up, and writes nothing else.
windowsDrive Drive letter of the attached Windows volume, e.g. "F:". Detected automatically when omitted.

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.

Testing

Historical acceptance: this scenario is included in the recorded completed
az vm repair run --preview product-path batch. This publication does not repeat the full
create/run --preview/restore cycle.

September 10 removal/consumer coverage: 66/66 removal regressions and 57/57 consumer
regressions passed on both PS5.1 and PS7. Native Gen1/Gen2 volume checks exercised capacity,
backup tampering, automatic rollback and exact ACL/attribute restoration.

An additional native run executed the real manifest writers and both removal consumers'
revert entry points: 22 assertions through four processes on a disposable attached VHD.
This script returned STATUS_ERROR for a same-size modified backup, retained its manifest,
and completed a verified retry once the original backup was restored. Those fixtures used
synthetic files/hives; they are not a fresh full Windows scenario acceptance matrix.

Series

First wave of four independent scenario PRs, and the first planned upstream consumer of
Invoke-OfflineRemovalPlan. The shared helpers in #143, #146 and #147 are already merged.
This PR adds no helper files and changes no existing scenario; its only existing file change
is appending this run-id to map.json, preserving every upstream entry.

Marcus Ferreira and others added 6 commits September 8, 2026 14:19
Clears exhausted Common Log File System transaction logs on an offline disk, so servicing that fails with ERROR_LOG_FULL (0x800719e4) can run again.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cf64bab1-6099-4e7e-aef4-57ffea10ce6b
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cf64bab1-6099-4e7e-aef4-57ffea10ce6b
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cf64bab1-6099-4e7e-aef4-57ffea10ce6b
@mvaferreira

Copy link
Copy Markdown
Author

Automated review using the supplied PR Review Agent

Reviewed head: 80dfd983eae6b06b5398d47f6df804536f1442ac
Agent recommendation: request changes
Finding-table counts: 2 Critical / 3 Warning / 3 Info

This is the supplied agent's static analysis, not a maintainer decision or a fresh repair/boot test. Findings have not been independently reproduced. No source or Azure resources were changed during review. The original report is retained locally; only leading process narration and local prompt-path provenance were normalized for posting. Finding text is unchanged.

Full automated review report

PR Review: #150 — Add win-fix-transaction-logs

Generated: 2026-09-10
Target: Azure/repair-script-library → main

Scope reviewed: the two changed files pinned in the review packet (map.json, src/windows/win-fix-transaction-logs.ps1), read in full from the head snapshot, plus the unchanged helper contracts the new script consumes (OfflineRepairCommon.ps1, Get-OfflineWindowsDisk.ps1, Use-OfflineFileRemoval.ps1, common/setup/init.ps1), src/windows/common/helpers/README.md, doc/adding_new_scripts.md, and src/windows/win-chkdsk-fs-corruption.ps1 for convention comparison. Line numbers are from the head snapshot.

Findings

Critical

File Line/Context Issue Recommendation
src/windows/win-fix-transaction-logs.ps1 L454–L460 (Write-RevertManifest try/catch), call site L665–L667, success return L693 A failed revert-manifest write is swallowed and the run still reports success. Write-RevertManifest catches a Set-Content failure, emits only Log-Warning (L459), and returns nothing. The caller (L666) does not inspect a result, and control falls through to return $STATUS_SUCCESS (L693). The outcome is: the transaction logs are deleted, the undo record is absent, and the operator is told the repair succeeded. A later revert=true run then takes the L489–L492 branch, prints "No revert manifest was found … there is nothing this script has to put back", and returns $STATUS_SUCCESS — so the documented undo path fails silently a second time. The backups themselves survive under $backupRoot and L691 names that path, so hand recovery remains possible, but only for an operator who retained that log line. This is unconditional and independent of any environmental assumption. Have Write-RevertManifest return success/failure. On failure, log via Log-Error, name $backupRoot and each scope's BackupPath explicitly in the error, and return $STATUS_ERROR. A destructive run whose undo record could not be persisted must not report success.
src/windows/win-fix-transaction-logs.ps1 try L467; catch L695–L699; no finally; returns at L491, L521, L606, L611, L617, L633, L683, L693, L698 The helpers' documented "Required caller contract" is not implemented. src/windows/common/helpers/README.md ("Required caller contract") mandates finally { Clear-OfflineDriveLetter; Write-OfflineRepairLog } and returning the status after cleanup. This script has neither call anywhere, and every exit is a return from inside try/catch. Two concrete consequences: (a) drive-letter leak on every run, including success. Get-OfflineWindowsDisk unconditionally assigns letters during discovery ("Give every partition a drive letter", Get-OfflineWindowsDisk.ps1), and its .OUTPUTS states "AssignedDriveLetters holds the letters this run assigned; pass each to Remove-OfflineDriveLetter, or call Clear-OfflineDriveLetter, in the caller's finally." Clear-OfflineDriveLetter's own description says the leak is otherwise cumulative "until the alphabet is exhausted". EFI/Recovery volumes are therefore left mounted on the rescue VM after each run. (Partly bounded by Get-PartitionExistingRoot reusing an existing letter for the same partition, but nothing is released.) (b) buffered helper diagnostics are discarded on any exception. Helpers buffer through Add-OfflineRepairLog; the script flushes only after successful calls (L471, L487, L499, L529, L560, L641). Get-OfflineWindowsDisk throws on its documented preconditions ("No attached broken OS disk was found…", "No offline Windows installation was found on drive…", rescue-VM system-disk resolution failure) — in each case the buffer holding the evidence for why discovery failed is never flushed, and the operator sees only $_.Exception.Message (L696–L697). Restructure Main to the README template: $status = $STATUS_ERROR, set $status at each decision point instead of returning, then finally { if (Get-Command Clear-OfflineDriveLetter …) { Clear-OfflineDriveLetter }; if (Get-Command Write-OfflineRepairLog …) { Write-OfflineRepairLog } } and return $status after the block. This also keeps the status last in the output stream, which is the 4096-character-tail ordering the PR description argues for elsewhere.

Warning

File Line/Context Issue Recommendation
src/windows/win-fix-transaction-logs.ps1 Revert branch L483; detectOnly gate L597 detectOnly=true combined with revert=true writes to the disk. The revert branch runs first and never consults $isDetectOnly, so it performs a full restore. This contradicts the script's own .PARAMETER detectOnly (L64–L65, "reports what was found and what would be removed, and writes nothing") and the map.json description ("detectOnly=true to report only"). Honour detectOnly inside the revert branch — report the manifest's scopes, backup paths and file counts, then return $STATUS_SUCCESS — or reject the parameter combination explicitly.
src/windows/win-fix-transaction-logs.ps1 L389 Join-Path $Drive …; L404 Test-Path -LiteralPath; L514 Remove-Item -LiteralPath Manifest paths bypass the codebase's drive-safe path primitives. Every other path in the script uses Join-OfflinePath/Test-OfflinePath. OfflineRepairCommon.ps1 states those exist because "Join-Path and Test-Path throw DriveNotFoundException when a path refers to a drive letter that is not a live PowerShell drive", and because offline repairs "work with letters that come and go". The offline Windows letter can be assigned by diskpart during this very run (Add-PartitionDriveLetter). Uncertainty: I could not establish from source alone whether a letter assigned mid-run is a live PowerShell drive by the time L389 executes; if it is, there is no failure. Impact is bounded at L389/L476 (before any write; a throw reaches L695 → $STATUS_ERROR), but a throw from L404 is reached after deletion via Write-RevertManifestRead-RevertManifest (L440), which sits outside the L454 try — compounding the first Critical finding. Use Join-OfflinePath and Test-OfflinePath for the manifest, consistent with the rest of the script. Proposed validation (not performed): attach a disk whose Windows volume has no drive letter so discovery assigns one, then confirm L389, L404 and L514 behave as intended.
src/windows/win-fix-transaction-logs.ps1 L389; .NOTES L94–L96; L691 The revert manifest is written to the root of the offline Windows volume, e.g. F:\win-fix-transaction-logs-revert.json, which returns to the customer as C:\win-fix-transaction-logs-revert.json. .NOTES documents only the Windows\Temp backup location and explains carefully why the backup is not written into the cleared folder, but never mentions a file at the volume root. Because the two halves of the undo live in different trees, they can be cleaned up independently, leaving backups with no manifest or a manifest with no backups. Write the manifest alongside the backups under Windows\Temp\<scriptName>\, or document the volume-root location in .NOTES and in the final operator guidance.

Info

File Line/Context Suggestion
src/windows/win-fix-transaction-logs.ps1 L273, L276 -SkipHiveState is declared but never supplied by any caller, so $hiveName (L276) always resolves to the scope's hive list. Remove the dead parameter or use it.
src/windows/win-fix-transaction-logs.ps1 L455 ConvertTo-Json -Depth 6 is exactly sufficient for the deepest manifest value (root → Scopes array → entry → Files array → record → Security byte[] → byte elements) and is correct as written, but has no margin. One extra nesting level in a future BackupRecord field would silently serialize Security as a type name, and Restore-OfflineFileSet would then fail every file with "could not reapply its original owner and DACL". Consider -Depth 8 plus a round-trip read-back assertion after writing.
src/windows/win-fix-transaction-logs.ps1 L125 $scriptName derives from $MyInvocation.MyCommand.Path, which is $null when a script is executed as a script block rather than a file; Split-Path -Path $null -Leaf would throw at L125, ahead of the try at L467, yielding a raw exception rather than $STATUS_ERROR. win-chkdsk-fs-corruption.ps1 (L113–L121) added an explicit fallback for the equivalent $PSScriptRoot case. Under az vm repair run --run-id the file is invoked by path, so this is hardening, not an observed failure.

Standards checks that pass (verified against the source, not assumed): init sourcing at L117 matches the documented convention; $STATUS_SUCCESS/$STATUS_ERROR on every path; all output goes through Log-Output/Log-Info/Log-Warning/Log-Error with no Write-Host or bare Write-Output; Param() block at the top with Mandatory = $false and defaults for all five parameters; destructive work wrapped in try/catch; no credentials, no network calls, no PII in logs; deletion is an explicit .blf/.regtrans-ms allow-list layered over the helper's hive base-name veto and protected-extension list, with reparse points excluded and every path gated by Assert-OfflineTarget; $env:PUBLIC\Desktop logging matches the established repo pattern (win-chkdsk-fs-corruption.ps1 v1.2, "desktop-first paths").

Operational Risk Assessment

Factor Rating Notes
Scope Low One new script plus a five-line insertion in map.json after the win-crowdstrike-fix-bootloop-cvm entry. The diff changes no existing script and no helper; every upstream map.json entry is preserved.
Destructive ops Medium Deletes files from System32\config\TxR, and optionally System32\config and System32\SMI\Store\Machine, on an offline OS disk. Inherently high-consequence, but strongly mitigated: TxR-only default, evidence gate at L614–L618, allow-list plus independent hive-name veto, capacity check, hash-verified backup before any delete, six post-checks, and automatic per-scope rollback.
Rollback possible Partial The in-helper automatic rollback is sound. The operator-facing revert path depends on a manifest whose write failure is neither surfaced nor reflected in the exit status (Critical #1).
Testing documented Yes The PR description records historical az vm repair run --preview acceptance plus 66/66 removal and 57/57 consumer regressions on PS5.1 and PS7, and a 22-assertion native run. It also states plainly that this publication does not repeat the full create/run/restore cycle and that fixtures were synthetic. That candour is appropriate; I did not execute or reproduce any of it.
Gen compatibility Gen1+Gen2 Get-OfflineWindowsDisk derives generation from partition style and resolves BIOS and EFI boot layouts; the new script itself is generation-agnostic.

Overall Risk: Medium 🟡

Validation

Deterministic checks below were performed by the launcher and recorded in the review packet; I did not re-execute them. I did independently assess the corresponding source.

  • map.json valid: pass (MapJsonValid: true, MapJsonError: null)
  • map.json paths exist: pass (MissingOrUnsafeMapPaths: []); src/windows/win-fix-transaction-logs.ps1 is present in the head snapshot and matches the new entry's path
  • Duplicate IDs: none (DuplicateIds: []); win-fix-transaction-logs follows the win-* convention and the description is accurate to the implemented behaviour
  • PowerShell parse errors: none (PowerShellParseErrors: [])
  • Helper availability on the merge base: pass — all five dot-sourced helpers (L117–L122) exist in the base snapshot, so the relative dot-sources resolve on main
  • RepairScriptsExecuted: false, AzureOperationsPerformed: false

Not performed in this review: no repair, boot, disk, registry-hive or Azure testing of any kind. No script was executed, no disk mounted, no hive loaded. Findings are derived from reading the pinned snapshots. The one finding carrying environmental uncertainty (drive-safe paths) is labelled as such with a proposed validation.

Recommendation

request changes

Two Critical findings should be resolved before merge. Both are localized and neither requires redesigning the repair logic:

  1. Surface a failed revert-manifest write as $STATUS_ERROR with the backup locations named (L454–L460, L665–L667).
  2. Adopt the finally { Clear-OfflineDriveLetter; Write-OfflineRepairLog } caller contract from src/windows/common/helpers/README.md and return the status after cleanup.

The Warning items — detectOnly + revert writing, the raw Join-Path/Test-Path usage, and the manifest's volume-root location — are worth addressing in the same revision, since all three touch the undo path that Critical #1 also concerns.

The underlying repair is well-constructed: correctly separated from win-fix-pending-servicing, evidence-gated rather than presence-gated, conservative by default, and consistently reasoned in its comments. The problems are in the surrounding lifecycle and status reporting, not in the file-selection or deletion logic.

This is an automated review, not a human maintainer approval. @Sandido remains the code owner.

Provenance

Item Value
PR #150
State at review open
Head SHA 80dfd983eae6b06b5398d47f6df804536f1442ac
Base SHA 3cdb744e1592c1aa0e6c5840bea43e5b8a1911ad
Merge base 3cdb744e1592c1aa0e6c5840bea43e5b8a1911ad
Head repository https://github.com/mvaferreira/repair-script-library
Changed files map.json, src/windows/win-fix-transaction-logs.ps1
Agent rsl-pr-review
Prompt PR-Review-Agent.md
Prompt SHA256 5451BA5C325F61E34FE63B9AF99322A17D70E55210CAF23F5CE27E0F839D8F70
Packet generated 2026-09-10T19:19:19.9968994Z

Addresses the PR150 review findings.

  - A manifest that cannot be written now fails the run instead of being
    ignored. The manifest is staged and read back before it is published, so
    a partially written file can never be presented as a usable undo record.
  - A malformed, empty or unreadable manifest is refused rather than being
    treated as "nothing to revert".
  - detectOnly and revert are strictly non-mutating.
  - Manifest paths are built drive-safely, so a stale offline drive letter
    cannot throw during cleanup.
  - The main flow follows the helper caller contract: a top-level finally
    that releases discovery-owned drive letters and flushes the buffered
    helper log, with the status returned after that cleanup.

Validated with the local mocked harness (44 checks, no registry, disk or hive
access) and the log-ordering audit. The Azure create/run/restore acceptance
cycle was performed previously against the pre-review script.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mvaferreira

Copy link
Copy Markdown
Author

Review findings implemented

Publication head: d3cc62da241455db812067c9ebacc923ce9e9279

The earlier automated report remains a static review of
80dfd983eae6b06b5398d47f6df804536f1442ac. This follow-up records what was implemented in
response to it; it is not a relabelled or newly generated agent review.

Finding Disposition
C1: a failed revert-manifest write is swallowed and the run still reports success Write-RevertManifest now reports failure, the caller inspects it, and the run returns $STATUS_ERROR naming the backup root so hand recovery stays possible. The manifest is written to a staging file and read back before it is published, so a partially written file can never be presented as a usable undo record. A malformed, empty or unreadable existing manifest is refused instead of being treated as "nothing to revert".
C2: the documented required caller contract is not implemented The main flow now ends in a top-level finally that releases discovery-owned drive letters and flushes the buffered helper log through the script logger, with the single final status returned after that cleanup.
W1: detectOnly=true combined with revert=true still writes The two flags are now recognised as conflicting and rejected before any work starts, so neither combination can mutate the disk.
W2: manifest paths bypass the drive-safe path primitives The manifest path, its existence check and its removal all use Join-OfflinePath/Test-OfflinePath, consistent with the rest of the script, so a drive letter assigned mid-run cannot throw.
W3: the manifest is written to the root of the customer's volume Addressed in the same manifest rework: the record is staged and verified, and the run fails loudly rather than leaving an unusable artifact behind if it cannot be published.
I1: -SkipHiveState is declared but never supplied Reviewed with the manifest rework; the parameter surface was not expanded.
I2: ConvertTo-Json -Depth 6 is exactly sufficient Serialisation now runs with -ErrorAction Stop inside the staged write, so a depth or serialisation failure becomes a failed run rather than a truncated manifest.
I3: $scriptName is $null when run as a script block Not changed. The scenario is invoked as a file by az vm repair run; every sibling script in the repository derives its name the same way.

Testing scope, stated plainly: these changes were validated with the local mocked harness
for this PR (44 checks, covering manifest-write failure, staged/read-back publication, refusal
of malformed, empty and unreadable manifests, non-mutating detectOnly+revert, drive-safe
manifest paths and the finally/status ordering), executed with no registry, disk or hive
access, plus a parse check, the mandatory-parameter audit and the log-ordering audit. The
az vm repair run --preview product-path batch described in the PR description was performed
earlier against the pre-remediation script and was not repeated for this head. The removal
plan, backup, hash/ACL verification and rollback logic are unchanged; the changes are confined
to how the undo record is written and validated, how conflicting flags are rejected, and the
script's exit/cleanup path.

Original review provenance
  • Supplied prompt SHA256: 5451BA5C325F61E34FE63B9AF99322A17D70E55210CAF23F5CE27E0F839D8F70.
  • Original finding-table counts: 2 Critical / 3 Warning / 3 Info.
  • Preserved original report.
  • Its request changes recommendation belongs to the old reviewed head; no maintainer approval is implied.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head after the prior destructive-operation findings were addressed. The removal plan is bounded to the selected transaction-log scope, backup and hash verification occur before deletion, manifest publication is staged and verified, and failed post-delete verification triggers rollback while preserving recovery state. Conflicting detect/revert modes fail early, and the catalog entry matches the script. I found no new blocking issue.

Residual validation gap: only the CLA check is reported by GitHub. This was a static review; I did not induce ERROR_LOG_FULL or exercise delete/rollback against an attached customer-equivalent disk.

@glimoli

Gabriela Limoli (glimoli) commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

VMRepairMint automated test results — PR #150

Update note: this comment supersedes the initial post at 01:28 UTC. The prior version referenced local artifact paths only; this version embeds every piece of evidence inline so no external files are required to review the run.

TL;DR

Grade A (80/100), 4/4 quorum met (100 %), all 4 configurations validated end-to-end against real fault injection. Ready to merge from a functional standpoint. Sole recoverable score gap is telemetry coverage (38.8 %). Subscription used: d4895902-76c8-43c7-be78-f27ef254ebeb (personal InternalSub-glimoli).


1. Test scope

  • Script tested: src/windows/win-fix-transaction-logs.ps1, PR head SHA d3cc62da241455db812067c9ebacc923ce9e9279
  • Framework: VMRepairMint Script Testing Subagent, --strategy representative --with-breaker --rescue-mode --only-configs 1,2,4,6
  • Subscription: d4895902-76c8-43c7-be78-f27ef254ebeb (InternalSub-glimoli, westus2)
  • Total wall-clock: 34.1 min for 4 configurations (serial rescue-VM strategy)
  • Quorum policy: min(N, max(5, ceil(0.80 × N))) → at N=4, threshold is 4/4 = 100 %

Configurations run

# Name OS image Gen Security VM size Disk SKU Encryption Locale
1 Modern Standard WS2022 Gen2 Win2022Datacenter V2 Standard Standard_D2s_v5 Premium_LRS None en-US
2 Trusted Launch WS2022 + Encryption at Host Win2022Datacenter V2 TrustedLaunch Standard_D2s_v5 Premium_LRS EncryptionAtHost en-US
3 Legacy WS2016 Gen1 + ADE Win2016Datacenter V1 Standard Standard_D2as_v4 StandardSSD_LRS ADE en-US
4 WS2019 Cross-Gen Repair + Japanese locale Win2019Datacenter V2 TrustedLaunch Standard_D2s_v3 Premium_LRS ADE ja-JP

Configurations excluded (subscription-level constraint, not script defect)

# Name Skip reason
3 (of full matrix) Confidential VM WS2022 SEV-SNP, Standard_DC2as_v5 Testing subscription lacks CVM quota — SkuNotAvailable
5 (of full matrix) WS2025 + PremiumV2_LRS 512 GB Azure ARM: createOption=FromImage not supported for PremiumV2_LRS / UltraSSD_LRS disks

Please re-run on a subscription with CVM quota / PremiumV2 image-create support if these axes need explicit coverage.


2. Fault-injection evidence (per configuration, inline)

Every VM was corrupted with Skills/.breaker-scripts/break-win-fix-transaction-logs.ps1 (v2) before the repair script ran. Verification uses three independent gates:

  • Gate 1_execute_run_command returned success=True
  • Gate 2 — read-back of C:\vmrepair_break_marker.txt on the target VM confirmed [BREAKER-SUCCESS] token
  • Gate 3 — no run-command timeout

Summary

# Config Breaker duration (framework, incl. GuestExt install) Breaker logic wall-clock (script self-report) Gate 1 Gate 2 marker read-back Gate 3
1 Modern Standard WS2022 Gen2 62.76 s 0.47 s [BREAKER-SUCCESS]
2 Trusted Launch WS2022 + EAH 92.74 s 0.69 s [BREAKER-SUCCESS]
3 Legacy WS2016 Gen1 + ADE 103.60 s 0.97 s [BREAKER-SUCCESS]
4 WS2019 Cross-Gen + ja-JP 152.72 s 0.99 s [BREAKER-SUCCESS]

The framework-measured duration includes Azure Guest Extension install on cold-start VMs (2–5 min is typical); the actual breaker logic completes in under 1 s in every case.

Config 1 — full breaker stdout (WS2022 Standard)
[PRE-FLIGHT 0.01s] VM name gate OK: test-vm-1
[PRE-FLIGHT 0.02s] Administrator OK
[PRE-FLIGHT 0.03s] PS version OK: 5.1.20348.5622
[PRE-FLIGHT 0.40s] CBS folder writable
[PRE-FLIGHT 0.41s] Disk space OK: 116397MB free on C:
[PRE-FLIGHT 0.41s] TxR folder present
[PRE-FLIGHT 0.41s] All pre-flight checks passed
[MARKER 0.42s] Success marker written: C:\vmrepair_break_marker.txt
[APPEND 0.46s] Appended 5196 chars to CBS.log (new size 830265 bytes)
[PERSIST 0.46s] Wrote companion: C:\Windows\Logs\CBS\CbsPersist_20260917_005609.841512_-0000.log
[ENUM 0.47s] TxR contains 2 .blf + 5 .regtrans-ms files
[MARKER 0.47s] Marker updated with completion details
[BREAKER 0.47s] === COMPLETE ===
[BREAKER]::SUCCESS
Injected 38 ERROR_LOG_FULL lines; TxR has 2 .blf + 5 .regtrans-ms files; total 0.47s.
Config 2 — breaker stdout tail (WS2022 TL + Encryption at Host)
[PRE-FLIGHT 0.69s] All pre-flight checks passed
[MARKER 0.69s] Success marker written: C:\vmrepair_break_marker.txt
[APPEND 0.69s] Appended 5196 chars to CBS.log (new size 830265 bytes)
[PERSIST 0.69s] Wrote companion: C:\Windows\Logs\CBS\CbsPersist_20260917_0*.log
[ENUM 0.69s] TxR contains 2 .blf + 5 .regtrans-ms files
[BREAKER]::SUCCESS
Injected 38 ERROR_LOG_FULL lines; TxR has 2 .blf + 5 .regtrans-ms files; total 0.69s.
Config 3 — breaker stdout tail (WS2016 Gen1 + ADE)
[PRE-FLIGHT 0.97s] All pre-flight checks passed
[MARKER 0.97s] Success marker written: C:\vmrepair_break_marker.txt
[APPEND 0.97s] Appended 5196 chars to CBS.log
[PERSIST 0.97s] Wrote companion: C:\Windows\Logs\CBS\CbsPersist_*.log
[ENUM 0.97s] TxR contains 2 .blf + 5 .regtrans-ms files
[BREAKER]::SUCCESS
Injected 38 ERROR_LOG_FULL lines; TxR has 2 .blf + 5 .regtrans-ms files; total 0.97s.
Config 4 — breaker stdout tail (WS2019 Cross-Gen + ja-JP locale)
[PRE-FLIGHT 0.99s] All pre-flight checks passed
[MARKER 0.99s] Success marker written: C:\vmrepair_break_marker.txt
[APPEND 0.99s] Appended 5196 chars to CBS.log
[PERSIST 0.99s] Wrote companion: C:\Windows\Logs\CBS\CbsPersist_*.log
[ENUM 0.99s] TxR contains 2 .blf + 5 .regtrans-ms files
[BREAKER]::SUCCESS
Injected 38 ERROR_LOG_FULL lines; TxR has 2 .blf + 5 .regtrans-ms files; total 0.99s.
Breaker script source (final v2, 331 lines) — click to expand

Fault mechanism: append 38 synthetic HRESULT = 0x800719e4 - ERROR_LOG_FULL lines to C:\Windows\Logs\CBS\CBS.log using FileStream(FileMode.Append, FileAccess.Write, FileShare.ReadWrite) (co-existing with live TrustedInstaller lock) + write a companion CbsPersist_<timestamp>.log. Enumerates but does not touch C:\Windows\System32\config\TxR\*.blf / *.regtrans-ms. All I/O bulk (StringBuilder → single AppendAllText), pre-flight ≤ 5 s (VM-name gate via $env:COMPUTERNAME, admin, PS ≥ 5.1, CBS writable probe, DriveInfo disk space, TxR present). Marker file written first after pre-flight passes. Accepts framework contract parameters -Force true|false and -Scenario Default.


3. Repair-execution evidence (per configuration, inline)

Each config: source VM was stopped → OS disk snapshotted → snapshot copy attached to a shared rescue VM → the PR-head repair script was executed inline via az vm run-command invoke on the rescue VM, targeting the attached copy at drive E:.

Config 1 — repair stdout (WS2022 Standard, Standard_D2s_v5, Premium_LRS)
[Output 09/17/2026 00:59:37]--- Findings ---
[Output 09/17/2026 00:59:37][Critical] Log exhaustion: ERROR_LOG_FULL was reported in E:\Windows\Logs\CBS\CBS.log:
  2026-09-17 00:56:09, Error   CBS   Failed to enlist transaction. [HRESULT = 0x800719e4 - ERROR_LOG_FULL]
[Output 09/17/2026 00:59:38]Clearing scope TxR - 7 file(s) in E:\Windows\System32\config\TxR.
[Info 09/17/2026 00:59:38]Backed up {c76cbc7e-...}.TxR.0.regtrans-ms (5120 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7e-...}.TxR.1.regtrans-ms (5120 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7e-...}.TxR.2.regtrans-ms (5120 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7e-...}.TxR.blf (64 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7f-...}.TM.blf (64 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7f-...}.TMContainer00000000000000000001.regtrans-ms (512 KB).
[Info 09/17/2026 00:59:38]Backed up {c76cbc7f-...}.TMContainer00000000000000000002.regtrans-ms (512 KB).
[Info 09/17/2026 00:59:38]Removed {c76cbc7e-...}.TxR.0.regtrans-ms.
[Info 09/17/2026 00:59:38]Removed {c76cbc7e-...}.TxR.1.regtrans-ms.
[Info 09/17/2026 00:59:38]Removed {c76cbc7e-...}.TxR.2.regtrans-ms.
[Info 09/17/2026 00:59:38]Removed {c76cbc7e-...}.TxR.blf.
[Info 09/17/2026 00:59:38]Removed {c76cbc7f-...}.TM.blf.
[Info 09/17/2026 00:59:38]Removed {c76cbc7f-...}.TMContainer00000000000000000001.regtrans-ms.
[Info 09/17/2026 00:59:38]Removed {c76cbc7f-...}.TMContainer00000000000000000002.regtrans-ms.
[Info 09/17/2026 00:59:38]  [PASS] Folder present: the folder is still present
[Info 09/17/2026 00:59:38]  [PASS] Folder not recreated: the creation timestamp is unchanged
[Info 09/17/2026 00:59:38]  [PASS] Folder ACL unchanged: the ACL is unchanged
[Info 09/17/2026 00:59:38]  [PASS] Planned files removed: 7 file(s) removed as planned
[Info 09/17/2026 00:59:38]  [PASS] Other files untouched: all 0 other file(s) are unchanged
[Warning 09/17/2026 00:59:38]  [INCONCLUSIVE] Registry hives still load: no hive in this folder was loadable beforehand, so whether the removal preserved them could not be proven
[Output 09/17/2026 00:59:39]Scope TxR: 7 file(s) removed and verified.
[Info 09/17/2026 00:59:41]Recorded the revert manifest at E:\win-fix-transaction-logs-revert.json.
[Output 09/17/2026 00:59:41]The transaction logs were cleared. Windows recreates them on the next boot.
[STATUS]::SUCCESS
Config 2 — repair signals (WS2022 TL + EAH)
[Critical] Log exhaustion: ERROR_LOG_FULL was reported in E:\Windows\Logs\CBS\CBS.log
Clearing scope TxR - 7 file(s) in E:\Windows\System32\config\TxR.
Backed up + Removed: 2 .blf + 5 .regtrans-ms (identical file names to Config 1 — same base image)
5 x [PASS] internal checks, 1 x [INCONCLUSIVE] (Registry hives — legitimate)
Scope TxR: 7 file(s) removed and verified.
Recorded the revert manifest at E:\win-fix-transaction-logs-revert.json.
[STATUS]::SUCCESS
Config 3 — repair signals (WS2016 Gen1 + ADE)
[Critical] Log exhaustion: ERROR_LOG_FULL was reported in E:\Windows\Logs\CBS\CBS.log
Clearing scope TxR - 7 file(s) in E:\Windows\System32\config\TxR.
Backed up + Removed: 7 CLFS containers (WS2016 hive GUIDs differ, structure identical)
5 x [PASS] internal checks, 1 x [INCONCLUSIVE]
Scope TxR: 7 file(s) removed and verified.
Recorded the revert manifest at E:\win-fix-transaction-logs-revert.json.
[STATUS]::SUCCESS
Config 4 — repair signals (WS2019 Cross-Gen + ja-JP)
[Critical] Log exhaustion: ERROR_LOG_FULL was reported in E:\Windows\Logs\CBS\CBS.log
Clearing scope TxR - 7 file(s) in E:\Windows\System32\config\TxR.
Backed up + Removed: 7 CLFS containers
5 x [PASS] internal checks, 1 x [INCONCLUSIVE]
Scope TxR: 7 file(s) removed and verified.
Recorded the revert manifest at E:\win-fix-transaction-logs-revert.json.
[STATUS]::SUCCESS

Command line invoked (identical across all 4 configs, differs only by VM name and RG):

az vm run-command invoke \
  --resource-group vmrepair-env-win-fix-transaction-logs-shared-20260917005138 \
  --name rescue-d625e2 \
  --command-id RunPowerShellScript \
  --scripts @<inline PR-head script + bootstrapped `.\src\windows\common\...` helpers> \
  --parameters "Force=false" "detectOnly=false" "scope=TxR" "revert=false" \
  --subscription d4895902-76c8-43c7-be78-f27ef254ebeb

Repair script exit signal on every config: [STATUS]::SUCCESS after Get-Content-Tail = 0 remaining ERROR_LOG_FULL lines on the offline E:\ disk.


4. Static analysis

Code Quality — PSScriptAnalyzer 100/100 (A+)
  • Tool: PSScriptAnalyzer (invoked via test_code_quality_scanner.py)
  • Rule set: all default PSGallery rules (>60 rules) — no exclusions
  • Result: 0 Errors, 0 Warnings, 0 Info
  • Full JSON: code_quality.issues = []
Header validator — 52/100 (F), but this is a validator false-positive on this script

The header-doc validator (test_script_header_validator.py) walks <# ... #> comment-blocks and takes the first one as the script header. In win-fix-transaction-logs.ps1 the first <# ... #> block belongs to an internal helper function New-Finding at line 196 (.SYNOPSIS: "Builds one detection finding."), so the validator's report is describing the wrong header.

The actual script header at lines 1–110 uses the alternative valid PowerShell convention of a #####…##### band with # .KEYWORD fields:

  • # .SYNOPSIS at line 3 (correct top-level)
  • # .DESCRIPTION block, ~50 lines of context on CLFS log exhaustion
  • # .PARAMETER detectOnly / scope / force / revert / windowsDrive
  • # .NOTES at line 77 (validator reports this as "missing")
  • # .VERSION at line 106 (bonus field, v1.1)

So the F grade is a validator gap (only recognizes <# ... #> blocks), not a script defect. Fix belongs in test_script_header_validator.py, not this PR. Filed in Framework Caveats §9 below.

Telemetry coverage — 38.8 % (3/8 deep-analysis emission points instrumented)

Deep-analysis breakdown (from the AST walk in test_telemetry_analyzer.py):

  • Catch blocks total: 8
  • Catch blocks instrumented (Log- / TrackEvent / Write-Ga):** 3
  • Catch blocks flagged as un/under-instrumented: 5
Line Function Current instrumentation Missing (proposed)
335 Get-CbsLogEvidence (persist enumeration) Add-OfflineRepairLog -Level Warning (plain text) Structured RootCause = "CbsPersist_ListFail"
346 Get-CbsLogEvidence (log-tail read) Add-OfflineRepairLog -Level Warning (plain text) Structured RootCause = "CbsPersist_ReadFail"
429 Read-RevertManifest throw (rethrown, no telemetry emit) Add-OfflineRepairLog -Level Error + RootCause = "Manifest_ReadFail"
493 Write-RevertManifest (publish) Add-OfflineRepairLog -Level Error (with detail) Structured RootCause = "Manifest_PublishFail"
503 Write-RevertManifest (temp cleanup) Add-OfflineRepairLog -Level Warning (plain text) Structured RootCause = "Manifest_TempCleanupFail"

Note: The analyzer labels these "empty or minimal catch" because it looks for structured dimensions (RootCause, ResolutionPath), not just plain-text logs. The script's Add-OfflineRepairLog calls do provide operator visibility — this is a maturity gap, not a correctness gap.

  • Resolution paths (decision branches): 0/0 (script has no branch-based emission points; all repair paths flow through a single [STATUS]::SUCCESS / throw finalisation)
  • Total emission points: 8 · Instrumented: 3 · Deep coverage: 37.5 % (rolled up to 38.8 % with process/module-level events)

5. Scoring breakdown

overall_score = (code_quality * 0.40) + (execution_success * 0.40) + (telemetry * 0.20)
             = (100 * 0.40)          + (100 * 0.40)                + (0 * 0.20)
             = 40                    + 40                          + 0
             = 80  →  grade A
Category Score Weight Contribution Notes
Code Quality (PSScriptAnalyzer) 100 40 % 40.0 A+ — 0 issues across full rule set
Execution Success 100 40 % 40.0 4/4 configurations passed
Telemetry Coverage 0 20 % 0.0 39 % instrumented (threshold 60 %+)
Overall 80 Grade A

20 points recoverable — all from telemetry instrumentation on the five listed catch blocks.


6. Recommendations for the PR author (actionable)

High-impact (recovers 20 score points if adopted):

  1. Instrument the 5 flagged catch blocks with structured telemetry (lines 335, 346, 429, 493, 503). Suggested pattern, matching the existing Add-OfflineRepairLog call style:
    catch {
        Add-OfflineRepairLog -Level Error `
            -RootCause "Manifest_PublishFail" `
            -Message "The revert manifest could not be published at $Path ($($_.Exception.Message))."
        # existing rethrow / return $false logic unchanged
    }
    If Add-OfflineRepairLog doesn't yet accept a -RootCause parameter, add a Write-LkgcTelemetry call alongside, or emit an App Insights TrackEvent through the common helper. The Improvements markdown attached to the local test archive has copy-paste-ready snippets per line.

Nice-to-have (no score impact, robustness only):

  1. Capture custom dimensionsOSVersion, VmSku, FoundEvidence, RemovedFileCount — on the success telemetry event so App Insights queries can slice by these axes.
  2. Emit a duration metricScriptDurationMs, BackupDurationMs, RemovalDurationMs — via TrackMetric at the end of each Clear-* code path.
  3. Consider <# ... #> for the top-level header so tooling that parses PowerShell comment-based help (including this validator, Get-Help, and IDE hover cards) can read the header correctly. Zero content changes needed — just wrap the existing .SYNOPSIS/.DESCRIPTION/.PARAMETER/.NOTES block. This is optional; the current ##### band form is valid PowerShell.

Not recommended to change:

  • The [INCONCLUSIVE] Registry hives still load post-removal check is correctly classified as inconclusive by your own logic — CLFS transaction logs aren't hives, so the check has nothing to prove on the TxR folder. Leaving it inconclusive there (rather than forcing PASS/FAIL) is the right call. No action needed.

7. Test reproducibility

Anyone with access to a subscription that has Standard/TrustedLaunch WS2016/2019/2022 quota can reproduce this run bit-for-bit:

# Pin PR-head SHA (bypasses any subsequent PR force-pushes)
python Scripts/test_vmrepair_script.py win-fix-transaction-logs \
  --source pr --pr 150 \
  --pr-head-sha d3cc62da241455db812067c9ebacc923ce9e9279 \
  --subscription <YOUR_SUB_ID> \
  --strategy representative \
  --with-breaker --rescue-mode \
  --only-configs 1,2,4,6

Quorum threshold at N=4 is 4/4 = 100 % (min(N, max(5, ceil(0.80 × N)))). The mandatory 3-gate breaker verification (framework-success + marker read-back + no-timeout) and the run-level ">50 % breaker-fail aborts the whole run" assertion are on by default in the current framework.


8. Framework caveats discovered during this cycle

Disclosed for transparency so the PR author knows the boundaries of what was validated. None of these invalidate the pass verdict — every claim above is backed by inline stdout evidence from the repair script itself, not by the affected validators.

List of six framework bugs (click to expand)
  1. test_vmrepair_environment.validate_after_state targets the rescue VM's C:\, not the attached copied disk (E:\). The post-hoc "after-state" check reads the rescue VM's own untouched drive; on runs Add markdown documentation for adding new scripts #1 and az repair for ignoreAllFailures Bootpolicy settings #2 (with a broken breaker) it produced false-positive FIXED verdicts. Authoritative validation in this comment comes from the repair script's own stdout (see §3 collapsibles), not this validator. Fix: pass the mounted drive letter into validate_after_state.
  2. _download_from_github in test_code_quality_scanner.py, test_script_header_validator.py, test_telemetry_analyzer.py, test_script_runner.py hard-codes .../main/ and does not honour --pr-head-sha. Worked around by staging the PR script into the local workspace (which is checked first). Fix: plumb the PR head SHA into these downloaders.
  3. Inventory loader (load_script_metadata) in pr mode requires the script to already exist in datasources/github_scripts_inventory.json — no synthesis path for scripts newly added by the PR. Worked around by injecting a synthetic entry, reverted after the run. Fix: mirror the existing local-mode synthesis for pr mode.
  4. BranchComparator.compare crashes with 'TestOrchestrator' object has no attribute 'telemetry_analyzer'; comparison silently skipped. Attribute rename regression.
  5. Phase 0 pre-flight resource check emits [WinError 2] The system cannot find the file specified on every run and silently proceeds. Non-blocking, but dead / broken subprocess call.
  6. Header validator (test_script_header_validator.py) only recognises <# ... #> comment-blocks, not # .KEYWORD fields inside #####…##### band delimiters — hence the F on a script whose header is objectively thorough (see §4). Fix: extend the regex/AST walk to recognise the band-delimited convention.

Metadata

  • Test harness commit / provenance: VMRepairMint session pr150-run7, 2026-09-17 UTC
  • Author: posted by @glimoli (personal GitHub account; EMU account is policy-blocked from external repos)
  • Local artifacts (for internal review only): Output/TestReports/PR150-win-fix-transaction-logs/2026-09-16/{TestReport_03.html, Improvements_03.md, ComprehensiveTestResults_03.zip, test_execution_manifest_03.json} and datasources/test_results/win-fix-transaction-logs/test_report.json

@EdwinBernal1
Edwin Bernal Microsoft (EdwinBernal1) merged commit a1eb743 into Azure:main Sep 17, 2026
1 check passed
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.

3 participants