PowerCLI Script to migrate VMs from one vCenter to another
- Ben Liebowitz
- 23
- 5397
Hey everyone, this is another guest post from Ben Liebowitz, Matt’s brother.
Before I get into the script, I’ll give you some background… We are working on migrating VMs off of our vSphere 4.1 environment to our new vSphere 5.5 environment. The 4.1 environment is using the Nexus 1000v Virtual Switch while the 5.5 environment is using a vDS switch.
We found the easiest way to migrate the VMs was to remove them from inventory on the 4.1 side and then add them to inventory on the 5.5 side (as all the hosts in each environment saw the same storage). Doing this manually for approx 250 VMs was painful, so I scripted the process.
I started with exporting the VM’s information regarding its name, datastore location, Network Portgroup(s), & folder location. I found that by using the folder location in Get-VM, I was just getting it’s current folder name and not the full path. Being I have folder name duplicates (one in an Internal Folder and the same structure in a DMZ folder) I needed a way to list the full path. Thankfully, I found a Function written by Luc Dekens (@LucD22 / www.lucd.info) that he called #BlueFolderPath that solved my problem.
After that, I was able to connect to the 4.1 vCenter, export my list, shutdown the VM, remove it from inventory, then connect to my 5.5 vCenter, browse that same datastore, add the VM to inventory on the other side, move the VM to its original folder location, set the proper network portgroup, upgrade the virtual hardware, power the VM on, and finally upgrade the VMware tools.
———————————————-
# Define Variables $vcenter41 = “your existing vcenter” $vcenter55 = “your new vcenter” $vsphere55host = “one of the vSphere 5.5 hosts to migrate to” $datacenter = “datacenter name” $csvfile = “path to csv” #Build the BlueFolderPath function New-VIProperty -Name 'FullPath' -ObjectType 'VirtualMachine' -Value { param($vm) $current = Get-View $vm.ExtensionData.Parent $path = "" do { $parent = $current if($parent.Name -ne "vm"){$path = $parent.Name + "/" + $path} $current = Get-View $current.Parent } while ($current.Parent -ne $null) $path.TrimEnd('/') } -Force | Out-Null #Build the Get-folderbypath function function Get-FolderByPath { <# .SYNOPSIS Retrieve folders by giving a path .DESCRIPTION The function will retrieve a folder by it's path. The path can contain any type of leave (folder or datacenter). .NOTES Author: Luc Dekens .PARAMETER Path The path to the folder. This is a required parameter. .PARAMETER Separator The character that is used to separate the leaves in the path. The default is '/' .EXAMPLE PS> Get-FolderByPath -Path "Folder1/Datacenter/Folder2" .EXAMPLE PS> Get-FolderByPath -Path "Folder1>Folder2" -Separator '>' #> param( [CmdletBinding()] [parameter(Mandatory = $true)] [System.String[]]${Path}, [char]${Separator} = '/' ) process{ if((Get-PowerCLIConfiguration).DefaultVIServerMode -eq "Multiple"){ $vcs = $defaultVIServers } else{ $vcs = $defaultVIServers[0] } foreach($vc in $vcs){ foreach($strPath in $Path){ $root = Get-Folder -Name Datacenters -Server $vc $strPath.TrimStart($Separator).Split($Separator) | %{ $root = Get-Inventory -Name $_ -Location $root -Server $vc -NoRecursion if((Get-Inventory -Location $root -NoRecursion | Select -ExpandProperty Name) -contains "vm"){ $root = Get-Inventory -Name "vm" -Location $root -Server $vc -NoRecursion } } $root | where {$_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.FolderImpl]}|%{ Get-Folder -Name $_.Name -Location $root.Parent -Server $vc } } } } } # Enable connections to multiple vCenters Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Scope AllUsers -Confirm:$false # Connect to 4.1 vCenter connect-viserver vsphere41 # export list of VMs to Migrate to CSV Get-Datacenter -name "datacenter" | Get-VM | Select Name, @{N="Datastore";E={Get-Datastore -VM $_ | Select -ExpandProperty Name}}, @{N="Network";E={Get-VirtualPortgroup -VM $_ | Select -ExpandProperty Name}}, @{N="Parent";E={$_.FullPath}} | Export-Csv "c:\migrate.csv" -NoTypeinformation -UseCulture # Get VMs to Migrate $migrate = Get-Datacenter -name "datacenter" # Shutdown VMs (cleanly) $migrate | Get-VM | Shutdown-VMGuest -Confirm:$false # Wait for VMs to shutdown start-sleep -s 60 # Remove VMs from Inventory Get-datacenter -name "datacenter" | Get-Inventory | Remove-Inventory -Confirm:$false # Connect to 5.5 vCenter connect-viserver vsphere55 # Set a host in vcenter5.5 to add them to. $esxhost = "vsphere01.domain.local" ## Add backto inventory & Change network for VMs foreach ($row in (Import-Csv $csvfile)) { $vmName = $row.Name $vmxfile = "[$($row.Datastore)] $($vmName)/$($vmName).vmx" $vmNewVM = New-VM -VMFilePath $vmxfile -VMhost $esxhost # Move VM to existing folder locaiton Move-VM -VM $row.Name -Destination (get-folderbypath $row.Parent) # Change Network Portgroup $vm = get-vm -Name $row.Name Get-NetworkAdapter -VM $vm | Set-NetworkAdapter -NetworkName $row.Network -Confirm:$false # Upgrade Virtual Hardware Set-VM -VM $vmName -Version v10 # Poweron VMs Start-VM -VM $row.Name -Confirm:$false | wait-tools Get-VMQuestion -VM $vm | Set-VMQuestion –Option "I moved it" -confirm:$false # Update VMware Tools update-tools $vmName -Confirm:$false start-sleep -s 300 } ## end foreach
———————————————-
Enjoy!
23 thoughts on “PowerCLI Script to migrate VMs from one vCenter to another”
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
Hi Matt,
I’m working on a script to migrate from vSphere 5.0 to 6.0 and came across yours which for the most part works (thank you BTW) but I’m seeing the following error when moving the VM to the destination folder. Thoughts?
Move-VM : Cannot convert ‘System.Object[]’ to the type ‘VMware.VimAutomation.ViCore.Types.V1.Inventory.VICo
ntainer’ required by parameter ‘Destination’. Specified method is not supported.
At C:\Users\****\VMWare\Scripts\Migration\migrate_guests.ps1:131 char:35
+ Move-VM -VM $row.Name -Destination <<<< (get-folderbypath $row.Parent)
+ CategoryInfo : InvalidArgument: (:) [Move-VM], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM
Regards,
Adrian
Hi Adrian,
What folder is the VM you’re attempting to migrate in? Did you look at the csv to see what is listed in the Folder column.
Yes, I do see the folder path in the csv and I confirmed the same path (folder structure) exists in the destination vCenter 6.0 so I don’t think that’s the issue.
I made the following changes to see what value was being returned by get-folderbypath $row.Parent
$DestFolder = get-folderbypath $row.Parent
Move-VM -VM $row.Name -Destination $DestFolder
When I debug the script I see 2 entries being returned for the $DestFolder variable.
Hi,
why do you have the variable $vsphere55host in the start of the script, then later in the script hardcoded it? ($esxhost = “vsphere01.domain.local”)
Also looking at using it for VC6 migration, so also awating you reply on Adrian’s question…..
Cheers
Erik
Hi Erik,
Unfortunately, this is a script I pieced together by taking other scripts I had found and put them all together. As I was very new at scripting when I put this together, I didn’t pickup on the variable being used in one place and not in another. I apologize about that.
As for Adrian’s comment, I haven’t run this script against a 5.5 environment. This script was written to move my VMs from 4.1 to 5.5, so there may be differences in the way some of the functions that were created run in 5.5. You may need to reach out to the author’s of those functions. I got them from here: http://www.lucd.info/2012/05/18/folder-by-path/
Sorry, I’m not an expert coder, just trying to share what I’ve learned/put together.
Thanks,
– Ben
I actually ended up modifying the original script in a few places and got it working for what I needed. Don’t want to take credit for Ben and LucD’s work. I don’t mind sharing, let me know if its cool to post here.
Would you mind sharing your script? I’m currently in the same boat
Sorry for the delayed reply, you’re more than welcome to post it here!
Here is the modified script. Basically I just wanted to migrate a selected set of VMs (at a time) by specifying the following parameters in a source.csv file e.g:
SrcVMName SrcCluster DestNetwork
win-test01.dev.foo.com LabCluster WebNetwork
The script will then gather more details on the VMs in the 5.0 environment and create/populate a migrate.csv file which it will use for execution. In this case, I’m also changing networks when migrating to the 6.0 environment.
———————————
# This script was modified from http://www.thelowercasew.com/powercli-script-to-migrate-vms-from-one-vcenter-to-another
# Modified by Adrian on 2015/11/09
# Define Variables. These will need to be updated for Production
$vcenter5 = “vcenter5.dev.foo.com”
$vcenter6 = “vcenter6.dev.foo.com”
$csvfile = “C:\tmp\migrate.csv”
$source = “C:\tmp\source.csv”
$LogFile = “C:\tmp\Logfile.txt”
“Create if doesn’t exist” | Out-File $LogFile -Append
#Clear contents of the log file for new migrations
Clear-Content $LogFile
# Build the BlueFolderPath function
New-VIProperty -Name ‘FullPath’ -ObjectType ‘VirtualMachine’ -Value {
param($vm)
$current = Get-View $vm.ExtensionData.Parent
$path = “”
do {
$parent = $current
if($parent.Name -ne “vm”){$path = $parent.Name + “/” + $path}
$current = Get-View $current.Parent
} while ($current.Parent -ne $null)
$path.TrimEnd(‘/’)
} -Force | Out-Null
# Build the Get-folderbypath function
function Get-FolderByPath {
Get-FolderByPath -Path “Folder1/Datacenter/Folder2”
.EXAMPLE
PS> Get-FolderByPath -Path “Folder1>Folder2” -Separator ‘>’
#>
param(
[CmdletBinding()]
[parameter(Mandatory = $true)]
[System.String[]]${Path},
[char]${Separator} = ‘/’
)
process{
$vc = $vcenter6
foreach($strPath in $Path){
$root = Get-Folder -Name Datacenters -Server $vc
$strPath.TrimStart($Separator).Split($Separator) | %{
$root = Get-Inventory -Name $_ -Location $root -Server $vc -NoRecursion
if((Get-Inventory -Location $root -NoRecursion | Select -ExpandProperty Name) -contains “vm”){
$root = Get-Inventory -Name “vm” -Location $root -Server $vc -NoRecursion
}
}
$root | where {$_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.FolderImpl]}|%{
Get-Folder -Name $_.Name -Location $root.Parent -Server $vc
}
}
}
}
# Enable connections to multiple vCenters
Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Confirm:$false
# Connect to 5.0 vCenter
connect-viserver $vcenter5
# export list of VMs to Migrate to CSV
# Create array for list of VMs to export
$AppendVM = @()
foreach ($row in (Import-Csv $source)) {
$SrcVM = $row.SrcVMName
$AppendVM += Get-Cluster -name $row.SrcCluster | Get-VM $SrcVm |
Select Name, @{N=”Datastore”;E={Get-Datastore -VM $_ | Select -ExpandProperty Name}},
@{N=”Cluster”;E={$row.SrcCluster}},
@{N=”Network”;E={Get-VirtualPortgroup -VM $_ | Select -ExpandProperty Name}},
@{N=”DestNetwork”;E={$row.DestNetwork}},
@{N=”Parent”;E={$_.FullPath}},
@{N=”VmxPath”;E={($VMView = $_ | Get-View).Config.Files.VmPathName }}
}
$AppendVM | Export-Csv -Path $csvfile -NoTypeinformation -UseCulture
$time = Get-Date -Format u
“$time Migration.csv file has been generated” | Out-File $LogFile -Append
# Get VMs to Migrate, shutdown and remove from inventory
foreach ($row in (Import-Csv $csvfile)) {
$vm = $row.Name
# Check if VMTools running, shutdown guest and wait for completion.
$toolsStatus = (Get-VM $vm | Get-View).Guest.ToolsStatus
if ($toolsStatus -eq ‘toolsOk’) {
# Shutdown VMs (cleanly)
Get-Cluster -name $row.Cluster | Get-VM -Name $row.Name | Stop-VMGuest -Confirm:$false
# Wait for VMs to shutdown.
start-sleep -s 20
# If VM is still powered on after time has elapsed, manually shutdown VM from OS and press “y” to continue.
$status = $vm.PowerState
if ( (Get-VM $vm).PowerState -eq “PoweredOn” ) {
do {$confirmation = Read-Host $vm “is still powered on. Please shutdown guest. Press ‘y’ to continue”}
while ($confirmation -ne ‘y’)
}
}
# VMTools is not running, shutdown VM from OS and press “y” to continue.
else {
do {$toolsConf = Read-Host $vm “Tools is not running. Please shutdown guest.Press ‘y’ to continue”}
while ($toolsConf -ne ‘y’)
}
# End of shutdown
$time = Get-Date -Format u
“$time $vm has been powered off” | Out-File $LogFile -Append
# Remove VMs from Inventory. Loop can be added here to check VM status before removing from inventory.
Get-Cluster -name $row.Cluster | Get-VM -Name $row.Name | Remove-Inventory -Confirm:$false
$time = Get-Date -Format u
“$time $vm has been removed from inventory” | Out-File $LogFile -Append
}
# Connect to 6.0 vCenter
connect-viserver $vcenter6
## Add VM back to inventory in new vCenter
# Move VM to folder
# Change VM network label
# Power on VM and answer qyestion
foreach ($row in (Import-Csv $csvfile)) {
$vm = $row.Name
# Add VM to specific host in Inventory
New-VM -VMFilePath $row.VmxPath -ResourcePool $row.Cluster
$time = Get-Date -Format u
“$time $vm has been added to vSphere 6.0 inventory” | Out-File $LogFile -Append
# Move VM to existing folder location
$DestFolder = get-folderbypath $row.Parent
Move-VM -VM $row.Name -Destination $DestFolder
$time = Get-Date -Format u
“$time $vm has been moved to folder: $DestFolder” | Out-File $LogFile -Append
# Change Network Portgroup
$DestNet = $row.DestNetwork
Get-NetworkAdapter -VM $row.Name | Set-NetworkAdapter -NetworkName $row.DestNetwork -Confirm:$false
$time = Get-Date -Format u
“$time $vm network label has been updated to $DestNet” | Out-File $LogFile -Append
# Upgrade Virtual Hardware (NOT ENABLED)
#Set-VM -VM $vmName -Version v10
# Power on VMs and answer VM question (I moved it). The error handling exception is for the “move VM” question.
try
{
try
{
Start-VM -VM $row.Name -ErrorAction Stop -ErrorVariable custErr
}
catch [System.Management.Automation.ActionPreferenceStopException]
{
throw $_.Exception
}
}
catch [VMware.VimAutomation.ViCore.Types.V1.ErrorHandling.VMBlockedByQuestionException]
{
Get-VMQuestion -VM $serverName | Set-VMQuestion –Option button.uuid.movedTheVM -confirm:$false
}
$time = Get-Date -Format u
“$time $vm has been powered on” | Out-File $LogFile -Append
# Update VMware Tools (NOT ENABLED)
#update-tools $vmName -Confirm:$false
#start-sleep -s 300
} ## end foreach
Adrian,
Thanks so much for sharing! I like the changes and can see why only wanting to do a select number of VMs could be necessary. For my testing, I put VMs in a “Migrate” folder under their existing folder and then used a get-folder “migrate” | get-vm string to get them. Then, After migrating, I just had to move them back up 1 level…
This simplifies that process!
Thanks again!
This is fantastic! Thanks so much!
Hello There,
I am very new to scripting, Need help of you guys.
Migrate.csv file is not generating any data. Can someone please help me.
I am migrating list of VM’s from 5.0 to 5.5 and Network will remain same.
Thanks a lot.
Manoj
Did you do in your datacenter name in place of the ‘datacenter’ on line 81?
Thank you Ben,
At first I was running script to fetch data in migrate.csv file. I have given single server name in “source.csv” and ran script till below.
———————————————————————————————————————-
# This script was modified from http://www.thelowercasew.com/powercli-script-to-migrate-vms-from-one-vcenter-to-another
# Modified by Adrian on 2015/11/09
# Define Variables. These will need to be updated for Production
$vcenter5 = “VirtualCenter5.0”
$vcenter55 = “VirtualCenter5.5”
$csvfile = “C:\tmp\migration.csv”
$source = “C:\tmp\source.csv”
$LogFile = “C:\tmp\Logfile.txt”
“Create if doesn’t exist” | Out-File $LogFile -Append
#Clear contents of the log file for new migrations
Clear-Content $LogFile
Add-PSSnapin VMware.VimAutomation.Core
# Build the BlueFolderPath function
New-VIProperty -Name ‘FullPath’ -ObjectType ‘VirtualMachine’ -Value {
param($vm)
$current = Get-View $vm.ExtensionData.Parent
$path = “”
do {
$parent = $current
if($parent.Name -ne “vm”){$path = $parent.Name + “/” + $path}
$current = Get-View $current.Parent
} while ($current.Parent -ne $null)
$path.TrimEnd(‘/’)
} -Force | Out-Null
# Build the Get-folderbypath function
function Get-FolderByPath {
Get-FolderByPath -Path “Folder1/Datacenter/Folder2″
.EXAMPLE
PS> Get-FolderByPath -Path “Folder1>Folder2″ -Separator ‘>’
#>
param(
[CmdletBinding()]
[parameter(Mandatory = $true)]
[System.String[]]${Path},
[char]${Separator} = ‘/’
)
process{
$vc = $vcenter55
foreach($strPath in $Path){
$root = Get-Folder -Name Datacenters -Server $vc
$strPath.TrimStart($Separator).Split($Separator) | %{
$root = Get-Inventory -Name $_ -Location $root -Server $vc -NoRecursion
if((Get-Inventory -Location $root -NoRecursion | Select -ExpandProperty Name) -contains “vm”){
$root = Get-Inventory -Name “vm” -Location $root -Server $vc -NoRecursion
}
}
$root | where {$_ -is [VMware.VimAutomation.ViCore.Impl.V1.Inventory.FolderImpl]}|%{
Get-Folder -Name $_.Name -Location $root.Parent -Server $vc
}
}
}
}
# Enable connections to multiple vCenters
Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Confirm:$false
# Connect to 5.0 vCenter
connect-viserver $vcenter5
# export list of VMs to Migrate to CSV
# Create array for list of VMs to export
$AppendVM = @()
foreach ($row in (Import-Csv $source)) {
$SrcVM = $row.SrcVMName
$AppendVM += Get-Cluster -Name $row.SrcCluster | Get-VM $SrcVm |
Select Name, @{N=”Datastore”;E={Get-Datastore -VM $_ | Select -ExpandProperty Name}},
@{N=”Cluster”;E={$row.SrcCluster}},
@{N=”Network”;E={Get-VirtualPortgroup -VM $_ | Select -ExpandProperty Name}},
@{N=”DestNetwork”;E={$row.DestNetwork}},
@{N=”Parent”;E={$_.FullPath}},
@{N=”VmxPath”;E={($VMView = $_ | Get-View).Config.Files.VmPathName }}
}
$AppendVM | Export-Csv -Path $csvfile -NoTypeinformation -UseCulture
$time = Get-Date -Format u
“$time Migration.csv file has been generated” | Out-File $LogFile -Append
It creates a Migrate.csv file but that file is of 0 KB and there is no data written to file.
I made certain changes like,
$vcenter5 ——- Given Name of ESXi 5.0
$vcenter55——-Given Name of ESXi 5.5
$source = “C:\tmp\source.csv”
Also added Add-PSSnapin VMware.VimAutomation.Core to script as it was not running in Powershell ISE.
Looking for your reply..
Thanks & Regards
Manoj
Manoj,
Can you send an output of the error you are seeing when you run the script?
Regards,
Adrian
Hello Adrian,
Script does not through any error, It gets complete but output is not generation in Migrate.csv
Thanks
Manoj
Manoj,
Sorry, but you basically wrote a whole new script with your changes… Unfortunately, I do not have an environment where i can test your script to see what’s going wrong.
You can try posting the script in the VMware PowerCLI community.
https://communities.vmware.com/community/vmtn/automationtools/powercli
No Problem, Thank you for your help.
Hi, Ben Liebowitz or anyone else that can help.
I’m trying to migrate vm’s from vcenter 5 to vcenter 6 using the modified script posted by Adrian, besides a couple of syntax errors (commented out lines which cause the function “Get-FolderByPath” to break the whole script. Original script includes comments and EXAMPLES and the first couple lines weren’t pasted in the modified script so I fixed this by commenting out everything between the open “{” of that function and before the “#>” which made it work. Or just paste the missing lines back in, either way the script works properly).
I have a bunch of vm’s in a vAPP in my vCenter 5, but I don’t want to carry the vAPP over to vCenter 6, just the folder structure (or I can just create these manually, no big deal). The Parent header in the migrate.csv however is blank where there is a vAPP in the path. I tried the updated script supplied by the Author on their website (Lucd) which mentions fixing this but it hasn’t, as this script as a whole was pasted here I was hoping someone could let me know how to get it to work with vAPPS?
Thanks!
Sorry, I haven’t tested this script migrating from 5.x to 6, nor do I have any vAPPs in my environment. 🙁
No worries, figured it out myself
@Ben Thanks for this post. Good template that can be modified for our custom configuration. Cheers!
Glad you find it helpful.