Preparing a Windows VM for Conversion from VMware to Hyper-V

Please prepare any self-admin windows machines before triggering a hyperv conversion.  OIT managed machines will have the preparation completed automatically.

NOTE: If you are viewing this information on the OIT web site, we recommend visiting the ServiceNow knowledge article for the correct formatting:   https://duke.service-now.com/kb_view.do?sysparm_article=KB0039009

Alternately, the code can be read ;/ downloaded from 

https://gitlab.oit.duke.edu/kb_store/windows_hyperv

Why this matters: 

  If a windows machine is migrated without preparing it, you may run into the following problems:

 

  • Networking: If the machine is on a static IP, the machine will not have networking until someone logs in and reassigns the IP.
  • MFA: If Duo is set to not fail-open and networking is not configured, the machine will not allow local console access.
  • Drives: If the machine has more than one harddrive, all secondary drives will be converted, but they will bet set as offline until you manually bring them online and re-assign drive letters, and any software reliant on secondary drives will fail until those drives are reassigned.
  • VMWare Tools: If vmware tools is left installed, it will not be able to be uninstalled once the machine is converted.   NOTE!!  Uninstalling vmware tools on windows 2016 or 2019 will immediately disable networking.  If you are not going to immediately shutdown the machine and complete the transfer, it may be better to skip the uninstall and manually remove the software after the fact.  Additionally, if you revert and reinstall vmware tools, the system may not retain it's original networking settings and you may have to manually re-ip the machine.   As such, the script below is only uninstalling vmware tools for 2022 and 2025 machines.  
    We have provided an manual uninstall script for vmware toosl that can be run once the machine is in hyper-v.  It is located in https://gitlab.oit.duke.edu/kb_store/windows_hyperv with the downloadable version of the conversion script.

 

OIT is providing a script that sets the ip address, drives, uninstalls VMware, and sets duo to fail open.  It is included below.  You are not required to run the script if you would prefer to address the previous issues using another solution.

The script provided can be run ahead of time and your machine will continue to operate fine on VMware, but console access might be difficult due to the uninstall of VMware tools.  Remote desktop will not be affected. 

If you make any changes to IP or your drive layout after you have run the preparation script, please re-run the script before converting.

To run the provided PowerShell script:

If you are running the script locally, make sure you are using an elevated-as-administrator PowerShell prompt.  If you are running it immediately before starting the conversion, you can run it with the flag  -Shutdownmachine which gracefully shutdown the machine at the end of the script.  If you do not use that flag, the clockworks conversion will force-stop the machine during conversion.

 

Example run:

 HpvConvertPrep.ps1 -ShutdownMachine

 

 Eventlogs detailing the steps are written to the application log under the source VmHypConv


Script:
https://gitlab.oit.duke.edu/kb_store/windows_hyperv/-/blob/main/HpvConvertPrep.ps1?ref_type=heads

<#
.SYNOPSIS
    sets the windows machine state to be ready for conversion from vmware to hyperv
.DESCRIPTION
    Script sets local information and configuress a startup script to fix any conversion issues on hyperv - requires local access or winrm
.EXAMPLE
    PS C:\> HpvConvertPrep.ps1 -ShutdownMachine
    Machine will log to eventlog and shut down if switch is set
.INPUTS
    switch for optional shutdwon
.OUTPUTS
    verbose info to screen, eventlog entries, no returns.
.NOTES
    General notes:
        - Created: 2025-08-29 - jch
        - Modified: 2025-09-24 - gb93
#>

[CmdletBinding()]
    param (
    [switch]$ShutdownMachine
)

    New-EventLog -LN Application -SRC VmHypConv -EA 0
    Write-EventLog -EventId 4255 -LogName Application -Message "Preparing machine for hyperv conversion" -Source VmHypConv -EntryType Information
    write-verbose "running locally"

    write-output "gathering nic and ip data"
    $NicDataFile = "C:\windows\temp\OriginalIPConfig.json"
    $DriveDataFile = "C:\windows\temp\OriginalDriveConfig.json"
    $NewConfigScript = "C:\windows\temp\NewConfig.ps1"
    $TaskName = "ReAssignIPandDisk"
    $IpConfigs = @()
    $Assigned = Get-NetIPConfiguration | Where-Object {$_.InterfaceAlias -notlike "*hyper*"}
    $Assigned | ForEach-Object {
        $Adapter = $_
        $Ip = $Adapter.IPv4Address.ipaddress
        $Nic = Get-netadapter -interfaceIndex $Adapter.InterfaceIndex
        $DNS = $Adapter.dnsserver.serveraddresses
        # Get netipconfiguration doesn't return prefix length, so we have to look it up separately
        Write-EventLog -EventId 4255 -LogName Application -Message "Retrieving PrefixLength for IP $Ip on InterfaceIndex $($Adapter.InterfaceIndex)" -Source VmHypConv -EntryType Information
        $PrefixLength = Get-NetIPAddress -IPAddress $Ip | Select-Object -Expand PrefixLength
        if ( -not $PrefixLength ) {
            throw "Could not determine PrefixLength for IP $Ip on InterfaceIndex $($Adapter.InterfaceIndex). Exiting..."
        }
        $IpConfigs += [PSCustomObject]@{
            InterfaceIndex = $Adapter.InterfaceIndex
            MACAddress     = $Nic.MacAddress
            IPAddress      = $Ip
            PrefixLength   = $PrefixLength
            DNS            = $DNS
            DefaultGateway = $Adapter.IPv4DefaultGateway.NextHop
        }
    }

    write-output "saving ipconfig to JSON"
    $IpConfigs | ConvertTo-Json -Depth 3 | Set-Content -Path $NicDataFile -force
    Write-EventLog -EventId 4250 -LogName Application -Message "Saved IPConfig to c:\windows\temp\OriginalIPConfig.json" -Source VmHypConv -EntryType Information

    write-output "gathering disk layout data"
    $DriveLayout = @()
    $DiskList = get-disk
    ForEach ($Disk in $DiskList) {
        If ($Disk.number -ne '0') {
            Write-EventLog -EventId 4250 -LogName Application -Message "Saving any extra disk to c:\windows\temp\OriginalDiskConfig.json" -Source VmHypConv -EntryType Information
            $Partition = get-partition -Disknumber $Disk.number | Where-Object { $_.DriveLetter }
            $DriveLayout += [PSCustomObject]@{
                DiskNumber     = $Disk.number
                PartitionNumber = $Partition.PartitionNumber
                DriveLetter     = $Partition.DriveLetter
            }
        }
    }

    write-output "saving drivelayout to JSON"
    $DriveLayout | ConvertTo-Json -Depth 3 | Set-Content -Path $DriveDataFile -force

    write-output "setting up script for scheduled task"
    $TaskScriptContent = @"
    New-EventLog -LN Application -SRC VmHypConv -EA 0
    Write-EventLog -EventId 4251 -LogName Application -Message "Running SchedTask script for post conversion setup" -Source VmHypConv -EntryType Information

    `$vmloc = Get-ComputerInfo | Select CsManufacturer, CsModel
    If (`$vmloc.CsManufacturer -like "*vmware*") {
        Write-EventLog -EventId 4256 -LogName Application -Message "Skipping post conversion steps because still on vmware" -Source VmHypConv -EntryType Information
        return }

    `$IpConfigs = Get-Content -Path $NicDataFile | ConvertFrom-Json
    `$CurrentAdapters = Get-NetAdapter | Where-Object { `$_.Status -eq "Up" }

    ForEach (`$OrigIp in `$IpConfigs) {
        #Pointing to same nic since that's getting reassigned with the conversion
        `$TargetNic = `$CurrentAdapters | Where-Object { `$_.MacAddress -eq `$OrigIp.MACAddress -and `$_.Name -notlike "*vether*"}

        # Assign IP if it's not already set correctly
        `$Existing = Get-NetIPAddress -InterfaceIndex `$TargetNic.InterfaceIndex -IPAddress `$OrigIp.IPAddress -ErrorAction SilentlyContinue
        if (`$OrigIp.Ipaddress -ne `$Existing.IpAddress) {
            New-NetIPAddress -InterfaceIndex `$TargetNic.InterfaceIndex -IPAddress `$OrigIp.IPAddress -PrefixLength `$OrigIp.PrefixLength -DefaultGateway `$OrigIp.DefaultGateway -ErrorAction SilentlyContinue
            Set-DnsClientServerAddress -InterfaceIndex  `$TargetNic.InterfaceIndex -ServerAddresses `$OrigIp.DNS
        }
    }
    Write-EventLog -EventId 4252 -LogName Application -Message "RE-assigning IP" -Source VmHypConv -EntryType Information

    #drop cd drive and bring any other disks back online
    `$CDDrive = Get-ciminstance -classname win32_Volume  -Filter "DriveType = 5"
    `$CDDrive | Set-CimInstance -Property @{DriveLetter ='Z:'}

    #Check if any letters need moved
    `$DriveLayout = Get-Content -Path $DriveDataFile | ConvertFrom-Json
    Foreach (`$Drive in `$DriveLayout) {
        `$OfflineDisk = Get-disk -number `$Drive.DiskNumber
        If (`$OfflineDisk.OperationalStatus -Eq "Offline") {
            `$OfflineDisk | Set-Disk -IsOffline `$False
        }
        `$NewDrive = Get-Partition -DiskNumber `$Drive.DiskNumber -PartitionNumber `$Drive.PartitionNumber
        If (`$NewDrive.Driveletter -ne `$Drive.Driveletter) {
            `$NewDrive | Set-Partition -NewDriveLetter `$Drive.DriveLetter
        }
    }

    #set all those drives back to writeable
    (Get-disk | where { `$_.IsReadOnly  -eq `$true}) | foreach { set-disk -Number `$_.Number -IsReadOnly `$false }
    Write-EventLog -EventId 4252 -LogName Application -Message "Re-assigning drives" -Source VmHypConv -EntryType Information

    #disable vmware services if they are still around
    `$vmservice = get-service -DisplayName "vmware*"
    Foreach (`$service in `$vmservice) {
        stop-service `$service
        Set-service `$service.name -StartupType Disabled
    }

    #turn of scheduled task as next to last step, then reboot
    Disable-ScheduledTask -TaskName "$TaskName"
    Write-EventLog -EventId 4252 -LogName Application -Message "Finished Task Scheduler script" -Source VmHypConv -EntryType Information
    If ((get-scheduledtask -Taskname "$TaskName").State -eq "Disabled") { restart-computer -force}
"@

    $TaskScriptContent | Set-Content -Path $NewConfigScript -force
    Write-EventLog -EventId 4251 -LogName Application -Message "Created script for post conversion setup" -Source VmHypConv -EntryType Information

    write-output "Creating and registering the scheduled task"
    Register-ScheduledTask -TaskName $TaskName `
        -Action (New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"$NewConfigScript`"") `
        -Trigger (New-ScheduledTaskTrigger -AtStartup) `
        -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest) `
        -Settings (New-ScheduledTaskSettingsSet  -ExecutionTimeLimit (New-TimeSpan -Minutes 5) ) -force

    Write-EventLog -EventId 4251 -LogName Application -Message "Created Scheduled Task" -Source VmHypConv -EntryType Information

    write-output "Turning on Fail open for DUO"
    # only an issue if networking doesn't pick back up, but a good safety measure
    If (Test-Path "hklm:\software\Duo Security\DuoCredProv") {
        reg add "hklm\software\Duo Security\DuoCredProv" /v FailOpen /d 1 /f
        Write-EventLog -EventId 4253 -LogName Application -Message "Duo set to Fail Open" -Source VmHypConv -EntryType Information
    }
    else {
        Write-EventLog -EventId 4253 -LogName Application -Message "Duo reg keys not found" -Source VmHypConv -EntryType Information
    }


    ### VMXNET3 section ###
    $vm_tools_package = Get-Package "vmware tools" -ea 0
    if ( -not $vm_tools_package ) {
        throw "MISSING VMWARE TOOLS package. Check vm tools installation. Every managed system should have vm tools. Something is wrong."
    }

    # if it's not Server 2022 or 2025...
    if ((Get-ComputerInfo).WindowsProductName -notmatch "Server (2022|2025)") {
        $Msg = "WARNING: Host is Windows Server 2019 or 2016 - skipping vmware removal due to vmxnet3 driver issues"
        Write-Output $Msg
        Write-EventLog -EventId 4252 -LogName Application -Message $Msg -Source VmHypConv -EntryType Information

    } else {
        write-output "uninstalling vmware tools"
        try { Get-Package "vmware tools"| Uninstall-Package -force -verbose -ErrorAction Stop} catch {
            Write-EventLog -EventId 4253 -LogName Application -Message "Failure removing vmware tools" -Source VmHypConv -EntryType Information
            Write-Error "vmware uninstall failed" -ErrorAction Stop
        }
        Write-EventLog -EventId 4253 -LogName Application -Message "Uninstalled VMWare Tools" -Source VmHypConv -EntryType Information
    }

    write-output "forcing machine off"

    If ($ShutdownMachine) {
        write-verbose "turning off machine"
        Write-EventLog -EventId 4253 -LogName Application -Message "Turning off computer" -Source VmHypConv -EntryType Information
        stop-computer -force
    } ELSE { write-verbose "Machine will be hard stopped if you have not cleanly shut it down" }
#end
 

Article number: KB0039009

Valid to: October 20, 2026