diff --git a/3-Uninstall-RustDesk.ps1 b/3-Uninstall-RustDesk.ps1 index 0813364..3afbc53 100644 --- a/3-Uninstall-RustDesk.ps1 +++ b/3-Uninstall-RustDesk.ps1 @@ -1,89 +1,128 @@ # ScoutIT Remote Support (RustDesk) - Uninstall Script +# Removes ALL installed versions: ScoutIT-RemoteSupport (hyphen), ScoutIT Remote Support +# (spaces), plain RustDesk — side-by-side installs included. $ErrorActionPreference = 'SilentlyContinue' # ── Config ──────────────────────────────────────────────────────────────────── -$rustdeskInstallDir = 'C:\Program Files\ScoutIT-RemoteSupport' -$rustdeskService = 'ScoutIT-RemoteSupport' -$rustdeskMsiName = 'estudio-scoutit-remotesupport' # used to find uninstall key in registry +# Pattern is case-insensitive via -match. [-\s]? tolerates hyphen OR space OR +# nothing between "ScoutIT", "Remote", and "Support" — covers every branding +# we've shipped. +$namePattern = 'ScoutIT[-\s]?Remote[-\s]?Support|ScoutIT|RustDesk' +$servicePattern = 'ScoutIT[-\s]?Remote[-\s]?Support|RustDesk' +$processPattern = 'ScoutIT[-\s]?Remote[-\s]?Support|scoutit|rustdesk' + +$fallbackInstallDirs = @( + 'C:\Program Files\ScoutIT-RemoteSupport', + 'C:\Program Files (x86)\ScoutIT-RemoteSupport', + 'C:\Program Files\ScoutIT Remote Support', + 'C:\Program Files (x86)\ScoutIT Remote Support', + 'C:\Program Files\RustDesk', + 'C:\Program Files (x86)\RustDesk' +) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Write-Host "Starting ScoutIT Remote Support uninstall..." +Write-Host "Starting ScoutIT Remote Support / RustDesk uninstall (all versions)..." -# ── Stop and unregister the service ────────────────────────────────────────── -$svc = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue -if ($null -ne $svc) { - Write-Host "Stopping ScoutIT Remote Support service..." - Stop-Service -Name $rustdeskService -Force -ErrorAction SilentlyContinue - Start-Sleep -Seconds 3 - - # Attempt graceful service uninstall via exe first - $rustdeskExe = Get-ChildItem $rustdeskInstallDir -Filter '*.exe' -ErrorAction SilentlyContinue | - Select-Object -First 1 - if ($rustdeskExe) { - Write-Host "Unregistering service via exe..." - Start-Process -FilePath $rustdeskExe.FullName -ArgumentList '--uninstall-service' -Wait - Start-Sleep -Seconds 5 +# ── Stop every matching service ────────────────────────────────────────────── +$services = Get-Service -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match $servicePattern -or $_.DisplayName -match $servicePattern } +if ($services) { + foreach ($s in $services) { + Write-Host "Stopping service: $($s.Name) ($($s.DisplayName))..." + Stop-Service -Name $s.Name -Force -ErrorAction SilentlyContinue } + Start-Sleep -Seconds 3 } else { - Write-Host "Service '$rustdeskService' not found - may already be removed." + Write-Host "No matching services found." } -# ── MSI uninstall via registry ProductCode ─────────────────────────────────── -Write-Host "Looking for MSI uninstall entry in registry..." +# ── Enumerate every matching registry uninstall entry ──────────────────────── +Write-Host "Scanning registry for all matching uninstall entries..." $uninstallKeys = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) -$productCode = $null -foreach ($key in $uninstallKeys) { - $match = Get-ItemProperty $key -ErrorAction SilentlyContinue | - Where-Object { $_.DisplayName -match 'scoutit|remotesupport|rustdesk' -or - $_.Publisher -match 'scoutit|rustdesk' } | - Select-Object -First 1 - if ($match) { - $productCode = $match.PSChildName - Write-Host "Found uninstall entry: $($match.DisplayName) [$productCode]" - break - } +$entries = foreach ($key in $uninstallKeys) { + Get-ItemProperty $key -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -match $namePattern -or $_.Publisher -match $namePattern } } -if ($productCode) { - Write-Host "Running MSI uninstall..." - Start-Process -FilePath "msiexec.exe" ` - -ArgumentList "/x `"$productCode`" /qn /norestart" ` - -Wait - Start-Sleep -Seconds 10 - Write-Host "MSI uninstall complete." +if ($entries) { + Write-Host "Found $($entries.Count) matching entry/entries:" + $entries | ForEach-Object { Write-Host " - $($_.DisplayName) [$($_.PSChildName)]" } } else { - Write-Warning "No MSI uninstall entry found in registry. Falling back to manual removal." + Write-Host "No matching uninstall entries in registry." +} + +# Collect InstallLocation values so we can both run exe --uninstall-service +# and clean the folders after MSI removal. +$installLocations = @() +foreach ($e in $entries) { + if ($e.InstallLocation -and (Test-Path $e.InstallLocation)) { + $installLocations += $e.InstallLocation.TrimEnd('\') + } +} +$installLocations += $fallbackInstallDirs | Where-Object { Test-Path $_ } +$installLocations = $installLocations | Select-Object -Unique + +# ── Graceful service unregister via every exe we can find ──────────────────── +foreach ($dir in $installLocations) { + Get-ChildItem $dir -Filter '*.exe' -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "Unregistering service via: $($_.FullName)" + Start-Process -FilePath $_.FullName -ArgumentList '--uninstall-service' -Wait -ErrorAction SilentlyContinue + } +} +if ($installLocations) { Start-Sleep -Seconds 5 } + +# ── Uninstall every matching MSI / entry ───────────────────────────────────── +foreach ($e in $entries) { + $name = $e.DisplayName + $code = $e.PSChildName + if ($code -match '^\{') { + Write-Host "Running MSI uninstall for $name [$code]..." + Start-Process -FilePath "msiexec.exe" ` + -ArgumentList "/x `"$code`" /qn /norestart" ` + -Wait + Start-Sleep -Seconds 10 + } elseif ($e.UninstallString) { + Write-Host "Running UninstallString for $name..." + $cmd = $e.UninstallString -replace '"', '' + $exe = ($cmd -split ' ')[0] + $rest = ($cmd -split ' ', 2)[1] + $args = "$rest /S /qn /norestart".Trim() + Start-Process -FilePath $exe -ArgumentList $args -Wait -ErrorAction SilentlyContinue + Start-Sleep -Seconds 10 + } else { + Write-Warning "No ProductCode or UninstallString for $name - skipping MSI step." + } } # ── Kill any remaining processes ───────────────────────────────────────────── -Write-Host "Killing any remaining RustDesk processes..." -Get-Process | Where-Object { $_.Path -match 'ScoutIT-RemoteSupport|rustdesk|scoutit' } | +Write-Host "Killing any remaining processes..." +Get-Process | Where-Object { $_.Path -match $processPattern } | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 -# ── Remove install directory ────────────────────────────────────────────────── -if (Test-Path $rustdeskInstallDir) { - Write-Host "Removing install directory: $rustdeskInstallDir" - Remove-Item -Path $rustdeskInstallDir -Recurse -Force -ErrorAction SilentlyContinue - if (Test-Path $rustdeskInstallDir) { - Write-Warning "Directory still exists - may have locked files. You may need to reboot and re-run." - } else { - Write-Host "Install directory removed." +# ── Remove install directories (registry-reported + fallbacks) ─────────────── +foreach ($dir in $installLocations) { + if (Test-Path $dir) { + Write-Host "Removing install directory: $dir" + Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue + if (Test-Path $dir) { + Write-Warning "Directory still exists: $dir (locked files - reboot may be required)" + } } -} else { - Write-Host "Install directory not found - already removed." } # ── Remove leftover AppData / config ───────────────────────────────────────── $appDataPaths = @( "$env:APPDATA\ScoutIT-RemoteSupport", + "$env:APPDATA\ScoutIT Remote Support", "$env:APPDATA\RustDesk", "$env:ProgramData\ScoutIT-RemoteSupport", + "$env:ProgramData\ScoutIT Remote Support", "$env:ProgramData\RustDesk" ) foreach ($path in $appDataPaths) { @@ -94,15 +133,21 @@ foreach ($path in $appDataPaths) { } # ── Confirm ─────────────────────────────────────────────────────────────────── -$svcFinal = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue -$dirFinal = Test-Path $rustdeskInstallDir +$svcRemaining = Get-Service -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match $servicePattern -or $_.DisplayName -match $servicePattern } +$regRemaining = foreach ($key in $uninstallKeys) { + Get-ItemProperty $key -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -match $namePattern -or $_.Publisher -match $namePattern } +} +$dirRemaining = $installLocations | Where-Object { Test-Path $_ } Write-Output "................................................." -if ($null -eq $svcFinal -and -not $dirFinal) { - Write-Output "ScoutIT Remote Support successfully uninstalled." +if (-not $svcRemaining -and -not $regRemaining -and -not $dirRemaining) { + Write-Output "ScoutIT Remote Support / RustDesk fully uninstalled (all versions)." } else { - if ($null -ne $svcFinal) { Write-Warning "Service '$rustdeskService' still present." } - if ($dirFinal) { Write-Warning "Install directory still present - reboot may be required." } + if ($svcRemaining) { Write-Warning "Services still present: $(($svcRemaining | Select-Object -Expand Name) -join ', ')" } + if ($regRemaining) { Write-Warning "Registry entries still present: $(($regRemaining | Select-Object -Expand DisplayName) -join ', ')" } + if ($dirRemaining) { Write-Warning "Directories still present: $($dirRemaining -join ', ') - reboot may be required." } } Write-Output "................................................."