Cómo eliminar VMware Tools tras migrar una máquina virtual a Proxmox

Una máquina virtual Windows puede arrancar correctamente después de migrarla desde VMware a Proxmox VE y, aun así, conservar una instalación dañada de VMware Tools. El problema aparece cuando el desinstalador falla, Windows Installer no encuentra el paquete original o varios servicios y controladores siguen registrados aunque la máquina ya no se ejecute sobre VMware.

Las claves para eliminar VMware Tools en 20 segundos

  • Antes de empezar debe existir una copia de seguridad recuperable de la máquina virtual.
  • La desinstalación normal con Windows Installer siempre debe ser el primer intento.
  • Si PowerShell bloquea el script puede habilitarse únicamente la sesión actual.
  • La limpieza forzada modifica servicios, carpetas y claves del Registro, así que no puede garantizarse que funcione en todos los sistemas.

El procedimiento que se explica a continuación está pensado para casos en los que VMware Tools se resiste a desaparecer después de una migración. Incluye una versión más prudente del script comunitario publicado por broestls, con registro de actividad, simulación mediante -WhatIf, copia de determinadas claves y una separación clara entre la desinstalación normal y la limpieza forzada.

Ni el autor del script original ni este artículo pueden garantizar que el proceso funcione en todas las instalaciones de Windows, versiones de VMware Tools o configuraciones de Proxmox. La ejecución queda bajo responsabilidad del administrador. Antes de modificar servicios, controladores o el Registro debe crearse una copia de seguridad completa de la máquina virtual y comprobarse que puede restaurarse.

Preparar la máquina virtual antes de borrar VMware Tools

La limpieza no debe comenzar eliminando carpetas o claves del Registro. VMware Tools puede incluir controladores de red, almacenamiento, vídeo, sincronización horaria y comunicación con el hipervisor. Si Windows todavía utiliza alguno de ellos, una retirada precipitada puede dejar la máquina sin conectividad o impedir que arranque.

La medida de seguridad más importante es disponer de una copia recuperable de la VM. Puede utilizarse una copia de seguridad de Proxmox, un clon apagado o una imagen realizada con la herramienta habitual de protección de la empresa. Un snapshot resulta útil para volver atrás después de un cambio pequeño, pero no debería ser la única protección cuando se van a eliminar servicios, entradas del instalador y controladores.

También debe comprobarse el acceso a la consola de Proxmox. No conviene depender únicamente del escritorio remoto porque la interfaz VMXNET3 podría desaparecer durante la retirada de VMware Tools. Si la máquina utiliza una dirección IP estática, esta puede seguir asociada al adaptador antiguo y ser necesario configurarla de nuevo sobre la tarjeta VirtIO.

Antes de continuar conviene revisar estos puntos:

  1. Existe una copia de seguridad o un clon que puede restaurarse.
  2. La consola de Proxmox permite iniciar sesión en Windows.
  3. Los controladores VirtIO necesarios están instalados.
  4. El disco de arranque funciona con el controlador configurado en Proxmox.
  5. La nueva interfaz de red tiene conectividad.
  6. QEMU Guest Agent está instalado o preparado para instalarse.
  7. Se conoce una cuenta local con permisos de administrador.

VirtIO y QEMU Guest Agent no cumplen la misma función. Los controladores VirtIO permiten que Windows utilice dispositivos paravirtualizados de almacenamiento, red, memoria o comunicaciones. QEMU Guest Agent crea un canal de comunicación entre Proxmox y el sistema invitado para tareas de gestión, consulta de información y coordinación de algunas copias de seguridad.

La desinstalación convencional de VMware Tools debe intentarse antes de ejecutar cualquier limpieza directa. Puede hacerse desde Aplicaciones instaladas, Programas y características o mediante Windows Installer:

msiexec.exe /x "{CÓDIGO-DEL-PRODUCTO}" /norestart /L*v "C:\Temp\vmware-tools-uninstall.log"

El código de producto cambia según la versión instalada, por lo que no debe copiarse de otro servidor. El script incluido en este artículo busca las entradas correspondientes sin recurrir a Win32_Product, cuya consulta puede provocar comprobaciones o reparaciones de paquetes MSI instalados.

Qué hacer si PowerShell no permite ejecutar el script

Uno de los errores más habituales no está relacionado con VMware Tools, sino con la política de ejecución de PowerShell. Windows puede bloquear archivos .ps1 cuando la política activa no admite scripts o cuando el archivo conserva la marca que identifica contenidos descargados de Internet.

El primer paso consiste en abrir PowerShell como administrador, situarse en la carpeta donde se ha guardado el archivo y consultar las políticas activas:

Get-ExecutionPolicy -List

El resultado puede mostrar valores distintos para MachinePolicy, UserPolicy, Process, CurrentUser y LocalMachine. La política efectiva depende de su precedencia.

La opción menos intrusiva suele ser permitir la ejecución únicamente en la ventana actual:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\Remove_VMwareTools.ps1

El alcance Process solo afecta a esa sesión de PowerShell. El cambio desaparece al cerrar la ventana y no modifica de forma permanente la configuración del usuario o del equipo.

Esto permite ejecutar el script temporalmente, pero no sustituye a la revisión del archivo. Antes de usar Bypass debe abrirse el script con un editor de texto y comprobarse qué servicios, carpetas y claves pretende modificar.

Si Windows ha marcado el archivo como descargado de Internet puede desbloquearse de forma explícita:

Unblock-File .\Remove_VMwareTools.ps1
.\Remove_VMwareTools.ps1

También puede combinarse el desbloqueo con una política temporal para la sesión:

Unblock-File .\Remove_VMwareTools.ps1
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\Remove_VMwareTools.ps1

Otra posibilidad es utilizar RemoteSigned para el usuario actual:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

Esta modificación persiste después de cerrar PowerShell. Por eso no debería aplicarse por comodidad sin revisar antes la política de seguridad de la organización. En servidores administrados suele ser preferible utilizar el alcance Process y limitar el cambio a la intervención concreta.

Si Get-ExecutionPolicy -List muestra una política en MachinePolicy o UserPolicy, la restricción puede proceder de una directiva de grupo. Esas políticas tienen prioridad sobre los cambios realizados en CurrentUser, LocalMachine o Process, por lo que el administrador deberá seguir el procedimiento definido por su organización. No conviene intentar eludir una política corporativa sin autorización.

También puede ejecutarse el script desde una llamada independiente de PowerShell:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\Remove_VMwareTools.ps1"

El parámetro -NoProfile evita que se carguen perfiles personales que puedan modificar el comportamiento de la sesión. Aun así, las directivas aplicadas mediante Group Policy pueden seguir teniendo prioridad.

Script completo y seguro para limpiar VMware Tools

El siguiente script intenta primero la desinstalación mediante Windows Installer. Si el proceso falla, conserva los registros y se detiene sin eliminar servicios ni claves.

La limpieza directa solo se activa con -ForceCleanup. Antes de ejecutarla realmente debe utilizarse -WhatIf, que muestra las operaciones previstas sin aplicarlas.

El archivo puede guardarse como:

Remove_VMwareTools.ps1

Script completo:

#Requires -Version 5.1
#Requires -RunAsAdministrator

<#
.SYNOPSIS
Retira VMware Tools de una VM Windows migrada a Proxmox VE. Creado por RevistaCloud.com

.DESCRIPTION
1. Localiza las entradas de VMware Tools en Windows Installer.
2. Intenta primero una desinstalación normal con msiexec.
3. Genera registros de diagnóstico.
4. Solo con -ForceCleanup elimina servicios, claves y carpetas residuales.
5. No elimina automáticamente controladores del DriverStore.

.ADVERTENCIA
Crear una copia de seguridad recuperable antes de ejecutar el script.
Comprobar primero el acceso mediante la consola de Proxmox.
Revisar las operaciones con -WhatIf antes de aplicar la limpieza.
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
[switch]$ForceCleanup,

[switch]$RemoveSharedVmwareFolder,

[string]$BaseWorkDir = "C:\Temp\VMwareToolsCleanup"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$runDir = Join-Path $BaseWorkDir $timestamp
$transcriptFile = Join-Path $runDir "cleanup-transcript.log"

New-Item -ItemType Directory -Path $runDir -Force | Out-Null

$transcriptStarted = $false

try {
try {
Start-Transcript -Path $transcriptFile -Force | Out-Null
$transcriptStarted = $true
}
catch {
Write-Warning "No se ha podido iniciar la transcripción: $($_.Exception.Message)"
}

Write-Host ""
Write-Host "Limpieza de VMware Tools para una VM migrada a Proxmox"
Write-Host "Directorio de trabajo: $runDir"
Write-Host ""

function Get-VMwareToolsUninstallEntries {
$roots = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)

$entries = foreach ($root in $roots) {
Get-ItemProperty -Path $root -ErrorAction SilentlyContinue |
Where-Object {
$_.DisplayName -eq "VMware Tools" -or
$_.DisplayName -like "VMware Tools *"
}
}

return @($entries)
}

function Get-VMwareInstallerProducts {
$root = "Registry::HKEY_CLASSES_ROOT\Installer\Products"

if (-not (Test-Path $root)) {
return @()
}

$products = foreach ($item in Get-ChildItem -Path $root -ErrorAction SilentlyContinue) {
$productName = [string]$item.GetValue("ProductName")

if ($productName -eq "VMware Tools") {
[pscustomobject]@{
RegId = $item.PSChildName
ProductName = $productName
ProductIcon = [string]$item.GetValue("ProductIcon")
}
}
}

return @($products)
}

function Get-MsiProductCodes {
param(
[array]$UninstallEntries,
[array]$InstallerProducts
)

$codes = @()
$guidPattern = "\{[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}"

foreach ($entry in $UninstallEntries) {
$values = @(
[string]$entry.PSChildName,
[string]$entry.UninstallString,
[string]$entry.QuietUninstallString
)

foreach ($value in $values) {
if ($value -match $guidPattern) {
$codes += $Matches[0].ToUpperInvariant()
}
}
}

foreach ($product in $InstallerProducts) {
if ($product.ProductIcon -match $guidPattern) {
$codes += $Matches[0].ToUpperInvariant()
}
}

return @($codes | Sort-Object -Unique)
}

function Export-RegistryKey {
param(
[Parameter(Mandatory = $true)]
[string]$NativePath,

[Parameter(Mandatory = $true)]
[string]$Destination
)

& reg.exe query $NativePath *> $null

if ($LASTEXITCODE -eq 0) {
& reg.exe export $NativePath $Destination /y | Out-Null

if ($LASTEXITCODE -eq 0) {
Write-Host "Copia del Registro: $Destination"
}
else {
Write-Warning "No se ha podido exportar $NativePath"
}
}
}

$uninstallEntries = @(Get-VMwareToolsUninstallEntries)
$installerProducts = @(Get-VMwareInstallerProducts)
$productCodes = @(Get-MsiProductCodes `
-UninstallEntries $uninstallEntries `
-InstallerProducts $installerProducts)

Write-Host "Entradas de desinstalación encontradas: $($uninstallEntries.Count)"
Write-Host "Productos de Windows Installer encontrados: $($installerProducts.Count)"
Write-Host "Códigos MSI encontrados: $($productCodes.Count)"
Write-Host ""

$msiWasAttempted = $false
$msiFailed = $false

foreach ($productCode in $productCodes) {
$safeCode = $productCode.Trim("{", "}")
$msiLog = Join-Path $runDir "msiexec-$safeCode.log"

if ($PSCmdlet.ShouldProcess(
"VMware Tools $productCode",
"Desinstalar mediante Windows Installer"
)) {
$msiWasAttempted = $true

Write-Host "Intentando desinstalar $productCode mediante msiexec..."

$arguments = @(
"/x",
$productCode,
"/qn",
"/norestart",
"/L*v",
"`"$msiLog`""
)

$process = Start-Process `
-FilePath "$env:SystemRoot\System32\msiexec.exe" `
-ArgumentList $arguments `
-Wait `
-PassThru

Write-Host "Código devuelto por msiexec: $($process.ExitCode)"
Write-Host "Registro MSI: $msiLog"

if ($process.ExitCode -notin @(0, 1605, 1614, 3010)) {
$msiFailed = $true
Write-Warning "La desinstalación MSI no se ha completado."
}
}
}

if ($msiWasAttempted -and -not $msiFailed -and -not $ForceCleanup) {
Write-Host ""
Write-Host "La desinstalación MSI ha terminado."
Write-Host "Reinicie Windows y compruebe si quedan servicios o carpetas."
return
}

if (-not $ForceCleanup) {
Write-Host ""
Write-Warning "No se realizará ninguna limpieza forzada."
Write-Warning "Revise los registros creados en: $runDir"
Write-Warning "Para simular la limpieza:"
Write-Warning ".\Remove_VMwareTools.ps1 -ForceCleanup -WhatIf"
Write-Warning "Para ejecutarla:"
Write-Warning ".\Remove_VMwareTools.ps1 -ForceCleanup"
return
}

Write-Host ""
Write-Warning "Se ha activado la limpieza forzada."
Write-Warning "Solo debe continuar si existe una copia recuperable de la VM."
Write-Host ""

Export-RegistryKey `
-NativePath "HKLM\SOFTWARE\VMware, Inc." `
-Destination (Join-Path $runDir "vmware-hklm.reg")

Export-RegistryKey `
-NativePath "HKLM\SOFTWARE\WOW6432Node\VMware, Inc." `
-Destination (Join-Path $runDir "vmware-wow6432.reg")

foreach ($product in $installerProducts) {
Export-RegistryKey `
-NativePath "HKCR\Installer\Products\$($product.RegId)" `
-Destination (Join-Path $runDir "installer-$($product.RegId).reg")
}

$knownServiceNames = @(
"VMTools",
"VGAuthService",
"vmvss",
"vm3dservice",
"vm3dservicehelper",
"GISvc"
)

$services = @(
Get-CimInstance -ClassName Win32_Service |
Where-Object {
($knownServiceNames -contains $_.Name) -or
(
$_.DisplayName -like "VMware*" -and
$_.PathName -match "(?i)\\VMware\\VMware Tools\\|\\Common Files\\VMware\\"
)
} |
Sort-Object -Property Name -Unique
)

foreach ($service in $services) {
if ($PSCmdlet.ShouldProcess(
"Servicio $($service.Name)",
"Detener y eliminar"
)) {
Write-Host "Eliminando servicio: $($service.Name)"

& sc.exe stop $service.Name | Out-Null
Start-Sleep -Seconds 1
& sc.exe delete $service.Name | Out-Null
}
}

$registryTargets = @()

foreach ($product in $installerProducts) {
$id = $product.RegId

$registryTargets += @(
"Registry::HKEY_CLASSES_ROOT\Installer\Features\$id",
"Registry::HKEY_CLASSES_ROOT\Installer\Products\$id",
"HKLM:\SOFTWARE\Classes\Installer\Features\$id",
"HKLM:\SOFTWARE\Classes\Installer\Products\$id",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\$id"
)
}

foreach ($entry in $uninstallEntries) {
if ($entry.PSPath) {
$registryTargets += [string]$entry.PSPath
}
}

$registryTargets += @(
"HKLM:\SOFTWARE\VMware, Inc.\VMware Tools",
"HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.\VMware Tools"
)

$registryTargets = @(
$registryTargets |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Sort-Object -Unique
)

foreach ($target in $registryTargets) {
if (Test-Path -LiteralPath $target) {
if ($PSCmdlet.ShouldProcess(
$target,
"Eliminar clave del Registro"
)) {
try {
Remove-Item `
-LiteralPath $target `
-Recurse `
-Force `
-ErrorAction Stop

Write-Host "Clave eliminada: $target"
}
catch {
Write-Warning "No se pudo eliminar $target`: $($_.Exception.Message)"
}
}
}
}

$folderTargets = @(
(Join-Path $env:ProgramFiles "VMware\VMware Tools"),
(Join-Path $env:ProgramData "VMware\VMware Tools")
)

if (${env:ProgramFiles(x86)}) {
$folderTargets += Join-Path ${env:ProgramFiles(x86)} "VMware\VMware Tools"
}

if ($RemoveSharedVmwareFolder) {
$folderTargets += Join-Path $env:CommonProgramFiles "VMware"

if (${env:CommonProgramFiles(x86)}) {
$folderTargets += Join-Path ${env:CommonProgramFiles(x86)} "VMware"
}
}

$folderTargets = @($folderTargets | Sort-Object -Unique)

foreach ($folder in $folderTargets) {
if (Test-Path -LiteralPath $folder) {
if ($PSCmdlet.ShouldProcess(
$folder,
"Eliminar carpeta"
)) {
try {
Remove-Item `
-LiteralPath $folder `
-Recurse `
-Force `
-ErrorAction Stop

Write-Host "Carpeta eliminada: $folder"
}
catch {
Write-Warning "No se pudo eliminar $folder`: $($_.Exception.Message)"
}
}
}
}

$driverPattern = "(?i)(vmxnet|pvscsi|vmmemctl|vmhgfs|vm3dmp|vmci|vmrawdsk)"

$remainingDrivers = @(
Get-CimInstance -ClassName Win32_SystemDriver |
Where-Object {
$_.Name -match $driverPattern -or
$_.PathName -match $driverPattern
} |
Sort-Object -Property Name -Unique
)

if ($remainingDrivers.Count -gt 0) {
Write-Host ""
Write-Warning "Se han detectado controladores VMware o relacionados:"

$remainingDrivers |
Select-Object Name, State, StartMode, PathName |
Format-Table -AutoSize

Write-Warning "El script no los elimina del DriverStore."
Write-Warning "Revise cada controlador antes de usar pnputil o el Administrador de dispositivos."
}

Write-Host ""
Write-Host "Limpieza terminada."
Write-Host "Reinicie Windows antes de realizar más cambios."
Write-Host "Registros y copias disponibles en: $runDir"
}
finally {
if ($transcriptStarted) {
try {
Stop-Transcript | Out-Null
}
catch {
# No se interrumpe la salida si la transcripción ya estaba cerrada.
}
}
}

El primer lanzamiento debe hacerse sin limpieza forzada:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Unblock-File .\Remove_VMwareTools.ps1
.\Remove_VMwareTools.ps1

Este intento busca el paquete MSI y ejecuta su desinstalador. Si termina correctamente, Windows debe reiniciarse antes de comprobar si quedan restos.

Si el desinstalador falla puede simularse la limpieza:

.\Remove_VMwareTools.ps1 -ForceCleanup -WhatIf

La salida debe revisarse con atención. -WhatIf muestra qué servicios, claves y directorios se eliminarían, pero no modifica el sistema.

Después de confirmar los objetivos puede iniciarse la limpieza real:

.\Remove_VMwareTools.ps1 -ForceCleanup

La opción siguiente también elimina carpetas compartidas de VMware:

.\Remove_VMwareTools.ps1 -ForceCleanup -RemoveSharedVmwareFolder

Este último parámetro exige especial precaución. No debe utilizarse cuando el servidor conserva agentes de VMware Horizon, productos de copia de seguridad, componentes de seguridad u otras aplicaciones que compartan directorios con VMware Tools.

Comprobaciones después del reinicio

Windows debe reiniciarse cuando termine la limpieza. Algunos servicios no se eliminan por completo hasta que dejan de estar abiertos por procesos del sistema.

Después del arranque conviene comprobar:

  • Que VMware Tools ya no aparece en Aplicaciones instaladas.
  • Que los servicios VMTools y VGAuthService han desaparecido.
  • Que Windows arranca sin errores.
  • Que el disco del sistema sigue accesible.
  • Que la interfaz VirtIO conserva conectividad.
  • Que la dirección IP está configurada en el adaptador correcto.
  • Que QEMU Guest Agent está iniciado.
  • Que Proxmox detecta correctamente el agente invitado.
  • Que las aplicaciones del servidor funcionan con normalidad.
  • Que las copias de seguridad posteriores terminan correctamente.

Si Windows informa de que una dirección IP ya está asignada a otro adaptador, probablemente siga vinculada a la tarjeta VMXNET3 oculta. El Administrador de dispositivos permite mostrar dispositivos ocultos y retirar la interfaz antigua después de verificar que la tarjeta VirtIO funciona.

El script no elimina automáticamente los controladores de VMware almacenados en el DriverStore. Esta limitación es intencionada. Borrar un controlador por una coincidencia parcial en su nombre puede afectar al almacenamiento, la red o incluso al arranque.

Los controladores restantes deben revisarse individualmente antes de utilizar pnputil o el Administrador de dispositivos. Cuando exista cualquier duda, resulta más seguro dejarlos instalados que eliminar un paquete todavía necesario.

Preguntas frecuentes

¿Es obligatorio hacer una copia de seguridad antes de ejecutar el script?

Sí. La limpieza modifica servicios, claves del Registro y carpetas del sistema. Debe existir una copia completa o un clon recuperable y comprobarse el acceso mediante la consola de Proxmox.

¿Qué hacer si PowerShell indica que la ejecución de scripts está deshabilitada?

Puede habilitarse únicamente la sesión actual con Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass. Si el archivo procede de Internet también puede ser necesario ejecutar Unblock-File.

¿Qué ocurre si la política procede de Group Policy?

Las políticas MachinePolicy y UserPolicy pueden tener prioridad sobre los cambios locales. En un entorno corporativo debe consultarse al administrador responsable y seguirse la política de seguridad de la empresa.

¿El procedimiento garantiza que VMware Tools se eliminará sin problemas?

No. Las instalaciones, versiones y dependencias varían entre máquinas. El procedimiento se ofrece con fines informativos y debe probarse primero sobre una copia o clon, siempre con una vía clara de restauración.

Fuentes:

  • Script comunitario Remove_VMwareTools.ps1, publicado por broestls en GitHub Gist.
  • Microsoft Learn, documentación de Set-ExecutionPolicy, Get-ExecutionPolicy y Unblock-File.
  • Microsoft Learn, opciones de desinstalación y registro de actividad de msiexec.
  • Microsoft Learn, funcionamiento de sc.exe delete.
  • Proxmox VE, documentación sobre máquinas virtuales, VirtIO y QEMU Guest Agent.
  • Proyecto virtio-win, controladores paravirtualizados para Windows.

encuentra artículos

newsletter

Recibe toda la actualidad del sector tech y cloud en tu email de la mano de RevistaCloud.com.

Suscripción boletín

LO ÚLTIMO

Las últimas novedades de tecnología y cloud

Suscríbete gratis al boletín de Revista Cloud. Cada semana la actualidad en tu buzón.

Suscripción boletín
×