skip to Main Content

Is there a better way in Powershell to handle multiple levels of returned values than nested foreach loops?

I need to get a list of each of our Azure Virtual Desktop session hosts for further processing. This requires me to iterate over a list of resource groups to get the host pool(s) in each of them; iterate over those to get the session hosts within each; and finally iterate over the session hosts:

foreach ($resourceGroup in $resourceGroups) {
    $hostPoolObjects = Get-AzWvdHostPool -ResourceGroupName $resourceGroup.ResourceGroupName
        
    foreach ($hostPool in $hostPoolObjects) {
        $sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $resourceGroup.ResourceGroupName -HostPoolName $hostPool.Name
        
        foreach ($sessionHost in $sessionHosts) {
            # Further processing
        }
    }
}

That seems inefficient. and, knowing how many session hosts I’m dealing with, memory intensive. Is there another method I could be using? I’m looking into whether multithreading would help in any way; maybe for the foreach ($hostPool in $hostPoolObjects) block.

2

Answers


  1. There is a way you can reduce 1 of the loops by using a Resource Graph query (Search-AzGraph). This example gets all Host Pools with their corresponding Resource Group and Subscription:

    $searchAzGraphSplat = @{
        Query = "
            resources
            | where ['type'] == 'microsoft.desktopvirtualization/hostpools
            | project name, resourceGroup, subscriptionId"
    }
    
    $allHostPools = do {
        $response = Search-AzGraph @searchAzGraphSplat
        $searchAzGraphSplat['SkipToken'] = $response.SkipToken
        if ($response.Data.Count) {
            $response.Data
        }
    }
    while ($response.SkipToken)
    
    foreach ($hostpool in $allHostPools) {
        $getAzWvdSessionHostSplat = @{
            ResourceGroupName = $hostpool.resourceGroup
            HostPoolName      = $hostPool.name
        }
    
        foreach ($sessionHost in Get-AzWvdSessionHost @getAzWvdSessionHostSplat) {
            # Further processing
        }
    }
    

    From there if you need faster processing you could use ForEach-Object -Parallel instead but you need PowerShell 7+ for that:

    $allHostPools | ForEach-Object -Parallel {
        $getAzWvdSessionHostSplat = @{
            ResourceGroupName = $_.resourceGroup
            HostPoolName      = $_.name
        }
    
        foreach ($sessionHost in Get-AzWvdSessionHost @getAzWvdSessionHostSplat) {
            # Further processing
        }
    }
    

    If you’re stuck with Windows PowerShell you can have a look at this Q&A and this module.

    Login or Signup to reply.
  2. Fundamentally, you can take advantage of the PowerShell pipeline‘s auto-enumeration behavior and the ability of most cmdlets to accept object-by-object input via the pipeline:

    $resourceGroups | ForEach-Object {
      Get-AzWvdHostPool -ResourceGroupName $resourceGroup.ResourceGroupName |
        Get-AzWvdSessionHost | ForEach-Object {      
          $sessionHost = $_ 
          # Further processing...
        }
    }
    

    Note:

    • The above invariably performs sequential object-by-object processing.

    • In PowerShell (Core) 7, you can speed up processing with parallel processing, using the ForEach-Object cmdlet’s -Parallel parameter; as noted in Santiago’s answer, alternative solutions are available in Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search