Rim Rim - 3 months ago 7
JSON Question

Where-Object doesn't find a single instance of a custom object but it finds multiple instances of it. Possible bug?

I'm working on a script that makes an API call and is returned a JSON result and is handled as an array of custom objects in Powershell. One of the properties of the custom object is Status so for each unique status, I wanted to get a count of the object with that status. I was surprised when my code for certain statuses would return a count of 0 because if it didn't exist to begin with, my script wouldn't be searching for that status.

I then noticed the code worked when there is more than one object with the status but when there is only one object with that status, my code would not find it using Where-Object. If I loop through the array of custom objects, and do an if() statment, it finds all the statuses including ones that only had one object.

Am I missing something here or is this a bug? My laptop is running PS 5.1 and I also tried it on a W2K12 Server running PS 4.0 and I get the same thing.

I've also been able to simulate this (removing the API and JSON parts) in the code below:

$testArray = @()
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name1' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name2' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status2' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name3' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject

$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name4' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name5' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name6' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject

$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name7' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name8' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name9' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject

$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name10' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject

$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name11' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name12' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name13' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status1' -Force
$testArray += $testObject

$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name14' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name15' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject
$testObject = New-Object -TypeName PSObject
$testObject | Add-Member -type NoteProperty -name Name -value 'Name16' -Force
$testObject | Add-Member -type NoteProperty -name Status -value 'Status3' -Force
$testArray += $testObject


Write-Host "`n`nPowerShell version:"
$PSVersionTable.PSVersion


Write-Host "`n`nUsing Where-Object on the array"
$uniqueStatuses = ( $testArray.Status | sort | Get-Unique )
foreach ($status in $uniqueStatuses)
{
$status
($testArray | Select-Object | Where-Object { $_.Status -eq $status }).Count

}


Write-Host "`n`nLooping through the array"
foreach ($status in $uniqueStatuses)
{
$status
[int]$count = 0
foreach ($object in $testArray)
{
if ($object.Status -eq $status)
{
$count++
}
}
$count
}


In the sample code above, the status Status2 only appears in one of the custom objects and Where-Object doesn't find it for some reason but using foreach does.

When I run the code above, I get:


PowerShell version:

Major Minor Build Revision




5 1 14393 0

Using Where-Object on the array

Status1

8

Status2

Status3

7

Looping through the array

Status1

8

Status2

1

Status3

7

Answer

In the first example (using Where-Object), there's no guarantee that the pipeline results in a collection - with Status2 it only returns a single object, which is why Count doesn't return anything.

Use the array subexpression operator @() and you'll see the correct count:

$uniqueStatuses = ( $testArray.Status | sort | Get-Unique )
foreach ($status in $uniqueStatuses)
{
    $status
    @($testArray | Select-Object | Where-Object { $_.Status -eq $status }).Count

}