Storage vmotion to different datastores for different vm hard disks

I’ve spent some time today figuring out how to do storage vmotion so that 1 disk could go to datastoreA, and 2nd disk could go to datastoreB, and forexample to choose if i want to move the configuration vm files to different datastore. After playing 20 minutes with onyx and reading api guide for 30 minutes i have finally made it 😉
RelocateSpec
RelocateSpec is most important thing in writing the relocatevm_task
relocateVM_task description

You can omit the $spec.datastore object when you do not want to move vm config files.
This is example if you would have 3 disks inside vm, and would like to send disk 1 to ds1, disk 2 do ds3, and disk3 to ds3.This example also moves configuration files of vm to datastore selected in $vmxds

$ds1=(Get-Datastore your_1_destination_datastore|get-view).moref.value
$ds2=(Get-Datastore your_2_destination_datastore|get-view).moref.value
$ds3=(Get-Datastore your_3_destination_datastore|get-view).moref.value
$vmxds= (Get-Datastore your_vmx_destination_datastore|get-view).moref.value
$myvmtomove=get-vm 'some_vm'
$disks=$myvmtomove|Get-harddisk|% {$_.id.split('/')[1]}  #-> 3 disk ids should be here
$spec = New-Object VMware.Vim.VirtualMachineRelocateSpec

$spec.datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.datastore.type = "Datastore"
$spec.datastore.Value = $vmxds

$spec.disk = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator[] (3)
$spec.disk[0] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$spec.disk[0].diskId = $disks[0]
$spec.disk[0].datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.disk[0].datastore.type = "Datastore"
$spec.disk[0].datastore.Value =  $ds1
$spec.disk[1] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$spec.disk[1].diskId = $disks[1]
$spec.disk[1].datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.disk[1].datastore.type = "Datastore"
$spec.disk[1].datastore.Value =  $ds2
$spec.disk[2] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$spec.disk[2].diskId = $disks[2]
$spec.disk[2].datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.disk[2].datastore.type = "Datastore"
$spec.disk[2].datastore.Value = $ds3

(Get-View -Id $myvmtomove.id).RelocateVM_Task($spec, "defaultPriority")

Moving VMX to location of Disk1 datastore in case there is only 1 hard disk in vm:

#Script:
#Get our VMa
$VMname = 'VMa'
$vm = Get-View  -viewtype virtualmachine -Filter @{'name'=$VMname}
#you need to make sure you get only 1 vm, normally that's the case...

#VMa vmx path
$VMXpath = ($vm.LayoutEx.File |?{$_.Type -eq 'Config'}).Name
Write-Host "VMx lays here: $VMXpath"

#don't do anything if you have more than 1 disk
if (@($vm.LayoutEx.Disk.Key).length -eq 1){

#don't do anything if you will spot snapshots
if (@($vm.LayoutEx.File |?{$_.Type -eq 'diskDescriptor'}).length -eq 1) {

#Disk1 path
$Disk1path = ($vm.LayoutEx.File |?{$_.Type -eq 'diskDescriptor'}).Name
Write-Host "Disk 1 lays here: $Disk1path"

$Disk1ID = $vm.LayoutEx.Disk.Key
$Disk1Device = $vm.Config.Hardware.device | ?{$_.Key -eq $Disk1ID}
$Disk1DeviceDatastoreMoref = $Disk1Device.Backing.Datastore
#Writing spec
#Move VMX to Disk1 datastore spec

$spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
$spec.datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.datastore.type = "Datastore"
$spec.datastore.Value = $Disk1DeviceDatastoreMoref.Value

#Disk1 stays where it is, if you dont add this , you will move the Disk to the vmx DS
$spec.disk = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator[] (1)
$spec.disk[0] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$spec.disk[0].diskId = $Disk1ID
$spec.disk[0].datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.disk[0].datastore.type = "Datastore"
$spec.disk[0].datastore.Value = $Disk1DeviceDatastoreMoref.Value

$vm.RelocateVM_Task($spec, "defaultPriority")

#check
$vm = Get-View  -viewtype virtualmachine -Filter @{'name'=$VMname}
$VMXpath = ($vm.LayoutEx.File |?{$_.Type -eq 'Config'}).Name
Write-Host "VMx lays here: $VMXpath"
$Disk1path = ($vm.LayoutEx.File |?{$_.Type -eq 'diskDescriptor'}).Name
Write-Host "Disk 1 lays here: $Disk1path"

}

}else {Write-Error "Amount of disks is not equal to 1"}

And i wrote recently this example , function to move VMX to destination DS of a particular disk name.

Function Move-VMX{
param([Parameter(Mandatory=$True,Position=1)][string]$VMname,[Parameter(Mandatory=$True,Position=2)][string]$disk)

#Get our VM
#disk is 'Hard disk 1' , or 'Hard disk 2', same as you see in vsphere client

$vm = Get-View -viewtype virtualmachine -Filter @{‘name’=$VMname}

if(($vm.Config.Hardware.Device |?{$_ -is [vmware.vim.virtualdisk]}) | ?{$_.DeviceInfo.Label -eq $disk})
{

$disks = $vm.Config.Hardware.Device |?{$_ -is [vmware.vim.virtualdisk]}
$VMXDestinationDisk = ($vm.Config.Hardware.Device |?{$_ -is [vmware.vim.virtualdisk]}) | ?{$_.DeviceInfo.Label -eq $disk}

$VMXDestinationDatastoreMorefValue = $VMXDestinationDisk.Backing.Datastore.Value

$numberOfDisks = @($disks).Length

#VM vmx path
$VMXpath = ($vm.LayoutEx.File |?{$_.Type -eq ‘Config’}).Name
Write-Host “VMx lays here: "$VMXpath

#DEstination disk path
Write-Host “Disk $disk lays here: "$VMXDestinationDisk.Backing.FileName

foreach ($dsk in $disks){
Write-Host "Disk "$dsk.DeviceInfo.Label" Lays in: " $dsk.Backing.FileName
}

#Writing spec
#Move VMX to Disk destination

$spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
$spec.datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.datastore.type = “Datastore”
$spec.datastore.Value = $VMXDestinationDatastoreMorefValue

#Specs to make our disks stay where they are
$spec.disk = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator[] ($numberOfDisks)
$i=0
Foreach($dsk in $disks) {
$spec.disk[$i] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator
$spec.disk[$i].diskId = $dsk.Key
$spec.disk[$i].datastore = New-Object VMware.Vim.ManagedObjectReference
$spec.disk[$i].datastore.type = “Datastore”
$spec.disk[$i].datastore.Value = $dsk.Backing.Datastore.Value
$i++
}

Write-Host 'Started task for moving now VMX '$VMXpath' to '$disk' location'
$task = $vm.RelocateVM_Task($spec, “defaultPriority”)

} else {Write-Error 'Could not find the disk, example: ''Hard disk 1 '' '}
}

Checking vmware tools upgrade policy using powercli, setting vmware tools upgrade policy

If you want to check what is the policy regarding upgrading vmware tools for vms in cluster, here it is 😉

get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'your-cluster').id | select name,@{N='ToolsUpgradePolicy';E={$_.Config.Tools.ToolsUpgradePolicy } }

If you want to query all vms in VC

get-view -ViewType virtualmachine | select name,@{N='ToolsUpgradePolicy';E={$_.Config.Tools.ToolsUpgradePolicy } }

Ok nice, we know what tools upgrade policy settings have our vms. Now we want to change this settings for example to ‘upgradeAtPowerCycle’. I am assuming that we want to change this setting only for vms that have ‘manual’ settings selected for toolsupgradepolicy. There is no need to force it on all vms.

get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'your_Cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' } | select name,@
{N='ToolsUpgradePolicy';E={$_.Config.Tools.ToolsUpgradePolicy } }

If you want to limit the vms for which we will be changing this setting, don’t forget that you still can filter them
|? {$_.guest.guestFamily -eq ‘windowsGuest’}
|? {$_.guest.guestFamily -eq ‘linuxGuest’}
|? {$_.guest.guestFullName -eq ‘Microsoft Windows Server 2008 R2 (64-bit)’}
And so on and on…
for example to apply this only for windows 2k8 r2 64bit, you would select them applying proper filter:

get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'your_Cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' } |? {$_.guest.guestFullName -eq 'Microsoft Windows Server 2008 R2 (64-bit)'} |select name,@ {N='ToolsUpgradePolicy';E={$_.Config.Tools.ToolsUpgradePolicy } }

As you can see by running get-view for vms using filter ‘Config.Tools.ToolsUpgradePolicy’ = ‘manual’ , we will select only those which have manual. Alright, now the upgrade part.
Right, so i could not find any cmdlet within powercli to do this. Still let’s not give up on this one yet 😉 What to do in this case ?
Onyx!
Download/extract onyx from vmware.com. While running onyx, select option to run using vSphere client. Make sure that onyx is recording from this moment

So we received vsphere api code that our vSphere client executed.

# ------- ReconfigVM_Task -------

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.changeVersion = "2012-08-01T08:26:54.934341Z"
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.toolsUpgradePolicy = "manual"

$_this = Get-View -Id 'VirtualMachine-vm-754'
$_this.ReconfigVM_Task($spec)
#-------------------

We need to apply this part of code to our vms that have wrong upgradetoolspolicy.
Let’s take our query part here first:

get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'your_Cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' }

Those will be our vms
Let’s make a loop for them

Foreach($vmview in get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'your_cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' } ) {
$vmview.name
}

Ok, loop with your get-view from vms is ready.

Now let’s take closer look at the configuration spec

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec 
$spec.changeVersion = "2012-08-01T08:26:54.934341Z" 
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo 
$spec.tools.toolsUpgradePolicy = "manual" 

vim.vm.ConfigSpec
changeVersion
vim.vm.ToolsConfigInfo
toolsUpgradePolicy

If you want to read about those objects and their specification in vmware api use the above links. One thing that could be not so simple to understand is the ChangeVersion. The rest should be self explanatory i believe.
1. Create configuration specification object
2. Populate changeversion
3. In order to set the toolsUpgradePolicy property , we have to initialize ToolsConfigInfo object first.
4. Execute method on vsphere api vm object called ReconfigVM_task which takes as a parameter the specification object which we have just created.

ad.2 To be honest it will work even without giving this parameter. It is not necessary. Let’s say we are going to configure this settings, but meanwhile some evil evil evil administrator has changed some other settings in some particular vm on which we are running our loop. Now, as we do not know what has he changed(this evil evil administrator) then we might ask, do we still want to make our change ? IF we do not care about this, i guess there is no point in filling the changeversion at all. If we do, we have first assign to some variable current changeversion number, and then while reconfiguring our vm, give the changeversion on which we wanted to work. If meanwhile (evil evil evil) administrator did some reconfiguration of this machine, the changeversion number will be not equal to our number. Guess what will happen ?

Our change will not proceed as the number does not match. When that evil…il..l… administrator has reconfigured our vm, the changeversion number has changed to some 9999 for example…, but when we started our script, and wrote to variable the current change number it was 7777, so we are trying to do a spec with $spec.changeVersion = “xxxxxx7777” , and it fails because it’s not 7777 anymore but 9999.
Back to our loop 😉

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec 
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo 
$spec.tools.toolsUpgradePolicy = "upgradeAtPowerCycle" 
Foreach($vmview in get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'my_cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' } ) {
$vmview.ReconfigVM_task($spec)
}

And this is it 😉
Lines: 1-3 : We create specification object , we are putting information bout the toolsUpgradePolicy
Lines: 4-6 : In our loop where we query for vms that have manual option for toolsUpgradePolicy, we invoke method ReconfigVM_Task and giving it our $spec specification object
Like i mentioned earlier you could use here the changeversion if you want to be really sure. Where i work i have no evil evil evil admins 😉 No need to set it here ! but… if you have them , do it like this 😉

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec 
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo 
$spec.tools.toolsUpgradePolicy = "upgradeAtPowerCycle" 
Foreach($vmview in get-view -ViewType virtualmachine -SearchRoot (get-Cluster 'my_cluster').id -Filter @{'Config.Tools.ToolsUpgradePolicy' = 'manual' } ) {
$spec.changeVersion=$vmview.Config.changeversion
$vmview.ReconfigVM_task($spec)
}

One more example, few seconds ago i had to update this only on vms that were in text file so in order to do that:

$spec = New-Object VMware.Vim.VirtualMachineConfigSpec 
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo 
$spec.tools.toolsUpgradePolicy = "upgradeAtPowerCycle" 
foreach ($vm in gc c:\vmlist.txt) {
$vmview = get-view -viewtype virtualmachine -Filter @{'name'=$vm} 
$vmview.ReconfigVM_task($spec)
}