De VMware a Proxmox: guía de migración paso a paso
Tras el giro de licencias de Broadcom, miles de empresas buscan salida. Cómo planificar la migración, qué herramientas de P2V/V2V usar y los errores que arruinan un proyecto.
-ForceCleanup solo intenta la desinstalación MSI; la limpieza directa exige activarla y simularse antes con -WhatIf.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.
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.
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. Si aún estás afinando tu estrategia de respaldo, conviene repasar la regla 3-2-1 con copias inmutables.
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:
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.
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.
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.
.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.
.NOTES
Script cortesía de https://cloudprivado.com
Basado en el script comunitario de broestls (GitHub Gist).
#>
[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 "Script cortesía de https://cloudprivado.com"
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"
Write-Host "Script cortesía de https://cloudprivado.com"
}
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.
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:
VMTools y VGAuthService han desaparecido.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.
Retirar VMware Tools es la última milla de una migración desde VMware, no un paso aislado. Tiene sentido hacerlo cuando la VM ya arranca sobre Proxmox con VirtIO y el QEMU Guest Agent funcionando, no antes. Si todavía estás planificando el traslado del parque completo, el marco general está en nuestra guía de migración de VMware a Proxmox y, si vienes de otro hipervisor, en la de Hyper-V a Proxmox. Y si la decisión aún es económica, conviene mirar el coste real de migrar de VMware a Proxmox antes de mover una sola máquina.
¿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.
Remove_VMwareTools.ps1, publicado por broestls en GitHub Gist.Set-ExecutionPolicy, Get-ExecutionPolicy y Unblock-File.msiexec.sc.exe delete.¿Estás migrando tu parque de VMware a un cloud privado sobre Proxmox y quieres hacerlo sin dejar restos ni sorpresas? En Cloud Privado planificamos y ejecutamos la migración completa —conversión de discos, drivers VirtIO, guest agent y limpieza del invitado—. Empieza por nuestra comparativa de cloud o hablemos de tu proyecto.