Loop Through Objects
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