Refactor Invoke-WPFSystemRepair to improve function naming, scalablility and fix on english os

This commit is contained in:
Marterich 2025-04-08 23:24:28 +02:00
parent 642367509d
commit ab97c776f9

View File

@ -1,6 +1,5 @@
function Invoke-WPFSystemRepair { function Invoke-WPFSystemRepair {
<# <#
.SYNOPSIS .SYNOPSIS
Checks for system corruption using Chkdsk, SFC, and DISM Checks for system corruption using Chkdsk, SFC, and DISM
@ -11,11 +10,7 @@ function Invoke-WPFSystemRepair {
4. SFC Run 2 - Fixes system file corruption, this time with an almost guaranteed uncorrupted system image 4. SFC Run 2 - Fixes system file corruption, this time with an almost guaranteed uncorrupted system image
#> #>
Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 0 function Invoke-Chkdsk {
# Wait for the first progress bar to show, otherwise the second one won't show
Start-Sleep -Milliseconds 200
function run_chkdsk {
<# <#
.SYNOPSIS .SYNOPSIS
Runs chkdsk on the system drive Runs chkdsk on the system drive
@ -24,10 +19,11 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose .PARAMETER verbose
If specified, print output from chkdsk If specified, print output from chkdsk
.NOTES .NOTES
VerbosePreference is defined locally, so it only affects this function. This is done by wrapping the code inside a script block and calling it with & { ... } VerbosePreference is set locally within a script block (& { ... }) to avoid affecting the global or parent scope.
#> #>
param( param(
[switch]$verbose [switch]$verbose,
[int]$parentProgressId = 0
) )
& { & {
if ($verbose) { if ($verbose) {
@ -37,7 +33,7 @@ function Invoke-WPFSystemRepair {
$VerbosePreference = "SilentlyContinue" $VerbosePreference = "SilentlyContinue"
} }
Write-Progress -Id 1 -Activity "Scanning for corruption" -Status "Running chkdsk..." -PercentComplete 0 Write-Progress -Id 1 -ParentId $parentProgressId -Activity $childProgressBarActivity -Status "Running chkdsk..." -PercentComplete 0
$oldpercent = 0 $oldpercent = 0
# 2>&1 redirects stdout, allowing iteration over the output # 2>&1 redirects stdout, allowing iteration over the output
chkdsk.exe /scan /perf 2>&1 | ForEach-Object { chkdsk.exe /scan /perf 2>&1 | ForEach-Object {
@ -46,16 +42,16 @@ function Invoke-WPFSystemRepair {
if ($_ -match "%.*?(\d+)%") { if ($_ -match "%.*?(\d+)%") {
[int]$percent = $matches[1] [int]$percent = $matches[1]
if ($percent -gt $oldpercent) { if ($percent -gt $oldpercent) {
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "Running chkdsk... ($percent%)" -PercentComplete $percent Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "Running chkdsk... ($percent%)" -PercentComplete $percent
$oldpercent = $percent $oldpercent = $percent
} }
} }
} }
Write-Progress -Id 1 -Activity "Scanning for corruption" -Status "chkdsk Completed" -PercentComplete 100 Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "chkdsk Completed" -PercentComplete 100 -Completed
} }
} }
function run_sfc { function Invoke-SFC {
<# <#
.SYNOPSIS .SYNOPSIS
Runs sfc on the system drive Runs sfc on the system drive
@ -64,10 +60,13 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose .PARAMETER verbose
If specified, print output from sfc If specified, print output from sfc
.NOTES .NOTES
VerbosePreference and ErrorPreference is defined locally, so it only affects this function. This is done by wrapping the code inside a script block and calling it with & { ... } VerbosePreference and ErrorActionPreference are set locally within a script block to isolate their effects. ErrorActionPreference suppresses false errors caused by sfc.exe output redirection.
A bug in SFC output buffering causes progress updates to appear in chunks when redirecting output
#> #>
param( param(
[switch]$verbose [switch]$verbose,
[int]$parentProgressId = 0
) )
& { & {
@ -78,29 +77,27 @@ function Invoke-WPFSystemRepair {
$VerbosePreference = "SilentlyContinue" $VerbosePreference = "SilentlyContinue"
} }
$ErrorActionPreference = "SilentlyContinue" $ErrorActionPreference = "SilentlyContinue"
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "Running SFC..." -PercentComplete 0 Write-Progress -Id 1 -ParentId $parentProgressId -Activity $childProgressBarActivity -Status "Running SFC..." -PercentComplete 0
$oldpercent = 0 $oldpercent = 0
# SFC has a bug when redirected which causes it to output only when the stdout buffer is full, causing the progress bar to move in chunks
sfc.exe /scannow 2>&1 | ForEach-Object { sfc.exe /scannow 2>&1 | ForEach-Object {
Write-Verbose $_ Write-Verbose $_
if ($_ -ne "") { if ($_ -ne "") {
# sfc.exe /scannow outputs unicode characters, so we directly remove null characters for optimization # sfc.exe /scannow outputs unicode characters, so we directly remove null characters for optimization
$utf8line = $_ -replace "`0", "" $utf8line = $_ -replace "`0", ""
if ($utf8line -match "(\d+)\s%") { if ($utf8line -match "(\d+)\s*%") {
# Write-Host "$($matches[0]) $($matches[1])"
[int]$percent = $matches[1] [int]$percent = $matches[1]
if ($percent -gt $oldpercent) { if ($percent -gt $oldpercent) {
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "Running SFC... ($percent%)" -PercentComplete $percent Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "Running SFC... ($percent%)" -PercentComplete $percent
$oldpercent = $percent $oldpercent = $percent
} }
} }
} }
} }
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "SFC Completed" -PercentComplete 100 Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "SFC Completed" -PercentComplete 100 -Completed
} }
} }
function run_dism { function Invoke-DISM {
<# <#
.SYNOPSIS .SYNOPSIS
Runs DISM on the system drive Runs DISM on the system drive
@ -113,10 +110,11 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose .PARAMETER verbose
If specified, print output from DISM If specified, print output from DISM
.NOTES .NOTES
VerbosePreference is defined locally, so it only affects this function. This is done by wrapping the code inside a script block and calling it with & { ... } VerbosePreference is set locally within a script block (& { ... }) to avoid affecting the global or parent scope.
#> #>
param( param(
[switch]$verbose [switch]$verbose,
[int]$parentProgressId = 0
) )
& { & {
if ($verbose) { if ($verbose) {
@ -126,7 +124,7 @@ function Invoke-WPFSystemRepair {
$VerbosePreference = "SilentlyContinue" $VerbosePreference = "SilentlyContinue"
} }
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "Running DISM..." -PercentComplete 0 Write-Progress -Id 1 -ParentId $parentProgressId -Activity $childProgressBarActivity -Status "Running DISM..." -PercentComplete 0
$oldpercent = 0 $oldpercent = 0
DISM /Online /Cleanup-Image /RestoreHealth | ForEach-Object { DISM /Online /Cleanup-Image /RestoreHealth | ForEach-Object {
Write-Verbose $_ Write-Verbose $_
@ -136,30 +134,30 @@ function Invoke-WPFSystemRepair {
[int]$percent = $matches[1] [int]$percent = $matches[1]
if ($percent -gt $oldpercent) { if ($percent -gt $oldpercent) {
# Update the progress bar # Update the progress bar
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "Running DISM... ($percent%)" -PercentComplete $percent Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "Running DISM... ($percent%)" -PercentComplete $percent
$oldpercent = $percent $oldpercent = $percent
} }
} }
} }
Write-Progress -Id 1 -ParentId 0 -Activity "Scanning for corruption" -Status "DISM Completed" -PercentComplete 100 Write-Progress -Id 1 -Activity $childProgressBarActivity -Status "DISM Completed" -PercentComplete 100 -Completed
} }
} }
# Scan system for corruption $childProgressBarActivity = "Scanning for corruption"
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... " -PercentComplete 0 Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 0
# Step 1: Run chkdsk to fix disk and filesystem corruption before proceeding with system file repairs # Step 1: Run chkdsk to fix disk and filesystem corruption before proceeding with system file repairs
run_chkdsk Invoke-Chkdsk
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (25%)" -PercentComplete 25 Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 25
# Step 2: Run SFC to fix system file corruption and ensure DISM can operate correctly # Step 2: Run SFC to fix system file corruption and ensure DISM can operate correctly
run_sfc Invoke-SFC
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (50%)" -PercentComplete 50 Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 50
# Step 3: Run DISM to repair the system image, which SFC relies on for accurate repairs # Step 3: Run DISM to repair the system image, which SFC relies on for accurate repairs
run_dism Invoke-DISM
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (75%)" -PercentComplete 75 Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 75
# Step 4: Run SFC again to ensure system files are repaired using the now-fixed system image # Step 4: Run SFC again to ensure system files are repaired using the now-fixed system image
run_sfc Invoke-SFC
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption completed" -PercentComplete 100 Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 100 -Completed
} }