<# .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)