PowerCLI Script to migrate VMs from one vCenter to another

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!

Share This:

23 thoughts on “PowerCLI Script to migrate VMs from one vCenter to another

  1. 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

    1. 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.

      1. 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.

  2. 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

    1. 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

      1. 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.

          1. 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

          2. 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!

  3. 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

      1. 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

          1. Hello Adrian,

            Script does not through any error, It gets complete but output is not generation in Migrate.csv

            Thanks
            Manoj

  4. 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!

    1. Sorry, I haven’t tested this script migrating from 5.x to 6, nor do I have any vAPPs in my environment. 🙁

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.