WinRepair/WinRepair.ps1
Ryan 53d18ecea2 v1.0.1!
- Due to friend suffering issues with the SFC command, added a watchdog to detect if a command hangs for >45s and pops up a Window asking the user if they'd like to retry the command, skip the command or just outright cancel the command.
- Moved Smark Disk Check and moved it to its own section with better output about your disk's help.
- Fixed an issue with the Debug Console where it used to say the program had crashed when actually it was just the user that closed the window.
- Improved the debug consoles logging capabilities.
2026-02-19 16:39:03 +00:00

1551 lines
75 KiB
PowerShell

<#
.SYNOPSIS
WinRepair - Portable Windows OS Repair Utility
.DESCRIPTION
A GUI-based system repair tool that runs common Windows repair commands
in the correct order, with real-time progress tracking and hidden execution.
Detects system theme (light/dark) and styles accordingly.
.NOTES
Requires Administrator privileges. Use RunRepair.bat for easy launching.
#>
param([switch]$DebugMode)
# ═══════════════════════════════════════════════════════════════════════════════
# ERROR LOGGING — catches any crash and writes to log file
# ═══════════════════════════════════════════════════════════════════════════════
$ErrorActionPreference = "Stop"
$script:ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$script:ErrorLogPath = Join-Path $script:ScriptDir "WinRepair_Error.log"
if ($DebugMode -or $env:WINREPAIR_DEBUG -eq "1") {
$Global:DebugMode = $true
Write-Host "DEBUG: Console Hiding DISABLED" -ForegroundColor Yellow
}
trap {
$_ | Out-File -FilePath $script:ErrorLogPath -Append
$_.ScriptStackTrace | Out-File -FilePath $script:ErrorLogPath -Append
# In debug mode, re-throw to the debug script's catch block instead of exiting
if ($Global:DebugMode) {
Write-Host "[TRAP] $($_.Exception.GetType().FullName): $($_.Exception.Message)" -ForegroundColor Red
Write-Host "[TRAP] $($_.ScriptStackTrace)" -ForegroundColor Yellow
break # Propagates error to caller's try/catch — keeps debug console open
}
[System.Windows.Forms.MessageBox]::Show(
"WinRepair encountered an error:`n`n$($_.Exception.Message)`n`nDetails saved to WinRepair_Error.log",
"WinRepair - Error",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
) | Out-Null
exit 1
}
# ═══════════════════════════════════════════════════════════════════════════════
# ADMIN SELF-ELEVATION
# ═══════════════════════════════════════════════════════════════════════════════
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
$scriptPath = $MyInvocation.MyCommand.Path
Start-Process "powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" -Verb RunAs
exit
}
# ═══════════════════════════════════════════════════════════════════════════════
# LOAD ASSEMBLIES
# ═══════════════════════════════════════════════════════════════════════════════
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# ═══════════════════════════════════════════════════════════════════════════════
# HIDE CONSOLE WINDOW
# ═══════════════════════════════════════════════════════════════════════════════
$signature = @'
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
'@
$WinAPI = Add-Type -MemberDefinition $signature -Name "WinAPI" -Namespace "ConsoleHelper" -PassThru
$hwnd = $WinAPI::GetConsoleWindow()
if ($hwnd -ne [IntPtr]::Zero -and -not $Global:DebugMode) {
$WinAPI::ShowWindow($hwnd, 0) | Out-Null
}
# ═══════════════════════════════════════════════════════════════════════════════
# THEME DETECTION
# ═══════════════════════════════════════════════════════════════════════════════
function Get-SystemTheme {
try {
$regPath = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"
$value = Get-ItemPropertyValue -Path $regPath -Name "AppsUseLightTheme" -ErrorAction Stop
if ($value -eq 0) { return "Dark" } else { return "Light" }
} catch {
return "Dark"
}
}
$script:Theme = Get-SystemTheme
# Theme color palettes
$script:Colors = @{}
if ($script:Theme -eq "Dark") {
$script:Colors = @{
Background = [System.Drawing.Color]::FromArgb(10, 22, 40)
CardBg = [System.Drawing.Color]::FromArgb(18, 31, 51)
CardBgAlt = [System.Drawing.Color]::FromArgb(14, 26, 45)
Text = [System.Drawing.Color]::FromArgb(224, 230, 240)
TextSecondary = [System.Drawing.Color]::FromArgb(140, 160, 185)
Border = [System.Drawing.Color]::FromArgb(30, 58, 95)
Accent = [System.Drawing.Color]::FromArgb(59, 130, 246)
Green = [System.Drawing.Color]::FromArgb(0, 200, 83)
Red = [System.Drawing.Color]::FromArgb(255, 23, 68)
Yellow = [System.Drawing.Color]::FromArgb(255, 214, 0)
Gray = [System.Drawing.Color]::FromArgb(80, 95, 115)
ButtonBg = [System.Drawing.Color]::FromArgb(30, 58, 95)
ButtonHover = [System.Drawing.Color]::FromArgb(40, 75, 120)
LogBg = [System.Drawing.Color]::FromArgb(8, 16, 30)
ProgressBg = [System.Drawing.Color]::FromArgb(20, 35, 60)
ProgressFill = [System.Drawing.Color]::FromArgb(59, 130, 246)
}
} else {
$script:Colors = @{
Background = [System.Drawing.Color]::FromArgb(240, 242, 245)
CardBg = [System.Drawing.Color]::FromArgb(255, 255, 255)
CardBgAlt = [System.Drawing.Color]::FromArgb(248, 249, 251)
Text = [System.Drawing.Color]::FromArgb(26, 26, 46)
TextSecondary = [System.Drawing.Color]::FromArgb(100, 110, 130)
Border = [System.Drawing.Color]::FromArgb(208, 213, 221)
Accent = [System.Drawing.Color]::FromArgb(37, 99, 235)
Green = [System.Drawing.Color]::FromArgb(22, 163, 74)
Red = [System.Drawing.Color]::FromArgb(220, 38, 38)
Yellow = [System.Drawing.Color]::FromArgb(202, 138, 4)
Gray = [System.Drawing.Color]::FromArgb(180, 190, 200)
ButtonBg = [System.Drawing.Color]::FromArgb(37, 99, 235)
ButtonHover = [System.Drawing.Color]::FromArgb(29, 78, 216)
LogBg = [System.Drawing.Color]::FromArgb(248, 249, 251)
ProgressBg = [System.Drawing.Color]::FromArgb(228, 233, 240)
ProgressFill = [System.Drawing.Color]::FromArgb(37, 99, 235)
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# FONTS (safe fallbacks — no comma-separated names)
# ═══════════════════════════════════════════════════════════════════════════════
$script:FontTitle = New-Object System.Drawing.Font("Segoe UI", 16, [System.Drawing.FontStyle]::Bold)
$script:FontSubtitle = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Regular)
$script:FontStep = New-Object System.Drawing.Font("Segoe UI", 9.5, [System.Drawing.FontStyle]::Regular)
$script:FontStepBold = New-Object System.Drawing.Font("Segoe UI", 9.5, [System.Drawing.FontStyle]::Bold)
$script:FontButton = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
$script:FontSmall = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Regular)
$script:FontPhase = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
# Log font — try Consolas (always available on Windows)
$script:FontLog = New-Object System.Drawing.Font("Consolas", 8.5, [System.Drawing.FontStyle]::Regular)
# ═══════════════════════════════════════════════════════════════════════════════
# REPAIR STEP DEFINITIONS
# ═══════════════════════════════════════════════════════════════════════════════
$script:RepairSteps = @(
@{
Name = "DISM - Check Health"
Description = "Quick health check of Windows image"
Command = "DISM /Online /Cleanup-Image /CheckHealth"
Phase = "System Integrity"
},
@{
Name = "DISM - Scan Health"
Description = "Deep scan of Windows image integrity"
Command = "DISM /Online /Cleanup-Image /ScanHealth"
Phase = "System Integrity"
},
@{
Name = "DISM - Restore Health"
Description = "Pull fresh image and repair component store"
Command = "DISM /Online /Cleanup-Image /RestoreHealth"
Phase = "System Integrity"
},
@{
Name = "DISM - Component Cleanup"
Description = "Clean up superseded components"
Command = "DISM /Online /Cleanup-Image /StartComponentCleanup"
Phase = "System Integrity"
},
@{
Name = "SFC - System File Checker"
Description = "Scan and repair system files using fresh image"
Command = "sfc /scannow"
Phase = "System Integrity"
},
@{
Name = "Clear Temporary Files"
Description = "Remove temp files to free disk space"
Command = "__SPECIAL_TEMP_CLEANUP__"
Phase = "Cleanup"
},
@{
Name = "Rebuild Icon Cache"
Description = "Fix broken or missing desktop icons"
Command = "__SPECIAL_ICON_CACHE__"
Phase = "Cleanup"
}
)
$script:NetworkSteps = @(
@{ Name = "Flush DNS Cache"; Command = "ipconfig /flushdns" },
@{ Name = "Reset Winsock"; Command = "netsh winsock reset" },
@{ Name = "Reset TCP/IP Stack"; Command = "netsh int ip reset" },
@{ Name = "Reset Firewall Rules"; Command = "netsh advfirewall reset" }
)
# ═══════════════════════════════════════════════════════════════════════════════
# STATE
# ═══════════════════════════════════════════════════════════════════════════════
$script:IsRunning = $false
$script:CancelRequested = $false
$script:CurrentProcess = $null
$script:StepIndicators = @()
$script:StepLabels = @()
$script:StepEnabled = @() # Tracks which repair steps are enabled (Advanced mode)
$script:NetworkEnabled = @() # Tracks which network steps are enabled (Advanced mode)
$script:AdvancedMode = $false
$script:LogBuilder = New-Object System.Text.StringBuilder
# ═══════════════════════════════════════════════════════════════════════════════
# HELPER: STYLED BUTTON
# ═══════════════════════════════════════════════════════════════════════════════
function New-StyledButton {
param(
[string]$Text,
[int]$X, [int]$Y, [int]$Width, [int]$Height,
[System.Drawing.Color]$BgColor,
[System.Drawing.Color]$FgColor,
[switch]$Danger
)
$btn = New-Object System.Windows.Forms.Button
$btn.Text = $Text
$btn.Location = New-Object System.Drawing.Point($X, $Y)
$btn.Size = New-Object System.Drawing.Size($Width, $Height)
$btn.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$btn.FlatAppearance.BorderSize = 1
if ($Danger) {
$btn.FlatAppearance.BorderColor = $script:Colors.Red
$btn.FlatAppearance.MouseOverBackColor = [System.Drawing.Color]::FromArgb(60, $script:Colors.Red.R, $script:Colors.Red.G, $script:Colors.Red.B)
} else {
$btn.FlatAppearance.BorderColor = $BgColor
$btn.FlatAppearance.MouseOverBackColor = $script:Colors.ButtonHover
}
$btn.BackColor = $BgColor
$btn.ForeColor = $FgColor
$btn.Font = $script:FontButton
$btn.Cursor = [System.Windows.Forms.Cursors]::Hand
return $btn
}
# ═══════════════════════════════════════════════════════════════════════════════
# MAIN FORM
# ═══════════════════════════════════════════════════════════════════════════════
$form = New-Object System.Windows.Forms.Form
$form.Text = "WinRepair - System Repair Utility"
$form.Size = New-Object System.Drawing.Size(780, 740)
$form.MinimumSize = New-Object System.Drawing.Size(700, 650)
$form.StartPosition = "CenterScreen"
$form.BackColor = $script:Colors.Background
$form.ForeColor = $script:Colors.Text
$form.Font = $script:FontStep
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Sizable
$form.Icon = [System.Drawing.SystemIcons]::Shield
# ═══════════════════════════════════════════════════════════════════════════════
# HEADER PANEL (explicit positioning — no docking, to avoid overlap with steps)
# ═══════════════════════════════════════════════════════════════════════════════
$headerHeight = 76
$headerPanel = New-Object System.Windows.Forms.Panel
$headerPanel.Location = New-Object System.Drawing.Point(0, 0)
$headerPanel.Size = New-Object System.Drawing.Size($form.ClientSize.Width, $headerHeight)
$headerPanel.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
$headerPanel.BackColor = $script:Colors.CardBg
$headerPanel.Padding = New-Object System.Windows.Forms.Padding(20, 12, 20, 12)
# Title
$lblTitle = New-Object System.Windows.Forms.Label
$lblTitle.Text = "WinRepair $([char]0x00A9) 2026 Ryan Harris / ZenDeuo"
$lblTitle.Font = $script:FontTitle
$lblTitle.ForeColor = $script:Colors.Accent
$lblTitle.AutoSize = $true
$lblTitle.Location = New-Object System.Drawing.Point(20, 12)
$headerPanel.Controls.Add($lblTitle)
# Status label
$script:lblStatus = New-Object System.Windows.Forms.Label
$script:lblStatus.Text = "Ready - Click 'Run System Repair' to begin"
$script:lblStatus.Font = $script:FontSubtitle
$script:lblStatus.ForeColor = $script:Colors.TextSecondary
$script:lblStatus.AutoSize = $true
$script:lblStatus.Location = New-Object System.Drawing.Point(22, 48)
$headerPanel.Controls.Add($script:lblStatus)
# ═══════════════════════════════════════════════════════════════════════════════
# MAIN CONTENT (SplitContainer: steps top, log bottom)
# ═══════════════════════════════════════════════════════════════════════════════
$splitContainer = New-Object System.Windows.Forms.SplitContainer
$splitContainer.Dock = [System.Windows.Forms.DockStyle]::None
$splitContainer.Orientation = [System.Windows.Forms.Orientation]::Horizontal
$splitContainer.SplitterDistance = 360
$splitContainer.SplitterWidth = 6
$splitContainer.BackColor = $script:Colors.Background
$splitContainer.Panel1.BackColor = $script:Colors.Background
$splitContainer.Panel2.BackColor = $script:Colors.Background
$splitContainer.BorderStyle = [System.Windows.Forms.BorderStyle]::None
# ═══════════════════════════════════════════════════════════════════════════════
# STEPS PANEL (scrollable)
# ═══════════════════════════════════════════════════════════════════════════════
$stepsContainer = New-Object System.Windows.Forms.Panel
$stepsContainer.Dock = [System.Windows.Forms.DockStyle]::Fill
$stepsContainer.AutoScroll = $true
$stepsContainer.Padding = New-Object System.Windows.Forms.Padding(16, 10, 16, 10)
$stepsContainer.BackColor = $script:Colors.Background
$yPos = 12
$currentPhase = ""
$stepIndex = 0
foreach ($step in $script:RepairSteps) {
# Phase header
if ($step.Phase -ne $currentPhase) {
$currentPhase = $step.Phase
$phaseLabel = New-Object System.Windows.Forms.Label
$phaseLabel.Text = $currentPhase.ToUpper()
$phaseLabel.Font = $script:FontPhase
$phaseLabel.ForeColor = $script:Colors.Accent
$phaseLabel.Location = New-Object System.Drawing.Point(20, $yPos)
$phaseLabel.AutoSize = $true
$stepsContainer.Controls.Add($phaseLabel)
$yPos += 28
}
# Step row panel
$stepPanel = New-Object System.Windows.Forms.Panel
$stepPanel.Location = New-Object System.Drawing.Point(16, $yPos)
$stepPanel.Size = New-Object System.Drawing.Size(700, 50)
if ($stepIndex % 2 -eq 0) {
$stepPanel.BackColor = $script:Colors.CardBg
} else {
$stepPanel.BackColor = $script:Colors.CardBgAlt
}
$stepPanel.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
# Traffic light indicator (custom painted circle)
$indicator = New-Object System.Windows.Forms.PictureBox
$indicator.Location = New-Object System.Drawing.Point(14, 14)
$indicator.Size = New-Object System.Drawing.Size(20, 20)
$indicator.BackColor = [System.Drawing.Color]::Transparent
$indicator.Tag = "pending"
$indicator.Add_Paint({
param($s, $e)
$g = $e.Graphics
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$clr = $script:Colors.Gray
switch ($s.Tag) {
"running" { $clr = $script:Colors.Yellow }
"success" { $clr = $script:Colors.Green }
"error" { $clr = $script:Colors.Red }
}
$br = New-Object System.Drawing.SolidBrush($clr)
$g.FillEllipse($br, 0, 0, 18, 18)
$br.Dispose()
# Glow effect for running state
if ($s.Tag -eq "running") {
$glowBr = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(60, $clr.R, $clr.G, $clr.B))
$g.FillEllipse($glowBr, -3, -3, 24, 24)
$glowBr.Dispose()
}
})
$stepPanel.Controls.Add($indicator)
$script:StepIndicators += $indicator
# Step number
$numLabel = New-Object System.Windows.Forms.Label
$numLabel.Text = "$($stepIndex + 1)."
$numLabel.Font = $script:FontStepBold
$numLabel.ForeColor = $script:Colors.TextSecondary
$numLabel.Location = New-Object System.Drawing.Point(42, 14)
$numLabel.Size = New-Object System.Drawing.Size(26, 20)
$stepPanel.Controls.Add($numLabel)
# Step name
$nameLabel = New-Object System.Windows.Forms.Label
$nameLabel.Text = $step.Name
$nameLabel.Font = $script:FontStepBold
$nameLabel.ForeColor = $script:Colors.Text
$nameLabel.Location = New-Object System.Drawing.Point(68, 6)
$nameLabel.AutoSize = $true
$stepPanel.Controls.Add($nameLabel)
$script:StepLabels += $nameLabel
# Step description
$descLabel = New-Object System.Windows.Forms.Label
$descLabel.Text = $step.Description
$descLabel.Font = $script:FontSmall
$descLabel.ForeColor = $script:Colors.TextSecondary
$descLabel.Location = New-Object System.Drawing.Point(68, 28)
$descLabel.AutoSize = $true
$stepPanel.Controls.Add($descLabel)
$stepsContainer.Controls.Add($stepPanel)
$yPos += 54
$stepIndex++
}
# Add bottom margin so step 8 description isn't clipped
$bottomSpacer = New-Object System.Windows.Forms.Panel
$bottomSpacer.Location = New-Object System.Drawing.Point(16, $yPos)
$bottomSpacer.Size = New-Object System.Drawing.Size(10, 20)
$stepsContainer.Controls.Add($bottomSpacer)
$splitContainer.Panel1.Controls.Add($stepsContainer)
# ═══════════════════════════════════════════════════════════════════════════════
# PROGRESS BAR
# ═══════════════════════════════════════════════════════════════════════════════
$progressPanel = New-Object System.Windows.Forms.Panel
$progressPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$progressPanel.Height = 36
$progressPanel.BackColor = $script:Colors.Background
$progressPanel.Padding = New-Object System.Windows.Forms.Padding(24, 8, 24, 6)
$script:progressBar = New-Object System.Windows.Forms.ProgressBar
$script:progressBar.Dock = [System.Windows.Forms.DockStyle]::Fill
$script:progressBar.Minimum = 0
$script:progressBar.Maximum = $script:RepairSteps.Count
$script:progressBar.Value = 0
$script:progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Continuous
$progressPanel.Controls.Add($script:progressBar)
# ═══════════════════════════════════════════════════════════════════════════════
# LOG PANEL
# ═══════════════════════════════════════════════════════════════════════════════
$logHeaderPanel = New-Object System.Windows.Forms.Panel
$logHeaderPanel.Dock = [System.Windows.Forms.DockStyle]::Top
$logHeaderPanel.Height = 28
$logHeaderPanel.BackColor = $script:Colors.CardBg
$lblLogTitle = New-Object System.Windows.Forms.Label
$lblLogTitle.Text = " OUTPUT LOG"
$lblLogTitle.Font = $script:FontPhase
$lblLogTitle.ForeColor = $script:Colors.Accent
$lblLogTitle.Dock = [System.Windows.Forms.DockStyle]::Fill
$lblLogTitle.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$logHeaderPanel.Controls.Add($lblLogTitle)
$script:txtLog = New-Object System.Windows.Forms.RichTextBox
$script:txtLog.Dock = [System.Windows.Forms.DockStyle]::Fill
$script:txtLog.BackColor = $script:Colors.LogBg
$script:txtLog.ForeColor = $script:Colors.Text
$script:txtLog.Font = $script:FontLog
$script:txtLog.ReadOnly = $true
$script:txtLog.BorderStyle = [System.Windows.Forms.BorderStyle]::None
$script:txtLog.ScrollBars = [System.Windows.Forms.RichTextBoxScrollBars]::Vertical
$splitContainer.Panel2.Controls.Add($script:txtLog)
$splitContainer.Panel2.Controls.Add($logHeaderPanel)
# ═══════════════════════════════════════════════════════════════════════════════
# BUTTON BAR
# ═══════════════════════════════════════════════════════════════════════════════
$buttonPanel = New-Object System.Windows.Forms.Panel
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
$buttonPanel.Height = 70
$buttonPanel.Width = $form.ClientSize.Width # Crucial for Right Anchor to work correctly
$buttonPanel.BackColor = $script:Colors.CardBg
$buttonPanel.Padding = New-Object System.Windows.Forms.Padding(16, 10, 16, 10)
$form.Controls.Add($buttonPanel)
# Top border for button panel
$bottomBorder = New-Object System.Windows.Forms.Panel
$bottomBorder.Size = New-Object System.Drawing.Size($form.ClientSize.Width, 1)
$bottomBorder.Location = New-Object System.Drawing.Point(0, 0)
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Top
$bottomBorder.BackColor = $script:Colors.Border
# Run System Repair button (Left)
$script:btnRun = New-StyledButton -Text "Run Repair" -X 16 -Y 12 -Width 160 -Height 45 `
-BgColor $script:Colors.ButtonBg -FgColor ([System.Drawing.Color]::White)
$buttonPanel.Controls.Add($script:btnRun)
# Cancel button (Left, next to Run)
$script:btnCancel = New-StyledButton -Text "Cancel" -X 186 -Y 12 -Width 90 -Height 45 `
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.TextSecondary
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
$script:btnCancel.Enabled = $false
$buttonPanel.Controls.Add($script:btnCancel)
# Network Reset button (Left, next to Cancel)
$script:btnNetwork = New-StyledButton -Text "Net Reset" -X 286 -Y 12 -Width 110 -Height 45 `
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
$script:btnNetwork.FlatAppearance.BorderColor = $script:Colors.Border
$buttonPanel.Controls.Add($script:btnNetwork)
# Disk Check Button (Left, next to Network)
$script:btnDisk = New-StyledButton -Text "Disk Check" -X 406 -Y 12 -Width 100 -Height 45 `
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
$script:btnDisk.FlatAppearance.BorderColor = $script:Colors.Border
$script:btnDisk.Add_Click({ Invoke-DiskCheck })
$buttonPanel.Controls.Add($script:btnDisk)
# Export Log button (Anchored Right)
$script:btnExport = New-StyledButton -Text "Export Log" -X ($buttonPanel.Width - 16 - 100) -Y 12 -Width 100 -Height 45 `
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
$script:btnExport.FlatAppearance.BorderColor = $script:Colors.Border
$script:btnExport.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
$buttonPanel.Controls.Add($script:btnExport)
# Advanced button (Anchored Right, left of Export)
$script:btnAdvanced = New-StyledButton -Text "Advanced" -X ($buttonPanel.Width - 16 - 100 - 10 - 90) -Y 12 -Width 90 -Height 45 `
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Accent
$script:btnAdvanced.FlatAppearance.BorderColor = $script:Colors.Accent
$script:btnAdvanced.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
$buttonPanel.Controls.Add($script:btnAdvanced)
# ═══════════════════════════════════════════════════════════════════════════════
# ADVANCED CONFIGURATION PANEL (overlay on split container Panel1)
# ═══════════════════════════════════════════════════════════════════════════════
$script:advancedPanel = New-Object System.Windows.Forms.Panel
$script:advancedPanel.Dock = [System.Windows.Forms.DockStyle]::Fill
$script:advancedPanel.BackColor = $script:Colors.Background
$script:advancedPanel.AutoScroll = $true
$script:advancedPanel.Visible = $false
$script:advancedPanel.Padding = New-Object System.Windows.Forms.Padding(20, 10, 20, 10)
# Helper to update Run button text based on checkbox state
function Update-RunButtonText {
if (-not $script:AdvancedMode) {
$script:btnRun.Text = "Run System Repair"
return
}
$anyUnchecked = $false
$anyChecked = $false
for ($ci = 0; $ci -lt $script:StepEnabled.Count; $ci++) {
if ($script:StepEnabled[$ci]) { $anyChecked = $true } else { $anyUnchecked = $true }
}
if (-not $anyChecked) {
$script:btnRun.Text = "Run System Repair"
$script:btnRun.Enabled = $false
} elseif ($anyUnchecked) {
$script:btnRun.Text = "Run Selected Steps"
if (-not $script:IsRunning) { $script:btnRun.Enabled = $true }
} else {
$script:btnRun.Text = "Run System Repair"
if (-not $script:IsRunning) { $script:btnRun.Enabled = $true }
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# DISK CHECK FUNCTION (Run separately via dedicated button)
# ═══════════════════════════════════════════════════════════════════════════════
function Invoke-DiskCheck {
if ($script:IsRunning) { return }
$script:IsRunning = $true
$script:btnRun.Enabled = $false
$script:btnNetwork.Enabled = $false
$script:btnDisk.Enabled = $false
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
try {
Write-Log "Starting Disk Health Scan (chkdsk C: /scan)..." -Level "CMD"
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "cmd.exe"
$psi.Arguments = "/c chkdsk C: /scan" # Force standard output for parsing
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.CreateNoWindow = $true
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Null
$script:CurrentProcess = $process
$output = New-Object System.Text.StringBuilder
while (-not $process.HasExited) {
$line = $process.StandardOutput.ReadLine()
if ($line) {
Write-Log $line -Level "INFO"
$output.AppendLine($line) | Out-Null
}
[System.Windows.Forms.Application]::DoEvents()
}
# Capture remainder
$remain = $process.StandardOutput.ReadToEnd()
if ($remain) {
Write-Log $remain -Level "INFO"
$output.AppendLine($remain) | Out-Null
}
$fullOutput = $output.ToString()
$script:CurrentProcess = $null
if ($fullOutput -match "Windows has scanned the file system and found no problems") {
Write-Log "Disk Scan Complete: No errors found." -Level "SUCCESS"
[System.Windows.Forms.MessageBox]::Show(
"Great news! Your disk appears to be healthy.`n`nNo corruption was found.",
"Disk Health - Healthy",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Information
)
} elseif ($fullOutput -match "found corruption" -or $fullOutput -match "errors found") {
Write-Log "WARNING: Disk corruption detected!" -Level "ERROR"
[System.Windows.Forms.MessageBox]::Show(
"Corruption was detected on your disk!`n`nPlease schedule a full repair by running 'chkdsk C: /f /r' in an Administrator Command Prompt and rebooting.",
"Disk Health - Corruption Found",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
} else {
Write-Log "Disk Scan Complete." -Level "INFO"
[System.Windows.Forms.MessageBox]::Show(
"Disk Scan finished. Check the log for details.",
"Disk Health - Complete",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Information
)
}
} catch {
Write-Log "Disk Scan Failed: $($_.Exception.Message)" -Level "ERROR"
[System.Windows.Forms.MessageBox]::Show("Failed to run Disk Scan.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
} finally {
$script:IsRunning = $false
$script:btnRun.Enabled = $true
$script:btnNetwork.Enabled = $true
$script:btnDisk.Enabled = $true
$form.Cursor = [System.Windows.Forms.Cursors]::Default
}
}
# Build advanced panel content
$advYPos = 10
# Title row
$advTitle = New-Object System.Windows.Forms.Label
$advTitle.Text = "ADVANCED CONFIGURATION"
$advTitle.Font = $script:FontPhase
$advTitle.ForeColor = $script:Colors.Accent
$advTitle.AutoSize = $true
$advTitle.Location = New-Object System.Drawing.Point(16, $advYPos)
$script:advancedPanel.Controls.Add($advTitle)
# Select All / Deselect All buttons
$btnSelectAll = New-Object System.Windows.Forms.LinkLabel
$btnSelectAll.Text = "Select All"
$btnSelectAll.Font = $script:FontSmall
$btnSelectAll.LinkColor = $script:Colors.Accent
$btnSelectAll.ActiveLinkColor = $script:Colors.Accent
$btnSelectAll.Location = New-Object System.Drawing.Point(280, ($advYPos + 2))
$btnSelectAll.AutoSize = $true
$btnSelectAll.Add_LinkClicked({
for ($ci = 0; $ci -lt $script:RepairCheckboxes.Count; $ci++) {
$script:RepairCheckboxes[$ci].Checked = $true
}
for ($ci = 0; $ci -lt $script:NetworkCheckboxes.Count; $ci++) {
$script:NetworkCheckboxes[$ci].Checked = $true
}
})
$script:advancedPanel.Controls.Add($btnSelectAll)
$btnDeselectAll = New-Object System.Windows.Forms.LinkLabel
$btnDeselectAll.Text = "Deselect All"
$btnDeselectAll.Font = $script:FontSmall
$btnDeselectAll.LinkColor = $script:Colors.TextSecondary
$btnDeselectAll.ActiveLinkColor = $script:Colors.TextSecondary
$btnDeselectAll.Location = New-Object System.Drawing.Point(360, ($advYPos + 2))
$btnDeselectAll.AutoSize = $true
$btnDeselectAll.Add_LinkClicked({
for ($ci = 0; $ci -lt $script:RepairCheckboxes.Count; $ci++) {
$script:RepairCheckboxes[$ci].Checked = $false
}
for ($ci = 0; $ci -lt $script:NetworkCheckboxes.Count; $ci++) {
$script:NetworkCheckboxes[$ci].Checked = $false
}
})
$script:advancedPanel.Controls.Add($btnDeselectAll)
$advYPos += 30
# Build repair step checkboxes grouped by phase
$script:RepairCheckboxes = @()
$lastPhase = ""
for ($si = 0; $si -lt $script:RepairSteps.Count; $si++) {
$step = $script:RepairSteps[$si]
$script:StepEnabled += $true
# Phase header
if ($step.Phase -ne $lastPhase) {
$phaseLabel = New-Object System.Windows.Forms.Label
$phaseLabel.Text = $step.Phase.ToUpper()
$phaseLabel.Font = $script:FontSmall
$phaseLabel.ForeColor = $script:Colors.Accent
$phaseLabel.AutoSize = $true
$phaseLabel.Location = New-Object System.Drawing.Point(16, $advYPos)
$script:advancedPanel.Controls.Add($phaseLabel)
$advYPos += 22
$lastPhase = $step.Phase
}
# Checkbox
$cb = New-Object System.Windows.Forms.CheckBox
$cb.Text = "$($step.Name) - $($step.Description)"
$cb.Font = $script:FontStep
$cb.ForeColor = $script:Colors.Text
$cb.Checked = $true
$cb.AutoSize = $true
$cb.Location = New-Object System.Drawing.Point(30, $advYPos)
$cb.Tag = $si # Store step index
$cb.Add_CheckedChanged({
param($sender, $e)
$idx = $sender.Tag
$script:StepEnabled[$idx] = $sender.Checked
Update-RunButtonText
})
$script:advancedPanel.Controls.Add($cb)
$script:RepairCheckboxes += $cb
$advYPos += 26
}
# Network steps section
$advYPos += 8
$netPhaseLabel = New-Object System.Windows.Forms.Label
$netPhaseLabel.Text = "NETWORK RESET"
$netPhaseLabel.Font = $script:FontSmall
$netPhaseLabel.ForeColor = $script:Colors.Accent
$netPhaseLabel.AutoSize = $true
$netPhaseLabel.Location = New-Object System.Drawing.Point(16, $advYPos)
$script:advancedPanel.Controls.Add($netPhaseLabel)
$advYPos += 22
$script:NetworkCheckboxes = @()
for ($ni = 0; $ni -lt $script:NetworkSteps.Count; $ni++) {
$netStep = $script:NetworkSteps[$ni]
$script:NetworkEnabled += $true
$ncb = New-Object System.Windows.Forms.CheckBox
$ncb.Text = $netStep.Name
$ncb.Font = $script:FontStep
$ncb.ForeColor = $script:Colors.Text
$ncb.Checked = $true
$ncb.AutoSize = $true
$ncb.Location = New-Object System.Drawing.Point(30, $advYPos)
$ncb.Tag = $ni
$ncb.Add_CheckedChanged({
param($sender, $e)
$idx = $sender.Tag
$script:NetworkEnabled[$idx] = $sender.Checked
})
$script:advancedPanel.Controls.Add($ncb)
$script:NetworkCheckboxes += $ncb
$advYPos += 26
}
# Add bottom spacer
$advYPos += 10
$advSpacer = New-Object System.Windows.Forms.Panel
$advSpacer.Location = New-Object System.Drawing.Point(0, $advYPos)
$advSpacer.Size = New-Object System.Drawing.Size(10, 10)
$script:advancedPanel.Controls.Add($advSpacer)
# Add advanced panel to splitContainer Panel1 (on top of stepsContainer)
$splitContainer.Panel1.Controls.Add($script:advancedPanel)
$script:advancedPanel.BringToFront() # Ensure it sits on top of stepsContainer
# Toggle Advanced panel
$script:btnAdvanced.Add_Click({
if ($script:IsRunning) { return }
$script:AdvancedMode = -not $script:AdvancedMode
$script:advancedPanel.Visible = $script:AdvancedMode
if ($script:AdvancedMode) { $script:advancedPanel.BringToFront() }
if ($script:AdvancedMode) {
$script:btnAdvanced.ForeColor = [System.Drawing.Color]::White
$script:btnAdvanced.BackColor = $script:Colors.Accent
$script:btnAdvanced.FlatAppearance.BorderColor = $script:Colors.Accent
} else {
$script:btnAdvanced.ForeColor = $script:Colors.Accent
$script:btnAdvanced.BackColor = $script:Colors.CardBgAlt
$script:btnAdvanced.FlatAppearance.BorderColor = $script:Colors.Accent
}
Update-RunButtonText
})
# ═══════════════════════════════════════════════════════════════════════════════
# ADD CONTROLS TO FORM
# Header and SplitContainer use explicit positioning.
# Bottom controls (progress, border, buttons) are docked Bottom.
# ═══════════════════════════════════════════════════════════════════════════════
$form.SuspendLayout()
# Bottom section (docked — add first so they process correctly)
$form.Controls.Add($progressPanel) # Bottom, closest to content
$form.Controls.Add($bottomBorder) # Bottom, between progress and buttons
$form.Controls.Add($buttonPanel) # Bottom, very bottom edge
# Calculate the bottom docked area height
$bottomDockedHeight = $progressPanel.Height + $bottomBorder.Height + $buttonPanel.Height # 36 + 1 + 60 = 97
# SplitContainer — explicit position below header, anchored all 4 sides
$splitContainer.Location = New-Object System.Drawing.Point(0, $headerHeight)
$splitContainer.Size = New-Object System.Drawing.Size(
$form.ClientSize.Width,
($form.ClientSize.Height - $headerHeight - $bottomDockedHeight)
)
$splitContainer.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right -bor [System.Windows.Forms.AnchorStyles]::Bottom
$form.Controls.Add($splitContainer)
# Header — explicit position at top, rendered on top (last added = front of Z-order)
$form.Controls.Add($headerPanel)
$form.ResumeLayout()
# ═══════════════════════════════════════════════════════════════════════════════
# LOGGING FUNCTION
# ═══════════════════════════════════════════════════════════════════════════════
function Write-Log {
param(
[string]$Message,
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "HH:mm:ss"
$prefix = switch ($Level) {
"CMD" { "[CMD] " }
"SUCCESS" { "[OK] " }
"ERROR" { "[ERROR] " }
"WARN" { "[WARN] " }
"STEP" { "[STEP] " }
default { "[INFO] " }
}
$color = switch ($Level) {
"CMD" { $script:Colors.Accent }
"SUCCESS" { $script:Colors.Green }
"ERROR" { $script:Colors.Red }
"WARN" { $script:Colors.Yellow }
"STEP" { $script:Colors.Accent }
default { $script:Colors.Text }
}
$consoleColor = switch ($Level) {
"CMD" { "Cyan" }
"SUCCESS" { "Green" }
"ERROR" { "Red" }
"WARN" { "Yellow" }
"STEP" { "Cyan" }
default { "White" }
}
$fullMsg = "$timestamp $prefix $Message"
$script:LogBuilder.AppendLine($fullMsg) | Out-Null
# Echo to console for Debug Mode visibility
Write-Host $fullMsg -ForegroundColor $consoleColor
$script:txtLog.SelectionStart = $script:txtLog.TextLength
$script:txtLog.SelectionLength = 0
$script:txtLog.SelectionColor = $color
$script:txtLog.AppendText("$fullMsg`r`n")
$script:txtLog.ScrollToCaret()
}
# ═══════════════════════════════════════════════════════════════════════════════
# UPDATE STEP INDICATOR
# ═══════════════════════════════════════════════════════════════════════════════
function Set-StepStatus {
param([int]$Index, [string]$Status)
if ($Index -ge 0 -and $Index -lt $script:StepIndicators.Count) {
$script:StepIndicators[$Index].Tag = $Status
$script:StepIndicators[$Index].Invalidate()
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
# ═══════════════════════════════════════════════════════════════════════════════
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
# ═══════════════════════════════════════════════════════════════════════════════
function Invoke-RepairCommand {
param([string]$Command, [int]$StepIndex)
$retryStep = $false
do {
$retryStep = $false
Write-Log "Executing: $Command" -Level "CMD"
# Add friendly feedback for slow-starting SFC
if ($Command -match "sfc /scannow") {
Write-Log "Initializing System File Checker (this may take up to 30-60 seconds)..." -Level "INFO"
}
Set-StepStatus -Index $StepIndex -Status "running"
$script:lblStatus.Text = "Running - $($script:RepairSteps[$StepIndex].Name)"
[System.Windows.Forms.Application]::DoEvents()
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "cmd.exe"
$psi.Arguments = "/c $Command 2>&1"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $false
$psi.CreateNoWindow = $true
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
try {
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Null
$script:CurrentProcess = $process
$lastActivity = Get-Date
# Use ReadLineAsync to poll output without blocking UI and avoiding
# the thread-safety crashes of OutputDataReceived events in PowerShell.
$reader = $process.StandardOutput
$readTask = $reader.ReadLineAsync()
while (-not $process.HasExited -or ($readTask -ne $null -and $readTask.IsCompleted)) {
# Process output
if ($readTask -ne $null -and $readTask.IsCompleted) {
try {
$line = $readTask.Result
if ($line -ne $null) {
$lastActivity = Get-Date # Reset timeout on activity
# Sanitize output: Remove control chars (like backspaces/nulls) but keep tabs
$cleanLine = $line -replace "[\x00-\x08\x0B-\x1F\x7F]", ""
if ($cleanLine.Trim() -ne "") { Write-Log $cleanLine.Trim() }
$readTask = $reader.ReadLineAsync()
} else {
$readTask = $null # End of stream
}
} catch {
$readTask = $null
}
}
# Timeout Check (45 Seconds of silence)
if (((Get-Date) - $lastActivity).TotalSeconds -gt 45) {
$result = [System.Windows.Forms.MessageBox]::Show(
"The current command appears to be stuck (no output for 45s).`n`nDo you want to retry the step, skip it, or cancel the repair?",
"Command Timeout Warning",
[System.Windows.Forms.MessageBoxButtons]::AbortRetryIgnore,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
# Abort = Cancel, Retry = Retry, Ignore = Skip
if ($result -eq [System.Windows.Forms.DialogResult]::Abort) {
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
Write-Log "Operation cancelled by user (Timeout)" -Level "WARN"
Set-StepStatus -Index $StepIndex -Status "error"
$script:CurrentProcess = $null
try { $process.Dispose() } catch {}
return $false
}
elseif ($result -eq [System.Windows.Forms.DialogResult]::Retry) {
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
Write-Log "Timeout detected. Retrying step..." -Level "WARN"
$script:CurrentProcess = $null
try { $process.Dispose() } catch {}
$retryStep = $true
break # Break inner loop to restart do-while
}
elseif ($result -eq [System.Windows.Forms.DialogResult]::Ignore) {
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
Write-Log "Timeout detected. Skipping step..." -Level "WARN"
Set-StepStatus -Index $StepIndex -Status "success" # Treat as success to continue? Or error? Usually skip implies 'ignore error'
$script:CurrentProcess = $null
try { $process.Dispose() } catch {}
return $true
}
}
if ($script:CancelRequested) {
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
Write-Log "Operation cancelled by user" -Level "WARN"
Set-StepStatus -Index $StepIndex -Status "error"
$script:CurrentProcess = $null
try { $process.Dispose() } catch {}
return $false
}
[System.Windows.Forms.Application]::DoEvents()
Start-Sleep -Milliseconds 50
}
if ($retryStep) { continue } # fast track to retry
# Drain any remaining output after exit
if ($readTask -ne $null) {
# process has exited, so just read to end synchronously
$remaining = $reader.ReadToEnd()
if ($remaining) {
foreach ($l in ($remaining -split "`n")) {
if ($l.Trim()) { Write-Log $l.Trim() }
}
}
}
$exitCode = $process.ExitCode
$script:CurrentProcess = $null
try { $process.Dispose() } catch {}
if ($exitCode -eq 0) {
Write-Log "$($script:RepairSteps[$StepIndex].Name) completed successfully" -Level "SUCCESS"
Set-StepStatus -Index $StepIndex -Status "success"
return $true
} else {
Write-Log "$($script:RepairSteps[$StepIndex].Name) exited with code $exitCode" -Level "ERROR"
Set-StepStatus -Index $StepIndex -Status "error"
return $false
}
}
catch {
Write-Log "Failed: $($_.Exception.Message)" -Level "ERROR"
Set-StepStatus -Index $StepIndex -Status "error"
$script:CurrentProcess = $null
return $false
}
} while ($retryStep)
}
# ═══════════════════════════════════════════════════════════════════════════════
# SPECIAL: CLEAR TEMP FILES
# ═══════════════════════════════════════════════════════════════════════════════
function Invoke-TempCleanup {
param([int]$StepIndex)
Write-Log "Starting temporary files cleanup..." -Level "CMD"
Set-StepStatus -Index $StepIndex -Status "running"
$script:lblStatus.Text = "Running - Clear Temporary Files"
[System.Windows.Forms.Application]::DoEvents()
$paths = @(
$env:TEMP,
"C:\Windows\Temp",
"C:\Windows\Prefetch"
)
$totalRemoved = 0
$totalFailed = 0
foreach ($path in $paths) {
if ($script:CancelRequested) {
Write-Log "Cleanup cancelled by user" -Level "WARN"
Set-StepStatus -Index $StepIndex -Status "error"
return $false
}
if (Test-Path $path) {
Write-Log "Cleaning: $path"
try {
$items = Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue
foreach ($item in $items) {
if ($script:CancelRequested) { break }
try {
Remove-Item -Path $item.FullName -Force -Recurse -ErrorAction Stop
$totalRemoved++
} catch {
$totalFailed++
}
}
[System.Windows.Forms.Application]::DoEvents()
} catch {
Write-Log "Warning: Could not fully clean $path" -Level "WARN"
}
}
}
Write-Log "Cleanup complete: $totalRemoved items removed, $totalFailed items skipped (in use)" -Level "SUCCESS"
Set-StepStatus -Index $StepIndex -Status "success"
return $true
}
# ═══════════════════════════════════════════════════════════════════════════════
# SPECIAL: REBUILD ICON CACHE
# ═══════════════════════════════════════════════════════════════════════════════
function Invoke-IconCacheRebuild {
param([int]$StepIndex)
Write-Log "Rebuilding icon cache..." -Level "CMD"
Set-StepStatus -Index $StepIndex -Status "running"
$script:lblStatus.Text = "Running - Rebuild Icon Cache"
[System.Windows.Forms.Application]::DoEvents()
try {
# Stop Explorer
Write-Log "Stopping Windows Explorer..."
Stop-Process -Name "explorer" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# Delete icon cache files
$iconCachePath = Join-Path $env:LOCALAPPDATA "IconCache.db"
$thumbCachePath = Join-Path $env:LOCALAPPDATA "Microsoft\Windows\Explorer"
if (Test-Path $iconCachePath) {
Remove-Item $iconCachePath -Force -ErrorAction SilentlyContinue
Write-Log "Deleted IconCache.db"
}
if (Test-Path $thumbCachePath) {
Get-ChildItem (Join-Path $thumbCachePath "iconcache_*.db") -ErrorAction SilentlyContinue | ForEach-Object {
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
Write-Log "Deleted $($_.Name)"
}
Get-ChildItem (Join-Path $thumbCachePath "thumbcache_*.db") -ErrorAction SilentlyContinue | ForEach-Object {
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
Write-Log "Deleted $($_.Name)"
}
}
# Restart Explorer
Write-Log "Restarting Windows Explorer..."
Start-Process "explorer.exe"
Start-Sleep -Seconds 2
Write-Log "Icon cache rebuilt successfully" -Level "SUCCESS"
Set-StepStatus -Index $StepIndex -Status "success"
return $true
}
catch {
Write-Log "Icon cache rebuild failed: $($_.Exception.Message)" -Level "ERROR"
Start-Process "explorer.exe" -ErrorAction SilentlyContinue
Set-StepStatus -Index $StepIndex -Status "error"
return $false
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# ERROR DIALOG
# ═══════════════════════════════════════════════════════════════════════════════
function Show-ErrorDialog {
param([string]$StepName, [string]$ErrorMessage)
$msg = "An error occurred during:`n`n$StepName`n`nError: $ErrorMessage`n`n"
$msg += "Would you like to skip this step and continue?`n`n"
$msg += "Yes = Skip and continue`nNo = Retry this step`nCancel = Stop all repairs"
$result = [System.Windows.Forms.MessageBox]::Show(
$msg,
"WinRepair - Error",
[System.Windows.Forms.MessageBoxButtons]::YesNoCancel,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
return $result
}
# ═══════════════════════════════════════════════════════════════════════════════
# NETWORK RESET WARNING DIALOG
# ═══════════════════════════════════════════════════════════════════════════════
function Show-NetworkWarning {
$msg = "WARNING: NETWORK RESET`n`n"
$msg += "This will reset ALL network settings to factory defaults:`n`n"
$msg += "- Clear DNS resolver cache`n"
$msg += "- Reset Winsock catalog (may remove network add-ons)`n"
$msg += "- Reset TCP/IP stack to default configuration`n"
$msg += "- Reset Windows Firewall (ALL custom rules will be DELETED!)`n`n"
$msg += "Your network connection will be temporarily lost.`n"
$msg += "Custom firewall rules, VPN configurations, and network`n"
$msg += "adapter settings may need to be reconfigured.`n`n"
$msg += "Only proceed if you are experiencing network issues.`n`n"
$msg += "Do you want to continue?"
$result = [System.Windows.Forms.MessageBox]::Show(
$msg,
"WinRepair - Network Reset Warning",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
return ($result -eq [System.Windows.Forms.DialogResult]::Yes)
}
# ═══════════════════════════════════════════════════════════════════════════════
# RUN SYSTEM REPAIR (MAIN BUTTON)
# ═══════════════════════════════════════════════════════════════════════════════
$script:btnRun.Add_Click({
if ($script:IsRunning) { return }
$script:IsRunning = $true
$script:CancelRequested = $false
$script:btnRun.Enabled = $false
$script:btnNetwork.Enabled = $false
$script:btnAdvanced.Enabled = $false
$script:btnCancel.Enabled = $true
$script:btnCancel.ForeColor = $script:Colors.Red
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Red
$script:btnCancel.FlatAppearance.MouseOverBackColor = [System.Drawing.Color]::FromArgb(60, $script:Colors.Red.R, $script:Colors.Red.G, $script:Colors.Red.B)
$script:progressBar.Value = 0
$script:txtLog.Clear()
$script:LogBuilder.Clear()
# Reset all indicators
for ($i = 0; $i -lt $script:StepIndicators.Count; $i++) {
Set-StepStatus -Index $i -Status "pending"
}
Write-Log "=======================================================" -Level "INFO"
Write-Log " WinRepair - System Repair Starting" -Level "STEP"
Write-Log " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level "INFO"
Write-Log "=======================================================" -Level "INFO"
Write-Log ""
$completedSteps = 0
$failedSteps = 0
for ($i = 0; $i -lt $script:RepairSteps.Count; $i++) {
# Skip unchecked steps in advanced mode
if ($script:StepEnabled.Count -gt $i -and -not $script:StepEnabled[$i]) {
Write-Log ""
Write-Log "---- Step $($i + 1) of $($script:RepairSteps.Count): $($script:RepairSteps[$i].Name) [SKIPPED] ----" -Level "WARN"
Set-StepStatus -Index $i -Status "pending"
$script:progressBar.Value = $i + 1
[System.Windows.Forms.Application]::DoEvents()
continue
}
if ($script:CancelRequested) {
Write-Log ""
Write-Log "Repair cancelled by user at step $($i + 1)" -Level "WARN"
break
}
$step = $script:RepairSteps[$i]
Write-Log ""
Write-Log "---- Step $($i + 1) of $($script:RepairSteps.Count): $($step.Name) ----" -Level "STEP"
# Progress updated via progress bar
[System.Windows.Forms.Application]::DoEvents()
$success = $false
do {
if ($step.Command -eq "__SPECIAL_TEMP_CLEANUP__") {
$success = Invoke-TempCleanup -StepIndex $i
}
elseif ($step.Command -eq "__SPECIAL_ICON_CACHE__") {
$success = Invoke-IconCacheRebuild -StepIndex $i
}
else {
$success = Invoke-RepairCommand -Command $step.Command -StepIndex $i
}
if (-not $success -and -not $script:CancelRequested) {
$dialogResult = Show-ErrorDialog -StepName $step.Name -ErrorMessage "Step did not complete successfully"
if ($dialogResult -eq [System.Windows.Forms.DialogResult]::Yes) {
Write-Log "User chose to skip $($step.Name)" -Level "WARN"
Set-StepStatus -Index $i -Status "error"
$failedSteps++
$success = $true
}
elseif ($dialogResult -eq [System.Windows.Forms.DialogResult]::No) {
Write-Log "Retrying $($step.Name)..." -Level "WARN"
}
else {
$script:CancelRequested = $true
break
}
} else {
$completedSteps++
}
} while (-not $success -and -not $script:CancelRequested)
$script:progressBar.Value = $i + 1
# Progress updated via progress bar
[System.Windows.Forms.Application]::DoEvents()
}
# Final status
Write-Log ""
Write-Log "=======================================================" -Level "INFO"
if ($script:CancelRequested) {
$script:lblStatus.Text = "Cancelled - Repair was stopped by user"
Write-Log " Repair CANCELLED by user" -Level "WARN"
}
elseif ($failedSteps -gt 0) {
$script:lblStatus.Text = "Complete with warnings - $failedSteps step(s) had issues"
Write-Log " Repair COMPLETE with $failedSteps warning(s)" -Level "WARN"
}
else {
$script:lblStatus.Text = "Complete - All repairs finished successfully!"
Write-Log " Repair COMPLETE - All steps successful!" -Level "SUCCESS"
}
Write-Log " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level "INFO"
Write-Log "=======================================================" -Level "INFO"
$script:IsRunning = $false
$script:btnRun.Enabled = $true
$script:btnNetwork.Enabled = $true
$script:btnAdvanced.Enabled = $true
$script:btnCancel.Enabled = $false
$script:btnCancel.ForeColor = $script:Colors.TextSecondary
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
})
# ═══════════════════════════════════════════════════════════════════════════════
# NETWORK RESET BUTTON
# ═══════════════════════════════════════════════════════════════════════════════
$script:btnNetwork.Add_Click({
if ($script:IsRunning) { return }
if (-not (Show-NetworkWarning)) {
Write-Log "Network reset cancelled by user" -Level "INFO"
return
}
$script:IsRunning = $true
$script:CancelRequested = $false
$script:btnRun.Enabled = $false
$script:btnNetwork.Enabled = $false
$script:btnAdvanced.Enabled = $false
$script:btnCancel.Enabled = $true
$script:btnCancel.ForeColor = $script:Colors.Red
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Red
$script:btnCancel.FlatAppearance.MouseOverBackColor = [System.Drawing.Color]::FromArgb(60, $script:Colors.Red.R, $script:Colors.Red.G, $script:Colors.Red.B)
Write-Log ""
Write-Log "=======================================================" -Level "INFO"
Write-Log " Network Reset Starting" -Level "STEP"
Write-Log "=======================================================" -Level "INFO"
for ($ni = 0; $ni -lt $script:NetworkSteps.Count; $ni++) {
$netStep = $script:NetworkSteps[$ni]
if ($script:CancelRequested) { break }
# Skip unchecked network steps in advanced mode
if ($script:NetworkEnabled.Count -gt $ni -and -not $script:NetworkEnabled[$ni]) {
Write-Log ""
Write-Log "$($netStep.Name) [SKIPPED]" -Level "WARN"
continue
}
$script:lblStatus.Text = "Running - $($netStep.Name)"
Write-Log ""
Write-Log "Executing: $($netStep.Command)" -Level "CMD"
[System.Windows.Forms.Application]::DoEvents()
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = "cmd.exe"
$psi.Arguments = "/c $($netStep.Command) 2>&1"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.CreateNoWindow = $true
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
try {
$process = [System.Diagnostics.Process]::Start($psi)
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()
if ($output.Trim() -ne "") {
foreach ($line in ($output -split "`n")) {
if ($line.Trim() -ne "") { Write-Log $line.Trim() }
}
}
Write-Log "$($netStep.Name) completed" -Level "SUCCESS"
}
catch {
Write-Log "Failed: $($_.Exception.Message)" -Level "ERROR"
}
[System.Windows.Forms.Application]::DoEvents()
}
Write-Log ""
Write-Log "=======================================================" -Level "INFO"
Write-Log " Network Reset Complete" -Level "SUCCESS"
Write-Log " You may need to reconnect to your network." -Level "WARN"
Write-Log "=======================================================" -Level "INFO"
$script:lblStatus.Text = "Network reset complete - You may need to reconnect"
$script:IsRunning = $false
$script:btnRun.Enabled = $true
$script:btnNetwork.Enabled = $true
$script:btnAdvanced.Enabled = $true
$script:btnCancel.Enabled = $false
$script:btnCancel.ForeColor = $script:Colors.TextSecondary
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
})
# ═══════════════════════════════════════════════════════════════════════════════
# CANCEL BUTTON
# ═══════════════════════════════════════════════════════════════════════════════
$script:btnCancel.Add_Click({
if (-not $script:IsRunning) { return }
$result = [System.Windows.Forms.MessageBox]::Show(
"Are you sure you want to cancel the current operation?`n`nThe current step will be stopped.",
"WinRepair - Cancel",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Question
)
if ($result -eq [System.Windows.Forms.DialogResult]::Yes) {
$script:CancelRequested = $true
if ($null -ne $script:CurrentProcess -and -not $script:CurrentProcess.HasExited) {
try { $script:CurrentProcess.Kill() } catch {}
}
}
})
# ═══════════════════════════════════════════════════════════════════════════════
# EXPORT LOG BUTTON
# ═══════════════════════════════════════════════════════════════════════════════
$script:btnExport.Add_Click({
$saveDialog = New-Object System.Windows.Forms.SaveFileDialog
$saveDialog.Filter = "Text files (*.txt)|*.txt|Log files (*.log)|*.log|All files (*.*)|*.*"
$saveDialog.FileName = "WinRepair_Log_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
$saveDialog.Title = "Export Repair Log"
$saveDialog.InitialDirectory = [Environment]::GetFolderPath("Desktop")
if ($saveDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
try {
$script:LogBuilder.ToString() | Out-File -FilePath $saveDialog.FileName -Encoding UTF8
[System.Windows.Forms.MessageBox]::Show(
"Log exported successfully to:`n$($saveDialog.FileName)",
"WinRepair - Export",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Information
) | Out-Null
}
catch {
[System.Windows.Forms.MessageBox]::Show(
"Failed to export log:`n$($_.Exception.Message)",
"WinRepair - Error",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
) | Out-Null
}
}
})
# ═══════════════════════════════════════════════════════════════════════════════
# FORM CLOSING — CONFIRM IF RUNNING
# ═══════════════════════════════════════════════════════════════════════════════
$form.Add_FormClosing({
param($sender, $e)
if ($script:IsRunning) {
$result = [System.Windows.Forms.MessageBox]::Show(
"A repair operation is currently in progress.`n`nAre you sure you want to close WinRepair?",
"WinRepair - Close",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
if ($result -ne [System.Windows.Forms.DialogResult]::Yes) {
$e.Cancel = $true
} else {
$script:CancelRequested = $true
if ($null -ne $script:CurrentProcess -and -not $script:CurrentProcess.HasExited) {
try { $script:CurrentProcess.Kill() } catch {}
}
}
}
})
# ═══════════════════════════════════════════════════════════════════════════════
# CHECK FOR PENDING WINDOWS UPDATES
# ═══════════════════════════════════════════════════════════════════════════════
function Test-PendingUpdates {
try {
Write-Log "Checking for pending Windows Updates..." -Level "INFO"
[System.Windows.Forms.Application]::DoEvents()
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$results = $searcher.Search("IsInstalled=0 AND IsHidden=0")
$pendingCount = $results.Updates.Count
if ($pendingCount -gt 0) {
Write-Log "Found $pendingCount pending Windows Update(s)!" -Level "WARN"
$updateList = ""
$maxShow = [Math]::Min($pendingCount, 5)
for ($u = 0; $u -lt $maxShow; $u++) {
$updateList += " - $($results.Updates.Item($u).Title)`n"
}
if ($pendingCount -gt 5) {
$updateList += " ... and $($pendingCount - 5) more`n"
}
$msg = "WARNING: PENDING WINDOWS UPDATES DETECTED`n`n"
$msg += "$pendingCount update(s) are waiting to be installed:`n`n"
$msg += $updateList
$msg += "`nPending updates can cause repair tools (SFC, DISM) to fail or"
$msg += " report false errors.`n`n"
$msg += "It is strongly recommended to install all updates first.`n`n"
$msg += "Click 'Yes' to exit and open Windows Update.`n"
$msg += "Click 'No' to continue anyway (not recommended)."
$result = [System.Windows.Forms.MessageBox]::Show(
$msg,
"WinRepair - Pending Updates",
[System.Windows.Forms.MessageBoxButtons]::YesNo,
[System.Windows.Forms.MessageBoxIcon]::Warning
)
if ($result -eq [System.Windows.Forms.DialogResult]::Yes) {
Write-Log "User chose to exit and install updates" -Level "INFO"
try {
Start-Process "ms-settings:windowsupdate"
} catch {
Start-Process "control" -ArgumentList "/name Microsoft.WindowsUpdate"
}
return $false # Signal to exit
} else {
Write-Log "User chose to continue despite pending updates" -Level "WARN"
return $true
}
} else {
Write-Log "No pending Windows Updates found - good to go!" -Level "SUCCESS"
return $true
}
}
catch {
Write-Log "Could not check for updates: $($_.Exception.Message)" -Level "WARN"
Write-Log "Continuing without update check..." -Level "INFO"
return $true
}
}
# ═══════════════════════════════════════════════════════════════════════════════
# LAUNCH
# ═══════════════════════════════════════════════════════════════════════════════
Write-Log "WinRepair initialized - System theme: $($script:Theme) mode" -Level "INFO"
Write-Log "Running as Administrator: Yes" -Level "INFO"
Write-Log "Windows version: $([System.Environment]::OSVersion.VersionString)" -Level "INFO"
Write-Log ""
# Check for pending updates before proceeding
$form.Add_Shown({
$script:lblStatus.Text = "Checking for pending Windows Updates..."
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
[System.Windows.Forms.Application]::DoEvents()
$canProceed = Test-PendingUpdates
$form.Cursor = [System.Windows.Forms.Cursors]::Default
if (-not $canProceed) {
$form.Close()
return
}
$script:lblStatus.Text = "Ready - Click 'Run System Repair' to begin"
Write-Log ""
Write-Log "Ready. Click 'Run System Repair' to begin." -Level "INFO"
})
[System.Windows.Forms.Application]::Run($form)
exit 0