Please prepare any self-admin windows machines before triggering a hyperv conversion. OIT managed machines will have the preparation completed automatically.
If a windows machine is migrated without preparing it, you may run into the following problems:
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.
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.
Eventlogs detailing the steps are written to the application log under the source VmHypConv
<#
.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