It might help to take a look at the types of the output from each example - see below for a breakdown.
Helper Functions
I'm using these the two helper functions in the sections below. Note - I'm guessing your config has an array at the root as that seems to reproduce the issue, but feel free to update your question if that's not true.
function GetConfigJson
{
return "[{""name"":""first""}, {""name"":""second""}]"
}
function Write-Value
{
param( $Value )
write-host $Value.GetType().FullName
write-host (ConvertTo-Json $Value -Compress)
}
And then using your examples:
Example 1
# example 1a - original example
PS> $a = 0
PS> GetConfigJson | ConvertFrom-Json | ForEach-Object { $a++ };
PS> $a
1
# example 1b - show return types and values
PS> GetConfigJson | ConvertFrom-Json | foreach-object { Write-Value $_ }
System.Object[]
{"value":[{"name":"first"},{"name":"second"}],"Count":2}
ConvertFrom-Json
returns an array object with two entries, but Foreach-Object
only runs once because it iterates over the single array object, not the 2 items in the array.
Example 2
# example 2a - original example
PS> $b = 0;
PS> ConvertFrom-Json GetConfigJson | foreach-object { $b++ }
ConvertFrom-Json : Invalid JSON primitive: GetConfigJson.
At line:1 char:1
+ ConvertFrom-Json GetConfigJson | foreach-object { $b++ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [ConvertFrom-Json], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand
# example 2b - show parameter types and values
PS> Write-Value GetConfigJson
System.String
"GetConfigJson"
ConvertFrom-Json
throws an exception because PowerShell is treating GetConfigJson
as a literal string, but "GetConfigJson"
obviously isn't valid json, hence the exception.
Example 3
# example 3a - original example
PS> $c = 0;
PS> ConvertFrom-Json (GetConfigJson) | ForEach-Object { $c++ };
PS> $c
1
# example 3b - show parameter types and values
PS> ConvertFrom-Json (GetConfigJson) | ForEach-Object { Write-Value $_ };
System.Object[]
{"value":[{"name":"first"},{"name":"second"}],"Count":2}
This uses the Grouping Operator ( ... )
around GetConfigJson
, so PowerShell evaluates GetConfigJson
as a call to a function instead of taking it as a literal string. It first executes the GetConfigJson
expression and then passes the result of that as a parameter into ConvertFrom-Json
. However, it's still iterating over the single array object rather than over the items, so the foreach-object
only runs once.
Example 4
# example 4a - original example
PS> $d = 0;
PS> (ConvertFrom-Json (GetConfigJson)) | ForEach-Object { $d++ };
PS> $d
2
# example 4b - show parameter types and values
PS> (ConvertFrom-Json (GetConfigJson)) | ForEach-Object { Write-Value $_ };
System.Management.Automation.PSCustomObject
{"name":"first"}
System.Management.Automation.PSCustomObject
{"name":"second"}
We're using the grouping operator twice here - once around GetConfigJson
to evaluate that as an expression as opposed to a string, and once around the whole ConvertFrom-Json (GetConfigJson)
. The outer ( ... )
causes PowerShell to "unroll" the single array object and emits its items into the pipeline consumed by Foreach-object
. This means ForEach-Object
iterates over the items and we see two separate values written out by ``Write-Value```
Summary
You managed to hit a lot of PowerShell quirks with this question - hopefully this answer helps understand what they're all doing and why you see the behaviour you do.
Update for PowerShell 7
Per the comment below from @mklement0, the behaviour of ConvertFrom-Json
changes from version 7 onwards - it doesn't enumerate arrays by default, and requires -NoEnumerate
to opt out.
E.g., '[ 1, 2 ]' | ConvertFrom-Json | Measure-Object
now reports 2 in v7+, whereas -NoEnumerate
is required to get the v6- behaviour: '[ 1, 2 ]' | ConvertFrom-Json -NoEnumerate | Measure-Object
(reports 1).