Skip to content

Instantly share code, notes, and snippets.

@broestls
Last active April 28, 2024 14:05
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • 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"
}
}
@broestls
Copy link
Author

broestls commented Feb 6, 2021

Used this VMware KB article as a reference: https://kb.vmware.com/s/article/1001354, though it is annoyingly wrong/incomplete for Windows 2019.

@broestls
Copy link
Author

broestls commented Feb 8, 2021

Fixes:

  • duplicate line in registry $targets
  • stops services before trying to delete any file paths
  • falls back to sc.exe on platforms where Remove-Service doesn't exist
  • Tests for paths before trying to delete them, so you can run again and again until everything is destroyed and not get errors.

@broestls
Copy link
Author

broestls commented Feb 9, 2021

More fixes:

  • Remove stale debug Write-Host for the sc.exe operations
  • Correct 'HKLM:\Software\VMware, Inc' -> 'HKLM:\Software\VMware, Inc.'
  • Do more testing for existence of files to cover for errors.

@skadann
Copy link

skadann commented Jun 13, 2022

Windows 2016, with VMTools 11.2.5, has a 5th VMware service "GISvc" that prevents the deletion of the Program Files folder. I added this line to include the service in the list of services being deleted.

$services += Get-Service -DisplayName "GISvc"

@cosentj
Copy link

cosentj commented May 19, 2023

Hello. Does anyone have the updated script showing the changes made?

@broestls
Copy link
Author

Hi @cosentj, I updated the script with the change @skadann made. From a code standpoint it looks fine to me. I haven't tested this revision or code in a while as we used this mostly for a one-time lift of our VM resources to another platform.

@cosentj
Copy link

cosentj commented May 22, 2023 via email

@Gadgetgeek2000
Copy link

Also found this location on my disk after removal of the tools, with 500MB of files:

C:\Program Files\Common Files\VMware

@Zucky16
Copy link

Zucky16 commented Oct 12, 2023

This was the only script that did anything at all and it cleared out the services very well.

To help it, I stopped all the running VM services and killed the VMTools Core process in task manager, doing this allowed everything to be removed nicely.

Thank you so much for this script, it's really sad that the vendor does not have a proper removal tool for this.

@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