Attached is a script I use to migrate an ESXi cluster from one vCenter to another. I'll be updating the script (will add VM folder locations and attributes, more qa, root credential storage - PASSWORD IS STORED IN TRANSCRIPT FILE - so, adjust this in script or cleanse transcript after run), but for now it handles HA settings, DRS settings, EVC, DRS vm rules (affinity/anti), folder import/export, permissions (datacenter, cluster, host, folder and vm) and maybe some other things I left out. Does not deal with dvs as we use local vswitches.
You'll need two VCs and a cluster you want to migrate. The script does not create the datacenter in the destination VC, but it will create the cluster, disconnect, remove an add the hosts.
I hope it can help someone as I've mainly been on the receiving end with regards to help from this forum . Special note to Luc for a lot of the items in the script (folder functions, splat etc ).
Just unzip to a directory and run with powercli (requested credentials must have VC privs to disconnect hosts etc.).
As always with offerings, use at your own risk!
*Using to go from 5.0 to 5.5.
Mark, thank you for the quick reply with the script.
Now im getting errors when I get to the import folders function
Please confirm your information before proceeding
Target cluster - ****
Source vCenter - ****
Source Datacenter - *****
Destination vCenter - *****
Destination datacenter - *****
Please confirm that the above information is correct and press any key to continue . . .
Running QA tests against the information provided
Cluster **** DOES exist in *****
Checking connection to the ESX hosts with the root password provided
*Script will halt if incorrect password is detected
Datacenter ***** DOES exist in *****
1
Exporting folders in source datacenter *****
Importing folders to destination datacenter *****
Confirm
Confirm
1/28/2015 10:47:00 AM Get-Folder Folder with name '*******' was not found using the specified filter(s).
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): a
get-folder : 1/28/2015 10:47:00 AM Get-Folder Folder with name 'Charlotte' was not found using the specified filter(s).
At C:\users\rnotaro\downloads\Migration Script\Migration Script\Functions\import_folder_function.ps1:28 char:61
+ $location = Get-Datacenter $dc | get-folder $type | get-folder $key
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-Folder], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetFolder
Action to take for this exception:
1/28/2015 10:47:04 AM New-Folder Value cannot be found for the mandatory parameter Location
[C] Continue [I] Silently Continue [B] Break [S] Suspend [?] Help (default is "C"): c
New-Folder : 1/28/2015 10:47:04 AM New-Folder Value cannot be found for the mandatory parameter Location
At C:\users\rnotaro\downloads\Migration Script\Migration Script\Functions\import_folder_function.ps1:33 char:11
+ New-Folder -Name $folder.Name -Location $location
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-Folder], VimException
+ FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.NewFolder
Can you attach this file from the script run if it's safe to do so (company info)?
.\$SourcevCenter\$Cluster\folderexport.csv
No but it looks like this. it has 67 rows and 3 columns
Name | Path | Type |
cluster1 | DC\cluster1 | blue |
cluster2 | DC\cluster2 | blue |
Ok, trying to understand what makes your folder structure different. Mine in testing was setup like this:
Name is the actual name of the folder.
Path is the path of the folder. vm\ is the datacenter, next is Discovered virtual machine folder and sub folders underneath the Discovered virtual machine level folder.
Type is all blue or virtual machine folder.
Name | Path | Type |
Discovered virtual machine | vm\Discovered virtual machine | blue |
Level1C | vm\Discovered virtual machine\Level1C | blue |
Level1B | vm\Discovered virtual machine\Level1B | blue |
Level1A | vm\Discovered virtual machine\Level1A | blue |
Level2C | vm\Discovered virtual machine\Level1C\Level2C | blue |
Level2B | vm\Discovered virtual machine\Level1B\Level2B | blue |
Level2A | vm\Discovered virtual machine\Level1A\Level2A | blue |
From what you provided it looks like the name field has names of clusters? Where is the folder that threw an error located in the folder chain? IE, is it a top-level folder where creation was attempted first?
Cluster1 is just name of the folder that the vms are stored. It happens to be the same name as the cluster. Each cluster is its own client.
All the folders are under the Datacenter object so DC\Cluster1, DC\Cluster2, etc
The error is on the datacenter name. Thats what it is saying it cant find. Do I need to make a folder below the Datacenter object and put all my vm folders in that for the script to work?
You can try that, but first edit the import_folder_function.ps1 script and remove this error action handling part:
BEFORE
foreach($folder in $VMfolder){
$key = @()
$key = ($folder.Path -split "\\")[-2]
if ($key -eq "vm") {
get-datacenter $dc | get-folder $type | New-Folder -Name $folder.Name -ErrorAction SilentlyContinue
} else
{
AFTER
foreach($folder in $VMfolder){
$key = @()
$key = ($folder.Path -split "\\")[-2]
if ($key -eq "vm") {
get-datacenter $dc | get-folder $type | New-Folder -Name $folder.Name
} else
{
I'm thinking it is throwing an error before, but not exposing it.
Looks the same with that piece removed.
Confirm
1/28/2015 03:20:42 PM Get-Folder Folder with name 'AS' was not found using the specified filter(s).
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"):
get-folder : 1/28/2015 03:20:42 PM Get-Folder Folder with name 'AS' was not found using the specified filter(s).
At C:\users\rnotaro\downloads\Migration Script\Migration Script\Functions\import_folder_function.ps1:28 char:61
+ $location = Get-Datacenter $dc | get-folder $type | get-folder $key
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (:) [Get-Folder], VimException
+ FullyQualifiedErrorId : Core_OutputHelper_WriteNotFoundError,VMware.VimAutomation.ViCore.Cmdlets.Commands.GetFolder
Action to take for this exception:
1/28/2015 03:20:42 PM New-Folder Value cannot be found for the mandatory parameter Location
[C] Continue [I] Silently Continue [B] Break [S] Suspend [?] Help (default is "C"):
New-Folder : 1/28/2015 03:20:42 PM New-Folder Value cannot be found for the mandatory parameter Location
At C:\users\rnotaro\downloads\Migration Script\Migration Script\Functions\import_folder_function.ps1:33 char:11
+ New-Folder -Name $folder.Name -Location $location
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-Folder], VimException
+ FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.NewFolder
Ok, I think I know what's going on. The function sorts the csv it uses by the Path column.
Path |
vm\Discovered virtual machine |
vm\Discovered virtual machine\Level1C |
vm\Discovered virtual machine\Level1B |
vm\Discovered virtual machine\Level1A |
vm\Discovered virtual machine\Level1C\Level2C |
vm\Discovered virtual machine\Level1B\Level2B |
vm\Discovered virtual machine\Level1A\Level2A |
So, in my case the sort is actually aligning the folders in the order they will be created. I think what is going on in your case is that when it does the sort it arranges the rows in a way that is causing the error - IE, the rows are not building on each other when it goes to import the folders back in. Does this make sense and if so, take a look at your export csv and see if the logic holds true. Basically, the first row (folder) must be implemented before the second one can be and so on.
For example, this would fail in my environment as the first level folder would not be created first. I can't imagine right now exactly what folder layout would cause problems, but I'm thinking that is what's going on.
Path |
vm\Discovered virtual machine\Level1A\Level2A |
vm\Discovered virtual machine |
vm\Discovered virtual machine\Level1C |
vm\Discovered virtual machine\Level1B |
vm\Discovered virtual machine\Level1A |
vm\Discovered virtual machine\Level1C\Level2C |
vm\Discovered virtual machine\Level1B\Level2B |
I noticed that import script sets the $type variable to "vm" but all of mine in my CSV are type "blue" Not sure if thats part of the problem.
I noticed you dont have a datacenter in your path so the script must not be pulling that in for you but for me it is
So lets say my first folder name is Red and my datacenter name is Colors
The the first line in my export csv is
Name Path type
Red Colors\Red blue
When it goes to import it back into the new VC there is no Colors datacenter obviously so is that why its failing?
Interesting, so the first record in your csv in the path column is Colors? Like this? - ignore the other items in the path, just keying on the first one.
Path
Colors\Discovered virtual machine\Level1A\Level2A
Colors\Discovered virtual machine
Colors\Discovered virtual machine\Level1C
Colors\Discovered virtual machine\Level1B
Colors\Discovered virtual machine\Level1A
Colors\Discovered virtual machine\Level1C\Level2C
Colors\Discovered virtual machine\Level1B\Level2B
If this is the case you may want to try to edit this portion of the import_folder_function.ps1
BEFORE
#Loop to import folders. Objects are sorted by path field in above import.
#Splits records by slashes and sets key as record just prior to last record.
#The if loop then creates the first folder and then the next does all the rest
# of the folders. The entire loop will fail without the first creation of the "vm" folder.
#Error action on first folder creation is set as this first folder, Discovered virtual machine folder will trigger errors after the first script run on a datacenter.
foreach($folder in $VMfolder){
$key = @()
$key = ($folder.Path -split "\\")[-2]
if ($key -eq "vm") {
get-datacenter $dc | get-folder $type | New-Folder -Name $folder.Name -ErrorAction SilentlyContinue
} else
{
$location = Get-Datacenter $dc | get-folder $type | get-folder $key
Try{
Get-Folder -Name $folder.Name -Location $location -ErrorAction Stop
}
Catch{
New-Folder -Name $folder.Name -Location $location
}
}
}
}
AFTER (Only adjusting the initial key scan to Colors instead of "vm".)
#Loop to import folders. Objects are sorted by path field in above import.
#Splits records by slashes and sets key as record just prior to last record.
#The if loop then creates the first folder and then the next does all the rest
# of the folders. The entire loop will fail without the first creation of the "vm" folder.
#Error action on first folder creation is set as this first folder, Discovered virtual machine folder will trigger errors after the first script run on a datacenter.
foreach($folder in $VMfolder){
$key = @()
$key = ($folder.Path -split "\\")[-2]
if ($key -eq "Colors") {
get-datacenter $dc | get-folder $type | New-Folder -Name $folder.Name -ErrorAction SilentlyContinue
} else
{
$location = Get-Datacenter $dc | get-folder $type | get-folder $key
Try{
Get-Folder -Name $folder.Name -Location $location -ErrorAction Stop
}
Catch{
New-Folder -Name $folder.Name -Location $location
}
}
}
}
Feel free! On my list, but lower priority as we don't use them .
Ok that fixed that issue!
Now when it gets down to the part where it adds the hosts to the new vCenter it fails again.
Finished removing. Disconnecting from the source vCenter BLAH
Adding the hosts into tne new cluster CLUSTER on BLAH2
Confirm
1/29/2015 09:19:46 AM Add-VMHost The specified parameter 'Location' expects a single value, but your name criteria 'CLUSTER' corresponds to multiple values.
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): y
Add-VMHost : 1/29/2015 09:19:46 AM Add-VMHost The specified parameter 'Location' expects a single value, but your name criteria 'CLUSTER' corresponds
to multiple values.
At C:\users\rnotaro\downloads\Migration Script\Migration Script\FROMCSV_vCenterMigration.ps1:532 char:33
+ foreach ($vmhost in $CSVhosts) {Add-VMHost $vmhost.name -Location $Cluster -User ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidResult: (System.Collecti...dObjectInterop]:List`1) [Add-VMHost], VimException
+ FullyQualifiedErrorId : Core_ObnSelector_SelectObjectByNameCore_MoreResultsThanExpected,VMware.VimAutomation.ViCore.Cmdlets.Commands.AddVMHost
Confirm
1/29/2015 09:20:08 AM Add-VMHost VIContainer parameter: Could not find any of the objects specified by name.
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): y
Add-VMHost : 1/29/2015 09:20:08 AM Add-VMHost VIContainer parameter: Could not find any of the objects specified by name.
At C:\users\rnotaro\downloads\Migration Script\Migration Script\FROMCSV_vCenterMigration.ps1:532 char:33
+ foreach ($vmhost in $CSVhosts) {Add-VMHost $vmhost.name -Location $Cluster -User ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (VMware.VimAutom...tainer Location:RuntimePropertyInfo) [Add-VMHost], ObnRecordProcessingFailedException
+ FullyQualifiedErrorId : Core_ObnSelector_SetNewParameterValue_ObjectNotFoundCritical,VMware.VimAutomation.ViCore.Cmdlets.Commands.AddVMHost
Confirm
1/29/2015 09:20:28 AM Add-VMHost The specified parameter 'Location' expects a single value, but your name criteria 'CLUSTER' corresponds to multiple values.
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): a
Add-VMHost : 1/29/2015 09:20:28 AM Add-VMHost The specified parameter 'Location' expects a single value, but your name criteria 'CLUSTER' corresponds
to multiple values.
At C:\users\rnotaro\downloads\Migration Script\Migration Script\FROMCSV_vCenterMigration.ps1:532 char:33
+ foreach ($vmhost in $CSVhosts) {Add-VMHost $vmhost.name -Location $Cluster -User ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidResult: (System.Collecti...dObjectInterop]:List`1) [Add-VMHost], VimException
+ FullyQualifiedErrorId : Core_ObnSelector_SelectObjectByNameCore_MoreResultsThanExpected,VMware.VimAutomation.ViCore.Cmdlets.Commands.AddVMHost
Add-VMHost : 1/29/2015 09:20:46 AM Add-VMHost VIContainer parameter: Could not find any of the objects specified by name.
At C:\users\rnotaro\downloads\Migration Script\Migration Script\FROMCSV_vCenterMigration.ps1:532 char:33
+ foreach ($vmhost in $CSVhosts) {Add-VMHost $vmhost.name -Location $Cluster -User ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (VMware.VimAutom...tainer Location:RuntimePropertyInfo) [Add-VMHost], ObnRecordProcessingFailedException
+ FullyQualifiedErrorId : Core_ObnSelector_SetNewParameterValue_ObjectNotFoundCritical,VMware.VimAutomation.ViCore.Cmdlets.Commands.AddVMHost
Can you please try this?
Close all powercli sessions. Launch a new powercli session and do not connect to any vCenter servers. Run this to confirm - disconnect-viserver * -confirm:$false
Now try and run the script. I think that error is being caused by another vCenter session being open.
*When I kill the script mid-run I always perform a quick clean up by running these two commands:
stop-transcript
and
disconnect-viserver * -confirm:$false
Ok I did that and ran the command to disconnect vcenter. Same error.
I manually added the hosts so the script would continue and it gives a similar error later in the script.
Importing permissions for the folders, hosts, virtual machines, datacenter and cluster
Importing DRS rules (if any were exported)
Importing VM folder locations
Confirm
1/29/2015 11:06:33 AM Move-VM The specified parameter 'Destination' expects a single value, but your name criteria 'CLUSTER' corresponds to multiple values.
[Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): y
Move-VM : 1/29/2015 11:06:33 AM Move-VM The specified parameter 'Destination' expects a single value, but your name criteria 'CLUSTER' corresponds
to multiple values.
At C:\users\rnotaro\Downloads\Migration Script\Migration Script\FROMCSV_vCenterMigration.ps1:662 char:105
+ ... ne") { } else {Move-VM -VM $vm.Name -Destination $vm.Folder -RunAsync}}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidResult: (System.Collecti...dObjectInterop]:List`1) [Move-VM], VimException
+ FullyQualifiedErrorId : Core_ObnSelector_SelectObjectByNameCore_MoreResultsThanExpected,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM
ok, I believe the issue is that the cluster name and folder name are the same and thats where the multiple names are coming from. I renamed my folder to be different from the cluster name and the script finished! Maybe there's a way to identtfy the folder or cluster by id instead of name?
So, one thing I am finding is that the import portion of the script does not like:
1. Duplicate folder names that exist under the same vCenter - ie, the same folder name existing say in different datacenters under the same VC.
2. VMs that are placed in the "vm" folder as this is a hidden type folder and it chokes when it tries to import them in.
Working now to update the script for this - may be related to what you are seeing. We've used the script hundreds of times on more simple clusters as far as layouts go and we are moving to more complex environments (larger folder trees, complex provisioning/placement), so that's why I haven't see this error before.
I'll update the script, but I added this tidbit to narrow down the scope to the datacenter you are working with.
#Import folder permissions. Will be executed at operator's discretion.
#Note that the $folder value is narrowed down to the destinationdatacenter as a duplicate folder in another datacenter will cause an error.
$folderperms = Import-Csv "./$SourcevCenter/$Cluster./perms_export_folders.csv"
foreach ($fpm in $folderperms) {
$svcgroup = $fpm.Principal
$folder = Get-Datacenter $DestinationDatacenter | Get-Folder -Name $fpm.Entity
$authMgr = Get-View AuthorizationManager
$perm = New-Object VMware.Vim.Permission
$perm.principal = $svcgroup
$perm.group = if ($fpm.IsGroup -eq "TRUE") {$true} else {$null}
$perm.propagate = if ($fpm.Propagate -eq "TRUE") {$true} else {$null}
$perm.roleid = ($authMgr.RoleList | where{$_.Name -eq $fpm.Role}).RoleId
$authMgr.SetEntityPermissions(($folder | Get-View).MoRef, $perm)
Trying to figure out how to narrow this down now:
#Begin import of virtual machine folder locations
$VMFolderLocationImport = Import-Csv .\folderlocation_export_vms.csv
Write-Host " "
Write-Host "Importing" -ForegroundColor Green -NoNewline
Write-Host " VM folder locations"
foreach ($vm in $VMFolderLocationImport) { If ($vm.Folder -Like "Discovered virtual machine") { } else {Move-VM -VM $vm.Name -Destination $vm.Folder -RunAsync}}
#End import of virtual machine folder locations
Here is the updated version, but still working on this portion:
#Begin import of virtual machine folder locations
$VMFolderLocationImport = Import-Csv .\folderlocation_export_vms.csv
Write-Host " "
Write-Host "Importing" -ForegroundColor Green -NoNewline
Write-Host " VM folder locations"
foreach ($vm in $VMFolderLocationImport) { If ($vm.Folder -Like "Discovered virtual machine") { } else {Move-VM -VM $vm.Name -Destination $vm.Folder -RunAsync}}
#End import of virtual machine folder locations
So, if the folder the script is trying to move the VM back to is not unique within the vCenter (ie, the folder name exists under two or more datacenters within your destination vCenter) it will error out.