How to Fully Uninstall and Clean Up VSAX Agent

If you need to uninstall the VSAX agent from your machine, you can use the cleanup tool provided below:

Purpose:
This PowerShell script (.ps1) fully removes the VSAX agent, including residual files and registry entries. 

Key Notes:

  • Cleans up the original VSAX agent files.

  • Removes remaining components and configurations.

  • Prepares the system for a clean reinstallation.

Usage:
Run the provided PowerShell script with administrative privileges to execute the cleanup process.

 

# Ensure the script is running with administrative privileges
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Start-Process -FilePath “powershell.exe” -ArgumentList “-File `”$($MyInvocation.MyCommand.Path)`”” -Verb RunAs
exit
}

# Stop and disable the VSAX service
try{
Stop-Service -Name “VSAX” -ErrorAction Stop
} catch {Write-Host “VSAX Service was previously removed”}

try{
Set-Service -Name “VSAX” -StartupType Disabled -ErrorAction Stop
} catch {}

# Kill all instances of pcmontask.exe and pcmonitorsrv.exe
try{
$pcmontaskPIDs = Get-Process -Name “pcmontask” -ErrorAction Stop | Select-Object -ExpandProperty Id
if ($pcmontaskPIDs) {Stop-Process -Id $pcmontaskPIDs -Force}
} catch {Write-Host “pcmontask was previously removed”}

try{
$pcmonitorsrvPIDs = Get-Process -Name “pcmonitorsrv” -ErrorAction Stop | Select-Object -ExpandProperty Id
if ($pcmonitorsrvPIDs) {Stop-Process -Id $pcmonitorsrvPIDs -Force}
} catch {Write-Host “pcmonitorsrv was previously removed”}


# Uninstall VSA X using uninstall string first, then Get-Package if it cant, and check if it installed for all users. complete removal
try {
# 1. Find the package name from the Uninstall registry keys (HKLM and HKCU)
$UninstallKeys = @(
Get-ChildItem “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall”, “HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall” | Get-ItemProperty
)

# Get all user profiles
$UserProfiles = Get-ChildItem -Path “Registry::HKEY_USERS” | Where-Object {$_.Name -notmatch “_Classes$”}

foreach ($UserProfile in $UserProfiles) {
try {
# Check if the Uninstall key exists before I try to access it
$UninstallPath32 = Join-Path $UserProfile.Name “Software\Microsoft\Windows\CurrentVersion\Uninstall”
# Wow6432Node is an annoying place that things sometimes get installed into. Gotta check it in case they installed as 32-bit for some reason
$UninstallPath64 = Join-Path $UserProfile.Name “Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall”

if (Test-Path -Path “Registry::$UninstallPath32”) { #Check 32 bit path
$UninstallKeys += Get-ChildItem -Path “Registry::$UninstallPath32” | Get-ItemProperty
} else {Write-Verbose “Uninstall key not found: $UninstallPath32” }
if (Test-Path -Path “Registry::$UninstallPath64”) { #Check 64 bit path
$UninstallKeys += Get-ChildItem -Path “Registry::$UninstallPath64” | Get-ItemProperty
} else {Write-Verbose “Uninstall key not found: $UninstallPath64” }

} catch {Write-Warning “Error accessing Uninstall keys for user $($UserProfile.Name): $_”}
}
$VSAXUninstallKey = $UninstallKeys | Where-Object {$_.DisplayName -eq “VSA X”} | Select-Object -First 1

if ($VSAXUninstallKey) {
Write-Host “Found VSA X in Uninstall registry”

# 1. Try Get-Package for msi even though it doesn’t always work, because Microsoft says so, and it’s fast when it works
try {
$packageName = $VSAXUninstallKey.PSChildName # Try using the key name as package name
$package = Get-Package -ProviderName Programs -Name $packageName -ErrorAction SilentlyContinue # Don’t stop if package not found

if ($package) {
Uninstall-Package -Name $package.Name -Confirm:$false -ErrorAction Stop
Write-Host “Uninstallation of $($package.Name) completed successfully.”
}
} catch {Write-Warning “Get-Package failed, trying UninstallString: $_”}
# 2. Fallback: Use UninstallString because of course I had to
try {
$UninstallString = $VSAXUninstallKey.UninstallString
if ($UninstallString) {
Write-Verbose “Using UninstallString: $UninstallString”

# Modify the UninstallString to include /qn (quiet, no UI) as a future proof in case this changes later on for reasons
$QuietUninstallString = if ($UninstallString -notmatch “/qn”) { # Check if /qn is already present
if ($UninstallString -match “msiexec\.exe”) { # Check if it’s an MSI uninstall because honestly you never know
“$UninstallString /qn” # Add /qn for MSI uninstalls
} else {$UninstallString} # Leave other uninstall strings as they are
} else {$UninstallString} # If /qn is already present, don’t add it again
try{
Start-Process -FilePath “cmd.exe” -ArgumentList “/c”, $QuietUninstallString -Wait # Execute using cmd to ensure functionality
Write-Host “VSA X succesfully uninstalled from add/remove programs”
try {
# Construct the path to the Uninstall key in the registry, because it gets left behind sometimes
$UninstallKeyPath = $VSAXUninstallKey.PSPath
# Check if the key still exists in the predicted location
if (Test-Path -Path $UninstallKeyPath) {
Write-Host “Removing leftover uninstall key: $($UninstallKeyPath.PSChildName)”
Remove-Item -Path “$UninstallKeyPath” -Force -Confirm:$false -Recurse -ErrorAction Stop
Write-Host “Uninstall key removed successfully.”
} else {Write-Verbose “Uninstall key not found (which is good): $UninstallKeyPath”}
} catch {Write-Error “Failed to remove leftover uninstall key: $_”}
} catch {“Something went wrong during this uninstall, and this indicates possible issues with the uninstall msi itself: $_”}
} else {Write-Error “No UninstallString found for $($VSAXUninstallKey.DisplayName).”}
} catch {Write-Error “UninstallString execution failed: $_”}
} else {Write-Host “VSA X was not found in the Uninstall registry keys.”}
} catch {Write-Error “Error accessing registry: $_”}

# Remove other registry keys related to VSA X
try{
Remove-Item -Path “HKLM:\SOFTWARE\Kaseya\PC Monitor” -Recurse -Force -ErrorAction Stop
Write-Host “HKLM:\SOFTWARE\Kaseya\PC Monitor detected and removed”
} catch {Write-Host “HKLM:\SOFTWARE\Kaseya\PC Monitor was previously removed”}

# Perform a deep clean of even more VSA X registry keys
try{
Get-ChildItem “HKLM:\SOFTWARE\Classes\Installer\Products” -ErrorAction Stop | Get-ItemProperty | Where-Object {$_.ProductName -eq “VSA X”} | ForEach-Object {
Remove-Item $_.PSPath -Recurse -Force -ErrorAction Stop
Write-Host “$($_.PSPath) detected and removed”
}
} catch {Write-Host “HKLM:\SOFTWARE\Classes\Installer\Products\VSA X\ was previously removed”}

try{
#This accounts for an error, I believe, in the early installers that used to write this to classes root. Accounts for old agents
Get-ChildItem “HKCR:\SOFTWARE\Classes\Installer\Products” -ErrorAction Stop | Get-ItemProperty | Where-Object {$_.ProductName -eq “VSA X”} | ForEach-Object {
Remove-Item $_.PSPath -Recurse -Force -ErrorAction Stop
Write-Host “$($_.PSPath) detected and removed”
}
} catch {Write-Host “HKCR:\SOFTWARE\Classes\Installer\Products\VSA X\ was previously removed”}

# Get all SIDs currently loaded in HKEY_USERS
$LoadedSIDs = Get-ChildItem -Path “Registry::HKEY_USERS” | Select-Object -ExpandProperty Name

foreach ($SID in $LoadedSIDs) {
# Skip any keys ending with _Classes
if ($SID -eq “_Classes$”) {
continue
}

# Path to the target registry hive and path for all loaded users including any possibly logged in users
$VSAXRegistryPath = “Registry::$SID\Software\Microsoft\Installer\Products”
try{
# silently continue here because it cant error out if running as admin. if there is an error here, it needs to be seen by the user because it would be a filesystem or hardware level failure
$possibleVSAXkeys = Get-ChildItem $VSAXRegistryPath -ErrorAction SilentlyContinue | Get-ItemProperty | Where-Object {$_.ProductName -eq “VSA X”}
if($possibleVSAXkeys){
foreach($key in $possibleVSAXkeys){
try{
Remove-Item $key.PSPath -Recurse -Force -ErrorAction Stop
Write-Host “$($key.PSPath) detected and removed”
} catch {Write-Host “For unexpected reasons that shouldn’t occur, $($key.PSPath) could not be removed. This might be a filesystem issue, hard drive failure, or an advanced persmissions event due to group policies or AntniVirus systems blocking this action despite running as administrator”}
}
} #else {Write-Output “All VSA X registry entries were previously removed from hive: $($SID)”}
} catch {
Write-Host “Unable to read HKU registry keys and values despite running as admin. This could be due to GPOs, AntiVirus systems, or prehaps even filesystem or disk reading errors”
}
}

# Delete scheduled task named VSA XServiceCheck
$TaskToDelete = “VSA XServiceCheck”

# Create Task Scheduler COM object
$TS = New-Object -ComObject Schedule.Service
# Connect to local task scheduler
$TS.Connect($env:COMPUTERNAME)
# Get tasks folder (in this case, the root of Task Scheduler Library)
$TaskFolder = $TS.GetFolder(“\”)
# Get tasks in folder
$Tasks = $TaskFolder.GetTasks(1)

# Step through all tasks in the folder
foreach ($Task in $Tasks) {
if ($Task.Name -eq $TaskToDelete) {
Write-Host (“Task ” + $Task.Name + ” detected and removed.”)
$TaskFolder.DeleteTask($Task.Name, 0)
}
}

# Finally let’s delete ANY “VSA X” folder left over in ANY drive on the PC. I only check for this folder in the Program Files and PF x86 folders that may exist in either of those drives.
# Get all available drives
$Drives = Get-PSDrive | Where-Object {$_.Root -match “^[A-Z]:\\”} | Select-Object -ExpandProperty Root

foreach ($Drive in $Drives) {
$ProgramFilesPath = Join-Path $Drive “Program Files”
$ProgramFilesX86Path = Join-Path $Drive “Program Files (x86)”

foreach ($Path in @($ProgramFilesPath, $ProgramFilesX86Path)) {
if (Test-Path $Path) { # Check if Program Files exists on this drive
$VSAPath = Join-Path $Path “VSA X”

if (Test-Path $VSAPath) { # Check if VSA X folder exists
try {
# Write-Host “Deleting folder: $VSAPath”
Remove-Item $VSAPath -Recurse -Force -ErrorAction Stop -Confirm:$false # -Confirm:$false for non-interactive deletion
Write-Host “Folder ‘$VSAPath’ detected and removed”
} catch {
Write-Error “Failed to delete ‘$VSAPath’: $_”
# If you are odifying this script and want to log errors to a file, below is how. My goal is to delete things, so leaving a trace such as this cannot be the default
# $Error | Out-File -FilePath “C:\VSAX_Deletion_Log.txt” -Append
}
}
}
}
}

Write-Host “Uninstallation and cleanup completed successfully, closing terminal window in 10 seconds”
# This pause at the end is so you can run the script manually via right click, and still see the output for a few seconds before it closes
Start-Sleep -Seconds 10