mirror of
https://github.com/ZenDeuo/WinRepair.git
synced 2026-06-22 01:40:59 +01:00
v1.0.1!
- Due to friend suffering issues with the SFC command, added a watchdog to detect if a command hangs for >45s and pops up a Window asking the user if they'd like to retry the command, skip the command or just outright cancel the command. - Moved Smark Disk Check and moved it to its own section with better output about your disk's help.
This commit is contained in:
parent
34dbb7f4f2
commit
cf9acdf786
1 changed files with 253 additions and 99 deletions
204
WinRepair.ps1
204
WinRepair.ps1
|
|
@ -179,12 +179,6 @@ $script:RepairSteps = @(
|
||||||
Command = "sfc /scannow"
|
Command = "sfc /scannow"
|
||||||
Phase = "System Integrity"
|
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"
|
Name = "Clear Temporary Files"
|
||||||
Description = "Remove temp files to free disk space"
|
Description = "Remove temp files to free disk space"
|
||||||
|
|
@ -468,44 +462,53 @@ $splitContainer.Panel2.Controls.Add($logHeaderPanel)
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
$buttonPanel = New-Object System.Windows.Forms.Panel
|
$buttonPanel = New-Object System.Windows.Forms.Panel
|
||||||
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
||||||
$buttonPanel.Height = 60
|
$buttonPanel.Height = 70
|
||||||
$buttonPanel.Width = $form.ClientSize.Width # Crucial for Right Anchor to work correctly
|
$buttonPanel.Width = $form.ClientSize.Width # Crucial for Right Anchor to work correctly
|
||||||
$buttonPanel.BackColor = $script:Colors.CardBg
|
$buttonPanel.BackColor = $script:Colors.CardBg
|
||||||
$buttonPanel.Padding = New-Object System.Windows.Forms.Padding(16, 10, 16, 10)
|
$buttonPanel.Padding = New-Object System.Windows.Forms.Padding(16, 10, 16, 10)
|
||||||
|
$form.Controls.Add($buttonPanel)
|
||||||
|
|
||||||
# Bottom border (between progress bar and buttons)
|
# Top border for button panel
|
||||||
$bottomBorder = New-Object System.Windows.Forms.Panel
|
$bottomBorder = New-Object System.Windows.Forms.Panel
|
||||||
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
$bottomBorder.Size = New-Object System.Drawing.Size($form.ClientSize.Width, 1)
|
||||||
$bottomBorder.Height = 1
|
$bottomBorder.Location = New-Object System.Drawing.Point(0, 0)
|
||||||
|
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Top
|
||||||
$bottomBorder.BackColor = $script:Colors.Border
|
$bottomBorder.BackColor = $script:Colors.Border
|
||||||
|
|
||||||
# Run System Repair button
|
# Run System Repair button (Left)
|
||||||
$script:btnRun = New-StyledButton -Text "Run System Repair" -X 16 -Y 10 -Width 200 -Height 38 `
|
$script:btnRun = New-StyledButton -Text "Run Repair" -X 16 -Y 12 -Width 160 -Height 45 `
|
||||||
-BgColor $script:Colors.ButtonBg -FgColor ([System.Drawing.Color]::White)
|
-BgColor $script:Colors.ButtonBg -FgColor ([System.Drawing.Color]::White)
|
||||||
$buttonPanel.Controls.Add($script:btnRun)
|
$buttonPanel.Controls.Add($script:btnRun)
|
||||||
|
|
||||||
# Network Reset button
|
# Cancel button (Left, next to Run)
|
||||||
$script:btnNetwork = New-StyledButton -Text "Network Reset" -X 226 -Y 10 -Width 160 -Height 38 `
|
$script:btnCancel = New-StyledButton -Text "Cancel" -X 186 -Y 12 -Width 90 -Height 45 `
|
||||||
-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
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.TextSecondary
|
||||||
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
|
$script:btnCancel.FlatAppearance.BorderColor = $script:Colors.Border
|
||||||
$script:btnCancel.Enabled = $false
|
$script:btnCancel.Enabled = $false
|
||||||
$buttonPanel.Controls.Add($script:btnCancel)
|
$buttonPanel.Controls.Add($script:btnCancel)
|
||||||
|
|
||||||
# Export Log button (anchored far right)
|
# Network Reset button (Left, next to Cancel)
|
||||||
$script:btnExport = New-StyledButton -Text "Export Log" -X 616 -Y 10 -Width 110 -Height 38 `
|
$script:btnNetwork = New-StyledButton -Text "Net Reset" -X 286 -Y 12 -Width 110 -Height 45 `
|
||||||
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
||||||
|
$script:btnNetwork.FlatAppearance.BorderColor = $script:Colors.Border
|
||||||
|
$buttonPanel.Controls.Add($script:btnNetwork)
|
||||||
|
|
||||||
|
# Disk Check Button (Left, next to Network)
|
||||||
|
$script:btnDisk = New-StyledButton -Text "Disk Check" -X 406 -Y 12 -Width 100 -Height 45 `
|
||||||
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
||||||
|
$script:btnDisk.FlatAppearance.BorderColor = $script:Colors.Border
|
||||||
|
$script:btnDisk.Add_Click({ Invoke-DiskCheck })
|
||||||
|
$buttonPanel.Controls.Add($script:btnDisk)
|
||||||
|
|
||||||
|
# Export Log button (Anchored Right)
|
||||||
|
$script:btnExport = New-StyledButton -Text "Export Log" -X ($buttonPanel.Width - 16 - 100) -Y 12 -Width 100 -Height 45 `
|
||||||
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
||||||
$script:btnExport.FlatAppearance.BorderColor = $script:Colors.Border
|
$script:btnExport.FlatAppearance.BorderColor = $script:Colors.Border
|
||||||
$script:btnExport.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
|
$script:btnExport.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
|
||||||
$buttonPanel.Controls.Add($script:btnExport)
|
$buttonPanel.Controls.Add($script:btnExport)
|
||||||
|
|
||||||
# Advanced button (anchored right, next to Export Log)
|
# Advanced button (Anchored Right, left of Export)
|
||||||
$script:btnAdvanced = New-StyledButton -Text "Advanced" -X 506 -Y 10 -Width 100 -Height 38 `
|
$script:btnAdvanced = New-StyledButton -Text "Advanced" -X ($buttonPanel.Width - 16 - 100 - 10 - 90) -Y 12 -Width 90 -Height 45 `
|
||||||
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Accent
|
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Accent
|
||||||
$script:btnAdvanced.FlatAppearance.BorderColor = $script:Colors.Accent
|
$script:btnAdvanced.FlatAppearance.BorderColor = $script:Colors.Accent
|
||||||
$script:btnAdvanced.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
|
$script:btnAdvanced.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right
|
||||||
|
|
@ -544,6 +547,91 @@ function Update-RunButtonText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# DISK CHECK FUNCTION (Run separately via dedicated button)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
function Invoke-DiskCheck {
|
||||||
|
if ($script:IsRunning) { return }
|
||||||
|
$script:IsRunning = $true
|
||||||
|
$script:btnRun.Enabled = $false
|
||||||
|
$script:btnNetwork.Enabled = $false
|
||||||
|
$script:btnDisk.Enabled = $false
|
||||||
|
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Log "Starting Disk Health Scan (chkdsk C: /scan)..." -Level "CMD"
|
||||||
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
|
$psi.FileName = "cmd.exe"
|
||||||
|
$psi.Arguments = "/c chkdsk C: /scan" # Force standard output for parsing
|
||||||
|
$psi.UseShellExecute = $false
|
||||||
|
$psi.RedirectStandardOutput = $true
|
||||||
|
$psi.CreateNoWindow = $true
|
||||||
|
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
||||||
|
|
||||||
|
$process = New-Object System.Diagnostics.Process
|
||||||
|
$process.StartInfo = $psi
|
||||||
|
$process.Start() | Out-Null
|
||||||
|
$script:CurrentProcess = $process
|
||||||
|
|
||||||
|
$output = New-Object System.Text.StringBuilder
|
||||||
|
|
||||||
|
while (-not $process.HasExited) {
|
||||||
|
$line = $process.StandardOutput.ReadLine()
|
||||||
|
if ($line) {
|
||||||
|
Write-Log $line -Level "INFO"
|
||||||
|
$output.AppendLine($line) | Out-Null
|
||||||
|
}
|
||||||
|
[System.Windows.Forms.Application]::DoEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Capture remainder
|
||||||
|
$remain = $process.StandardOutput.ReadToEnd()
|
||||||
|
if ($remain) {
|
||||||
|
Write-Log $remain -Level "INFO"
|
||||||
|
$output.AppendLine($remain) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullOutput = $output.ToString()
|
||||||
|
$script:CurrentProcess = $null
|
||||||
|
|
||||||
|
if ($fullOutput -match "Windows has scanned the file system and found no problems") {
|
||||||
|
Write-Log "Disk Scan Complete: No errors found." -Level "SUCCESS"
|
||||||
|
[System.Windows.Forms.MessageBox]::Show(
|
||||||
|
"Great news! Your disk appears to be healthy.`n`nNo corruption was found.",
|
||||||
|
"Disk Health - Healthy",
|
||||||
|
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||||
|
[System.Windows.Forms.MessageBoxIcon]::Information
|
||||||
|
)
|
||||||
|
} elseif ($fullOutput -match "found corruption" -or $fullOutput -match "errors found") {
|
||||||
|
Write-Log "WARNING: Disk corruption detected!" -Level "ERROR"
|
||||||
|
[System.Windows.Forms.MessageBox]::Show(
|
||||||
|
"Corruption was detected on your disk!`n`nPlease schedule a full repair by running 'chkdsk C: /f /r' in an Administrator Command Prompt and rebooting.",
|
||||||
|
"Disk Health - Corruption Found",
|
||||||
|
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||||
|
[System.Windows.Forms.MessageBoxIcon]::Error
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Write-Log "Disk Scan Complete." -Level "INFO"
|
||||||
|
[System.Windows.Forms.MessageBox]::Show(
|
||||||
|
"Disk Scan finished. Check the log for details.",
|
||||||
|
"Disk Health - Complete",
|
||||||
|
[System.Windows.Forms.MessageBoxButtons]::OK,
|
||||||
|
[System.Windows.Forms.MessageBoxIcon]::Information
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Log "Disk Scan Failed: $($_.Exception.Message)" -Level "ERROR"
|
||||||
|
[System.Windows.Forms.MessageBox]::Show("Failed to run Disk Scan.", "Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
|
||||||
|
} finally {
|
||||||
|
$script:IsRunning = $false
|
||||||
|
$script:btnRun.Enabled = $true
|
||||||
|
$script:btnNetwork.Enabled = $true
|
||||||
|
$script:btnDisk.Enabled = $true
|
||||||
|
$form.Cursor = [System.Windows.Forms.Cursors]::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Build advanced panel content
|
# Build advanced panel content
|
||||||
$advYPos = 10
|
$advYPos = 10
|
||||||
|
|
||||||
|
|
@ -755,9 +843,21 @@ function Write-Log {
|
||||||
default { $script:Colors.Text }
|
default { $script:Colors.Text }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$consoleColor = switch ($Level) {
|
||||||
|
"CMD" { "Cyan" }
|
||||||
|
"SUCCESS" { "Green" }
|
||||||
|
"ERROR" { "Red" }
|
||||||
|
"WARN" { "Yellow" }
|
||||||
|
"STEP" { "Cyan" }
|
||||||
|
default { "White" }
|
||||||
|
}
|
||||||
|
|
||||||
$fullMsg = "$timestamp $prefix $Message"
|
$fullMsg = "$timestamp $prefix $Message"
|
||||||
$script:LogBuilder.AppendLine($fullMsg) | Out-Null
|
$script:LogBuilder.AppendLine($fullMsg) | Out-Null
|
||||||
|
|
||||||
|
# Echo to console for Debug Mode visibility
|
||||||
|
Write-Host $fullMsg -ForegroundColor $consoleColor
|
||||||
|
|
||||||
$script:txtLog.SelectionStart = $script:txtLog.TextLength
|
$script:txtLog.SelectionStart = $script:txtLog.TextLength
|
||||||
$script:txtLog.SelectionLength = 0
|
$script:txtLog.SelectionLength = 0
|
||||||
$script:txtLog.SelectionColor = $color
|
$script:txtLog.SelectionColor = $color
|
||||||
|
|
@ -776,13 +876,25 @@ function Set-StepStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
|
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
function Invoke-RepairCommand {
|
function Invoke-RepairCommand {
|
||||||
param([string]$Command, [int]$StepIndex)
|
param([string]$Command, [int]$StepIndex)
|
||||||
|
|
||||||
|
$retryStep = $false
|
||||||
|
|
||||||
|
do {
|
||||||
|
$retryStep = $false
|
||||||
Write-Log "Executing: $Command" -Level "CMD"
|
Write-Log "Executing: $Command" -Level "CMD"
|
||||||
|
|
||||||
|
# Add friendly feedback for slow-starting SFC
|
||||||
|
if ($Command -match "sfc /scannow") {
|
||||||
|
Write-Log "Initializing System File Checker (this may take up to 30-60 seconds)..." -Level "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
Set-StepStatus -Index $StepIndex -Status "running"
|
Set-StepStatus -Index $StepIndex -Status "running"
|
||||||
$script:lblStatus.Text = "Running - $($script:RepairSteps[$StepIndex].Name)"
|
$script:lblStatus.Text = "Running - $($script:RepairSteps[$StepIndex].Name)"
|
||||||
[System.Windows.Forms.Application]::DoEvents()
|
[System.Windows.Forms.Application]::DoEvents()
|
||||||
|
|
@ -802,6 +914,7 @@ function Invoke-RepairCommand {
|
||||||
|
|
||||||
$process.Start() | Out-Null
|
$process.Start() | Out-Null
|
||||||
$script:CurrentProcess = $process
|
$script:CurrentProcess = $process
|
||||||
|
$lastActivity = Get-Date
|
||||||
|
|
||||||
# Use ReadLineAsync to poll output without blocking UI and avoiding
|
# Use ReadLineAsync to poll output without blocking UI and avoiding
|
||||||
# the thread-safety crashes of OutputDataReceived events in PowerShell.
|
# the thread-safety crashes of OutputDataReceived events in PowerShell.
|
||||||
|
|
@ -814,6 +927,7 @@ function Invoke-RepairCommand {
|
||||||
try {
|
try {
|
||||||
$line = $readTask.Result
|
$line = $readTask.Result
|
||||||
if ($line -ne $null) {
|
if ($line -ne $null) {
|
||||||
|
$lastActivity = Get-Date # Reset timeout on activity
|
||||||
# Sanitize output: Remove control chars (like backspaces/nulls) but keep tabs
|
# Sanitize output: Remove control chars (like backspaces/nulls) but keep tabs
|
||||||
$cleanLine = $line -replace "[\x00-\x08\x0B-\x1F\x7F]", ""
|
$cleanLine = $line -replace "[\x00-\x08\x0B-\x1F\x7F]", ""
|
||||||
if ($cleanLine.Trim() -ne "") { Write-Log $cleanLine.Trim() }
|
if ($cleanLine.Trim() -ne "") { Write-Log $cleanLine.Trim() }
|
||||||
|
|
@ -826,6 +940,42 @@ function Invoke-RepairCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Timeout Check (45 Seconds of silence)
|
||||||
|
if (((Get-Date) - $lastActivity).TotalSeconds -gt 45) {
|
||||||
|
$result = [System.Windows.Forms.MessageBox]::Show(
|
||||||
|
"The current command appears to be stuck (no output for 45s).`n`nDo you want to retry the step, skip it, or cancel the repair?",
|
||||||
|
"Command Timeout Warning",
|
||||||
|
[System.Windows.Forms.MessageBoxButtons]::AbortRetryIgnore,
|
||||||
|
[System.Windows.Forms.MessageBoxIcon]::Warning
|
||||||
|
)
|
||||||
|
|
||||||
|
# Abort = Cancel, Retry = Retry, Ignore = Skip
|
||||||
|
if ($result -eq [System.Windows.Forms.DialogResult]::Abort) {
|
||||||
|
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||||
|
Write-Log "Operation cancelled by user (Timeout)" -Level "WARN"
|
||||||
|
Set-StepStatus -Index $StepIndex -Status "error"
|
||||||
|
$script:CurrentProcess = $null
|
||||||
|
try { $process.Dispose() } catch {}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
elseif ($result -eq [System.Windows.Forms.DialogResult]::Retry) {
|
||||||
|
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||||
|
Write-Log "Timeout detected. Retrying step..." -Level "WARN"
|
||||||
|
$script:CurrentProcess = $null
|
||||||
|
try { $process.Dispose() } catch {}
|
||||||
|
$retryStep = $true
|
||||||
|
break # Break inner loop to restart do-while
|
||||||
|
}
|
||||||
|
elseif ($result -eq [System.Windows.Forms.DialogResult]::Ignore) {
|
||||||
|
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||||
|
Write-Log "Timeout detected. Skipping step..." -Level "WARN"
|
||||||
|
Set-StepStatus -Index $StepIndex -Status "success" # Treat as success to continue? Or error? Usually skip implies 'ignore error'
|
||||||
|
$script:CurrentProcess = $null
|
||||||
|
try { $process.Dispose() } catch {}
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($script:CancelRequested) {
|
if ($script:CancelRequested) {
|
||||||
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||||
Write-Log "Operation cancelled by user" -Level "WARN"
|
Write-Log "Operation cancelled by user" -Level "WARN"
|
||||||
|
|
@ -839,9 +989,11 @@ function Invoke-RepairCommand {
|
||||||
Start-Sleep -Milliseconds 50
|
Start-Sleep -Milliseconds 50
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($retryStep) { continue } # fast track to retry
|
||||||
|
|
||||||
# Drain any remaining output after exit
|
# Drain any remaining output after exit
|
||||||
if ($readTask -ne $null) {
|
if ($readTask -ne $null) {
|
||||||
# process has exited, so just read to end synchronously (it should be buffered)
|
# process has exited, so just read to end synchronously
|
||||||
$remaining = $reader.ReadToEnd()
|
$remaining = $reader.ReadToEnd()
|
||||||
if ($remaining) {
|
if ($remaining) {
|
||||||
foreach ($l in ($remaining -split "`n")) {
|
foreach ($l in ($remaining -split "`n")) {
|
||||||
|
|
@ -870,6 +1022,7 @@ function Invoke-RepairCommand {
|
||||||
$script:CurrentProcess = $null
|
$script:CurrentProcess = $null
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
} while ($retryStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
@ -1395,3 +1548,4 @@ $form.Add_Shown({
|
||||||
})
|
})
|
||||||
|
|
||||||
[System.Windows.Forms.Application]::Run($form)
|
[System.Windows.Forms.Application]::Run($form)
|
||||||
|
exit 0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue