Loop Through Objects

Loop Through Objects

2015, Nov 18    

Looping through objects can be a pain, until you know what you’re doing. First thing you need to do is determine what kind of object you’re dealing with

Note: if it’s anything besides the above types, it’s like a child of a PSObject, and you can use that method.

Before I dive into this I’ll state that shorthand is not preferred for scripts; it’s less readable. The % operator is considered shorthand of ForEach-Object. This was the only time you will see a percent sign in this post.

In scripts, I prefer to use foreach instead of ForEach-Object. Unless, of course, there’s a speed benefit; such as using workflows, executing commands across several computers, etc. Speed increases aren’t always realized. Regardless, my decision is because readability is lost by using the automatic, implied $_ variable.

Array

Arrays are pretty simple, but I’ll cover them anyway for thoroughness.

[array] $arr = @(1, 2, 3)

foreach ($item in $arr) {
    Write-Host "Item: ${item}"
}

Output:

Item: 1
Item: 2
Item: 3

ForEach-Object example:

$arr | ForEach-Object {
    Write-Host "Item: ${_}"
}

Same output as previous example.

Hashtable

I originally posted this on stackoverflow. I’ll cover a couple of options, but they’ll use the same variable:

$hash = @{
    a = 1
    b = 2
    c = 3
}

Use GetEnumerator()

Note: personal preference; syntax is easier to read for someone that’s new to PowerShell.

The GetEnumerator() method would be done as shown:

foreach ($h in $hash.GetEnumerator()) {
    Write-Host "$($h.Name): $($h.Value)"
}

Output:

c: 3
b: 2
a: 1

ForEach-Object example:

$hash.GetEnumerator() | ForEach-Object {
    Write-Host "$($_.Name): $($_.Value)"
}

Same output as previous example.

Use Keys

The Keys method would be done as shown:

foreach ($h in $hash.Keys) {
    Write-Host "${h}: $($hash.Item($h))"
}

Output:

c: 3
b: 2
a: 1

ForEach-Object example:

$hash.Keys | ForEach-Object {
    Write-Host "${_}: $($hash.Item($_))"
}

Same output as previous example.

PSObject

I’ll cover a couple of options, and they’ll use the same variable structure as the hashtable:

$pso = New-Object PSObject                                       
$Object | Add-Member NoteProperty a 1
$Object | Add-Member NoteProperty b 2
$Object | Add-Member NoteProperty c 3

That’s kind of a pain, so I’ll show you an easier way that been available since PowerShell 2.0:

$pso = New-Object PSObject -Property @{
    a = 1
    b = 2
    c = 3
}

Use PSObject.Properties

The PSObject property is available, albeit hidden, on all PS Objects.

foreach ($item in $pso.PSObject.Properties) {
    Write-Host "$($item.Name): $($item.Value)"
}

Output:

c: 3
b: 2
a: 1

ForEach-Object example:

$pso.PSObject.Properties | ForEach-Object {
    Write-Host "${_}: $($hash.Item($_))"
}

Practical Example

I originally posted this on stackoverflow.

$ipinfo = Invoke-WebRequest 'http://ipinfo.io/json' -UseBasicParsing | ConvertFrom-Json
foreach ($info in $ipinfo.PSObject.Properties) {
    Write-Host "$($info.Name): $($info.Value)"
}

Outputs:

ip: 1.2.3.4
hostname: No Hostname
city: Denton
region: Texas
country: US
loc: 33.2148,-97.1331
org: AS589 University of North Texas
postal: 76203

This example was originally done with POSH 4.0; ConvertTo-Json and ConvertFrom-Json were introduced in POSH 3.0. I haven’t tested POSH 3.0. I have tested in PowerShell 5.0 and 5.1; I got the same output.

ForEach-Object example:

(Invoke-WebRequest 'http://ipinfo.io/json' -UseBasicParsing | ConvertFrom-Json).PSObject.Properties | ForEach-Object {
    Write-Host "$($_.Name): $($_.Value)"
}

Same output as previous example.

Additional information

Be careful sorting your hashtable… Sort-Object may change it to an array:

PS> $hash.GetType()

IsPublic IsSerial Name      BaseType
-------- -------- ----      --------
True     True     Hashtable System.Object


PS> $hash = $hash.GetEnumerator() | Sort-Object Name
PS> $hash.GetType()

IsPublic IsSerial Name     BaseType
-------- -------- ----     --------
True     True     Object[] System.Array