Home > Software design >  How would I get all values of a Powershell Object added to a collection from a Foreach loop?
How would I get all values of a Powershell Object added to a collection from a Foreach loop?

Time:06-01

I have this function that takes the InterfaceIndex from $interfaceIPs and obtains the matching NetRoute data, and puts all of that together in a collection. Currently on the terminal I have 3 separate active IPv4 addresses, but when I print the collection, I only get one, matched with the data from its respective NetRoutes.

function Get-InterfaceRoutes {
    $collection = @()
    $interfaceIPs = Get-NetIPConfiguration | Select-Object -Property IPv4Address, InterfaceIndex

    Foreach ($interfaceIP in $interfaceIPs) {
        $route = Get-NetRoute -InterfaceIndex ($interfaceIP.InterfaceIndex) | Select-Object -Property ifINdex, DestinationPrefix, NextHop, RouteMetric, ifMetric
        $collection = [PSCustomObject]@{
            Index = ($interfaceIp.InterfaceIndex)
            Address = ($interfaceIP.IPv4Address)
            DestinationPrefix = ($route.DestinationPrefix)
            NextHop = ($route.NextHop)
        }
    }

    Write-Host  "Collection of items: " ($collection | Format-List | Out-String)
}

And this is the output:

Collection of items:

Index : 16
Address : {169.254.71.100}
DestinationPrefix : {255.255.255.255/32, 224.0.0.0/4, ff00::/8, fe80::b0b4:b9b0:df22:4764/128...}
NextHop : {0.0.0.0, 0.0.0.0, ::, ::...}

I want to have the collection show all of the active IP addresses, which should be 3, matched by the Index to its corresponding NetRoute data.

If I am way off base and there is a MUCH easier way to do this, please feel free to tell me!

CodePudding user response:

The helpful comments from Santiago Squarzon already explained the basic problem, the re-assignment to the variable $collection.

In PowerShell though, most of the time you don't need to explicitly create and add to arrays because PowerShell automatically creates an array when you capture output of a function or a statement into a variable. This is also much more efficient because PowerShell has to recreate an array every time you use = (arrays are actually of fixed size!). When PowerShell automatically creates an array from captured output, it uses a more efficient data structure under the hood.

So I would modify the function like this:

function Get-InterfaceRoutes {
    $interfaceIPs = Get-NetIPConfiguration | Select-Object -Property IPv4Address, InterfaceIndex

    Foreach ($interfaceIP in $interfaceIPs) {
        $route = Get-NetRoute -InterfaceIndex ($interfaceIP.InterfaceIndex) | Select-Object -Property ifINdex, DestinationPrefix, NextHop, RouteMetric, ifMetric

        # Implicit output!
        [PSCustomObject]@{
            Index = ($interfaceIp.InterfaceIndex)
            Address = ($interfaceIP.IPv4Address)
            DestinationPrefix = ($route.DestinationPrefix)
            NextHop = ($route.NextHop)
        }
    }
}

Note that I have removed the $collection variable alltogether!

Instead, the [PSCustomObject]@{} is implicit output of the function. PowerShell writes it to the success stream, from where it can be captured by the caller or simply let it pass through to display it in the console.

Now when you actually want to collect the output of that function into an array variable, you simply assign it to the variable.

$routesArray = @(Get-InterfaceRoutes)

"Collection of items:" 
$routesArray | Format-List

The @() array subexpression operator makes sure that you always create an array, even if the function outputs only a single object.


That being said, there are cases where you actually want to collect intermediate output in an array variable. Say you wanted to store the output from the foreach in a local variable for further processing. In this case you still don't need to explicitly create an array, just assign from the foreach statement to a variable:

# [array] is an alternative to @() for ensuring an array
[array] $collection = Foreach ($interfaceIP in $interfaceIPs) {
    $route = Get-NetRoute -InterfaceIndex ($interfaceIP.InterfaceIndex) | Select-Object -Property ifINdex, DestinationPrefix, NextHop, RouteMetric, ifMetric

    # Implicit output, will be captured and added to $collection
    [PSCustomObject]@{
        Index = ($interfaceIp.InterfaceIndex)
        Address = ($interfaceIP.IPv4Address)
        DestinationPrefix = ($route.DestinationPrefix)
        NextHop = ($route.NextHop)
    }
}

Finally, if you really really want to explicitly create an array (e. g. when you need to add to two arrays from one loop), I recommend to use the List class, which is more efficient than a raw array for repeated additions. In addition it supports all operations of a raw array, e. g. you can use the index operator [] to access its elements.

# Create an instance of List, with elements of type Object.
$collection = [Collections.Generic.List[Object]]::new() 

Foreach ($interfaceIP in $interfaceIPs) {
    $route = Get-NetRoute -InterfaceIndex ($interfaceIP.InterfaceIndex) | Select-Object -Property ifINdex, DestinationPrefix, NextHop, RouteMetric, ifMetric

    # Add a custom object to the list
    $collection.Add( [PSCustomObject]@{
        Index = ($interfaceIp.InterfaceIndex)
        Address = ($interfaceIP.IPv4Address)
        DestinationPrefix = ($route.DestinationPrefix)
        NextHop = ($route.NextHop)
    })
}
  • Related