For loop used on 3 groups

Welcome Forums General PowerShell Q&A For loop used on 3 groups

Viewing 17 reply threads
  • Author
    Posts
    • #221022
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Hello to all,

      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 ...
      $on8 = Get-VM | ? {$_.Name -match "^$vm1" -or $_.Name -match "^$vm2" -and $_.VMHost -like "$host8"}
      $on16 = Get-VM | ? {$_.Name -match "^$vm1" -or $_.Name -match "^$vm2" -and $_.VMHost -like "$host16"}
      
      ##### testing
      $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"}
      ##### testing
      
      # Define host status check
      $host_check = Get-VMHost | ? {$_.Powerstate -like "poweredon" -and $_.ConnectionState -like "connected"}
      
      <#
      # Windows cluster VMs migration
      if ($host_check -Match "$host8" -and $host_check -Match "$host16") {
      if ($on8 -Match "$vm2" -and $on8 -Match "$vm1") {
      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
      }
      
      elseif ($on16 -Match "^$vm1" -and $on16 -Match "^$vm2") {
      Move-VM "$vm1" -Destination "$host8" | out-null
      $vm_check = Get-VM | ? {$_.Name -match "^$vm1" -and $_.VMHost -like "$host8"}
      if ($vm_check -Match "^$vm1") {
      $out="migrated successfuly"
      }
      else {
      $out="NOT MIGRATED"
      } 
      Write-Output "$(Get-Date): $vm1 $out to $host8 and $vm2 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
      }
    • #221025
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      Simply use a foreach loop:

      $gr1 = @('$vm1', '$vm2')
      $gr2 = @('$vm3', '$vm4')
      $gr3 = @('$vm5', '$vm6')
      
      $gr = @($gr1, $gr2, $gr3)
      
      foreach ($group in $gr) {
          $group
      }

      … output …

      $vm1
      $vm2
      $vm3
      $vm4
      $vm5
      $vm6
    • #221028
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Tried that, but I can’t pass both members from gr1 to the first run.

      Line19: if ($on8 -Match “$gr1[1]” -and $on8 -Match “$gr1[0]“)

      LE: just noticed that also $on8 and $on16 should be placed inside the foreach loop

      • This reply was modified 1 month, 1 week ago by Adrian R.
    • #221034
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      Line19: if ($on8 -Match “$gr1[1]” -and $on8 -Match “$gr1[0]“)

      Did you check what’s in $on8? Can it match both – $vm1 AND $vm2?

    • #221043
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Line19: if ($on8 -Match “$gr1[1]” -and $on8 -Match “$gr1[0]“)

      Did you check what’s in $on8? Can it match both – $vm1 AND $vm2?

      Well, if it can’t match, it will exit the “if” without any action. that’s not a issue (commented lines 42-44)

      • This reply was modified 1 month, 1 week ago by Adrian R.
    • #221049
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      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?

    • #221052
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Output for $on8 and $on16 is OK, I get the VMs that are running on them and match the name, no need for host8*.

       

      I want to do a foreach loop and parse group members to IF statement, like this:

      if ($on8 -Match “$gr1_mebmer1” -and $on8 -Match “$gr1_member2”) {
      Move-VM “$gr1_member2” -Destination “$host16” | out-null

      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 …

    • #221061
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      I want to do a foreach loop and parse group members to IF statement, like this:

      if ($on8 -Match “$gr1_mebmer1” -and $on8 -Match “$gr1_member2”) {

      Move-VM “$gr1_member2” -Destination “$host16” | out-null

      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)

    • #221064
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      This is the output of $on8:

      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…

      • This reply was modified 1 month, 1 week ago by Adrian R.
      • This reply was modified 1 month, 1 week ago by Adrian R.
    • #221079
      Participant
      Topics: 12
      Replies: 523
      Points: 1,214
      Helping Hand
      Rank: Community Hero

      Please clarify the blessed use case. For example;
      – 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])"
          }
      }
      
    • #221082
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      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.

      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)“.

      But, again, my issue is not there. Is on the FOR part.

      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?

      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.

      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.

    • #221085
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Hi Sam,

      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.

    • #221106
      Participant
      Topics: 12
      Replies: 523
      Points: 1,214
      Helping Hand
      Rank: Community Hero

      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
      
    • #221112
      Participant
      Topics: 4
      Replies: 2247
      Points: 5,484
      Helping Hand
      Rank: Community MVP

      LE: @Olaf, $gr1[0], $gr1[1] etc should be autofilled from that $i statement.

      Then your loop definition was wrong and I did not see such a piece of code in your post. 😉 … it should have been something like this:

      $gr1 = @($vm1, $vm2)
      $gr2 = @($vm3, $vm4)
      $gr3 = @($vm5, $vm6)
      
      $gr = @($gr1, $gr2, $gr3)
      
      for($i = 0 ; $i -lt $gr.Count; $i++) {
          $gr[$i]
      }

      (of course you should remove the quotes around the variables when you use the code in production where you have filled in your variables 😉 )

    • #221142
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Thanks a lot guys!

       

      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.

    • #221184
      Participant
      Topics: 12
      Replies: 523
      Points: 1,214
      Helping Hand
      Rank: Community Hero

      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

      $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

      • This reply was modified 1 month, 1 week ago by Sam Boutros.
    • #221400
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Thanks again for detailed explanations and for your time!

       

       

    • #221847
      Participant
      Topics: 1
      Replies: 8
      Points: 14
      Rank: Member

      Looks like I still need some help.

      I tested the second script, and I have the following question:

      This part should return Host where VM is running, right?

      # Get Current VM Information
      $CompleteVMList = Get-VM | select Name,@{n='Host';e={$_.guest.hostname}}

      If yes, then VMHost parameter should be used. If not, what’s the point of getting the guest hostname?

      Also, if what I said aboe is right, on $myVMList.Host, should be VMHost.

       

      I tested with the mentioned changes, but on the following line I get no value for $myVMList:

      $myVMList = $AntiAffinityGroup.Members | foreach { $CompleteVMList | where Name -EQ $_ }

       

      I get output for first echo, but nothing (except 1- and 2-) from next echos

      echo START $AntiAffinityGroup.Members $CompleteVMList
      
      $myVMList = $AntiAffinityGroup.Members | foreach { $CompleteVMList | where Name -EQ $_ }
      echo 1-$myVMList
      Write-Verbose ($myVMList | FT -a | Out-String).trim()
      echo 2-$myVMList
Viewing 17 reply threads
  • You must be logged in to reply to this topic.