Compare commits

..

No commits in common. "main" and "v1.0.0-Debug" have entirely different histories.

View file

@ -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