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 {
<#
.SYNOPSIS
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
#>
Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 0
# Wait for the first progress bar to show, otherwise the second one won't show
Start-Sleep -Milliseconds 200
function run_chkdsk {
function Invoke-Chkdsk {
<#
.SYNOPSIS
Runs chkdsk on the system drive
@ -24,10 +19,11 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose
If specified, print output from chkdsk
.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(
[switch]$verbose
[switch]$verbose,
[int]$parentProgressId = 0
)
& {
if ($verbose) {
@ -37,7 +33,7 @@ function Invoke-WPFSystemRepair {
$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
# 2>&1 redirects stdout, allowing iteration over the output
chkdsk.exe /scan /perf 2>&1 | ForEach-Object {
@ -46,16 +42,16 @@ function Invoke-WPFSystemRepair {
if ($_ -match "%.*?(\d+)%") {
[int]$percent = $matches[1]
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
}
}
}
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
Runs sfc on the system drive
@ -64,10 +60,13 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose
If specified, print output from sfc
.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(
[switch]$verbose
[switch]$verbose,
[int]$parentProgressId = 0
)
& {
@ -78,29 +77,27 @@ function Invoke-WPFSystemRepair {
$VerbosePreference = "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
# 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 {
Write-Verbose $_
if ($_ -ne "") {
# sfc.exe /scannow outputs unicode characters, so we directly remove null characters for optimization
$utf8line = $_ -replace "`0", ""
if ($utf8line -match "(\d+)\s%") {
# Write-Host "$($matches[0]) $($matches[1])"
if ($utf8line -match "(\d+)\s*%") {
[int]$percent = $matches[1]
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
}
}
}
}
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
Runs DISM on the system drive
@ -113,10 +110,11 @@ function Invoke-WPFSystemRepair {
.PARAMETER verbose
If specified, print output from DISM
.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(
[switch]$verbose
[switch]$verbose,
[int]$parentProgressId = 0
)
& {
if ($verbose) {
@ -126,7 +124,7 @@ function Invoke-WPFSystemRepair {
$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
DISM /Online /Cleanup-Image /RestoreHealth | ForEach-Object {
Write-Verbose $_
@ -136,30 +134,30 @@ function Invoke-WPFSystemRepair {
[int]$percent = $matches[1]
if ($percent -gt $oldpercent) {
# 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
}
}
}
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
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... " -PercentComplete 0
$childProgressBarActivity = "Scanning for corruption"
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
run_chkdsk
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (25%)" -PercentComplete 25
Invoke-Chkdsk
Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 25
# Step 2: Run SFC to fix system file corruption and ensure DISM can operate correctly
run_sfc
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (50%)" -PercentComplete 50
Invoke-SFC
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
run_dism
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption... (75%)" -PercentComplete 75
Invoke-DISM
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
run_sfc
Write-Progress -Id 0 -Activity "Repairing Windows" -Status "Scanning for corruption completed" -PercentComplete 100
Invoke-SFC
Write-Progress -Id 0 -Activity "Repairing Windows" -PercentComplete 100 -Completed
}