Public/InstallUninstall/Uninstall-CWAA.ps1
|
function Uninstall-CWAA { <# .SYNOPSIS Completely uninstalls the ConnectWise Automate Agent from the local computer. .DESCRIPTION Performs a comprehensive removal of the ConnectWise Automate Agent from a Windows computer. This function is more thorough than a standard MSI uninstall, as it also removes residual files, registry keys, and services that may not be cleaned up by the normal uninstall process. The uninstall process performs the following operations: 1. Downloads official uninstaller files (Agent_Uninstall.msi and Agent_Uninstall.exe) from the server 2. Optionally creates a backup of the current agent installation (if -Backup is specified) 3. Stops all running agent services (LTService, LTSvcMon, LabVNC) 4. Terminates any running agent processes 5. Unregisters the wodVPN.dll component 6. Runs the MSI uninstaller (Agent_Uninstall.msi) 7. Runs the agent uninstaller executable (Agent_Uninstall.exe) 8. Removes agent Windows services 9. Removes all agent files from the installation directory 10. Removes all agent-related registry keys (over 30 different registry locations) 11. Verifies the uninstall was successful Probe Agent Protection: By default, this function will refuse to uninstall probe agents to prevent accidental removal of critical infrastructure. Use -Force to override this protection. .PARAMETER Server One or more ConnectWise Automate server URLs to download uninstaller files from. If not specified, reads the server URL from the agent's current registry configuration. If that fails, prompts interactively for a server URL. Example: https://automate.domain.com .PARAMETER Backup Creates a complete backup of the agent installation before uninstalling by calling New-CWAABackup. .PARAMETER Force Forces uninstallation even when a probe agent is detected. Use with extreme caution, as probe agents are typically critical infrastructure components. .EXAMPLE Uninstall-CWAA Uninstalls the agent using the server URL from the agent's registry settings. .EXAMPLE Uninstall-CWAA -Backup Creates a backup of the agent installation before uninstalling. .EXAMPLE Uninstall-CWAA -Server "https://automate.company.com" Uninstalls using the specified server URL to download uninstaller files. .EXAMPLE Uninstall-CWAA -Server "https://primary.company.com","https://backup.company.com" Provides multiple server URLs with fallback. Tries each until uninstaller files download successfully. .EXAMPLE Uninstall-CWAA -Force Forces uninstallation even if a probe agent is detected. .EXAMPLE Uninstall-CWAA -WhatIf Simulates the uninstall process without making any actual changes. .NOTES Author: Chris Taylor Alias: Uninstall-LTService Requires: Administrator privileges .LINK https://github.com/christaylorcodes/ConnectWiseAutomateAgent #> [CmdletBinding(SupportsShouldProcess = $True)] [Alias('Uninstall-LTService')] Param( [Parameter(ValueFromPipelineByPropertyName = $true)] [AllowNull()] [string[]]$Server, [Parameter(ValueFromPipelineByPropertyName = $true)] [switch]$Backup, [switch]$Force, [switch]$SkipCertificateCheck ) Begin { Write-Debug "Starting $($myInvocation.InvocationName)" # Lazy initialization of SSL/TLS, WebClient, and proxy configuration. # Only runs once per session, skips immediately on subsequent calls. $Null = Initialize-CWAANetworking -SkipCertificateCheck:$SkipCertificateCheck if (-not ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent() | Select-Object -Expand groups -EA 0) -match 'S-1-5-32-544'))) { Throw "Needs to be ran as Administrator" } $serviceInfo = Get-CWAAInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False if ($serviceInfo -and ($serviceInfo | Select-Object -Expand Probe -EA 0) -eq '1') { if ($Force -eq $True) { Write-Output 'Probe Agent Detected. UnInstall Forced.' } else { Write-Error -Exception [System.OperationCanceledException]"Probe Agent Detected. UnInstall Denied." -ErrorAction Stop } } if ($Backup) { if ($PSCmdlet.ShouldProcess('LTService', 'Backup Current Service Settings')) { New-CWAABackup } } $BasePath = Get-CWAAInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False | Select-Object -Expand BasePath -EA 0 if (-not $BasePath) { $BasePath = $Script:CWAAInstallPath } New-PSDrive HKU Registry HKEY_USERS -ErrorAction SilentlyContinue -WhatIf:$False -Confirm:$False -Debug:$False | Out-Null $regs = @( 'Registry::HKEY_LOCAL_MACHINE\Software\LabTechMSP', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\LabTech\Service', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\LabTech\LabVNC', 'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\LabTech\Service', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Managed\\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\D1003A85576B76D45A1AF09A0FC87FAC\InstallProperties', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3426921d-9ad5-4237-9145-f15dee7e3004}', 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Appmgmt\{40bf8c82-ed0d-4f66-b73e-58a3d7ab6582}', 'Registry::HKEY_CLASSES_ROOT\Installer\Dependencies\{3426921d-9ad5-4237-9145-f15dee7e3004}', 'Registry::HKEY_CLASSES_ROOT\Installer\Dependencies\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}', 'Registry::HKEY_CLASSES_ROOT\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F', 'Registry::HKEY_CLASSES_ROOT\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC', 'Registry::HKEY_CLASSES_ROOT\CLSID\{09DF1DCA-C076-498A-8370-AD6F878B6C6A}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{15DD3BF6-5A11-4407-8399-A19AC10C65D0}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{3C198C98-0E27-40E4-972C-FDC656EC30D7}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{459C65ED-AA9C-4CF1-9A24-7685505F919A}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{7BE3886B-0C12-4D87-AC0B-09A5CE4E6BD6}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{7E092B5C-795B-46BC-886A-DFFBBBC9A117}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{9D101D9C-18CC-4E78-8D78-389E48478FCA}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{B0B8CDD6-8AAA-4426-82E9-9455140124A1}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{B1B00A43-7A54-4A0F-B35D-B4334811FAA4}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{BBC521C8-2792-43FE-9C91-CCA7E8ACBCC9}', 'Registry::HKEY_CLASSES_ROOT\CLSID\{C59A1D54-8CD7-4795-AEDD-F6F6E2DE1FE7}', 'Registry::HKEY_CLASSES_ROOT\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F', 'Registry::HKEY_CLASSES_ROOT\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC', 'Registry::HKEY_CURRENT_USER\SOFTWARE\LabTech\Service', 'Registry::HKEY_CURRENT_USER\SOFTWARE\LabTech\LabVNC', 'Registry::HKEY_CURRENT_USER\Software\Microsoft\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F', 'HKU:\*\Software\Microsoft\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F' ) if ($WhatIfPreference -ne $True) { Remove-Item 'Uninstall.exe', 'Uninstall.exe.config' -ErrorAction SilentlyContinue -Force -Confirm:$False New-Item "$Script:CWAAInstallerTempPath\Installer" -type directory -ErrorAction SilentlyContinue | Out-Null } $uninstallArguments = "/x ""$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi"" /qn" } Process { if (-not $Server) { $Server = Get-CWAAInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False | Select-Object -Expand 'Server' -EA 0 } if (-not $Server) { $Server = Read-Host -Prompt 'Provide the URL to your Automate server (https://automate.domain.com):' } # Resolve the first reachable server and its advertised version $serverResult = Resolve-CWAAServer -Server $Server if (-not $serverResult) { return } $serverUrl = $serverResult.ServerUrl Try { # Download the uninstall MSI (same URL for all server versions) $installer = "$serverUrl/LabTech/Service/LabTechRemoteAgent.msi" $installerTest = [System.Net.WebRequest]::Create($installer) if (($Script:LTProxy.Enabled) -eq $True) { Write-Debug "Proxy Configuration Needed. Applying Proxy Settings to request." $installerTest.Proxy = $Script:LTWebProxy } $installerTest.KeepAlive = $False $installerTest.ProtocolVersion = '1.0' $installerResult = $installerTest.GetResponse() $installerTest.Abort() if ($installerResult.StatusCode -ne 200) { Write-Warning "Unable to download Agent_Uninstall.msi from server $serverUrl." return } if ($PSCmdlet.ShouldProcess("$installer", 'DownloadFile')) { Write-Debug "Downloading Agent_Uninstall.msi from $installer" $Script:LTServiceNetWebClient.DownloadFile($installer, "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi") if (-not (Test-CWAADownloadIntegrity -FilePath "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi" -FileName 'Agent_Uninstall.msi')) { return } $AlternateServer = $serverUrl } # Download the uninstall EXE (same URI for all versions) $uninstaller = "$serverUrl/LabTech/Service/LabUninstall.exe" $uninstallerTest = [System.Net.WebRequest]::Create($uninstaller) if (($Script:LTProxy.Enabled) -eq $True) { Write-Debug "Proxy Configuration Needed. Applying Proxy Settings to request." $uninstallerTest.Proxy = $Script:LTWebProxy } $uninstallerTest.KeepAlive = $False $uninstallerTest.ProtocolVersion = '1.0' $uninstallerResult = $uninstallerTest.GetResponse() $uninstallerTest.Abort() if ($uninstallerResult.StatusCode -ne 200) { Write-Warning "Unable to download Agent_Uninstall from server." return } if ($PSCmdlet.ShouldProcess("$uninstaller", 'DownloadFile')) { Write-Debug "Downloading Agent_Uninstall.exe from $uninstaller" $Script:LTServiceNetWebClient.DownloadFile($uninstaller, "${env:windir}\temp\Agent_Uninstall.exe") # Uninstall EXE is smaller than MSI — use 80 KB threshold if (-not (Test-CWAADownloadIntegrity -FilePath "${env:windir}\temp\Agent_Uninstall.exe" -FileName 'Agent_Uninstall.exe' -MinimumSizeKB 80)) { return } } if ($WhatIfPreference -eq $True) { $GoodServer = $serverUrl } Elseif ((Test-Path "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi") -and (Test-Path "${env:windir}\temp\Agent_Uninstall.exe")) { $GoodServer = $serverUrl Write-Verbose "Successfully downloaded files from $serverUrl." } else { Write-Warning "Error encountered downloading from $serverUrl. Uninstall file(s) could not be received." } } Catch { Write-Warning "Error encountered downloading from $serverUrl." } } End { if ($GoodServer -match 'https?://.+' -or $AlternateServer -match 'https?://.+') { Try { Write-Output 'Starting Uninstall.' Try { Stop-CWAA -ErrorAction SilentlyContinue } Catch { Write-Debug "Stop-CWAA encountered an error: $_" } # Kill all running processes from %ltsvcdir% if (Test-Path $BasePath) { $Executables = (Get-ChildItem $BasePath -Filter *.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -Expand FullName) if ($Executables) { Write-Verbose "Terminating Automate agent processes from $BasePath if found running: $(($Executables) -replace [Regex]::Escape($BasePath),'' -replace '^\\','')" Get-Process | Where-Object { $Executables -contains $_.Path } | ForEach-Object { Write-Debug "Terminating Process $($_.ProcessName)" $_ | Stop-Process -Force -ErrorAction SilentlyContinue } Get-ChildItem $BasePath -Filter labvnc.exe -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction 0 } if ($PSCmdlet.ShouldProcess("$BasePath\wodVPN.dll", 'Unregister DLL')) { Write-Debug "Executing Command ""regsvr32.exe /u $BasePath\wodVPN.dll /s""" Try { & "$env:windir\system32\regsvr32.exe" /u "$BasePath\wodVPN.dll" /s 2>'' } Catch { Write-Output 'Error calling regsvr32.exe.' } } } if ($PSCmdlet.ShouldProcess("msiexec.exe $uninstallArguments", 'Execute MSI Uninstall')) { if (Test-Path "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi") { Write-Verbose 'Launching MSI Uninstall.' Write-Debug "Executing Command ""msiexec.exe $uninstallArguments""" Start-Process -Wait -FilePath "$env:windir\system32\msiexec.exe" -ArgumentList $uninstallArguments -WorkingDirectory $env:TEMP Start-Sleep -Seconds 5 } else { Write-Verbose "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi was not found." } } if ($PSCmdlet.ShouldProcess("${env:windir}\temp\Agent_Uninstall.exe", 'Execute Agent Uninstall')) { if (Test-Path "${env:windir}\temp\Agent_Uninstall.exe") { # Remove previously extracted SFX files to prevent UnRAR overwrite prompts Remove-Item "$env:TEMP\Uninstall.exe", "$env:TEMP\Uninstall.exe.config" -ErrorAction SilentlyContinue -Force -Confirm:$False Write-Verbose 'Launching Agent Uninstaller' Write-Debug "Executing Command ""${env:windir}\temp\Agent_Uninstall.exe""" Start-Process -Wait -FilePath "${env:windir}\temp\Agent_Uninstall.exe" -WorkingDirectory $env:TEMP Start-Sleep -Seconds 5 } else { Write-Verbose "${env:windir}\temp\Agent_Uninstall.exe was not found." } } Write-Verbose 'Removing Services if found.' @('LTService', 'LTSvcMon', 'LabVNC') | ForEach-Object { if (Get-Service $_ -EA 0) { if ($PSCmdlet.ShouldProcess($_, 'Remove Service')) { Write-Debug "Removing Service: $_" Try { & "$env:windir\system32\sc.exe" delete "$_" 2>'' if ($LASTEXITCODE -ne 0) { Write-Warning "sc.exe delete returned exit code $LASTEXITCODE for service '$_'." } } Catch { Write-Output 'Error calling sc.exe.' } } } } Write-Verbose 'Cleaning Files remaining if found.' # Depth-first removal to get as much removed as possible if complete removal fails @($BasePath, "${env:windir}\temp\_ltupdate") | ForEach-Object { if (Test-Path $_ -EA 0) { Remove-CWAAFolderRecursive -Path $_ } } Write-Verbose 'Removing agent installation msi file.' if ($PSCmdlet.ShouldProcess('Agent_Uninstall.msi', 'Remove File')) { $MsiPath = "$Script:CWAAInstallerTempPath\Installer\Agent_Uninstall.msi" $tries = 0 Try { Do { $MsiExists = Test-Path $MsiPath Start-Sleep -Seconds 10 Remove-Item $MsiPath -ErrorAction SilentlyContinue $tries++ } While ($MsiExists -and $tries -lt 4) } Catch { Write-Verbose "Unable to remove Agent_Uninstall.msi: $($_.Exception.Message)" } } Write-Verbose 'Cleaning Registry Keys if found.' # Depth First Value Removal, then Key Removal Foreach ($reg in $regs) { if (Test-Path $reg -EA 0) { Write-Debug "Found Registry Key: $reg" if ($PSCmdlet.ShouldProcess($reg, 'Remove Registry Key')) { Try { Get-ChildItem -Path $reg -Recurse -Force -ErrorAction SilentlyContinue | Sort-Object { $_.name.length } -Descending | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False Remove-Item -Recurse -Force -Path $reg -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False } Catch { Write-Debug "Error removing registry key '$reg': $($_.Exception.Message)" } } } } } Catch { Write-CWAAEventLog -EventId 1012 -EntryType Error -Message "Agent uninstall failed. Error: $($_.Exception.Message)" Write-Error "There was an error during the uninstall process. $($_.Exception.Message)" -ErrorAction Stop } if ($WhatIfPreference -ne $True) { # Post Uninstall Check If ((Test-Path $Script:CWAAInstallPath) -or (Test-Path "${env:windir}\temp\_ltupdate") -or (Test-Path registry::HKLM\Software\LabTech\Service) -or (Test-Path registry::HKLM\Software\WOW6432Node\Labtech\Service)) { Start-Sleep -Seconds 10 } If ((Test-Path $Script:CWAAInstallPath) -or (Test-Path "${env:windir}\temp\_ltupdate") -or (Test-Path registry::HKLM\Software\LabTech\Service) -or (Test-Path registry::HKLM\Software\WOW6432Node\Labtech\Service)) { Write-Error "Remnants of previous install still detected after uninstall attempt. Please reboot and try again." Write-CWAAEventLog -EventId 1011 -EntryType Warning -Message 'Remnants of previous install detected after uninstall. Reboot recommended.' } else { Write-Output 'Automate agent has been successfully uninstalled.' Write-CWAAEventLog -EventId 1010 -EntryType Information -Message 'Agent uninstalled successfully.' } } } Elseif ($WhatIfPreference -ne $True) { Write-Error "No valid server was reached to use for the uninstall." -ErrorAction Stop } Write-Debug "Exiting $($myInvocation.InvocationName)" } } |