Uninstall all RustDesk/ScoutIT versions, not just the first match
Previous script used Select-Object -First 1 + break, so side-by-side installs (hyphen vs space branding, old + new) left entries behind. Now iterates every matching registry entry, collects every InstallLocation, and stops every matching service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b89e9c0ff2
commit
fca02b2435
+101
-56
@@ -1,89 +1,128 @@
|
|||||||
# ScoutIT Remote Support (RustDesk) - Uninstall Script
|
# 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'
|
$ErrorActionPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
# ── Config ────────────────────────────────────────────────────────────────────
|
# ── Config ────────────────────────────────────────────────────────────────────
|
||||||
$rustdeskInstallDir = 'C:\Program Files\ScoutIT-RemoteSupport'
|
# Pattern is case-insensitive via -match. [-\s]? tolerates hyphen OR space OR
|
||||||
$rustdeskService = 'ScoutIT-RemoteSupport'
|
# nothing between "ScoutIT", "Remote", and "Support" — covers every branding
|
||||||
$rustdeskMsiName = 'estudio-scoutit-remotesupport' # used to find uninstall key in registry
|
# 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
|
[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 ──────────────────────────────────────────
|
# ── Stop every matching service ──────────────────────────────────────────────
|
||||||
$svc = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue
|
$services = Get-Service -ErrorAction SilentlyContinue |
|
||||||
if ($null -ne $svc) {
|
Where-Object { $_.Name -match $servicePattern -or $_.DisplayName -match $servicePattern }
|
||||||
Write-Host "Stopping ScoutIT Remote Support service..."
|
if ($services) {
|
||||||
Stop-Service -Name $rustdeskService -Force -ErrorAction SilentlyContinue
|
foreach ($s in $services) {
|
||||||
Start-Sleep -Seconds 3
|
Write-Host "Stopping service: $($s.Name) ($($s.DisplayName))..."
|
||||||
|
Stop-Service -Name $s.Name -Force -ErrorAction SilentlyContinue
|
||||||
# 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
|
|
||||||
}
|
}
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
} else {
|
} else {
|
||||||
Write-Host "Service '$rustdeskService' not found - may already be removed."
|
Write-Host "No matching services found."
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── MSI uninstall via registry ProductCode ───────────────────────────────────
|
# ── Enumerate every matching registry uninstall entry ────────────────────────
|
||||||
Write-Host "Looking for MSI uninstall entry in registry..."
|
Write-Host "Scanning registry for all matching uninstall entries..."
|
||||||
$uninstallKeys = @(
|
$uninstallKeys = @(
|
||||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
||||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||||
)
|
)
|
||||||
|
|
||||||
$productCode = $null
|
$entries = foreach ($key in $uninstallKeys) {
|
||||||
foreach ($key in $uninstallKeys) {
|
Get-ItemProperty $key -ErrorAction SilentlyContinue |
|
||||||
$match = Get-ItemProperty $key -ErrorAction SilentlyContinue |
|
Where-Object { $_.DisplayName -match $namePattern -or $_.Publisher -match $namePattern }
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($productCode) {
|
if ($entries) {
|
||||||
Write-Host "Running MSI uninstall..."
|
Write-Host "Found $($entries.Count) matching entry/entries:"
|
||||||
|
$entries | ForEach-Object { Write-Host " - $($_.DisplayName) [$($_.PSChildName)]" }
|
||||||
|
} else {
|
||||||
|
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" `
|
Start-Process -FilePath "msiexec.exe" `
|
||||||
-ArgumentList "/x `"$productCode`" /qn /norestart" `
|
-ArgumentList "/x `"$code`" /qn /norestart" `
|
||||||
-Wait
|
-Wait
|
||||||
Start-Sleep -Seconds 10
|
Start-Sleep -Seconds 10
|
||||||
Write-Host "MSI uninstall complete."
|
} elseif ($e.UninstallString) {
|
||||||
} else {
|
Write-Host "Running UninstallString for $name..."
|
||||||
Write-Warning "No MSI uninstall entry found in registry. Falling back to manual removal."
|
$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 ─────────────────────────────────────────────
|
# ── Kill any remaining processes ─────────────────────────────────────────────
|
||||||
Write-Host "Killing any remaining RustDesk processes..."
|
Write-Host "Killing any remaining processes..."
|
||||||
Get-Process | Where-Object { $_.Path -match 'ScoutIT-RemoteSupport|rustdesk|scoutit' } |
|
Get-Process | Where-Object { $_.Path -match $processPattern } |
|
||||||
Stop-Process -Force -ErrorAction SilentlyContinue
|
Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
Start-Sleep -Seconds 2
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
# ── Remove install directory ──────────────────────────────────────────────────
|
# ── Remove install directories (registry-reported + fallbacks) ───────────────
|
||||||
if (Test-Path $rustdeskInstallDir) {
|
foreach ($dir in $installLocations) {
|
||||||
Write-Host "Removing install directory: $rustdeskInstallDir"
|
if (Test-Path $dir) {
|
||||||
Remove-Item -Path $rustdeskInstallDir -Recurse -Force -ErrorAction SilentlyContinue
|
Write-Host "Removing install directory: $dir"
|
||||||
if (Test-Path $rustdeskInstallDir) {
|
Remove-Item -Path $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
Write-Warning "Directory still exists - may have locked files. You may need to reboot and re-run."
|
if (Test-Path $dir) {
|
||||||
} else {
|
Write-Warning "Directory still exists: $dir (locked files - reboot may be required)"
|
||||||
Write-Host "Install directory removed."
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Write-Host "Install directory not found - already removed."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Remove leftover AppData / config ─────────────────────────────────────────
|
# ── Remove leftover AppData / config ─────────────────────────────────────────
|
||||||
$appDataPaths = @(
|
$appDataPaths = @(
|
||||||
"$env:APPDATA\ScoutIT-RemoteSupport",
|
"$env:APPDATA\ScoutIT-RemoteSupport",
|
||||||
|
"$env:APPDATA\ScoutIT Remote Support",
|
||||||
"$env:APPDATA\RustDesk",
|
"$env:APPDATA\RustDesk",
|
||||||
"$env:ProgramData\ScoutIT-RemoteSupport",
|
"$env:ProgramData\ScoutIT-RemoteSupport",
|
||||||
|
"$env:ProgramData\ScoutIT Remote Support",
|
||||||
"$env:ProgramData\RustDesk"
|
"$env:ProgramData\RustDesk"
|
||||||
)
|
)
|
||||||
foreach ($path in $appDataPaths) {
|
foreach ($path in $appDataPaths) {
|
||||||
@@ -94,15 +133,21 @@ foreach ($path in $appDataPaths) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── Confirm ───────────────────────────────────────────────────────────────────
|
# ── Confirm ───────────────────────────────────────────────────────────────────
|
||||||
$svcFinal = Get-Service -Name $rustdeskService -ErrorAction SilentlyContinue
|
$svcRemaining = Get-Service -ErrorAction SilentlyContinue |
|
||||||
$dirFinal = Test-Path $rustdeskInstallDir
|
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 "................................................."
|
Write-Output "................................................."
|
||||||
if ($null -eq $svcFinal -and -not $dirFinal) {
|
if (-not $svcRemaining -and -not $regRemaining -and -not $dirRemaining) {
|
||||||
Write-Output "ScoutIT Remote Support successfully uninstalled."
|
Write-Output "ScoutIT Remote Support / RustDesk fully uninstalled (all versions)."
|
||||||
} else {
|
} else {
|
||||||
if ($null -ne $svcFinal) { Write-Warning "Service '$rustdeskService' still present." }
|
if ($svcRemaining) { Write-Warning "Services still present: $(($svcRemaining | Select-Object -Expand Name) -join ', ')" }
|
||||||
if ($dirFinal) { Write-Warning "Install directory still present - reboot may be required." }
|
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 "................................................."
|
Write-Output "................................................."
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user