mirror of
https://github.com/ZenDeuo/WinRepair.git
synced 2026-06-22 01:40:59 +01:00
1397 lines
67 KiB
PowerShell
1397 lines
67 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 = "CHKDSK - Disk Scan"
|
|
Description = "Scan C: drive for filesystem errors"
|
|
Command = "chkdsk C: /scan"
|
|
Phase = "Disk Health"
|
|
},
|
|
@{
|
|
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 = 60
|
|
$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)
|
|
|
|
# Bottom border (between progress bar and buttons)
|
|
$bottomBorder = New-Object System.Windows.Forms.Panel
|
|
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
|
$bottomBorder.Height = 1
|
|
$bottomBorder.BackColor = $script:Colors.Border
|
|
|
|
# Run System Repair button
|
|
$script:btnRun = New-StyledButton -Text "Run System Repair" -X 16 -Y 10 -Width 200 -Height 38 `
|
|
-BgColor $script:Colors.ButtonBg -FgColor ([System.Drawing.Color]::White)
|
|
$buttonPanel.Controls.Add($script:btnRun)
|
|
|
|
# Network Reset button
|
|
$script:btnNetwork = New-StyledButton -Text "Network Reset" -X 226 -Y 10 -Width 160 -Height 38 `
|
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
|
$script:btnNetwork.FlatAppearance.BorderColor = $script:Colors.Border
|
|
$buttonPanel.Controls.Add($script:btnNetwork)
|
|
|
|
# Cancel button (starts disabled with muted style, turns red when active)
|
|
$script:btnCancel = New-StyledButton -Text "Cancel" -X 396 -Y 10 -Width 100 -Height 38 `
|
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.TextSecondary
|
|
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
|
|
$script:btnCancel.Enabled = $false
|
|
$buttonPanel.Controls.Add($script:btnCancel)
|
|
|
|
# Export Log button (anchored far right)
|
|
$script:btnExport = New-StyledButton -Text "Export Log" -X 616 -Y 10 -Width 110 -Height 38 `
|
|
-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, next to Export Log)
|
|
$script:btnAdvanced = New-StyledButton -Text "Advanced" -X 506 -Y 10 -Width 100 -Height 38 `
|
|
-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 }
|
|
}
|
|
}
|
|
|
|
# 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 }
|
|
}
|
|
|
|
$fullMsg = "$timestamp $prefix $Message"
|
|
$script:LogBuilder.AppendLine($fullMsg) | Out-Null
|
|
|
|
$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
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
function Invoke-RepairCommand {
|
|
param([string]$Command, [int]$StepIndex)
|
|
|
|
Write-Log "Executing: $Command" -Level "CMD"
|
|
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
|
|
|
|
# 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) {
|
|
# 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
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# Drain any remaining output after exit
|
|
if ($readTask -ne $null) {
|
|
# process has exited, so just read to end synchronously (it should be buffered)
|
|
$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
|
|
}
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 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)
|