1
votes

I know that this question has already been answered for hashtable keys... but it does not seem to work for hashtable values.

I'm creating a hash of VM's based on the cluster they reside in. So the hashtable looks like this

$clusters[$clustername][$clustervms] = @{}

The reason each VM is a hashtable is because i'm trying to associate it with their VM tag as well (vmware).

This code works incredibly fast but destroys the keys, by injecting values as keys... or in other words, Rather than key/value pairs - values become keys, keys become values ... it's just a shit show.

foreach ($value in $($clusters.values)) { 
$clusters[$value] = (get-tagassignment -entity ($value).name).tag

This code works - but it is unbelievably slow.

foreach ($key in $($clusters.keys)) {
       $vms = (Get-Cluster -Name $key | Get-Vm).name
       foreach ($vm in $vms) {
            $clusters[$key][$vm] = @{};
            $tag = (Get-TagAssignment -Entity $vm).tag;
            $clusters[$key][$vm] = $tag;
       }
}

When i say unbelievably slow - i mean getting the VM names takes about 5 seconds. Getting the tag assignments through the first code (codename: shit show) takes about 7 seconds. I've waited a minute on this code, and it's only gone through 6 VM's in that time. So i know there's a better way.

Thanks,

1
Its not the iteration of the hash keys thats slow, its the Get-Cluster call in the middle of the loop. Once the loop is populated you dont need to know the names. If you just want the keys, and the nested hash, do the same thing you did on the outer hash with the value of the inner loop. If you need to write up an answer with an example im willing - Jake H
Thanks for your response, but i don't think it is the 'get-cluster' call that makes it slow. I say that because if i run that exact same command, but take out the '$tag =' line and the $clusters[$key][$vm] line... it runs quickly. - Justin Beagley
I should have been clearer. Its not the iteration/usage/consumption of the hash that takes any time. Its the external calls. You say if you comment out the $tag= it speeds up. Thats because the $tag=... line is calling another powershell command, and from experience calling into get vm/cloud metadata is making a HTTP request. Load the info ahead of time and cache it. See below. ( I would bet if you actually timed all of the Get-TagAssignment calls that happen in total it would equal your wait time ) - Jake H
I see what you're saying, and i figured that that was the issue. I just couldn't make sense of why the external call worked quickly with the $clusters.values. But i'm guessing that it's only making 1 external call for the entire array of values. This would be much easier if vmware had a cluster and tag attribute when you use command 'get-vm'. Thanks a lot for your help. - Justin Beagley
since your question was titled 'foreach through hashtable values' i would appreciate if you marked my answer below as accepted - Jake H

1 Answers

1
votes

I commented on this above, I wrote an example script which should make this more clear. Also note this powershell is meant to be illustrative, and some/many/or all things could be done in a more efficient manner.

# for example, im just using sourcedata variable to make this clearer. 
# you would normally be populating this hash programatically

# lets say a VM has this payload data:
#   @{ vm_name="bar"; os="win" }
$SourceData = @(
    @{  
        cluster_name = "foo";  
        vms = @( @{ vm_name="bar" ; os="win" }, @{ vm_name="baz"; os="linux" }) 
    }, @{  
        cluster_name = "taco"; 
        vms = @( @{ vm_name="guac"; os="win" }, @{ vm_name="hot"; os="win"   }) 
    })

$clusters = @{}

# load the sourcedata into our clusters catalog
$SourceData | %{         
    $clusternm = $_.cluster_name
    $clusters[ $clusternm ] = @{}
    $_.vms | %{ 
        $vmnm = $_.vm_name 
        $clusters[ $clusternm ][ $vmnm ] = $_
    }
}

# show the whole thing
$clusters | ConvertTo-Json | Write-Output
<#
{
    "taco":  {
                 "hot":  {
                             "os":  "win",
                             "vm_name":  "hot"
                         },
                 "guac":  {
                              "os":  "win",
                              "vm_name":  "guac"
                          }
             },
    "foo":  {
                "bar":  {
                            "os":  "win",
                            "vm_name":  "bar"
                        },
                "baz":  {
                            "os":  "linux",
                            "vm_name":  "baz"
                        }
            }
}
#>

# show just a vm
$clusters['foo']['bar']  | ConvertTo-Json | Write-Output
<# 
{
    "os":  "win",
    "vm_name":  "bar"
}
#>

And finally, to assure you that iterating hashes takes no appreciable time:

# now lets iterate each cluster, and each vm in that cluster. in this example, just dump the OS of each vm in each cluster
$clusters.Keys | %{
    $clusternm = $_
    $clusters[$clusternm].Keys | %{
        $vmnm = $_
        Write-Output "${clusternm}/${vmnm}: os: $( $clusters[$clusternm][$vmnm].os )"
    }

}
<#
taco/hot: os: win
taco/guac: os: win
foo/bar: os: win
foo/baz: os: linux
#>

Whole script runs immediately. Only the json conversion methods to have illustrative output added 0.1s