Skip to content

Instantly share code, notes, and snippets.

@broestls
Last active May 13, 2024 06:16
Show Gist options
  • Save broestls/f872872a00acee2fca02017160840624 to your computer and use it in GitHub Desktop.
Save broestls/f872872a00acee2fca02017160840624 to your computer and use it in GitHub Desktop.
Force removal of VMware Tools, Program Files, and Windows Services
# This script will manually rip out all VMware Tools registry entries and files for Windows 2008-2019
# Tested for 2019, 2016, and probably works on 2012 R2 after the 2016 fixes.
# This function pulls out the common ID used for most of the VMware registry entries along with the ID
# associated with the MSI for VMware Tools.
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
}
}
}
}
$vmware_tools_ids = Get-VMwareToolsInstallerID
# Targets we can hit with the common registry ID from $vmware_tools_ids.reg_id
$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 = "C:\Program Files\VMware"
$VMware_Common_Directory = "C:\Program Files\Common Files\VMware"
# Create an empty array to hold all the uninstallation targets and compose the entries into the target array
$targets = @()
If ($vmware_tools_ids) {
foreach ($item in $reg_targets) {
$targets += $item + $vmware_tools_ids.reg_id
}
# Add the MSI installer ID regkey
$targets += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{$($vmware_tools_ids.msi_id)}"
}
# This is a bit of a shotgun approach, but if we are at a version less than 2016, add the Uninstaller entries we don't
# try to automatically determine.
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}"
}
# Add the VMware, Inc regkey
If (Test-Path "HKLM:\SOFTWARE\VMware, Inc.") {
$targets += "HKLM:\SOFTWARE\VMware, Inc."
}
# Add the VMware Tools directory
If(Test-Path $VMware_Tools_Directory) {
$targets += $VMware_Tools_Directory
}
# Thanks to @Gadgetgeek2000 for pointing out that the script leaves some 500mb of extra artifacts on disk.
# This blob removes those.
If(Test-Path $VMware_Common_Directory) {
$targets += $VMware_Common_Directory
}
# Create a list of services to stop and remove
$services = Get-Service -DisplayName "VMware*"
$services += Get-Service -DisplayName "GISvc"
# Warn the user about what is about to happen
# Takes only y for an answer, bails otherwise.
Write-Host "The following registry keys, filesystem folders, and services will be deleted:"
If (!$targets -and !$services ) {
Write-Host "Nothing to do!"
}
Else {
$targets
$services
$user_confirmed = Read-Host "Continue (y/n)"
If ($user_confirmed -eq "y") {
# Stop all running VMware Services
$services | Stop-Service -Confirm:$false
# Cover for Remove-Service not existing in PowerShell versions < 6.0
If (Get-Command Remove-Service -errorAction SilentlyContinue) {
$services | Remove-Service -Confirm:$false
}
Else {
foreach ($s in $services) {
sc.exe DELETE $($s.Name)
}
}
# Remove all the files that are listed in $targets
foreach ($item in $targets) {
If(Test-Path $item) {
Remove-Item -Path $item -Recurse
}
}
Write-Host "Done. Reboot to complete removal."
}
Else {
Write-Host "Failed to get user confirmation"
}
}
@MiketheMaker18
Copy link

MiketheMaker18 commented Dec 6, 2023

Thanks for taking the time to make this. Unfortunately I found this after painfully going through VMware's KB on it with all the wrong AppID's. I can confirm Gadgetgeek2000 comment too. Just added this to the scrip to cleanup those files.

Remove-Item -Recurse -Force 'C:\Program Files\Common Files\VMware'

@broestls
Copy link
Author

broestls commented Dec 8, 2023

New revision adds some logic to remove the C:\Program Files\Common Files\VMware directory based on feedback @Gadgetgeek2000. Thanks everyone for your interest and contributions!

@silvergo
Copy link

Thank you very much!
Also I had to manually unregister the vmStatsProvider.dll:
REGSVR32 /U "c:\Program Files\VMware\VMware Tools\vmStatsProvider\win64\vmStatsProvider.dll"
Then I succeeded in removing the entire folder tree.

@Jeremyflaugh
Copy link

Jeremyflaugh commented Feb 28, 2024

I'm sure someone can make this better, but my co-worker and I took this code and made a script to remove VMware tools from 300 domain servers remotely after migrating from on-prem to AWS. We also had issues deleting c:\Program Files\VMware\VMware Tools because eventlog and WMI services were running. We also removed the ask to continue prompts.

This script copies "removesvmware.ps1" and executes it on the target host.

# Step 1: Get a list of computers using get-ADcomputer
$computers = Get-ADComputer -Filter 'Name -like "*" -and Enabled -eq "True"' -Property * | Select-Object Name, OperatingSystem, OperatingSystemServicePack

# Step 2: Robocopy a ps1 file from the source machine to each computer in the list
foreach ($computer in $computers) {
    robocopy "C:\temp" "\\$($computer.Name)\C$\temp" RemoveVMwareTools.ps1 /copy:dat /is /r:2 /w:2
}

# Step 3 & 4: Check if the ps1 file exists on the destination machine and execute it
foreach ($computer in $computers) {
    $destinationPath = "\\$($computer.Name)\C$\Temp"
    $ps1FilePath = "\\$($computer.Name)\C$\Temp\RemoveVMwareTools.ps1"

    # Check if the ps1 file exists
    if (Test-Path $ps1FilePath) {
        # If the ps1 file exists, read its content
        $scriptContent = Get-Content $ps1FilePath -Raw

        # Execute the script content in a remote session
        Invoke-Command -ComputerName $computer.Name -ScriptBlock {
            param($scriptContent)
            Invoke-Expression $using:scriptContent
        } -ArgumentList $scriptContent
    } else {
        Write-Host "PS1 file not found on $($computer.Name)"
    }
}



This is removevmware.ps1

# This script will manually rip out all VMware Tools registry entries and files for Windows 2008-2019
# Tested for 2019, 2016, and probably works on 2012 R2 after the 2016 fixes.

# This function pulls out the common ID used for most of the VMware registry entries along with the ID
# associated with the MSI for VMware Tools.
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
            }
        }
    }
}

$vmware_tools_ids = Get-VMwareToolsInstallerID

# Targets we can hit with the common registry ID from $vmware_tools_ids.reg_id
$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 = "C:\Program Files\VMware"
$VMware_Common_Directory = "C:\Program Files\Common Files\VMware"

# Create an empty array to hold all the uninstallation targets and compose the entries into the target array
$targets = @()

If ($vmware_tools_ids) {
    foreach ($item in $reg_targets) {
        $targets += $item + $vmware_tools_ids.reg_id
    }
    # Add the MSI installer ID regkey
    $targets += "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{$($vmware_tools_ids.msi_id)}"
}

# This is a bit of a shotgun approach, but if we are at a version less than 2016, add the Uninstaller entries we don't
# try to automatically determine.
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}"
}

# Add the VMware, Inc regkey
If (Test-Path "HKLM:\SOFTWARE\VMware, Inc.") {
    $targets += "HKLM:\SOFTWARE\VMware, Inc."
}

# Add the VMware Tools directory
If(Test-Path $VMware_Tools_Directory) {
    $targets += $VMware_Tools_Directory
}

# Thanks to @Gadgetgeek2000 for pointing out that the script leaves some 500mb of extra artifacts on disk.
# This blob removes those.
If(Test-Path $VMware_Common_Directory) {
    $targets += $VMware_Common_Directory
}

# Create a list of services to stop and remove
$services = Get-Service -DisplayName "VMware*"
$services += Get-Service -DisplayName "GISvc"

# Warn the user about what is about to happen
# Takes only y for an answer, bails otherwise.
#Write-Host "The following registry keys, filesystem folders, and services will be deleted:"
If (!$targets -and !$services ) {
    #Write-Host "Nothing to do!"
}
Else {
    $targets
    $services
    #$user_confirmed = Read-Host "Continue (y/n)"
    If ($user_confirmed -eq "y") {

    # Stop all running VMware Services
    $services | Stop-Service -Confirm:$false

    

    # Cover for Remove-Service not existing in PowerShell versions < 6.0
    If (Get-Command Remove-Service -errorAction SilentlyContinue) {
        $services | Remove-Service -Confirm:$false
    }
    Else {
        foreach ($s in $services) {
            sc.exe DELETE $($s.Name)
        }
    }
    Get-Service -Name "EventLog" | Format-List -Property Name, DependentServices
    Stop-Service -Name "EventLog" -Force
    Stop-Service -Name "wmiApSvc" -Force
    Start-Sleep -Seconds 5
    

    # Remove all the files that are listed in $targets
    foreach ($item in $targets) {
        If(Test-Path $item) {
            Remove-Item -Path $item -Recurse -Force
        }
    }
    Start-Service -Name "EventLog"
    Start-Service -Name "wmiApSvc"
    Write-Host "Done. Reboot to complete removal."
    }
    Else {
        Write-Host "Failed to get user confirmation"
    }
}


@julianomorona
Copy link

Thanks, it was very useful in a migration from VMWare to Proxmox.

@AdamTheManTyler
Copy link

I ran this script against Server 2022 today. Seems to have done the job mostly. I noticed this was left behind.

I removed this line too, this 5th service didn't seem to exist in my case. I guess it wouldn't have hurt anything to leave it in.
$services += Get-Service -DisplayName "GISvc"

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment