I’m trying to find a solution for my case where I have 3 groups that contains 2 members each, and want to pass each member group to a for loop.
Members for the groups are actually VMs (like DC, proxy) and I want to keep them on different vcenter hosts.
So, instead of $vm1 and $vm2, I tried to define groups and run the if into a loop, but can’t figure it out how to pass gr1[0] and gr[1] to first for loop and so on.
Any advice or idea will be great.
Thanks,
Adrian
$gr1 = @($vm1,$vm2)
$gr2 = @($vm3,$vm4)
$gr3 = @($vm5,$vm6)
$gr = @($gr1,$gr2,$gr3)
for ($i in $gr) {
if ...
I actually still don’t know what your issue is. Did you try to output $On8 and $on16. If you use -like in a condition you should add an asterisk (). So instead of $.VMHost -like “$host8” it should be $.VMHost -like "$host8". Why do you use -match to filter for the name of your VMs. Don’t you know their names? What is it what you ACTUALLY try to achieve?
and repeat this for gr2 and 3. But I can’t figure it out how to do it.
This script will be used to keep vms from those groups (1, 2 and 3) to run on different hosts, if both hosts are available. I don’t care if vm1 from gr1 runs on same host as vm1 from gr2, the idea is to keep separate the vms from the same group.
I’m sorry if I’m not explaining very clear, I’m trying …
OK, but $on8 is not a plain list of VM names, right? It should be an array of objects with properties if I got everything right. You could use if($on8.name -contains $vm1) to determine if an element is in an array of elements.
Edit: Or actually it should be if($on8.name -contains $vm1.name)
Name PowerState Num CPUs MemoryGB
---- ---------- -------- --------
vm_name PoweredOn 4 8.000
So, if the output match both vm1 and vm2, the script will continue will migrate the one that I specify.
But, again, my issue is not there. Is on the FOR part.
### this part should dissapear
$vm1 = Get-VM | ? {$_.Name -match "^NAME_TEST1"}
$vm2 = Get-VM | ? {$_.Name -match "^NAME_TEST2"}
$vm3 = Get-VM | ? {$_.Name -match "^NAME_TEST3"}
$vm4 = Get-VM | ? {$_.Name -match "^NAME_TEST4"}
$vm5 = Get-VM | ? {$_.Name -match "^NAME_TEST5"}
$vm6 = Get-VM | ? {$_.Name -match "^NAME_TEST6"}
###
### this should replace the above variables
$gr1 = @($vm1,$vm2)
$gr2 = @($vm3,$vm4)
$gr3 = @($vm5,$vm6)
$gr = @($gr1,$gr2,$gr3)
for ($i in $gr) {
$on8 = Get-VM | ? {$_.Name -match "^$vm1" -or $_.Name -match "^$vm2" -and $_.VMHost -like "$host8"} ### $vm1 should be replaced first with $gr1[0], then with $gr2[0], then with $gr3[0] and $vm2 should be replaced with $gr1[1] etc..
$on16 = Get-VM | ? {$_.Name -match "^$vm1" -or $_.Name -match "^$vm2" -and $_.VMHost -like "$host16"} ## same here
if ($on8 -Match "$vm2" -and $on8 -Match "$vm1") { ### same on the rest of the script
Move-VM "$vm2" -Destination "$host16" | out-null
$vm_check = Get-VM | ? {$_.Name -match "^vm2" -and $_.VMHost -like "$host16"}
if ($vm_check -Match "^$vm2") {
$out="migrated successfuly"
}
else {
$out="NOT MIGRATED"
}
Write-Output "$(Get-Date): $vm1 still runs on $host8 and VM $vm2 $out to $host16" >> /root/test.txt
}
I don’t know how to parse grX[Y] values to $i
I want this to avoid to write the same IF statement for every group of VMs that I want to keep on different hosts. It’s easier to define a new group than to copy entire IF statement, change vm1 and vm2 with new names etc…
I wish to ensure certain VMs are not hosted on the same ESXi host
Also, I’m assuming there’s a reason you wouldn’t just use the native VMWare Affinity rules and anti-affinity rules
$gr1 = @('vm1','vm2')
$gr2 = @('vm3','vm4')
$gr3 = @('vm5','vm6')
$gr = @($gr1,$gr2,$gr3)
# 1. Iterate through the elements of parent array
0..($gr.Count-1) | foreach {
"This is parent array #$($_+1), whose elements are $($gr[$_] -join ', '| Out-String)"
}
# 2. Use Nested loops to deifferentiate between elements of the parent vs child arrays
foreach ($ParentArrayElement in $gr) {
"This is parent array '$(($ParentArrayElement -join ', '| Out-String).Trim())', whose elements are:"
foreach ($ChildArrayElement in $ParentArrayElement) {
" $ChildArrayElement"
}
}
# 3. Use a 2 dimensional array as explained in https://superwidgets.wordpress.com/2018/01/01/practical-guide-to-powershell-arrays/
$gr = [String[,]]::new(3,2)
# Populating the 2D array with test data:
$gr[0,0] = 'vm1'
$gr[0,1] = 'vm2'
$gr[1,0] = 'vm3'
$gr[1,1] = 'vm4'
$gr[2,0] = 'vm5'
$gr[2,1] = 'vm6'
foreach ($VMList in 0..$gr.GetUpperBound(0)) {
foreach ($HostList in 0..$gr.GetUpperBound(1)) {
"gr[$VMList,$HostList] $($gr[$VMList,$HostList])"
}
}
[quote quote=221064]This is the output of $on8:
…
So, if the output match both vm1 and vm2, the script will continue will migrate the one that I specify.[/quote]
But your condition is wrong and cannot work reliably. You should only compare the according properties with each other as I wrote above “if($on8.name -contains $vm1.name)”.
That’s the point. As I mentioned above - you should use a foreach loop - not a for loop. And you create a variable $i in your loop statement but you never use in your script block. If you don’t use it why using such a loop at all?
Shouldn’t this be set up in your virtualisation solution setup? I know you can set those thinkgs in a Hyper-V cluster for example.
Can’t use affinity rules because it’s a standard license and DRS is not included.
For example, gr1 contains 2 Domain Controllers so, if a vcenter node will enter in error state, we don’t want downtime untill the HA is making his job, that’s why I want to implement this script. Same situations for gr 2 and 3: adfs and adfs-proxy servers.
Thanks for the details. I’ll try and get back.
LE: @Olaf, $gr1[0], $gr1[1] etc should be autofilled from that $i statement.
20 some years ago when I was taking the 3-credit Data-Structures college course, I thought it was the dumbest thing I ever heard, and it will be almost never useful and it was for pure academic interest. Boy was I wrong.
IMHO the core of the solution depends on using the most suitable data structure for the task. Here being a hash table not multidimensional array. In the solution below I use PS object which is based on hash tables.
# https://powershell.org/forums/topic/for-loop-used-on-3-groups
<#
PS script to perform same functionality as VMWare Anti-Affinity groups
Definition: Anti-Affinity group is a group of VMs that should not reside on teh same hypervisor
Sam Boutros - 21 April 2020
#>
[CmdletBinding()]
#region Input
$AntiAffinityGroupList = @(
[PSCustomObject][Ordered]@{
Name = 'Domain Controllers'
Members = @('DC1','DC2')
}
[PSCustomObject][Ordered]@{
Name = 'ADFS Servers'
Members = @('ADFS1','ADFS2')
}
)
#endregion
#region Process
# Get Current VM Information
$CompleteVMList = Get-VM | select Name,@{n='Host';e={$_.guest.hostname}}
foreach ($AntiAffinityGroup in $AntiAffinityGroupList) {
Write-Verbose "Processing AntiAffinity Group '$($AntiAffinityGroup.Name)'"
$myVMList = $AntiAffinityGroup.Members | foreach { $CompleteVMList | where Name -EQ $_ }
Write-Verbose ($myVMList | FT -a | Out-String).trim()
if (($myVMList.Host | select -Unique).Count -eq 1) { # They're all on the same host
# Code to V-Motion the VM to another host
}
}
#endregion
I managed to understand and solve it using the info and solutions you provided. This is the final version that’s working (host 8 and 16 are also defined)
# define Windows VMs
$adfs_g = @('adfs2','adfs1')
$proxy_g = @('adfs-proxy1','adfs-proxy2')
$dc_g = @('dc1','dc2')
#$test = @('test1','test2')
### WIN CLUSTER migration
# define general group
$vmg = @($adfs_g,$proxy_g,$dc_g)
# Define host status check
$win_host_check = Get-VMHost | ? {$_.Powerstate -like "poweredon" -and $_.ConnectionState -like "connected"}
# First part from Sam: 1. Iterate through the elements of parent array
0..($vmg.Count-1) | foreach {
# "This is parent array #$($_+1), whose elements are $($vmg[$_] -join ', '| Out-String)"
}
# Second part from Sam: 2. Use Nested loops to differentiate between elements of the parent vs child arrays
foreach ($ParentArrayElement in $vmg) {
### Get info of the host wher VMs are running
$on8 = Get-VM | ? {$_.Name -match "^$($ParentArrayElement[0])" -or $_.Name -match "^$($ParentArrayElement[1])" -and $_.VMHost -like "$host8"}
$on16 = Get-VM | ? {$_.Name -match "^$($ParentArrayElement[0])" -or $_.Name -match "^$($ParentArrayElement[1])" -and $_.VMHost -like "$host16"}
#echo $ParentArrayElement[0] $ParentArrayElement[1]
if ($win_host_check -Match "$host8" -and $win_host_check -Match "$host16") {
if ($on8 -Match "^$($ParentArrayElement[1])" -and $on8 -Match "^$($ParentArrayElement[0])") {
Move-VM $ParentArrayElement[1] -Destination "$host16" | out-null
$vm_check = Get-VM | ? {$_.Name -match "^$($ParentArrayElement[1])" -and $_.VMHost -like "$host16"}
if ($vm_check -Match "^$($ParentArrayElement[1])") {
$out="migrated successfuly"
}
else {
$out="NOT MIGRATED"
}
Write-Output "$(Get-Date): $($ParentArrayElement[0]) still runs on $host8 and VM $($ParentArrayElement[1]) $out to $host16" >> /root/test.txt
}
elseif ($on16 -Match "^$($ParentArrayElement[0])" -and $on16 -Match "^$($ParentArrayElement[1])") {
Move-VM $ParentArrayElement[0] -Destination "$host8" | out-null
$vm_check = Get-VM | ? {$_.Name -match "^$($ParentArrayElement[0])" -and $_.VMHost -like "$host8"}
if ($vm_check -Match "^$($ParentArrayElement[0])") {
$out="migrated successfuly"
}
else {
$out="NOT MIGRATED"
}
Write-Output "$(Get-Date): $($ParentArrayElement[0]) $out to $host8 and $($ParentArrayElement[1]) still runs on $host16" >> /root/test.txt
}
#else {
#Write-Output "$(Get-Date): Everything looks good, VMs are running on different hosts." >> /root/test.txt
#}
}
else {
exit
}
}
I’ll do almost the same for the second cluster (linux), where are 3 nodes and VM groups will contain 3 VMs.
Sam, I want to try the latest script you posted, but I’m having trouble understanding this part:
if (($myVMList.Host | select -Unique).Count -eq 1) { # They're all on the same host
# Code to V-Motion the VM to another host
}
I refer to “# they’re all on the same host”. For the next line, I understand that it’s the Move-VM command.
[quote quote=221142]Sam, I want to try the latest script you posted, but I’m having trouble understanding this part:
if (($myVMList.Host | select -Unique).Count -eq 1) { # They’re all on the same host
[/quote]
$myVMList will contain information on the VMs that belong to a given $AntiAffinityGroup
such as DC1 and DC2
specifically the VM’s Name and Host properties such as DC1 and Host8
$myVMList.Host will have the hosts of DC1 and DC2 in this example
such as Host8 and Host8
($myVMList.Host | select -Unique) will return Host8 in this example
If the VMs were on 2 different hosts like Host1 and Host8, ($myVMList.Host | select -Unique) will return an array with 2 elements: Host1, Host8
($myVMList.Host | select -Unique).Count will be the count of elements returned from this expression
If this count is 1, then DC1 and DC2 reside on the same host. If it’s more than 1 then they reside on more than 1 host