diff --git a/bin/optimize.ps1 b/bin/optimize.ps1 index 1f46075..ada13fe 100644 --- a/bin/optimize.ps1 +++ b/bin/optimize.ps1 @@ -120,10 +120,23 @@ function Show-SystemHealth { # Optimization Tasks # ============================================================================ +function Get-SystemDriveLetter { + if (-not $env:SystemDrive) { + throw "SystemDrive environment variable is not set." + } + + $match = [regex]::Match($env:SystemDrive, '^[A-Za-z]') + if (-not $match.Success) { + throw "Could not determine the system drive letter from '$env:SystemDrive'." + } + + return $match.Value.ToUpperInvariant() +} + function Optimize-DiskDrive { <# .SYNOPSIS - Optimize disk (defrag for HDD, TRIM for SSD) + Optimize the system drive using Windows defaults #> $esc = [char]27 @@ -143,20 +156,10 @@ function Optimize-DiskDrive { } try { - # Check if SSD or HDD - $diskNumber = (Get-Partition -DriveLetter $env:SystemDrive[0]).DiskNumber - $mediaType = (Get-PhysicalDisk | Where-Object { $_.DeviceId -eq $diskNumber }).MediaType - - if ($mediaType -eq "SSD") { - Write-Host " Running TRIM on SSD..." - $null = Optimize-Volume -DriveLetter $env:SystemDrive[0] -ReTrim -ErrorAction Stop - Write-Host " $esc[32m$($script:Icons.Success)$esc[0m SSD TRIM completed" - } - else { - Write-Host " Running defragmentation on HDD..." - $null = Optimize-Volume -DriveLetter $env:SystemDrive[0] -Defrag -ErrorAction Stop - Write-Host " $esc[32m$($script:Icons.Success)$esc[0m Defragmentation completed" - } + $systemDriveLetter = Get-SystemDriveLetter + Write-Host " Running Windows default optimization on drive ${systemDriveLetter}:..." + $null = Optimize-Volume -DriveLetter $systemDriveLetter -ErrorAction Stop + Write-Host " $esc[32m$($script:Icons.Success)$esc[0m Drive optimization completed" $script:OptimizationsApplied++ } catch { @@ -623,9 +626,82 @@ function Repair-SearchIndex { } try { + function Wait-ForServiceStatus { + param( + [Parameter(Mandatory)] + [string]$Name, + [Parameter(Mandatory)] + [System.ServiceProcess.ServiceControllerStatus]$ExpectedStatus, + [int]$TimeoutSeconds = 15 + ) + + $service = Get-Service -Name $Name -ErrorAction Stop + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + + do { + $service.Refresh() + if ($service.Status -eq $ExpectedStatus) { + return $true + } + Start-Sleep -Milliseconds 500 + } while ((Get-Date) -lt $deadline) + + $service.Refresh() + return $service.Status -eq $ExpectedStatus + } + + function Stop-ServiceSafely { + param( + [Parameter(Mandatory)] + [string]$Name, + [int]$TimeoutSeconds = 20 + ) + + $service = Get-Service -Name $Name -ErrorAction Stop + if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Stopped) { + return + } + + if ($service.Status -ne [System.ServiceProcess.ServiceControllerStatus]::StopPending) { + try { + Stop-Service -Name $Name -Force -ErrorAction Stop + } + catch { + $service.Refresh() + if ($service.Status -ne [System.ServiceProcess.ServiceControllerStatus]::StopPending) { + throw + } + } + } + + if (-not (Wait-ForServiceStatus -Name $Name -ExpectedStatus ([System.ServiceProcess.ServiceControllerStatus]::Stopped) -TimeoutSeconds $TimeoutSeconds)) { + throw "Windows Search service did not stop within $TimeoutSeconds seconds." + } + } + + function Start-ServiceSafely { + param( + [Parameter(Mandatory)] + [string]$Name, + [int]$TimeoutSeconds = 20 + ) + + $service = Get-Service -Name $Name -ErrorAction Stop + if ($service.Status -eq [System.ServiceProcess.ServiceControllerStatus]::Running) { + return + } + + if ($service.Status -ne [System.ServiceProcess.ServiceControllerStatus]::StartPending) { + Start-Service -Name $Name -ErrorAction Stop + } + + if (-not (Wait-ForServiceStatus -Name $Name -ExpectedStatus ([System.ServiceProcess.ServiceControllerStatus]::Running) -TimeoutSeconds $TimeoutSeconds)) { + throw "Windows Search service did not start within $TimeoutSeconds seconds." + } + } + Write-Host " $esc[90mStopping Windows Search service...$esc[0m" - Stop-Service -Name "WSearch" -Force -ErrorAction Stop - Start-Sleep -Seconds 3 + Stop-ServiceSafely -Name "WSearch" if (Test-Path $searchIndexPath) { Write-Host " $esc[90mDeleting search index...$esc[0m" @@ -633,7 +709,7 @@ function Repair-SearchIndex { } Write-Host " $esc[90mRestarting Windows Search service...$esc[0m" - Start-Service -Name "WSearch" -ErrorAction Stop + Start-ServiceSafely -Name "WSearch" Write-Host " $esc[32m$($script:Icons.Success)$esc[0m Search index reset successfully" Write-Host " $esc[33m$($script:Icons.Warning)$esc[0m Indexing will rebuild in the background (may take hours)" diff --git a/bin/update.ps1 b/bin/update.ps1 index fdd6ba1..fb88012 100644 --- a/bin/update.ps1 +++ b/bin/update.ps1 @@ -32,6 +32,44 @@ function Show-UpdateHelp { Write-Host "" } +function Invoke-GitCommand { + param( + [string]$WorkingDirectory, + [Parameter(Mandatory)] + [string[]]$Arguments + ) + + $previousNativeErrorPreference = $null + $hasNativeErrorPreference = $false + + if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { + $previousNativeErrorPreference = $PSNativeCommandUseErrorActionPreference + $PSNativeCommandUseErrorActionPreference = $false + $hasNativeErrorPreference = $true + } + + try { + $gitArguments = @() + if ($WorkingDirectory) { + $gitArguments += @("-C", $WorkingDirectory) + } + $gitArguments += $Arguments + + $output = & git @gitArguments 2>&1 + $exitCode = $LASTEXITCODE + + return [pscustomobject]@{ + ExitCode = $exitCode + Text = ((@($output) | ForEach-Object { "$_" }) -join [Environment]::NewLine).Trim() + } + } + finally { + if ($hasNativeErrorPreference) { + $PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference + } + } +} + function Test-InstallDirOnUserPath { param([string]$Path) @@ -64,45 +102,49 @@ if (-not (Test-Path $gitDir)) { exit 1 } -$dirtyOutput = & git -C $windowsDir status --porcelain --untracked-files=no 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to inspect git status: $dirtyOutput" -ForegroundColor Red +$dirtyStatus = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("status", "--porcelain", "--untracked-files=no") +if ($dirtyStatus.ExitCode -ne 0) { + Write-Host "Failed to inspect git status: $($dirtyStatus.Text)" -ForegroundColor Red exit 1 } -if ($dirtyOutput) { +if ($dirtyStatus.Text) { Write-Host "Local tracked changes detected in the installation directory." -ForegroundColor Red Write-Host "Commit or discard them before running 'mo update'." -ForegroundColor Yellow exit 1 } -$remote = (& git -C $windowsDir remote get-url origin 2>&1).Trim() -if ($LASTEXITCODE -ne 0 -or -not $remote) { +$remoteResult = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("remote", "get-url", "origin") +$remote = $remoteResult.Text +if ($remoteResult.ExitCode -ne 0 -or -not $remote) { Write-Host "Git remote 'origin' is not configured for this install." -ForegroundColor Red exit 1 } -$branch = (& git -C $windowsDir branch --show-current 2>&1).Trim() -if ($LASTEXITCODE -ne 0 -or -not $branch) { +$branchResult = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("branch", "--show-current") +$branch = $branchResult.Text +if ($branchResult.ExitCode -ne 0 -or -not $branch) { $branch = "windows" } -$before = (& git -C $windowsDir rev-parse --short HEAD 2>&1).Trim() -if ($LASTEXITCODE -ne 0 -or -not $before) { +$beforeResult = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("rev-parse", "--short", "HEAD") +$before = $beforeResult.Text +if ($beforeResult.ExitCode -ne 0 -or -not $before) { Write-Host "Failed to read current revision." -ForegroundColor Red exit 1 } Write-Host "Updating source from $remote ($branch)..." -ForegroundColor Cyan -$pullOutput = & git -C $windowsDir pull --ff-only origin $branch 2>&1 -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to update source: $pullOutput" -ForegroundColor Red +$pullResult = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("pull", "--ff-only", "origin", $branch) +if ($pullResult.ExitCode -ne 0) { + Write-Host "Failed to update source: $($pullResult.Text)" -ForegroundColor Red exit 1 } -$after = (& git -C $windowsDir rev-parse --short HEAD 2>&1).Trim() -if ($LASTEXITCODE -ne 0 -or -not $after) { +$afterResult = Invoke-GitCommand -WorkingDirectory $windowsDir -Arguments @("rev-parse", "--short", "HEAD") +$after = $afterResult.Text +if ($afterResult.ExitCode -ne 0 -or -not $after) { Write-Host "Updated source, but failed to read the new revision." -ForegroundColor Red exit 1 } diff --git a/quick-install.ps1 b/quick-install.ps1 index f8be772..bfaf9d8 100644 --- a/quick-install.ps1 +++ b/quick-install.ps1 @@ -42,6 +42,44 @@ function Test-SourceInstall { return (Test-Path (Join-Path $Path ".git")) } +function Invoke-GitCommand { + param( + [string]$WorkingDirectory, + [Parameter(Mandatory)] + [string[]]$Arguments + ) + + $previousNativeErrorPreference = $null + $hasNativeErrorPreference = $false + + if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) { + $previousNativeErrorPreference = $PSNativeCommandUseErrorActionPreference + $PSNativeCommandUseErrorActionPreference = $false + $hasNativeErrorPreference = $true + } + + try { + $gitArguments = @() + if ($WorkingDirectory) { + $gitArguments += @("-C", $WorkingDirectory) + } + $gitArguments += $Arguments + + $output = & git @gitArguments 2>&1 + $exitCode = $LASTEXITCODE + + return [pscustomobject]@{ + ExitCode = $exitCode + Text = ((@($output) | ForEach-Object { "$_" }) -join [Environment]::NewLine).Trim() + } + } + finally { + if ($hasNativeErrorPreference) { + $PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference + } + } +} + # Main installation try { Write-Host "" @@ -64,22 +102,22 @@ try { if (Test-SourceInstall -Path $InstallDir) { Write-Step "Existing source install found, refreshing..." - Push-Location $InstallDir - try { - git fetch --quiet origin windows 2>&1 | Out-Null - if ($LASTEXITCODE -ne 0) { - Write-ErrorMsg "Failed to fetch latest source" - exit 1 - } - - git pull --ff-only origin windows 2>&1 | Out-Null - if ($LASTEXITCODE -ne 0) { - Write-ErrorMsg "Failed to fast-forward source install" - exit 1 + $fetchResult = Invoke-GitCommand -WorkingDirectory $InstallDir -Arguments @("fetch", "--quiet", "origin", "windows") + if ($fetchResult.ExitCode -ne 0) { + Write-ErrorMsg "Failed to fetch latest source" + if ($fetchResult.Text) { + Write-Host " $($fetchResult.Text)" } + exit 1 } - finally { - Pop-Location + + $pullResult = Invoke-GitCommand -WorkingDirectory $InstallDir -Arguments @("pull", "--ff-only", "origin", "windows") + if ($pullResult.ExitCode -ne 0) { + Write-ErrorMsg "Failed to fast-forward source install" + if ($pullResult.Text) { + Write-Host " $($pullResult.Text)" + } + exit 1 } } else { @@ -91,7 +129,14 @@ try { else { Write-Step "Cloning Mole source..." - git clone --quiet --depth 1 --branch windows https://github.com/tw93/Mole.git $InstallDir 2>&1 | Out-Null + $cloneResult = Invoke-GitCommand -Arguments @("clone", "--quiet", "--depth", "1", "--branch", "windows", "https://github.com/tw93/Mole.git", $InstallDir) + if ($cloneResult.ExitCode -ne 0) { + Write-ErrorMsg "Failed to clone source installer" + if ($cloneResult.Text) { + Write-Host " $($cloneResult.Text)" + } + exit 1 + } if (-not (Test-Path (Join-Path $InstallDir "install.ps1"))) { Write-ErrorMsg "Failed to clone source installer"