Files
8x8-Cleanup-Script/8x8-Cleanup-script.ps1

367 lines
14 KiB
PowerShell

<#
.SYNOPSIS
Detects 8x8 Work installations with version <= 8.36.2.3, silently uninstalls them,
and cleans up leftover processes, files, temp directories, and registry entries.
.DESCRIPTION
1. Scans Machine (64-bit & 32-bit) and User (HKCU + loaded user hives) Uninstall registry keys for "8x8 Work".
2. Checks if the installed version is less than or equal to 8.36.2.3.
3. Stops running 8x8 Work processes.
4. Runs silent uninstallation (MSI /x /qn or EXE silent flags).
5. Cleans up leftover folders (Program Files, ProgramData, AppData Local/Roaming, Temp) and registry entries.
.NOTES
Run as Administrator for system-wide cleanup, or in user context if only removing per-user installs.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[version]$MaxTargetVersion = [version]"8.36.2.3",
[string]$AppNamePattern = "*8x8 Work*"
)
$ErrorActionPreference = "Stop"
function Write-Log {
param (
[string]$Message,
[ValidateSet("INFO", "WARN", "ERROR")][string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Output "[$timestamp] [$Level] $Message"
}
function ConvertTo-SafeVersion {
param ([string]$VersionString)
if ([string]::IsNullOrWhiteSpace($VersionString)) { return $null }
# Extract numeric dot-separated version component (e.g. "8.36.2.3-beta" -> "8.36.2.3")
if ($VersionString -match '(\d+(\.\d+){1,3})') {
try {
return [version]$Matches[1]
} catch {
return $null
}
}
return $null
}
Write-Log "Starting check for 8x8 Work (Target version <= $MaxTargetVersion)..."
# 1. Gather Uninstall Registry Paths (System + Current User + Loaded User Hives)
$UninstallPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
# Also check other loaded user hives in HKU when running as SYSTEM/Admin
Get-ChildItem -Path "Registry::HKEY_USERS" -ErrorAction SilentlyContinue | Where-Object {
$_.PSChildName -match '^S-1-5-21-[\d\-]+$'
} | ForEach-Object {
$UninstallPaths += "Registry::HKEY_USERS\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
}
$FoundInstallations = @()
foreach ($regPath in $UninstallPaths) {
if (Test-Path $regPath) {
$items = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue | Where-Object {
$_.DisplayName -like $AppNamePattern
}
if ($items) {
$FoundInstallations += $items
}
}
}
# Also check per-user Squirrel/Electron executable path in case registry entry is missing/corrupted
$UserProfileList = Get-ChildItem -Path "$env:SystemDrive\Users" -Directory -ErrorAction SilentlyContinue
foreach ($userDir in $UserProfileList) {
$possibleExePaths = @(
Join-Path $userDir.FullName "AppData\Local\8x8-work\8x8 Work.exe"
Join-Path $userDir.FullName "AppData\Local\Programs\8x8-work\8x8 Work.exe"
Join-Path $userDir.FullName "AppData\Local\8x8 Work\8x8 Work.exe"
)
foreach ($exePath in $possibleExePaths) {
if (Test-Path $exePath) {
$fileVer = (Get-Item $exePath).VersionInfo.ProductVersion
if (-not ($FoundInstallations | Where-Object { $_.InstallLocation -and ($exePath -like "$($_.InstallLocation)*") })) {
$FoundInstallations += [PSCustomObject]@{
DisplayName = "8x8 Work (File Detection)"
DisplayVersion = $fileVer
UninstallString = $null
QuietUninstallString = $null
InstallLocation = Split-Path $exePath -Parent
PSPath = $null
}
}
}
}
}
if ($FoundInstallations.Count -eq 0) {
Write-Log "8x8 Work was not found on this device. Exiting."
exit 0
}
# Filter installations matching version <= 8.36.2.3
$TargetInstalls = @()
foreach ($install in $FoundInstallations) {
$parsedVer = ConvertTo-SafeVersion -VersionString $install.DisplayVersion
Write-Log "Found '$($install.DisplayName)' with version '$($install.DisplayVersion)' (Parsed: $parsedVer)"
if ($null -ne $parsedVer -and $parsedVer -le $MaxTargetVersion) {
$TargetInstalls += $install
} elseif ($null -eq $parsedVer) {
Write-Log "Could not parse version '$($install.DisplayVersion)' for $($install.DisplayName). Skipping version match." -Level "WARN"
} else {
Write-Log "Installed version ($parsedVer) is newer than $MaxTargetVersion. Skipping uninstall."
}
}
if ($TargetInstalls.Count -eq 0) {
Write-Log "No 8x8 Work installation <= $MaxTargetVersion found. Nothing to uninstall."
exit 0
}
# 2. Stop running 8x8 Work processes before uninstalling
Write-Log "Stopping any running 8x8 Work processes..."
$ProcessNames = @("8x8 Work*", "8x8-work*", "VirtualOffice*")
foreach ($procPattern in $ProcessNames) {
Get-Process -Name $procPattern -ErrorAction SilentlyContinue | ForEach-Object {
try {
Write-Log "Stopping process: $($_.ProcessName) (PID: $($_.Id))"
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
} catch {
Write-Log "Failed to stop process $($_.ProcessName): $_" -Level "WARN"
}
}
}
Start-Sleep -Seconds 2
# 3. Uninstall matching versions silently
foreach ($app in $TargetInstalls) {
Write-Log "Uninstalling $($app.DisplayName) (Version: $($app.DisplayVersion))..."
$uninstallCmd = if ($app.QuietUninstallString) { $app.QuietUninstallString } else { $app.UninstallString }
if ($app.PSChildName -match '^\{[0-9A-Fa-f\-]{36}\}$' -or $uninstallCmd -match 'msiexec') {
# MSI-based Uninstall
$guid = if ($app.PSChildName -match '^\{[0-9A-Fa-f\-]{36}\}$') {
$app.PSChildName
} elseif ($uninstallCmd -match '(\{[0-9A-Fa-f\-]{36}\})') {
$Matches[1]
}
if ($guid) {
$msiArgs = "/x `"$guid`" /qn /norestart REBOOT=ReallySuppress"
Write-Log "Executing MSI uninstall: msiexec.exe $msiArgs"
$proc = Start-Process -FilePath "msiexec.exe" -ArgumentList $msiArgs -Wait -PassThru -NoNewWindow
Write-Log "MSI uninstall finished with exit code: $($proc.ExitCode)"
}
}
elseif (-not [string]::IsNullOrWhiteSpace($uninstallCmd)) {
# EXE-based Uninstall (Squirrel / NSIS / InstallShield / Burn)
if ($uninstallCmd -match '^"([^"]+)"\s*(.*)$') {
$exePath = $Matches[1]
$existingArgs = $Matches[2]
} else {
$parts = $uninstallCmd -split '\s+', 2
$exePath = $parts[0]
$existingArgs = if ($parts.Count -gt 1) { $parts[1] } else { "" }
}
if (Test-Path $exePath) {
# Add appropriate silent flags if not already present
$silentArgs = $existingArgs
if ($exePath -like "*Update.exe*") {
if ($silentArgs -notmatch '--uninstall') { $silentArgs += " --uninstall" }
if ($silentArgs -notmatch '-s') { $silentArgs += " -s" }
} else {
if ($silentArgs -notmatch '(/S|/silent|/quiet|/qn|--silent)') {
$silentArgs = "$silentArgs /S /quiet /norestart".Trim()
}
}
Write-Log "Executing EXE uninstall: `"$exePath`" $silentArgs"
$proc = Start-Process -FilePath $exePath -ArgumentList $silentArgs -Wait -PassThru -NoNewWindow
Write-Log "EXE uninstall finished with exit code: $($proc.ExitCode)"
} else {
Write-Log "Uninstaller executable not found at: $exePath" -Level "WARN"
}
}
# Remove the Uninstall Registry Key if it still exists
if ($app.PSPath -and (Test-Path $app.PSPath)) {
Write-Log "Removing leftover Uninstall registry entry: $($app.PSPath)"
Remove-Item -Path $app.PSPath -Recurse -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
# 4. Clean up File System Leftovers (Program Files, ProgramData, User AppData, Temp, Shortcuts)
Write-Log "Cleaning up leftover directories, caches, shortcuts, and temporary files..."
$SystemFoldersToClean = @(
"$env:ProgramFiles\8x8\8x8 Work",
"$env:ProgramFiles\8x8 Work",
"${env:ProgramFiles(x86)}\8x8\8x8 Work",
"${env:ProgramFiles(x86)}\8x8 Work",
"$env:ProgramData\8x8\8x8 Work",
"$env:ProgramData\8x8 Work",
"$env:ProgramData\8x8-work",
"$env:ProgramData\Microsoft\Windows\Start Menu\Programs\8x8 Work*",
"$env:PUBLIC\Desktop\8x8 Work*.lnk"
)
foreach ($path in $SystemFoldersToClean) {
Get-Item -Path $path -Force -ErrorAction SilentlyContinue | ForEach-Object {
try {
Write-Log "Removing system path: $($_.FullName)"
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
} catch {
Write-Log "Could not remove $($_.FullName): $_" -Level "WARN"
}
}
}
# Clean Windows Temp
$WinTempPatterns = @(
"$env:SystemRoot\Temp\*8x8*",
"$env:TEMP\*8x8*"
)
foreach ($pattern in $WinTempPatterns) {
Get-Item -Path $pattern -Force -ErrorAction SilentlyContinue | ForEach-Object {
Write-Log "Removing temp item: $($_.FullName)"
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
}
# Clean Per-User Leftovers across all user profiles on the machine
foreach ($userDir in $UserProfileList) {
$uPath = $userDir.FullName
$UserItemsToClean = @(
"$uPath\AppData\Local\8x8\8x8 Work",
"$uPath\AppData\Local\8x8 Work",
"$uPath\AppData\Local\8x8-work",
"$uPath\AppData\Local\Programs\8x8-work",
"$uPath\AppData\Local\Programs\8x8 Work",
"$uPath\AppData\Local\SquirrelTemp",
"$uPath\AppData\Roaming\8x8\8x8 Work",
"$uPath\AppData\Roaming\8x8 Work",
"$uPath\AppData\Roaming\8x8-work",
"$uPath\AppData\Local\Temp\*8x8*",
"$uPath\Desktop\8x8 Work*.lnk",
"$uPath\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\8x8 Work*",
"$uPath\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\8x8 Inc*"
)
foreach ($itemPattern in $UserItemsToClean) {
Get-Item -Path $itemPattern -Force -ErrorAction SilentlyContinue | ForEach-Object {
# Only remove SquirrelTemp if it references 8x8 or is empty
if ($_.Name -eq "SquirrelTemp") {
$squirrelLog = Join-Path $_.FullName "SquirrelSetup.log"
if ((Test-Path $squirrelLog) -and (Select-String -Path $squirrelLog -Pattern "8x8" -Quiet -ErrorAction SilentlyContinue)) {
Write-Log "Removing 8x8 SquirrelTemp folder: $($_.FullName)"
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
} else {
Write-Log "Removing user path: $($_.FullName)"
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
}
# Remove empty parent "8x8" directories if no other 8x8 software is inside them
$Parent8x8Dirs = @(
"$env:ProgramFiles\8x8",
"${env:ProgramFiles(x86)}\8x8",
"$env:ProgramData\8x8"
) + ($UserProfileList | ForEach-Object {
"$($_.FullName)\AppData\Local\8x8"
"$($_.FullName)\AppData\Roaming\8x8"
})
foreach ($parentDir in $Parent8x8Dirs) {
if (Test-Path $parentDir) {
$remaining = Get-ChildItem -Path $parentDir -Force -ErrorAction SilentlyContinue
if ($null -eq $remaining -or $remaining.Count -eq 0) {
Write-Log "Removing empty parent folder: $parentDir"
Remove-Item -Path $parentDir -Force -ErrorAction SilentlyContinue
}
}
}
# 5. Clean up Leftover Registry Keys & Startup Entries
Write-Log "Cleaning up leftover registry keys and startup entries..."
$RegistrySoftwarePaths = @(
"HKLM:\SOFTWARE\8x8\8x8 Work",
"HKLM:\SOFTWARE\8x8 Work",
"HKLM:\SOFTWARE\WOW6432Node\8x8\8x8 Work",
"HKLM:\SOFTWARE\WOW6432Node\8x8 Work",
"HKCU:\SOFTWARE\8x8\8x8 Work",
"HKCU:\SOFTWARE\8x8 Work"
)
# Include loaded user hives
Get-ChildItem -Path "Registry::HKEY_USERS" -ErrorAction SilentlyContinue | Where-Object {
$_.PSChildName -match '^S-1-5-21-[\d\-]+$'
} | ForEach-Object {
$sid = $_.PSChildName
$RegistrySoftwarePaths += @(
"Registry::HKEY_USERS\$sid\Software\8x8\8x8 Work",
"Registry::HKEY_USERS\$sid\Software\8x8 Work"
)
}
foreach ($regKey in $RegistrySoftwarePaths) {
if (Test-Path $regKey) {
Write-Log "Removing registry key: $regKey"
Remove-Item -Path $regKey -Recurse -Force -ErrorAction SilentlyContinue
}
}
# Clean Windows Installer Product/Upgrade Codes in HKCR\Installer\Products if orphaned
$InstallerProductsPath = "Registry::HKEY_CLASSES_ROOT\Installer\Products"
if (Test-Path $InstallerProductsPath) {
Get-ChildItem -Path $InstallerProductsPath -ErrorAction SilentlyContinue | ForEach-Object {
$prodName = (Get-ItemProperty -Path $_.PSPath -Name "ProductName" -ErrorAction SilentlyContinue).ProductName
if ($prodName -like $AppNamePattern) {
Write-Log "Removing orphaned Windows Installer product registration: $($_.PSPath) ($prodName)"
Remove-Item -Path $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue
}
}
}
# Clean Run / Startup registry values referencing 8x8 Work
$RunKeys = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
)
Get-ChildItem -Path "Registry::HKEY_USERS" -ErrorAction SilentlyContinue | Where-Object {
$_.PSChildName -match '^S-1-5-21-[\d\-]+$'
} | ForEach-Object {
$RunKeys += "Registry::HKEY_USERS\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Run"
}
foreach ($runKey in $RunKeys) {
if (Test-Path $runKey) {
$props = Get-ItemProperty -Path $runKey -ErrorAction SilentlyContinue
if ($props) {
$props.PSObject.Properties | Where-Object {
$_.Name -notIn @("PSPath", "PSParentPath", "PSChildName", "PSDrive", "PSProvider") -and
($_.Name -like "*8x8*" -or $_.Value -like "*8x8*")
} | ForEach-Object {
Write-Log "Removing startup Run value '$($_.Name)' from $runKey"
Remove-ItemProperty -Path $runKey -Name $_.Name -Force -ErrorAction SilentlyContinue
}
}
}
}
Write-Log "8x8 Work uninstallation and cleanup completed successfully."
exit 0