|
| 1 | +# PersistenceAI Uninstaller (PowerShell) |
| 2 | +# This script can be downloaded and executed via: |
| 3 | +# iwr -useb https://persistence-ai.github.io/Landing/uninstall.ps1 | iex |
| 4 | +# curl -fsSL https://persistence-ai.github.io/Landing/uninstall.ps1 | powershell -ExecutionPolicy Bypass -Command - |
| 5 | + |
| 6 | +$ErrorActionPreference = "Continue" |
| 7 | + |
| 8 | +# ============================================================================ |
| 9 | +# Output Functions (Matching install.ps1 style) |
| 10 | +# ============================================================================ |
| 11 | + |
| 12 | +function Write-Info { |
| 13 | + param([string]$msg) |
| 14 | + Write-Host " " -NoNewline |
| 15 | + Write-Host "[i]" -ForegroundColor Cyan -NoNewline |
| 16 | + Write-Host " $msg" -ForegroundColor Gray |
| 17 | +} |
| 18 | + |
| 19 | +function Write-Success { |
| 20 | + param([string]$msg) |
| 21 | + Write-Host " " -NoNewline |
| 22 | + Write-Host "[+]" -ForegroundColor Green -NoNewline |
| 23 | + Write-Host " $msg" -ForegroundColor Gray |
| 24 | +} |
| 25 | + |
| 26 | +function Write-Error { |
| 27 | + param([string]$msg) |
| 28 | + Write-Host " " -NoNewline |
| 29 | + Write-Host "[x]" -ForegroundColor Red -NoNewline |
| 30 | + Write-Host " $msg" -ForegroundColor Gray |
| 31 | +} |
| 32 | + |
| 33 | +function Write-Warning { |
| 34 | + param([string]$msg) |
| 35 | + Write-Host " " -NoNewline |
| 36 | + Write-Host "[!]" -ForegroundColor Yellow -NoNewline |
| 37 | + Write-Host " $msg" -ForegroundColor Gray |
| 38 | +} |
| 39 | + |
| 40 | +function Write-Step { |
| 41 | + param([string]$msg) |
| 42 | + Write-Host " " -NoNewline |
| 43 | + Write-Host "[>]" -ForegroundColor Cyan -NoNewline |
| 44 | + Write-Host " $msg" -ForegroundColor White |
| 45 | +} |
| 46 | + |
| 47 | +# ============================================================================ |
| 48 | +# Banner |
| 49 | +# ============================================================================ |
| 50 | + |
| 51 | +Write-Host "" |
| 52 | +Write-Host " " -NoNewline; Write-Host "========================================" -ForegroundColor Magenta |
| 53 | +Write-Host " " -NoNewline; Write-Host "|" -ForegroundColor Magenta -NoNewline |
| 54 | +Write-Host " " -NoNewline; Write-Host "PersistenceAI" -ForegroundColor Magenta -NoNewline; Write-Host " Uninstaller" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "|" -ForegroundColor Magenta |
| 55 | +Write-Host " " -NoNewline; Write-Host "========================================" -ForegroundColor Magenta |
| 56 | +Write-Host "" |
| 57 | + |
| 58 | +# ============================================================================ |
| 59 | +# Windows API for MoveFileEx (Delete on Reboot) |
| 60 | +# ============================================================================ |
| 61 | + |
| 62 | +Add-Type -TypeDefinition @" |
| 63 | +using System; |
| 64 | +using System.Runtime.InteropServices; |
| 65 | +public class Win32 { |
| 66 | + [DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)] |
| 67 | + public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags); |
| 68 | + public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4; |
| 69 | +} |
| 70 | +"@ |
| 71 | + |
| 72 | +function Register-ForDeletionOnReboot { |
| 73 | + param([string]$FilePath) |
| 74 | + try { |
| 75 | + if (Test-Path $FilePath) { |
| 76 | + $result = [Win32]::MoveFileEx($FilePath, $null, [Win32]::MOVEFILE_DELAY_UNTIL_REBOOT) |
| 77 | + if ($result) { |
| 78 | + Write-Info "Marked for deletion on reboot: $FilePath" |
| 79 | + return $true |
| 80 | + } |
| 81 | + } |
| 82 | + } catch { |
| 83 | + # Silently fail - not critical |
| 84 | + } |
| 85 | + return $false |
| 86 | +} |
| 87 | + |
| 88 | +# ============================================================================ |
| 89 | +# Process Termination (Aggressive - handles file locks) |
| 90 | +# ============================================================================ |
| 91 | + |
| 92 | +function Stop-PersistenceAIProcesses { |
| 93 | + Write-Step "Terminating PersistenceAI processes..." |
| 94 | + |
| 95 | + # Find processes by name |
| 96 | + $processes = Get-Process | Where-Object { |
| 97 | + ($_.ProcessName -eq "pai") -or |
| 98 | + ($_.ProcessName -eq "persistenceai") -or |
| 99 | + ($_.ProcessName -like "*persistenceai*") |
| 100 | + } -ErrorAction SilentlyContinue |
| 101 | + |
| 102 | + if ($processes) { |
| 103 | + Write-Info "Found $($processes.Count) process(es) to terminate" |
| 104 | + foreach ($proc in $processes) { |
| 105 | + try { |
| 106 | + Write-Info "Terminating process: $($proc.ProcessName) (PID: $($proc.Id))" |
| 107 | + # Use taskkill with /T to kill process tree (child processes) |
| 108 | + $result = Start-Process -FilePath "taskkill" -ArgumentList "/F", "/T", "/PID", $proc.Id -Wait -NoNewWindow -PassThru -ErrorAction SilentlyContinue |
| 109 | + if ($result -and $result.ExitCode -eq 0) { |
| 110 | + Write-Success "Terminated process tree: $($proc.ProcessName)" |
| 111 | + } else { |
| 112 | + # Fallback to Stop-Process |
| 113 | + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue |
| 114 | + } |
| 115 | + } catch { |
| 116 | + Write-Warning "Error terminating process: $_" |
| 117 | + } |
| 118 | + } |
| 119 | + } else { |
| 120 | + Write-Info "No PersistenceAI processes found" |
| 121 | + } |
| 122 | + |
| 123 | + # Wait for processes to fully terminate |
| 124 | + Start-Sleep -Seconds 3 |
| 125 | +} |
| 126 | + |
| 127 | +# ============================================================================ |
| 128 | +# File/Directory Removal with Retry and Reboot Marking |
| 129 | +# ============================================================================ |
| 130 | + |
| 131 | +function Remove-ItemWithRetry { |
| 132 | + param( |
| 133 | + [string]$Path, |
| 134 | + [int]$MaxRetries = 3, |
| 135 | + [int]$DelaySeconds = 2 |
| 136 | + ) |
| 137 | + |
| 138 | + if (-not (Test-Path $Path)) { |
| 139 | + return $true |
| 140 | + } |
| 141 | + |
| 142 | + for ($i = 1; $i -le $MaxRetries; $i++) { |
| 143 | + try { |
| 144 | + if (Test-Path $Path -PathType Container) { |
| 145 | + Remove-Item -Path $Path -Recurse -Force -ErrorAction Stop |
| 146 | + } else { |
| 147 | + Remove-Item -Path $Path -Force -ErrorAction Stop |
| 148 | + } |
| 149 | + Write-Success "Removed: $Path" |
| 150 | + return $true |
| 151 | + } catch { |
| 152 | + if ($i -lt $MaxRetries) { |
| 153 | + Write-Warning "Attempt $i failed, retrying in $DelaySeconds seconds..." |
| 154 | + Start-Sleep -Seconds $DelaySeconds |
| 155 | + } else { |
| 156 | + Write-Warning "Failed to remove after $MaxRetries attempts: $Path" |
| 157 | + Write-Info "Marking for deletion on reboot..." |
| 158 | + if (Test-Path $Path -PathType Container) { |
| 159 | + # For directories, mark all files inside |
| 160 | + Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object { |
| 161 | + Register-ForDeletionOnReboot $_.FullName |
| 162 | + } |
| 163 | + Register-ForDeletionOnReboot $Path |
| 164 | + } else { |
| 165 | + Register-ForDeletionOnReboot $Path |
| 166 | + } |
| 167 | + return $false |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + return $false |
| 172 | +} |
| 173 | + |
| 174 | +# ============================================================================ |
| 175 | +# Main Uninstall Logic |
| 176 | +# ============================================================================ |
| 177 | + |
| 178 | +Write-Step "Starting uninstall of PersistenceAI..." |
| 179 | + |
| 180 | +# Step 1: Stop all processes (aggressive termination) |
| 181 | +Stop-PersistenceAIProcesses |
| 182 | + |
| 183 | +# Step 2: Find all installation directories |
| 184 | +Write-Step "Locating installation directories..." |
| 185 | + |
| 186 | +$dirsToRemove = @( |
| 187 | + "$env:USERPROFILE\.persistenceai", |
| 188 | + "$env:USERPROFILE\.pai", |
| 189 | + "$env:USERPROFILE\.config\persistenceai", |
| 190 | + "$env:USERPROFILE\.config\pai", |
| 191 | + "$env:LOCALAPPDATA\persistenceai", |
| 192 | + "$env:LOCALAPPDATA\pai" |
| 193 | +) |
| 194 | + |
| 195 | +# Also find installation directories from PATH |
| 196 | +$paiCmd = Get-Command -Name "pai" -ErrorAction SilentlyContinue |
| 197 | +$persistenceaiCmd = Get-Command -Name "persistenceai" -ErrorAction SilentlyContinue |
| 198 | + |
| 199 | +if ($paiCmd) { |
| 200 | + $binDir = Split-Path $paiCmd.Source -Parent |
| 201 | + $installDir = Split-Path $binDir -Parent |
| 202 | + if ($installDir -and $installDir -notin $dirsToRemove) { |
| 203 | + $dirsToRemove += $installDir |
| 204 | + } |
| 205 | + # Also add the bin directory itself |
| 206 | + if ($binDir -and $binDir -notin $dirsToRemove) { |
| 207 | + $dirsToRemove += $binDir |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +if ($persistenceaiCmd) { |
| 212 | + $binDir = Split-Path $persistenceaiCmd.Source -Parent |
| 213 | + $installDir = Split-Path $binDir -Parent |
| 214 | + if ($installDir -and $installDir -notin $dirsToRemove) { |
| 215 | + $dirsToRemove += $installDir |
| 216 | + } |
| 217 | + # Also add the bin directory itself |
| 218 | + if ($binDir -and $binDir -notin $dirsToRemove) { |
| 219 | + $dirsToRemove += $binDir |
| 220 | + } |
| 221 | +} |
| 222 | + |
| 223 | +# Remove duplicates |
| 224 | +$dirsToRemove = $dirsToRemove | Select-Object -Unique |
| 225 | + |
| 226 | +# Step 3: Remove directories with retry logic |
| 227 | +Write-Step "Removing installation directories..." |
| 228 | + |
| 229 | +$removedCount = 0 |
| 230 | +$markedForReboot = 0 |
| 231 | + |
| 232 | +foreach ($dir in $dirsToRemove) { |
| 233 | + if (Test-Path $dir) { |
| 234 | + Write-Info "Removing: $dir" |
| 235 | + if (Remove-ItemWithRetry -Path $dir) { |
| 236 | + $removedCount++ |
| 237 | + } else { |
| 238 | + $markedForReboot++ |
| 239 | + } |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +# Step 4: Clean PATH |
| 244 | +Write-Step "Cleaning PATH environment variable..." |
| 245 | + |
| 246 | +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") |
| 247 | +if ($userPath) { |
| 248 | + $newUserPath = ($userPath -split ";" | Where-Object { |
| 249 | + $_ -and $_ -notmatch "\.persistenceai" -and $_ -notmatch "\.pai" |
| 250 | + }) -join ";" |
| 251 | + [Environment]::SetEnvironmentVariable("Path", $newUserPath, "User") |
| 252 | + Write-Success "Cleaned user PATH" |
| 253 | +} |
| 254 | + |
| 255 | +# Update current session PATH |
| 256 | +$env:Path = ($env:Path -split ";" | Where-Object { |
| 257 | + $_ -and $_ -notmatch "\.persistenceai" -and $_ -notmatch "\.pai" |
| 258 | +}) -join ";" |
| 259 | + |
| 260 | +# Step 5: Summary |
| 261 | +Write-Host "" |
| 262 | +Write-Host " " -NoNewline; Write-Host "================================" -ForegroundColor DarkGray |
| 263 | +if ($markedForReboot -gt 0) { |
| 264 | + Write-Warning "Some files could not be removed and were marked for deletion on reboot" |
| 265 | + Write-Info "Please restart your computer to complete the uninstallation" |
| 266 | + Write-Info "Removed $removedCount directory/directories" |
| 267 | + Write-Info "$markedForReboot item(s) marked for deletion on reboot" |
| 268 | +} else { |
| 269 | + Write-Success "PersistenceAI uninstalled successfully" |
| 270 | + Write-Info "Removed $removedCount directory/directories" |
| 271 | +} |
| 272 | +Write-Host " " -NoNewline; Write-Host "================================" -ForegroundColor DarkGray |
| 273 | +Write-Host "" |
| 274 | +Write-Host " " -NoNewline; Write-Host "Note:" -ForegroundColor Yellow -NoNewline; Write-Host " Restart PowerShell for PATH changes to take effect" -ForegroundColor Gray |
| 275 | +Write-Host "" |
0 commit comments