mirror of
https://github.com/ZenDeuo/WinRepair.git
synced 2026-06-22 01:40:59 +01:00
Compare commits
No commits in common. "main" and "v1.0.0-Debug" have entirely different histories.
main
...
v1.0.0-Deb
1 changed files with 99 additions and 253 deletions
352
WinRepair.ps1
352
WinRepair.ps1
|
|
@ -179,6 +179,12 @@ $script:RepairSteps = @(
|
|||
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"
|
||||
|
|
@ -462,53 +468,44 @@ $splitContainer.Panel2.Controls.Add($logHeaderPanel)
|
|||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
$buttonPanel = New-Object System.Windows.Forms.Panel
|
||||
$buttonPanel.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
||||
$buttonPanel.Height = 70
|
||||
$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)
|
||||
$form.Controls.Add($buttonPanel)
|
||||
|
||||
# Top border for button panel
|
||||
# Bottom border (between progress bar and buttons)
|
||||
$bottomBorder = New-Object System.Windows.Forms.Panel
|
||||
$bottomBorder.Size = New-Object System.Drawing.Size($form.ClientSize.Width, 1)
|
||||
$bottomBorder.Location = New-Object System.Drawing.Point(0, 0)
|
||||
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Top
|
||||
$bottomBorder.Dock = [System.Windows.Forms.DockStyle]::Bottom
|
||||
$bottomBorder.Height = 1
|
||||
$bottomBorder.BackColor = $script:Colors.Border
|
||||
|
||||
# Run System Repair button (Left)
|
||||
$script:btnRun = New-StyledButton -Text "Run Repair" -X 16 -Y 12 -Width 160 -Height 45 `
|
||||
# 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)
|
||||
|
||||
# Cancel button (Left, next to Run)
|
||||
$script:btnCancel = New-StyledButton -Text "Cancel" -X 186 -Y 12 -Width 90 -Height 45 `
|
||||
# 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)
|
||||
|
||||
# Network Reset button (Left, next to Cancel)
|
||||
$script:btnNetwork = New-StyledButton -Text "Net Reset" -X 286 -Y 12 -Width 110 -Height 45 `
|
||||
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
||||
$script:btnNetwork.FlatAppearance.BorderColor = $script:Colors.Border
|
||||
$buttonPanel.Controls.Add($script:btnNetwork)
|
||||
|
||||
# Disk Check Button (Left, next to Network)
|
||||
$script:btnDisk = New-StyledButton -Text "Disk Check" -X 406 -Y 12 -Width 100 -Height 45 `
|
||||
-BgColor $script:Colors.CardBgAlt -FgColor $script:Colors.Text
|
||||
$script:btnDisk.FlatAppearance.BorderColor = $script:Colors.Border
|
||||
$script:btnDisk.Add_Click({ Invoke-DiskCheck })
|
||||
$buttonPanel.Controls.Add($script:btnDisk)
|
||||
|
||||
# Export Log button (Anchored Right)
|
||||
$script:btnExport = New-StyledButton -Text "Export Log" -X ($buttonPanel.Width - 16 - 100) -Y 12 -Width 100 -Height 45 `
|
||||
# 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, left of Export)
|
||||
$script:btnAdvanced = New-StyledButton -Text "Advanced" -X ($buttonPanel.Width - 16 - 100 - 10 - 90) -Y 12 -Width 90 -Height 45 `
|
||||
# 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
|
||||
|
|
@ -547,91 +544,6 @@ 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
|
||||
$advYPos = 10
|
||||
|
||||
|
|
@ -843,20 +755,8 @@ function Write-Log {
|
|||
default { $script:Colors.Text }
|
||||
}
|
||||
|
||||
$consoleColor = switch ($Level) {
|
||||
"CMD" { "Cyan" }
|
||||
"SUCCESS" { "Green" }
|
||||
"ERROR" { "Red" }
|
||||
"WARN" { "Yellow" }
|
||||
"STEP" { "Cyan" }
|
||||
default { "White" }
|
||||
}
|
||||
|
||||
$fullMsg = "$timestamp $prefix $Message"
|
||||
$script:LogBuilder.AppendLine($fullMsg) | Out-Null
|
||||
|
||||
# Echo to console for Debug Mode visibility
|
||||
Write-Host $fullMsg -ForegroundColor $consoleColor
|
||||
|
||||
$script:txtLog.SelectionStart = $script:txtLog.TextLength
|
||||
$script:txtLog.SelectionLength = 0
|
||||
|
|
@ -876,153 +776,100 @@ function Set-StepStatus {
|
|||
}
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# RUN A COMMAND IN HIDDEN PROCESS WITH REAL-TIME OUTPUT
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
function Invoke-RepairCommand {
|
||||
param([string]$Command, [int]$StepIndex)
|
||||
|
||||
$retryStep = $false
|
||||
|
||||
do {
|
||||
$retryStep = $false
|
||||
Write-Log "Executing: $Command" -Level "CMD"
|
||||
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
|
||||
|
||||
# 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"
|
||||
}
|
||||
$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()
|
||||
|
||||
Set-StepStatus -Index $StepIndex -Status "running"
|
||||
$script:lblStatus.Text = "Running - $($script:RepairSteps[$StepIndex].Name)"
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = "cmd.exe"
|
||||
$psi.Arguments = "/c $Command 2>&1"
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
||||
|
||||
try {
|
||||
$process = New-Object System.Diagnostics.Process
|
||||
$process.StartInfo = $psi
|
||||
|
||||
$process.Start() | Out-Null
|
||||
$script:CurrentProcess = $process
|
||||
$lastActivity = Get-Date
|
||||
|
||||
# Use ReadLineAsync to poll output without blocking UI and avoiding
|
||||
# the thread-safety crashes of OutputDataReceived events in PowerShell.
|
||||
$reader = $process.StandardOutput
|
||||
$readTask = $reader.ReadLineAsync()
|
||||
|
||||
while (-not $process.HasExited -or ($readTask -ne $null -and $readTask.IsCompleted)) {
|
||||
# Process output
|
||||
if ($readTask -ne $null -and $readTask.IsCompleted) {
|
||||
try {
|
||||
$line = $readTask.Result
|
||||
if ($line -ne $null) {
|
||||
$lastActivity = Get-Date # Reset timeout on activity
|
||||
# Sanitize output: Remove control chars (like backspaces/nulls) but keep tabs
|
||||
$cleanLine = $line -replace "[\x00-\x08\x0B-\x1F\x7F]", ""
|
||||
if ($cleanLine.Trim() -ne "") { Write-Log $cleanLine.Trim() }
|
||||
$readTask = $reader.ReadLineAsync()
|
||||
} else {
|
||||
$readTask = $null # End of stream
|
||||
}
|
||||
} catch {
|
||||
$readTask = $null
|
||||
}
|
||||
}
|
||||
|
||||
# Timeout Check (45 Seconds of silence)
|
||||
if (((Get-Date) - $lastActivity).TotalSeconds -gt 45) {
|
||||
$result = [System.Windows.Forms.MessageBox]::Show(
|
||||
"The current command appears to be stuck (no output for 45s).`n`nDo you want to retry the step, skip it, or cancel the repair?",
|
||||
"Command Timeout Warning",
|
||||
[System.Windows.Forms.MessageBoxButtons]::AbortRetryIgnore,
|
||||
[System.Windows.Forms.MessageBoxIcon]::Warning
|
||||
)
|
||||
|
||||
# Abort = Cancel, Retry = Retry, Ignore = Skip
|
||||
if ($result -eq [System.Windows.Forms.DialogResult]::Abort) {
|
||||
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||
Write-Log "Operation cancelled by user (Timeout)" -Level "WARN"
|
||||
Set-StepStatus -Index $StepIndex -Status "error"
|
||||
$script:CurrentProcess = $null
|
||||
try { $process.Dispose() } catch {}
|
||||
return $false
|
||||
}
|
||||
elseif ($result -eq [System.Windows.Forms.DialogResult]::Retry) {
|
||||
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||
Write-Log "Timeout detected. Retrying step..." -Level "WARN"
|
||||
$script:CurrentProcess = $null
|
||||
try { $process.Dispose() } catch {}
|
||||
$retryStep = $true
|
||||
break # Break inner loop to restart do-while
|
||||
}
|
||||
elseif ($result -eq [System.Windows.Forms.DialogResult]::Ignore) {
|
||||
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||
Write-Log "Timeout detected. Skipping step..." -Level "WARN"
|
||||
Set-StepStatus -Index $StepIndex -Status "success" # Treat as success to continue? Or error? Usually skip implies 'ignore error'
|
||||
$script:CurrentProcess = $null
|
||||
try { $process.Dispose() } catch {}
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:CancelRequested) {
|
||||
try { if (-not $process.HasExited) { $process.Kill() } } catch {}
|
||||
Write-Log "Operation cancelled by user" -Level "WARN"
|
||||
Set-StepStatus -Index $StepIndex -Status "error"
|
||||
$script:CurrentProcess = $null
|
||||
try { $process.Dispose() } catch {}
|
||||
return $false
|
||||
}
|
||||
|
||||
[System.Windows.Forms.Application]::DoEvents()
|
||||
Start-Sleep -Milliseconds 50
|
||||
}
|
||||
|
||||
if ($retryStep) { continue } # fast track to retry
|
||||
|
||||
# Drain any remaining output after exit
|
||||
if ($readTask -ne $null) {
|
||||
# process has exited, so just read to end synchronously
|
||||
$remaining = $reader.ReadToEnd()
|
||||
if ($remaining) {
|
||||
foreach ($l in ($remaining -split "`n")) {
|
||||
if ($l.Trim()) { Write-Log $l.Trim() }
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
$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"
|
||||
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
|
||||
}
|
||||
catch {
|
||||
Write-Log "Failed: $($_.Exception.Message)" -Level "ERROR"
|
||||
|
||||
# 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"
|
||||
$script:CurrentProcess = $null
|
||||
return $false
|
||||
}
|
||||
} while ($retryStep)
|
||||
}
|
||||
catch {
|
||||
Write-Log "Failed: $($_.Exception.Message)" -Level "ERROR"
|
||||
Set-StepStatus -Index $StepIndex -Status "error"
|
||||
$script:CurrentProcess = $null
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -1548,4 +1395,3 @@ $form.Add_Shown({
|
|||
})
|
||||
|
||||
[System.Windows.Forms.Application]::Run($form)
|
||||
exit 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue