Skip to content

Instantly share code, notes, and snippets.

@Jason-Clark-FG
Last active November 26, 2025 15:30
Show Gist options
  • Select an option

  • Save Jason-Clark-FG/50ed92dd46b08ccd6c06124d99c15c00 to your computer and use it in GitHub Desktop.

Select an option

Save Jason-Clark-FG/50ed92dd46b08ccd6c06124d99c15c00 to your computer and use it in GitHub Desktop.
An attempt to merge the modified msi removal with the forced removal/cleanup from https://gist.github.com/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8 and https://gist.github.com/broestls/f872872a00acee2fca02017160840624 with the ability to run remotely
<#
.SYNOPSIS
Uninstalls and removes VMware Tools from remote Windows systems.
.DESCRIPTION
This script uses PowerShell remoting to execute VMware Tools cleanup on one or more
remote computers. It first attempts to uninstall VMware Tools using the MSI installer
method, then performs comprehensive cleanup by removing registry entries, filesystem
folders, services, and devices.
WARNING: If running this script on a system that has been migrated to a different
hypervisor (e.g., Proxmox with VirtIO drivers), removing VMware storage drivers may
cause boot failures. In such cases:
- Ensure VirtIO drivers (or equivalent) are installed BEFORE running this script
- If boot issues occur, change the disk controller type to IDE or SATA in the
hypervisor settings, boot the system, then reinstall the appropriate drivers
- Consider taking a snapshot before running this script if on a virtualized system
.PARAMETER ComputerName
One or more computer names or IP addresses to target. Accepts pipeline input.
.PARAMETER Credential
PSCredential object for authenticating to remote computers. If not specified, uses current credentials.
.PARAMETER Force
Bypass the confirmation prompt and proceed with uninstall and cleanup automatically on all targets.
.PARAMETER Reboot
Reboot the remote systems after cleanup completes. If -Force is not specified, prompts for confirmation per computer.
.PARAMETER ThrottleLimit
Maximum number of computers to process in parallel. Default is 5.
.EXAMPLE
.\Cleanup-VMwareToolsRemote.ps1 -ComputerName SERVER01
Prompts for confirmation before cleaning VMware Tools from SERVER01.
.EXAMPLE
.\Cleanup-VMwareToolsRemote.ps1 -ComputerName SERVER01,SERVER02,SERVER03 -Force
Cleans VMware Tools from multiple servers without prompting.
.EXAMPLE
Get-Content servers.txt | .\Cleanup-VMwareToolsRemote.ps1 -Credential (Get-Credential) -Force -Reboot
Reads server names from a file, cleans VMware Tools, and reboots automatically.
.EXAMPLE
.\Cleanup-VMwareToolsRemote.ps1 -ComputerName SERVER01 -ThrottleLimit 10
Process up to 10 computers in parallel.
.NOTES
This script combines techniques from two sources:
- MSI uninstaller method: https://gist.github.com/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8
- Brute-force cleanup method: https://gist.github.com/broestls/f872872a00acee2fca02017160840624
Requirements:
- PowerShell Remoting must be enabled on target computers
- User must have administrative privileges on target computers
- WinRM service must be running on target computers
.LINK
https://gist.github.com/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8
.LINK
https://gist.github.com/broestls/f872872a00acee2fca02017160840624
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('CN','Name','PSComputerName')]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$Credential,
[Parameter(Mandatory=$false)]
[switch]$Force,
[Parameter(Mandatory=$false)]
[switch]$Reboot,
[Parameter(Mandatory=$false)]
[int]$ThrottleLimit = 5
)
Begin {
#region Script Setup
# Initialize script timer
$scriptStartTime = Get-Date
Write-Host "=== VMware Tools Remote Cleanup Started ===" -ForegroundColor Cyan
Write-Host "Script started at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
Write-Host "Throttle limit: $ThrottleLimit concurrent computers`n" -ForegroundColor Gray
# Collect all computer names for batch processing
$allComputers = @()
#endregion
#region Cleanup ScriptBlock
# This scriptblock will be executed on each remote computer
$cleanupScriptBlock = {
param(
[bool]$ForceCleanup,
[bool]$RebootAfter
)
# Helper function for remote execution
function Get-VMwareToolsInstallerID {
foreach ($item in $(Get-ChildItem Registry::HKEY_CLASSES_ROOT\Installer\Products)) {
if ($item.GetValue('ProductName') -eq 'VMware Tools') {
return @{
reg_id = $item.PSChildName;
msi_id = [Regex]::Match($item.GetValue('ProductIcon'), '(?<={)(.*?)(?=})') | Select-Object -ExpandProperty Value
}
}
}
}
$result = @{
ComputerName = $env:COMPUTERNAME
Success = $false
Message = ""
VMwareToolsFound = $false
UninstallSuccess = $false
CleanupSuccess = $false
}
try {
#region Gather VMware Tools Information
$vmware_tools_ids = Get-VMwareToolsInstallerID
if ($vmware_tools_ids) {
$result.VMwareToolsFound = $true
Write-Host " VMware Tools installer IDs found" -ForegroundColor Green
}
else {
Write-Host " VMware Tools installer IDs not found in registry" -ForegroundColor Yellow
}
#endregion
#region Step 1: MSI-based Uninstallation
Write-Host " Attempting MSI-based uninstallation..." -ForegroundColor Cyan
if ($vmware_tools_ids) {
$installer = New-Object -ComObject WindowsInstaller.Installer
$localPackage = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$($vmware_tools_ids.reg_id)\InstallProperties" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty LocalPackage
if ($localPackage) {
Write-Host " VMware Tools MSI found: $localPackage" -ForegroundColor Yellow
# Open and modify the MSI database
$database = $installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $installer, @("${localPackage}", 2))
$query = "DELETE FROM CustomAction WHERE Action='VM_LogStart' OR Action='VM_CheckRequirements'"
$view = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $database, @($query))
$view.GetType().InvokeMember("Execute", "InvokeMethod", $null, $view, $null)
$view.GetType().InvokeMember("Close", "InvokeMethod", $null, $view, $null)
[void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($view)
$database.GetType().InvokeMember("Commit", "InvokeMethod", $null, $database, $null)
[void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($database)
Write-Host " MSI database modified successfully" -ForegroundColor Green
# Uninstall
Write-Host " Uninstalling VMware Tools via MSI..." -ForegroundColor Yellow
Start-Process msiexec.exe -ArgumentList "/x `"${localPackage}`" /qn /norestart" -Wait -NoNewWindow
Write-Host " MSI uninstallation completed" -ForegroundColor Green
$result.UninstallSuccess = $true
}
}
#endregion
#region Step 2: Comprehensive Cleanup
Write-Host " Performing comprehensive cleanup..." -ForegroundColor Cyan
$reg_targets = @(
"Registry::HKEY_CLASSES_ROOT\Installer\Features\",
"Registry::HKEY_CLASSES_ROOT\Installer\Products\",
"HKLM:\SOFTWARE\Classes\Installer\Features\",
"HKLM:\SOFTWARE\Classes\Installer\Products\",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\"
)
$VMware_Tools_Directory = "${env:SystemDrive}\Program Files\VMware"
$VMware_Common_Directory = "${env:SystemDrive}\Program Files\Common Files\VMware"
$VMware_Startmenu_Entry = "${env:SystemDrive}\ProgramData\Microsoft\Windows\Start Menu\Programs\VMware\"
$VMware_ProgramData_Directory = "${env:SystemDrive}\ProgramData\VMware"
$targets = @()
if ($vmware_tools_ids) {
foreach ($item in $reg_targets) {
$targets += $item + $vmware_tools_ids.reg_id
}
$targets += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{$($vmware_tools_ids.msi_id)}"
}
if ([Environment]::OSVersion.Version.Major -lt 10) {
$targets += "HKCR:\CLSID\{D86ADE52-C4D9-4B98-AA0D-9B0C7F1EBBC8}"
$targets += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{9709436B-5A41-4946-8BE7-2AA433CAF108}"
$targets += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{FE2F6A2C-196E-4210-9C04-2B1BC21F07EF}"
}
if (Test-Path "HKLM:\SOFTWARE\VMware, Inc.") {
$targets += "HKLM:\SOFTWARE\VMware, Inc."
}
if (Test-Path "HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.") {
$targets += "HKLM:\SOFTWARE\WOW6432Node\VMware, Inc."
}
# Add the VMware User Process run key value
$runKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
if (Test-Path $runKeyPath) {
$runKey = Get-ItemProperty -Path $runKeyPath -ErrorAction SilentlyContinue
if ($runKey."VMware User Process") {
$targets += "$runKeyPath|VMware User Process"
}
}
if (Test-Path $VMware_Tools_Directory) { $targets += $VMware_Tools_Directory }
if (Test-Path $VMware_Common_Directory) { $targets += $VMware_Common_Directory }
if (Test-Path $VMware_Startmenu_Entry) { $targets += $VMware_Startmenu_Entry }
if (Test-Path $VMware_ProgramData_Directory) { $targets += $VMware_ProgramData_Directory }
$services = @(Get-Service -DisplayName "VMware*" -ErrorAction SilentlyContinue)
$services += @(Get-Service -DisplayName "GISvc" -ErrorAction SilentlyContinue)
$vmwareDevices = Get-PnpDevice | Where-Object { $_.FriendlyName -like "*VMware*" }
if (!$targets -and !$services) {
Write-Host " No cleanup targets found" -ForegroundColor Yellow
$result.Message = "No VMware components found to clean up"
}
else {
$global:ErrorActionPreference = 'SilentlyContinue'
# Unregister vmStatsProvider.dll
$vmStatsProvider = "c:\Program Files\VMware\VMware Tools\vmStatsProvider\win64\vmStatsProvider.dll"
if (Test-Path $vmStatsProvider) {
Write-Host " Unregistering vmStatsProvider.dll..." -ForegroundColor Yellow
Regsvr32 /s /u $vmStatsProvider
}
# Stop and remove VMware services
Write-Host " Stopping and removing VMware services..." -ForegroundColor Yellow
$services | Stop-Service -Confirm:$false -ErrorAction SilentlyContinue
if (Get-Command Remove-Service -ErrorAction SilentlyContinue) {
$services | Remove-Service -Confirm:$false -ErrorAction SilentlyContinue
}
else {
foreach ($s in $services) {
sc.exe DELETE $($s.Name) | Out-Null
}
}
# Stop dependent services
Write-Host " Stopping dependent services temporarily..." -ForegroundColor Yellow
$dep = Get-Service -Name "EventLog" -DependentServices | Select-Object -Property Name
Stop-Service -Name "EventLog" -Force -ErrorAction SilentlyContinue
Stop-Service -Name "wmiApSrv" -Force -ErrorAction SilentlyContinue
$dep += Get-Service -Name "winmgmt" -DependentServices | Select-Object -Property Name
Stop-Service -Name "winmgmt" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 5
# Remove registry keys, values, and filesystem folders
Write-Host " Removing registry keys, values, and filesystem folders..." -ForegroundColor Yellow
foreach ($item in $targets) {
if ($item -match '^(.+)\|(.+)$') {
$regPath = $Matches[1]
$valueName = $Matches[2]
if (Test-Path $regPath) {
Remove-ItemProperty -Path $regPath -Name $valueName -Force -ErrorAction SilentlyContinue
}
}
elseif (Test-Path $item) {
Get-Childitem -Path $item -Recurse | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue
Remove-Item -Path $item -Recurse -Force -ErrorAction SilentlyContinue
}
}
# Restart dependent services
Write-Host " Restarting dependent services..." -ForegroundColor Yellow
Start-Service -Name "EventLog" -ErrorAction SilentlyContinue
Start-Service -Name "wmiApSrv" -ErrorAction SilentlyContinue
Start-Service -Name "winmgmt" -ErrorAction SilentlyContinue
foreach ($service in $dep) {
Start-Service $service.Name -ErrorAction SilentlyContinue
}
# Remove VMware devices
if ($vmwareDevices.Count -gt 0) {
Write-Host " Removing VMware devices..." -ForegroundColor Yellow
foreach ($device in $vmwareDevices) {
pnputil /remove-device $device.InstanceId 2>&1 | Out-Null
}
}
# Remove VMware driver packages
Write-Host " Removing VMware driver packages..." -ForegroundColor Yellow
$pnpOutput = pnputil /enum-drivers
$vmwareDrivers = @()
for ($i = 0; $i -lt $pnpOutput.Count; $i++) {
if ($pnpOutput[$i] -match "Published Name\s*:\s*(oem\d+\.inf)") {
$oemInf = $Matches[1]
$driverBlock = $pnpOutput[$i..($i+5)] -join " "
if ($driverBlock -match "VMware") {
$vmwareDrivers += $oemInf
}
}
}
if ($vmwareDrivers.Count -gt 0) {
Write-Host " Found $($vmwareDrivers.Count) VMware driver package(s) in driver store" -ForegroundColor Yellow
foreach ($driver in $vmwareDrivers) {
pnputil /delete-driver $driver /uninstall /force 2>&1 | Out-Null
}
}
Start-Sleep -Seconds 2
Write-Host " Cleanup completed successfully" -ForegroundColor Green
$result.CleanupSuccess = $true
$result.Message = "VMware Tools cleanup completed successfully"
}
#endregion
#region Handle Reboot
if ($RebootAfter) {
Write-Host " Initiating system reboot..." -ForegroundColor Yellow
Restart-Computer -Force
$result.Message += " (Rebooting)"
}
#endregion
$result.Success = $true
}
catch {
$result.Success = $false
$result.Message = "Error: $($_.Exception.Message)"
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Red
}
# Explicitly output the result object to ensure it's returned through the remoting pipeline
Write-Output $result
}
#endregion
}
Process {
# Collect all computer names from pipeline
$allComputers += $ComputerName
}
End {
#region Execute Remote Cleanup
if ($allComputers.Count -eq 0) {
Write-Host "No computers specified for cleanup." -ForegroundColor Red
return
}
Write-Host "Targets: $($allComputers -join ', ')" -ForegroundColor Cyan
# Test connectivity first
Write-Host "`n=== Testing Connectivity ===" -ForegroundColor Cyan
$reachableComputers = @()
$unreachableComputers = @()
foreach ($computer in $allComputers) {
# Check if this is the local computer
$isLocal = ($computer -eq 'localhost') -or
($computer -eq '127.0.0.1') -or
($computer -eq $env:COMPUTERNAME) -or
($computer -eq "$env:COMPUTERNAME.$env:USERDNSDOMAIN")
Write-Host "Testing $computer..." -NoNewline
if ($isLocal) {
Write-Host " OK (Local)" -ForegroundColor Cyan
$reachableComputers += $computer
}
elseif (Test-WSMan -ComputerName $computer -ErrorAction SilentlyContinue) {
Write-Host " OK" -ForegroundColor Green
$reachableComputers += $computer
}
else {
Write-Host " FAILED" -ForegroundColor Red
$unreachableComputers += $computer
}
}
if ($unreachableComputers.Count -gt 0) {
Write-Host "`nUnreachable computers:" -ForegroundColor Red
$unreachableComputers | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
}
if ($reachableComputers.Count -eq 0) {
Write-Host "`nNo reachable computers found. Exiting." -ForegroundColor Red
return
}
Write-Host "`n=== Starting Cleanup on $($reachableComputers.Count) Computer(s) ===" -ForegroundColor Cyan
# Separate local and remote computers
$localComputers = @()
$remoteComputers = @()
[System.Collections.ArrayList]$results = @()
foreach ($computer in $reachableComputers) {
$isLocal = ($computer -eq 'localhost') -or
($computer -eq '127.0.0.1') -or
($computer -eq $env:COMPUTERNAME) -or
($computer -eq "$env:COMPUTERNAME.$env:USERDNSDOMAIN")
if ($isLocal) {
$localComputers += $computer
}
else {
$remoteComputers += $computer
}
}
# Execute on local computer(s) directly (without remoting)
if ($localComputers.Count -gt 0) {
Write-Host "Executing locally on: $($localComputers -join ', ')" -ForegroundColor Cyan
foreach ($computer in $localComputers) {
$localResult = & $cleanupScriptBlock -ForceCleanup $Force.IsPresent -RebootAfter $Reboot.IsPresent
[void]$results.Add($localResult)
}
}
# Execute on remote computer(s) via Invoke-Command
if ($remoteComputers.Count -gt 0) {
Write-Host "Executing remotely on: $($remoteComputers -join ', ')" -ForegroundColor Cyan
# Build Invoke-Command parameters
$invokeParams = @{
ComputerName = $remoteComputers
ScriptBlock = $cleanupScriptBlock
ArgumentList = @($Force.IsPresent, $Reboot.IsPresent)
ThrottleLimit = $ThrottleLimit
ErrorAction = 'Continue'
ErrorVariable = 'remoteErrors'
}
if ($Credential) {
$invokeParams.Credential = $Credential
}
# Execute cleanup on remote computers in parallel
$remoteResults = Invoke-Command @invokeParams
if ($remoteResults) {
foreach ($r in $remoteResults) {
[void]$results.Add($r)
}
}
# Display any errors that occurred during remote execution
if ($remoteErrors -and $remoteErrors.Count -gt 0) {
Write-Host "`nRemoting Errors Detected:" -ForegroundColor Red
foreach ($err in $remoteErrors) {
Write-Host " $err" -ForegroundColor Red
}
}
}
#endregion
#region Display Results
Write-Host "`n=== Cleanup Results ===" -ForegroundColor Cyan
if (!$results -or $results.Count -eq 0) {
Write-Host "No results returned from remote execution." -ForegroundColor Red
Write-Host "This may indicate an issue with PowerShell remoting or the scriptblock execution." -ForegroundColor Yellow
Write-Host "`nTroubleshooting tips:" -ForegroundColor Yellow
Write-Host " 1. Ensure PowerShell remoting is enabled: Enable-PSRemoting -Force" -ForegroundColor Gray
Write-Host " 2. Check if WinRM service is running: Get-Service WinRM" -ForegroundColor Gray
Write-Host " 3. Verify you have administrative privileges on the target computer(s)" -ForegroundColor Gray
return
}
foreach ($result in $results) {
Write-Host "`n$($result.ComputerName):" -ForegroundColor Cyan
Write-Host " VMware Tools Found: $($result.VMwareToolsFound)" -ForegroundColor $(if ($result.VMwareToolsFound) { "Yellow" } else { "Gray" })
Write-Host " Uninstall Success: $($result.UninstallSuccess)" -ForegroundColor $(if ($result.UninstallSuccess) { "Green" } else { "Gray" })
Write-Host " Cleanup Success: $($result.CleanupSuccess)" -ForegroundColor $(if ($result.CleanupSuccess) { "Green" } else { "Gray" })
Write-Host " Status: $($result.Message)" -ForegroundColor $(if ($result.Success) { "Green" } else { "Red" })
}
#endregion
#region Finalize
$scriptDuration = (Get-Date) - $scriptStartTime
# Calculate statistics
$successCount = 0
$failCount = 0
foreach ($r in $results) {
if ($r.Success -eq $true) {
$successCount++
}
else {
$failCount++
}
}
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Total script execution time: $($scriptDuration.TotalSeconds.ToString('F2')) seconds ($($scriptDuration.ToString('mm\:ss')))" -ForegroundColor Cyan
Write-Host "Script completed at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
Write-Host "Total computers processed: $($results.Count)" -ForegroundColor Cyan
Write-Host "Successful: $successCount" -ForegroundColor Green
Write-Host "Failed: $failCount" -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Cyan
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment