Powershell - Migrate VMs from Hyper-V to vSphere

  • 18 October 2021
  • 8 comments
  • 104 views

Userlevel 1
Badge
[CmdletBinding()]
param (
# CSv file tab delimited with name and Vlan
[Parameter(Mandatory = $true)]
[string[]]
$CSV,

# The datacenter where you want to move the workload to.
[Parameter(Mandatory = $true)]
[string]
$Datacenter,

# The Cluster where you want to move the workload to.
[Parameter(Mandatory = $true)]
[string]
$Cluster,

# Errorlog
[Parameter()]
[string]
$ErrorLog = 'E:\Scripts\ErrorLog.txt',

# Enable error logging
[Parameter()]
[switch]
$LogErrors,

# Enable error logging
[Parameter()]
[Int32]
$HostIndex = 0
)

#$Global:ErrorLog = 'E:\Scripts\ErrorLog.txt'

function Write-ToLog {
[cmdletbinding()]
Param (
[string]
[parameter(ValueFromPipeline)]
$Text
)

if ($LogErrors) {
$timestamp = Get-Date
Write-Output "$timestamp :: $Text" | Tee-Object $ErrorLog -Append
}
}

function Move-Workload {
<#
.SYNOPSIS
Performs a migration of VMs running on Hyper-V to vSphere.
.DESCRIPTION
Move-Workload uses Veeam's Instant VM Recovery capability to migrate VMs from Hyper-V to Vsphere.
The script will accept one or multiple Computers/Servers to migrate. It will then lookup the latest restore
point for each Computer and start a Veeam Instant VM Recovery. The restored VM on vSphere will run
directly from the Backup Repository. After the Veeam Instant VM Recovery, a storage vMotion will be
initiated to migrate the VM to production storage and finalize the migration.
.PARAMETER ComputerName
One or more computer names or VM names.
.PARAMETER Datacenter
The Datacenter to where you want to migrate.
.PARAMETER Cluster
The Cluster in the Datacenter to where you want to migrate.
.PARAMETER ErrorLog
When used with -LogErrors, specifies the file path and name to which failed computer names will be written.
.PARAMETER LogErrors
Specify this switch to create a text log file of computers that could not be migrated.
.EXAMPLE
PS C:\> Get-Content "E:\Scripts\Computers.csv" | Move-Workload -Datacenter 'DCR2' -Cluster 'Workloads'
The command will read the list of ComputerNames/hostnames from the CSV file and then migrate each of them
to the Datacenter and Cluster provided.
.EXAMPLE
PS C:\> Move-Workload MAHD-BRU-946,DCRX-LWS-D908 -Datacenter 'DCR2' -Cluster 'Workloads' -LogErrors -Verbose
.INPUTS
Inputs (if any)
.OUTPUTS
Output (if any)
.NOTES
General notes
#>

[CmdletBinding()]
param (
# The name of the computer to migrate and Vlan
[Parameter(Mandatory = $true)]
[string[]]
$ComputerName,

# The Vlan to connect to
[Parameter(Mandatory = $true)]
[string[]]
$vlan,

# The datacenter where you want to move the workload to.
[Parameter(Mandatory = $true)]
[string]
$Datacenter,

# The Cluster where you want to move the workload to.
[Parameter(Mandatory = $true)]
[string]
$Cluster
)

begin {

}

process {
Write-ToLog "Latest available restore point for server $ComputerName was created on $($restorepoint.CreationTime)"

Write-ToLog "Selecting the most suitable ESXi host and Datastore based on available resources"
#$vmhost=(Get-Datacenter -Name $Datacenter | Get-Cluster -Name $Cluster |Get-VMHost -State Connected |Sort-Object $_.MemoryUsageMhz -Descending | Select-Object -First 1)
$vmhost=(Get-Datacenter -Name $Datacenter | Get-Cluster -Name $Cluster |Get-VMHost | Select-Object -Index $HostIndex)
$server = Get-VBRServer -Name $vmhost.name
$datastore = Find-VBRViDatastore -Server $server -Name "*HSA*" |Sort-Object -Property FreeSpace -Descending | Select-Object -First 1

Write-ToLog "Server $ComputerName will be migrated to VMware Host $($server.Name) and stored on Datastore $datastore"

Write-ToLog "Start Veeam Instant VM Recovery of server $ComputerName"
Start-VBRViComputerInstantRecovery -RestorePoint $restorepoint -Server $server -CacheDatastore $datastore -PowerOnAfterRestoring:$false -ConnectVMToNetwork:$true -Reason "VM migration"

Write-ToLog "Veeam Instant VM Recovery for server $ComputerName completed."

#Migrate to production site:
Write-ToLog "Starting migration of server $ComputerName to production Datastore"
$entity = Find-VBRViEntity -Name $ComputerName

Start-VBRQuickMigration -Entity $entity -server $server -datastore $datastore
Write-ToLog "Server $ComputerName has been moved to production Datastore"

#Stop Instant VM Recovery
Write-ToLog "Stopping Veeam Instant VM Recovery"
Get-VBRInstantRecovery |Where-Object{$_.VmName -like $ComputerName} |Stop-VBRInstantRecovery
Write-ToLog "Server $ComputerName has been successfully migrated"

#Change to correct VLAN
$VMwareNetwork = Import-csv ".\vlan.csv" |Where-Object {$_.vlanid -eq $vlan -or $_.system -match $vlan} |Select-Object -Expand Portgroupname
$NIC = Get-NetworkAdapter -VM $ComputerName
Set-NetworkAdapter -NetworkAdapter $NIC -Portgroup $VMwareNetwork -Confirm:$false

Write-ToLog "$ComputerName has been successfully migrated"

#Start-vm $ComputerName
}

end {

}
}

function Set-VMXNET3 {

<#
.SYNOPSIS
Short description
.DESCRIPTION
Long description
.EXAMPLE
PS C:\> <example usage>
Explanation of what the example does
.INPUTS
Inputs (if any)
.OUTPUTS
Output (if any)
.NOTES
General notes
#>

[CmdletBinding()]
param (
# CSv file tab delimited with name and Vlan
[Parameter(Mandatory = $true)]
[string[]]
$ComputerName,

# Parameter help description
[Parameter()]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]
$GuestCred = [System.Management.Automation.PSCredential]::Empty

)

begin {

}

process {
Write-ToLog "Determining if the Guest OS is Windows or Linux"

$VM = Get-VM -Name $ComputerName

if ($VM.GuestId -like "*Windows*") {
Write-ToLog "The Guest OS is Windows"

Write-ToLog "Starting VM $ComputerName"
Start-VM $ComputerName -Verbose

#Waiting for VMTools to be installed and running
while ($VM.ExtensionData.Guest.ToolsRunningStatus -ne "guestToolsRunning") {
Write-ToLog "Waiting for VMTools installation via SCCM"
Start-Sleep 15
$VM = Get-VM -Name $ComputerName
}

Write-ToLog "VMWare Tools installed and running on $Computername"

try {
#Backup network configuration
Write-ToLog "Taking backup of network configuration"
Invoke-VMScript -VM $ComputerName -ScriptText 'netsh interface dump > "c:\temp\netcfg.dat"' -GuestCredential $GuestCred -Verbose

$ScriptRun = $true

}
catch {
Write-ToLog "Unable to take network configuration backup"
}

if ($ScriptRun) {
try {
Write-ToLog "Checking for network configuration backup file"
Invoke-VMScript -VM $ComputerName -ScriptText 'dir "c:\temp\netcfg.dat"' -GuestCredential $GuestCred -Verbose

$ScriptRun = $true
}
catch {
Write-ToLog "Network configuration backup file not present"
}
}

if ($ScriptRun) {
Write-ToLog "Network configuration backup created"

Write-ToLog "Shutting down VM $ComputerName"
$VM | Shutdown-VMGuest -Confirm:$false

}

while ($VM.PowerState -ne "PoweredOff") {
Write-ToLog "Waiting for $ComputerName to shutdown"
Start-Sleep 5
$VM = Get-VM -Name $ComputerName

}

Write-ToLog "$Computername has been shutdown"

#Change E1000 to VMXNET3
Write-ToLog "Change NIC from E1000 to VMXNET3"
Get-VM -Name $ComputerName |Get-NetworkAdapter |Set-NetworkAdapter -Type Vmxnet3 -Confirm:$false -Verbose

Write-ToLog "Starting VM $ComputerName"
Start-VM -VM $ComputerName

while ($VM.ExtensionData.Guest.ToolsRunningStatus -ne "guestToolsRunning") {
Write-ToLog "Waiting for $ComputerName to Power On"
Start-Sleep 5
$VM = Get-VM -Name $ComputerName

}

#Restore network configuration on new NICs
Write-ToLog "Restore network configuration to the new VMXNET3 network adapter"
Invoke-VMScript -VM $ComputerName -ScriptText 'netsh exec "c:\temp\netcfg.dat"' -Verbose

}
else{
Write-ToLog "The Guest OS is Linux"
Write-ToLog "Setting VM Guest OS to CentOS7 "
get-vm -name $ComputerName |set-vm -guestid "centos7_64Guest" -Confirm:$false

#Change E1000 to VMXNET3
Write-ToLog "Changing NIC from E1000 to VMXNET3"
Get-VM -Name $ComputerName |Get-NetworkAdapter |Set-NetworkAdapter -Type Vmxnet3 -Confirm:$false

#Move VM to LNX folder
Write-ToLog "Moving VM to LNX folder"
$VMFolder = Get-Folder -Type VM -Name "LNX"
Get-VM -Name $ComputerName | Move-VM -Destination $VMFolder

Write-ToLog "Starting VM $ComputerName"
Start-VM $ComputerName
}
}

end {

}
}

function Set-VMHardware {
[CmdletBinding()]
param (
#
[Parameter(Mandatory = $true)]
[string]
$ComputerName
)

begin {

}

process {
Write-ToLog "Change VM $ComputerName Hardware version"
Set-VM $ComputerName -HardwareVersion vmx-19 -Confirm:$false -Verbose
}

end {

}
}

function Remove-FromHyperVBackup {
[CmdletBinding()]
param (
#
[Parameter(Mandatory = $true)]
[string]
$ComputerName
)

begin {

}

process {
#Exclude VM from Hyper-V Backup job
Get-VBRJob |Where-Object{$_.SourceType -eq "HyperV"} | Get-VBRJobObject -Name $ComputerName | Remove-VBRJobObject -Verbose
Write-ToLog "VM $ComputerName has been migrated and excluded from the Veeam Hyper-V Backup Job"

}

end {

}
}

function Start-Migration {
[CmdletBinding()]
param (

)

begin {
$StartTime = Get-Date
Write-ToLog "`n`n"
Write-ToLog "Start Migration at $StartTime"
Write-ToLog "Log name is $ErrorLog"
Write-ToLog "CSV file with list of computers used to migrate is $CSV"
Write-ToLog ("=" * 80)

#Connect to local Veeam Backup Server
Connect-VBRServer

#Connect to vSphere
$credVC = Get-Credential -Message "Provide credentials to connect to vCenter"

Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false -Scope User -ParticipateInCeip $false
Connect-VIServer -Server vcenter.labo.be -credential $credVC

#Store Windows Guest Credentials
$GuestCred = Get-Credential -Message "Provide Windows Guest Credentials or cancel if Linux"

}

process {

$computers = Import-csv -delimiter "`t" $csv

foreach ($Computer in $Computers) {

$ComputerName = $computer.name
$vlan = $computer.vlan

Write-ToLog "Querying computer $ComputerName for restorepoints"
$VBRBackup = Get-VBRBackup |Where-Object {$_.JobSourceType -eq "HyperV"}
$restorepoint = $VBRBackup | Get-VBRRestorePoint -Name $ComputerName | Sort-Object creationtime -Descending | Select-Object -First 1

if ($null -eq $restorepoint) {
Write-Warning "Could not start migration for server $ComputerName. No restore points found."

if ($LogErrors) {
Write-ToLog "WARNING :: $ComputerName No restore points found"
}
}
else {
Move-Workload -ComputerName $ComputerName -vlan $vlan -Datacenter $Datacenter -Cluster $Cluster -Verbose

Set-VMHardware -ComputerName $ComputerName -Verbose

Set-VMXNET3 -ComputerName $ComputerName -GuestCred $GuestCred -Verbose

Remove-FromHyperVBackup -ComputerName $ComputerName -Verbose
}
}


}

end {
#Disconnect from local Veeam Backup Server
Disconnect-VBRServer

#Disconnect from vCenter
Disconnect-VIServer -Confirm:$false

}
}

$data
Start-Migration

Hi guys

I wanted to share a powershell script we made for a customer to migrate VMs from Hyper-V to vSphere. It’s not perfect but it gets the job done.

There are still some issues with the transformation of E1000 to VMXNET3. The Linux team managed to get this working by using puppet. I’m still working on the Windows part.

As the Veeam environment is not domain joined, we cannot use remote powershell.

VMware Tools on Windows machines are pushed by SCCM.

 

 

 


8 comments

Userlevel 7
Badge +5

Nice script, thank you @filipsmeets :thumbsup_tone3:

Userlevel 7
Badge +6

Wow another one to add to the library.  Nice one @filipsmeets 

Userlevel 7
Badge +3

Nice share, just to add another info for this thread: vcenter converter / standalone converter do the V2V trick too.

Userlevel 1
Badge

Thx all.

I created another script to automate the E1000 to VMXNET3 change on the Windows machines. You will have to place this script locally on the machine and then schedule a task so that it runs at startup.

You can do this for example with a Group Policy.

$path = "C:\TEMP\NetIP.json"

if (Test-Path $path) {
Write-Host "IP Configuration backup already exists. Skipping ..."

}
else {
$NetIP = Get-NetIPConfiguration

#Check if IP address is not APIPA
if ($NetIP.IPv4Address.IPAddress -inotlike "169.25*") {
$myObject = [PSCustomObject]@{
IPAddress = $NetIP.IPv4Address.IPAddress
Subnet = $NetIP.IPv4Address.PrefixLength
Gateway = $NetIP.IPv4DefaultGateway.NextHop
DNS1 = $NetIP.DNSServer.ServerAddresses[0]
DNS2 = $NetIP.DNSServer.ServerAddresses[1]
}

$myObject | ConvertTo-Json -depth 1 | Out-File $path

}
}

$hypervisor = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object Manufacturer

#Check if VM is running on VMware
if ($hypervisor -match "vmware") {

##Get CD-ROM driveletter and path to VMTools installer
$cddrive = Get-Volume | Where-Object{$_.DriveType -eq "CD-ROM"}
$installer = $cddrive.DriveLetter + ":\setup64.exe"
$arg = "/S /v /qn ADDLOCAL=ALL"

#Check if VMTools ISO is mounted
if (Test-Path $installer) {
Start-Process $installer -ArgumentList $arg -Wait
}

#Check if there is a backup of the IP configuration and restore to new VMXNET3
elseif (Test-Path $path) {
$myObject = Get-Content $path | ConvertFrom-Json
$Alias = Get-NetIPConfiguration | Where-Object {$_.InterfaceDescription -like "*vmxnet*" } | Select-Object -ExpandProperty InterfaceAlias
New-NetIPAddress -InterfaceAlias $alias -IPAddress $myObject.IPAddress -PrefixLength $myObject.Subnet -DefaultGateway $myObject.Gateway
Set-DnsClientServerAddress -InterfaceAlias $alias -ServerAddress ($myObject.DNS1,$myObject.DNS2)
}
}

For the scheduled task action, use:

Program: powershell.exe

Arguments: -ExecutionPolicy Bypass -file "C:\Temp\InstallVMTools.ps1”

 

Userlevel 7
Badge +6

Thx all.

I created another script to automate the E1000 to VMXNET3 change on the Windows machines. You will have to place this script locally on the machine and then schedule a task so that it runs at startup.

You can do this for example with a Group Policy.

$path = "C:\TEMP\NetIP.json"

if (Test-Path $path) {
Write-Host "IP Configuration backup already exists. Skipping ..."

}
else {
$NetIP = Get-NetIPConfiguration

#Check if IP address is not APIPA
if ($NetIP.IPv4Address.IPAddress -inotlike "169.25*") {
$myObject = [PSCustomObject]@{
IPAddress = $NetIP.IPv4Address.IPAddress
Subnet = $NetIP.IPv4Address.PrefixLength
Gateway = $NetIP.IPv4DefaultGateway.NextHop
DNS1 = $NetIP.DNSServer.ServerAddresses[0]
DNS2 = $NetIP.DNSServer.ServerAddresses[1]
}

$myObject | ConvertTo-Json -depth 1 | Out-File $path

}
}

$hypervisor = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object Manufacturer

#Check if VM is running on VMware
if ($hypervisor -match "vmware") {

##Get CD-ROM driveletter and path to VMTools installer
$cddrive = Get-Volume | Where-Object{$_.DriveType -eq "CD-ROM"}
$installer = $cddrive.DriveLetter + ":\setup64.exe"
$arg = "/S /v /qn ADDLOCAL=ALL"

#Check if VMTools ISO is mounted
if (Test-Path $installer) {
Start-Process $installer -ArgumentList $arg -Wait
}

#Check if there is a backup of the IP configuration and restore to new VMXNET3
elseif (Test-Path $path) {
$myObject = Get-Content $path | ConvertFrom-Json
$Alias = Get-NetIPConfiguration | Where-Object {$_.InterfaceDescription -like "*vmxnet*" } | Select-Object -ExpandProperty InterfaceAlias
New-NetIPAddress -InterfaceAlias $alias -IPAddress $myObject.IPAddress -PrefixLength $myObject.Subnet -DefaultGateway $myObject.Gateway
Set-DnsClientServerAddress -InterfaceAlias $alias -ServerAddress ($myObject.DNS1,$myObject.DNS2)
}
}

For the scheduled task action, use:

Program: powershell.exe

Arguments: -ExecutionPolicy Bypass -file "C:\Temp\InstallVMTools.ps1”

 

This is great and completes things.  Nice work.

Userlevel 7
Badge +5

Nice, good addition to your script. :thumbsup_tone3:

Userlevel 7
Badge +3

Nice work man!

Thank you so much! This was such a great help as we are in the exact same process of migrating quite a lot of Hyper-V VMs to Vmware.

Comment