Are there 2 kinds of $null in PowerShell? [duplicate] - json

Apparently, in PowerShell (ver. 3) not all $null's are the same:
>function emptyArray() { #() }
>$l_t = #() ; $l_t.Count
0
>$l_t1 = #(); $l_t1 -eq $null; $l_t1.count; $l_t1.gettype()
0
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
>$l_t += $l_t1; $l_t.Count
0
>$l_t += emptyArray; $l_t.Count
0
>$l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
True
0
You cannot call a method on a null-valued expression.
At line:1 char:38
+ $l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
>$l_t += $l_t2; $l_t.Count
0
>$l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
True
You cannot call a method on a null-valued expression.
At line:1 char:32
+ $l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
>$l_t += $l_t3; $l_t.count
1
>function addToArray($l_a, $l_b) { $l_a += $l_b; $l_a.count }
>$l_t = #(); $l_t.Count
0
>addToArray $l_t $l_t1
0
>addToArray $l_t $l_t2
1
So how and why is $l_t2 different from $l_t3? In particular, is $l_t2 really $null or not? Note that $l_t2 is NOT an empty array ($l_t1 is, and $l_t1 -eq $null returns nothing, as expected), but neither is it truly $null, like $l_t3. In particular, $l_t2.count returns 0 rather than an error, and furthermore, adding $l_t2 to $l_t behaves like adding an empty array, not like adding $null. And why does $l_t2 suddenly seem to become "more $null" when it gets passed in the the function addToArray as a parameter???????
Can anyone explain this behaviour, or point me to documentation that would explain it?
Edit:
The answer by PetSerAl below is correct. I have also found this stackOverflow post on the same issue.
Powershell version info:
>$PSVersionTable
Name Value
---- -----
WSManStackVersion 3.0
PSCompatibleVersions {1.0, 2.0, 3.0}
SerializationVersion 1.1.0.1
BuildVersion 6.2.9200.16481
PSVersion 3.0
CLRVersion 4.0.30319.1026
PSRemotingProtocolVersion 2.2

In particular, is $l_t2 really $null or not?
$l_t2 is not $null, but a [System.Management.Automation.Internal.AutomationNull]::Value. It is a special instance of PSObject. It is returned when a pipeline returns zero objects. That is how you can check it:
$a=&{} #shortest, I know, pipeline, that returns zero objects
$b=[System.Management.Automation.Internal.AutomationNull]::Value
$ReferenceEquals=[Object].GetMethod('ReferenceEquals')
$ReferenceEquals.Invoke($null,($a,$null)) #returns False
$ReferenceEquals.Invoke($null,($a,$b)) #returns True
I call ReferenceEquals thru Reflection to prevent conversion from AutomationNull to $null by PowerShell.
$l_t1 -eq $null returns nothing
For me it returns an empty array, as I expect from it.
$l_t2.count returns 0
It is a new feature of PowerShell v3:
You can now use Count or Length on any object, even if it didn’t have the property. If the object didn’t have a Count or Length property, it will will return 1 (or 0 for $null). Objects that have Count or Length properties will continue to work as they always have.
PS> $a = 42
PS> $a.Count
1
 
And why does $l_t2 suddenly seem to become "more $null" when it gets passed in the the function addToArray as a parameter???????
It seems that PowerShell converts AutomationNull to $null in some cases, like calling .NET methods. In PowerShell v2, even when saving AutomationNull to a variable it gets converted to $null.

To complement PetSerAl's great answer with a pragmatic summary:
Commands that happen to produce no output do not return $null, but the [System.Management.Automation.Internal.AutomationNull]::Value singleton,
which can be thought of as an "array-valued $null" or, to coin a term, null enumeration. It is sometimes also called "AutomationNull", for its type name.
Note that, due to PowerShell's automatic enumeration of collections, even a command that explicitly outputs an empty collection object such as #() has no output (unless enumeration is explicitly prevented, such as with Write-Output -NoEnumerate).
In short, this special value behaves like $null in scalar contexts, and like an empty array in enumeration contexts, notably in the pipeline, as the examples below demonstrate.
Given that $null and the null enumeration situationally behave differently, distinguishing between the two via reflection may be necessary, which is currently far from trivial; GitHub issue #13465 proposes implementing a test that would allow you to use $someValue -is [AutomationNull].
As of PowerShell 7.3.0, the following, obscure test is required:
$null -eq $someValue -and $someValue -is [psobject]
Caveats:
Passing [System.Management.Automation.Internal.AutomationNull]::Value as a cmdlet / function parameter value invariably converts it to $null.
See GitHub issue #9150.
In PSv3+, even an actual (scalar) $null is not enumerated in a foreach loop; it is enumerated in a pipeline, however - see bottom.
In PSv2-, saving a null enumeration in a variable quietly converted it to $null and $null was enumerated in a foreach loop as well (not just in a pipeline) - see bottom.
# A true $null value:
$trueNull = $null
# An operation with no output returns
# the [System.Management.Automation.Internal.AutomationNull]::Value singleton,
# which is treated like $null in a scalar expression context,
# but behaves like an empty array in a pipeline or array expression context.
$automationNull = & {} # calling (&) an empty script block ({}) produces no output
# In a *scalar expression*, [System.Management.Automation.Internal.AutomationNull]::Value
# is implicitly converted to $null, which is why all of the following commands
# return $true.
$null -eq $automationNull
$trueNull -eq $automationNull
$null -eq [System.Management.Automation.Internal.AutomationNull]::Value
& { param($param) $null -eq $param } $automationNull
# By contrast, in a *pipeline*, $null and
# [System.Management.Automation.Internal.AutomationNull]::Value
# are NOT the same:
# Actual $null *is* sent as data through the pipeline:
# The (implied) -Process block executes once.
$trueNull | % { 'input received' } # -> 'input received'
# [System.Management.Automation.Internal.AutomationNull]::Value is *not* sent
# as data through the pipeline, it behaves like an empty array:
# The (implied) -Process block does *not* execute (but -Begin and -End blocks would).
$automationNull | % { 'input received' } # -> NO output; effectively like: #() | % { 'input received' }
# Similarly, in an *array expression* context
# [System.Management.Automation.Internal.AutomationNull]::Value also behaves
# like an empty array:
(#() + $automationNull).Count # -> 0 - contrast with (#() + $trueNull).Count, which returns 1.
# CAVEAT: Passing [System.Management.Automation.Internal.AutomationNull]::Value to
# *any parameter* converts it to actual $null, whether that parameter is an
# array parameter or not.
# Passing [System.Management.Automation.Internal.AutomationNull]::Value is equivalent
# to passing true $null or omitting the parameter (by contrast,
# passing #() would result in an actual, empty array instance).
& { param([object[]] $param)
[Object].GetMethod('ReferenceEquals').Invoke($null, #($null, $param))
} $automationNull # -> $true; would be the same with $trueNull or no argument at all.
The [System.Management.Automation.Internal.AutomationNull]::Value documentation states:
Any operation that returns no actual value should return AutomationNull.Value.
Any component that evaluates a Windows PowerShell expression should be prepared to deal with receiving and discarding this result. When received in an evaluation where a value is required, it should be replaced with null.
PSv2 vs. PSv3+, and general inconsistencies:
PSv2 offered no distinction between [System.Management.Automation.Internal.AutomationNull]::Value and $null for values stored in variables:
Using a no-output command directly in a foreach statement / pipeline did work as expected - nothing was sent through the pipeline / the foreach loop wasn't entered:
Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' }
foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' }
By contrast, if a no-output commands was saved in a variable or an explicit $null was used, the behavior was different:
# Store the output from a no-output command in a variable.
$result = Get-ChildItem nosuchfiles* # PSv2-: quiet conversion to $null happens here
# Enumerate the variable.
$result | ForEach-Object { 'hi1' }
foreach ($f in $result) { 'hi2' }
# Enumerate a $null literal.
$null | ForEach-Object { 'hi3' }
foreach ($f in $null) { 'hi4' }
PSv2: all of the above commands output a string starting with hi, because $null is sent through the pipeline / being enumerated by foreach:
Unlike in PSv3+, [System.Management.Automation.Internal.AutomationNull]::Value is converted to $null on assigning to a variable, and $null is always enumerated in PSv2.
PSv3+: The behavior changed in PSv3, both for better and worse:
Better: Nothing is sent through the pipeline for the commands that enumerate $result: The foreach loop is not entered, because the [System.Management.Automation.Internal.AutomationNull]::Value is preserved when assigning to a variable, unlike in PSv2.
Possibly Worse: foreach no longer enumerates $null (whether specified as a literal or stored in a variable), so that foreach ($f in $null) { 'hi4' } perhaps surprisingly produces no output.
On the plus side, the new behavior no longer enumerates uninitialized variables, which evaluate to $null (unless prevented altogether with Set-StrictMode).
Generally, however, not enumerating $null would have been more justified in PSv2, given its inability to store the null-collection value in a variable.
In summary, the PSv3+ behavior:
takes away the ability to distinguish between $null and [System.Management.Automation.Internal.AutomationNull]::Value in the context of a foreach statement
thereby introduces an inconsistency with pipeline behavior, where this distinction is respected.
For the sake of backward compatibility, the current behavior cannot be changed. This comment on GitHub proposes a way to resolve these inconsistencies for a (hypothetical) potential future PowerShell version that needn't be backward-compatible.

When you return a collection from a PowerShell function, by default PowerShell determines the data type of the return value as follows:
If the collection has more than one element, the return result is an array. Note that the data type of the return result is System.Array even if the object being returned is a collection of a different type.
If the collection has a single element, the return result is the value of that element, rather than a collection of one element, and the data type of the return result is the data type of that element.
If the collection is empty, the return result is $null
$l_t = #() assigns an empty array to $l_t.
$l_t2 = emptyArray assigns $null to $l_t2, because the function emptyArray returns an empty collection, and therefore the return result is $null.
$l_t2 and $l_t3 are both null, and they behave the same way. Since you've pre-declared $l_t as an empty array, when you add either $l_t2 or $l_t3 to it, either with the += operator or the addToArray function, an element whose value is **$null* is added to the array.
If you want to force the function to preserve the data type of the collection object you're returning, use the comma operator:
PS> function emptyArray {,#()}
PS> $l_t2 = emptyArray
PS> $l_t2.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> $l_t2.Count
0
Note: The empty parentheses after emtpyArray in the function declaration is superfluous. You only need parentheses after the function name if you're using them to declare parameters.
An interesting point to be aware of is that the comma operator doesn't necessarily make the return value an array.
Recall that as I mentioned in the first bullet point, by default the data type of the return result of a collection with more than one element is System.Array regardless of the actual data type of the collection. For example:
PS> $list = New-Object -TypeName System.Collections.Generic.List[int]
PS> $list.Add(1)
PS> $list.Add(2)
PS> $list.Count
2
PS> $list.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
Note that the data type of this collection is List`1, not System.Array.
However, if you return it from a function, within the function the data type of $list is List`1, but it's returned as a System.Array containing the same elements.
PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return $list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
If you want the return result to be a collection of the same data type as the one within the function that you're returning, the comma operator will accomplish that:
PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return ,$list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
This isn't limited to array-like collection objects. As far as I've seen, any time PowerShell changes the data type of the object you're returning, and you want the return value to preserve the object's original data type, you can do that by preceding the object being returned with a comma. I first encountered this issue when writing a function that queried a database and returned a DataTable object. The return result was an array of hashtables instead of a DataTable. Changing return $my_datatable_object to return ,$my_datatable_object made the function return an actual DataTable object.

Related

How to set json property on powershell using a parameter value as the property name [duplicate]

Say I have JSON like:
{
"a" : {
"b" : 1,
"c" : 2
}
}
Now ConvertTo-Json will happily create PSObjects out of that. I want to access an item I could do $json.a.b and get 1 - nicely nested properties.
Now if I have the string "a.b" the question is how to use that string to access the same item in that structure? Seems like there should be some special syntax I'm missing like & for dynamic function calls because otherwise you have to interpret the string yourself using Get-Member repeatedly I expect.
No, there is no special syntax, but there is a simple workaround, using iex, the built-in alias[1] for the Invoke-Expression cmdlet:
$propertyPath = 'a.b'
# Note the ` (backtick) before $json, to prevent premature expansion.
iex "`$json.$propertyPath" # Same as: $json.a.b
# You can use the same approach for *setting* a property value:
$newValue = 'foo'
iex "`$json.$propertyPath = `$newValue" # Same as: $json.a.b = $newValue
Caveat: Do this only if you fully control or implicitly trust the value of $propertyPath.
Only in rare situation is Invoke-Expression truly needed, and it should generally be avoided, because it can be a security risk.
Note that if the target property contains an instance of a specific collection type and you want to preserve it as-is (which is not common) (e.g., if the property value is a strongly typed array such as [int[]], or an instance of a list type such as [System.Collections.Generic.List`1]), use the following:
# "," constructs an aux., transient array that is enumerated by
# Invoke-Expression and therefore returns the original property value as-is.
iex ", `$json.$propertyPath"
Without the , technique, Invoke-Expression enumerates the elements of a collection-valued property and you'll end up with a regular PowerShell array, which is of type [object[]] - typically, however, this distinction won't matter.
Note: If you were to send the result of the , technique directly through the pipeline, a collection-valued property value would be sent as a single object instead of getting enumerated, as usual. (By contrast, if you save the result in a variable first and the send it through the pipeline, the usual enumeration occurs). While you can force enumeration simply by enclosing the Invoke-Expression call in (...), there is no reason to use the , technique to begin with in this case, given that enumeration invariably entails loss of the information about the type of the collection whose elements are being enumerated.
Read on for packaged solutions.
Note:
The following packaged solutions originally used Invoke-Expression combined with sanitizing the specified property paths in order to prevent inadvertent/malicious injection of commands. However, the solutions now use a different approach, namely splitting the property path into individual property names and iteratively drilling down into the object, as shown in Gyula Kokas's helpful answer. This not only obviates the need for sanitizing, but turns out to be faster than use of Invoke-Expression (the latter is still worth considering for one-off use).
The no-frills, get-only, always-enumerate version of this technique would be the following function:
# Sample call: propByPath $json 'a.b'
function propByPath { param($obj, $propPath) foreach ($prop in $propPath.Split('.')) { $obj = $obj.$prop }; $obj }
What the more elaborate solutions below offer: parameter validation, the ability to also set a property value by path, and - in the case of the propByPath function - the option to prevent enumeration of property values that are collections (see next point).
The propByPath function offers a -NoEnumerate switch to optionally request preserving a property value's specific collection type.
By contrast, this feature is omitted from the .PropByPath() method, because there is no syntactically convenient way to request it (methods only support positional arguments). A possible solution is to create a second method, say .PropByPathNoEnumerate(), that applies the , technique discussed above.
Helper function propByPath:
function propByPath {
param(
[Parameter(Mandatory)] $Object,
[Parameter(Mandatory)] [string] $PropertyPath,
$Value, # optional value to SET
[switch] $NoEnumerate # only applies to GET
)
Set-StrictMode -Version 1
# Note: Iteratively drilling down into the object turns out to be *faster*
# than using Invoke-Expression; it also obviates the need to sanitize
# the property-path string.
$props = $PropertyPath.Split('.') # Split the path into an array of property names.
if ($PSBoundParameters.ContainsKey('Value')) { # SET
$parentObject = $Object
if ($props.Count -gt 1) {
foreach ($prop in $props[0..($props.Count-2)]) { $parentObject = $parentObject.$prop }
}
$parentObject.($props[-1]) = $Value
}
else { # GET
$value = $Object
foreach ($prop in $props) { $value = $value.$prop }
if ($NoEnumerate) {
, $value
} else {
$value
}
}
}
Instead of the Invoke-Expression call you would then use:
# GET
propByPath $obj $propertyPath
# GET, with preservation of the property value's specific collection type.
propByPath $obj $propertyPath -NoEnumerate
# SET
propByPath $obj $propertyPath 'new value'
You could even use PowerShell's ETS (extended type system) to attach a .PropByPath() method to all [pscustomobject] instances (PSv3+ syntax; in PSv2 you'd have to create a *.types.ps1xml file and load it with Update-TypeData -PrependPath):
'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
Update-TypeData -TypeName { $_ } `
-MemberType ScriptMethod -MemberName PropByPath -Value { #`
param(
[Parameter(Mandatory)] [string] $PropertyPath,
$Value
)
Set-StrictMode -Version 1
$props = $PropertyPath.Split('.') # Split the path into an array of property names.
if ($PSBoundParameters.ContainsKey('Value')) { # SET
$parentObject = $this
if ($props.Count -gt 1) {
foreach ($prop in $props[0..($props.Count-2)]) { $parentObject = $parentObject.$prop }
}
$parentObject.($props[-1]) = $Value
}
else { # GET
# Note: Iteratively drilling down into the object turns out to be *faster*
# than using Invoke-Expression; it also obviates the need to sanitize
# the property-path string.
$value = $this
foreach ($prop in $PropertyPath.Split('.')) { $value = $value.$prop }
$value
}
}
You could then call $obj.PropByPath('a.b') or $obj.PropByPath('a.b', 'new value')
Note: Type Deserialized.System.Management.Automation.PSCustomObject is targeted in addition to System.Management.Automation.PSCustomObject in order to also cover deserialized custom objects, which are returned in a number of scenarios, such as using Import-CliXml, receiving output from background jobs, and using remoting.
.PropByPath() will be available on any [pscustomobject] instance in the remainder of the session (even on instances created prior to the Update-TypeData call [2]); place the Update-TypeData call in your $PROFILE (profile file) to make the method available by default.
[1] Note: While it is generally advisable to limit aliases to interactive use and use full cmdlet names in scripts, use of iex to me is acceptable, because it is a built-in alias and enables a concise solution.
[2] Verify with (all on one line) $co = New-Object PSCustomObject; Update-TypeData -TypeName System.Management.Automation.PSCustomObject -MemberType ScriptMethod -MemberName GetFoo -Value { 'foo' }; $co.GetFoo(), which outputs foo even though $co was created before Update-TypeData was called.
This workaround is maybe useful to somebody.
The result goes always deeper, until it hits the right object.
$json=(Get-Content ./json.json | ConvertFrom-Json)
$result=$json
$search="a.c"
$search.split(".")|% {$result=$result.($_) }
$result
You can have 2 variables.
$json = '{
"a" : {
"b" : 1,
"c" : 2
}
}' | convertfrom-json
$a,$b = 'a','b'
$json.$a.$b
1

Eliminate Nulls

I'm out of luck finding information...
This powershell script collecting cert info in LocalMachine:
$cert_days = get-childitem cert:LocalMAchine -recurse |
select #{Name="{#CERTINFO}"; Expression={($_.FriendlyName)}} |
Sort "{#CERTINFO}"
write-host "{"
write-host " `"data`":`n"
convertto-json $cert_days
write-host
write-host "}"
I can't exclude Nulls or empty items like " ".
Using -ne $Null i get boolean results like true or false...
I would appreciate to hear Yours advice how to eliminate nulls or empty entries
To exclude empty entries, you could add a filter to remove those, preferably before the Sort-Object call., e.g.
$certs = ls Cert:\LocalMachine\ -Recurse |
Select #{Name = '{#CertInfo}'; Expression = {$_.FriendlyName}} |
Where { $_.'{#CertInfo}' } |
Sort '{#CertInfo}'
Robert Westerlund's helpful answer shows one way of filtering out $null and '' (empty-string) values, using the Where-Object cmdlet, which coerces the output from the script block to a Boolean, causing both $null and '' evaluate to $False and thus causing them to be filtered out.
This answer shows an alternative approach and discusses other aspects of the question.
tl;dr:
#{
data = #((Get-ChildItem -Recurse Cert:\LocalMachine).FriendlyName) -notlike '' |
Sort-Object | Select-Object #{ n='{#CERTINFO}'; e={ $_ } }
} | ConvertTo-Json
Using -ne $Null i get boolean results like true or false...
You only get a Boolean if the LHS is a scalar rather than an array - in the case of an array, the matching array elements are returned.
To ensure that the LHS (or any expression or command output) is an array, wrap it in #(...) the array-subexpression operator (the following uses PSv3+ syntax ):
#((Get-ChildItem -Recurse Cert:\LocalMachine).FriendlyName) -notlike ''
Note the use of -notlike '' to weed out both $null and '' values: -notlike forces the LHS to a string, and $null is converted to ''.
By contrast, if you wanted to use -ne $null, you'd have to use -ne '' too so as to also eliminate empty strings (though, in this particular case you could get away with just -ne '', because ConvertTo-Json would simply ignore $null values in its input).
Calling .FriendlyName on the typically array-valued output of Get-ChildItem directly is a PSv3+ feature called member-access enumeration: the .FriendlyName property access is applied to each element of the array, and the results are returned as a new array.
Filtering and sorting the values before constructing the wrapper objects with the {#CERTINFO} property not only simplifies the command, but is also more efficient.
Further thoughts:
Do not use Write-Host to output data: Write-Host bypasses PowerShell's (success) output stream; instead, use Write-Output, which you rarely need to call explicitly however, because its use is implied.
Instead of write-host "{", use write-output "{" or - preferably - simply "{" by itself.
PowerShell supports multi-line strings (see Get-Help about_Quoting_Rules), so there's no need to output the result line by line:
#"
{
"data":
$(<your ConvertTo-Json pipeline>)
}
"#
However, given that you're invoking ConvertTo-Json anyway, it's simpler to provide the data wrapper as a PowerShell object (in the simplest form as a hashtable) to ConvertTo-Json, as shown above.

Access JSON nested property using dynamic proprty name [duplicate]

I'm trying to dynamically parse & build-up a data structure of some incoming JSON files I'm to be supplied with (that'll be in non-standard structure) via Powershell to then process the data in those files & hand them over to the next step.
As part of that, I'm trying to build up the data structure of the JSON file into essentially a list of of data-paths for me to parse through & grab the data out of, so that I can cope with arrays, nested JSON objects and so on. So far so good.
Where I fall into some sort of Powershell peculiarity is in handling 2+ levels of depth via a variable. Let me give you a nice code-block to demonstrate the problem...
# Generate a Quick JSON file with different data types & levels
[object]$QuickJson = #'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
},
"DoubleNestedJSON" : {
"FirstLevel" : 1,
"DataValue" : "I am at first nested JSON level!",
"Second_JSON_Level" : {
"SecondLevel" : 2,
"SecondDataValue" : "I am on the 2nd nested level"
}
}
}
'#
# Import our JSON file into Powershell
[object]$MyPSJson = ConvertFrom-Json -InputObject $QuickJson
# Two quick string variables to access our JSON data paths
[string]$ShortJsonPath = "Name"
[string]$NestedJsonPath = "NestedJson.Version"
# Long string to access a double-nested JSON object
[string]$LongNestedJsonPath = "DoubleNestedJSON.Second_JSON_Level.SecondDataValue"
# Both of these work fine
Write-Host ("JSON Name (Direct) ==> " + $MyPSJson.Name)
Write-Host ("JSON Name (via Variable) ==> " + $MyPSJson.$ShortJsonPath)
# The following way to access a single nested Json Path works fine
Write-Host ("Nested JSON Version (via direct path) ==> " + $MyPSJson.NestedJson.Version)
# And THIS returns an empty line / is where I fall afoul of something in Powershell
Write-Host ("Nested JSON Version (via variable) ==> " + $MyPSJson.$NestedJsonPath)
# Other things I tried -- all returning an empty line / failing in effect
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson.$NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $MyPSJson.$($NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson).$($NestedJsonPath))
# Similarly, while THIS works...
$MyPSJson | select-object -Property NestedJSON
# This will fail / return me nothing
$MyPSJson | select-object -Property NestedJSON.Version
... in doing a bunch of research around this, I came across a suggestion to transform this into a Hashtable -- but that has the same problem, sadly. So with the above code-snippet, the following will transform the JSON object into a hashtable.
# Same problem with a hash-table if constructed from the JSON file...
[hashtable]$MyHash = #{}
# Populate $MyHash with the data from our quickie JSON file...
$QuickJson | get-member -MemberType NoteProperty | Where-Object{ -not [string]::IsNullOrEmpty($QuickJson."$($_.name)")} | ForEach-Object {$MyHash.add($_.name, $QuickJson."$($_.name)")}
# ... and even then -- $MyHash."$($NestedJsonPath)" -- fails, while a single level deep string works fine in the variable! :(
So it's pretty clear that I'm running into "something" of a Powershell internal logic problem, but I can't get Powershell to be overly helpful in WHY that is. Adding a '-debug' or similar in an attempt to increase verbosity hasn't helped shed light on this.
I suspect it's something akin to the items raised in this article here ( https://blogs.technet.microsoft.com/heyscriptingguy/2011/10/16/dealing-with-powershell-hash-table-quirks/ ) but just specific with variables.
I've not had any luck in finding anything obvious in the Powershell language specification (3.0 still being the latest from here as far as I can tell -- https://www.microsoft.com/en-usdownload/details.aspx?id=36389 ) either. It may be in there, I may just miss it.
Any advice in how to get Powershell to play nice with this would be greatly appreciated. I'm not sure how / why Powershell is fine with a simple string but seems to have issues with a 'something.somethingelse' type string here.
Thank you.
Further notes & addenda to the original:
It seems there are several issues to attack. One is "dealing with a single nested level". The "quick fix" for that seems to be using "Invoke-Expression" to resolve the statement, so for instance (IMPORTANT - take note of the back-tick with the first variable!):
iex "`$MyPSJson.$NestedJsonPath"
That use of Invoke-Expression also works with multi-nested situations:
iex "`$MyPSJson.$LongNestedJsonPath"
An alternative approach that was mentioned is the use of multiple select statements ... but I've not been able to get that to work with multi-nested objects (Powershell seems to not resolve those properly for some reason).
So for instance in this scenario:
($MyComp | select $_.DoubleNestedJSON | select FirstLevel)
Powershell returns
FirstLevel
----------
... instead of the actual data value. So - for now, it seems that selects won't work with multi-level nested objects due to Powershell apparently not resolving them?
When you write something like
$MyPSJson.Name
this will attempt to retrieve the member named Name from the object $MyPSJson. If there is no such member, you'll get $null.
Now, when you do that with variables for the member name:
$MyPSJson.$ShortJsonPath
this works pretty much identical in that the member with the name stored in $ShortJsonPath is looked up and its value retrieved. No surprises here.
When you try that with a member that doesn't exist on the object, such as
$MyPSJson.$NestedJsonPath
# equivalent to
# $MyPSJson.'NestedJSON.Version'
you'll get $null, as detailed before. The . operator will only ever access a member of the exact object that is the result of its left-hand-side expression. It will never go through a member hierarchy in the way you seem to expect it to do. To be frank, I'm not aware of a language that works that way.
The reason it works with Invoke-Expression is, that you effectively converting the $NestedJsonPath string into part of an expression resulting in:
$MyPSJson.NestedJSON.Version
which Invoke-Expression then evaluates.
You can, of course, define your own function that works that way (and I'd much prefer that instead of using Invoke-Expression, a cmdlet that should rarely, if ever, used (heck, it's eval for PowerShell – few languages with eval advocate its use)):
function Get-DeepProperty([object] $InputObject, [string] $Property) {
$path = $Property -split '\.'
$obj = $InputObject
$path | %{ $obj = $obj.$_ }
$obj
}
PS> Get-DeepProperty $MyPSJson NestedJson.Version
5,0
You could even make it a filter, so you can use it more naturally on the pipeline:
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
$path | %{ $obj = $obj.$_ }
$obj
}
PS> $MyPSJson | Get-DeepProperty nestedjson.version
5,0
Why this doesn't work
When you provide the properties that you'd like within a string, like this
[string]$NestedJsonPath = "NestedJson.Version"
Powershell looks for a property called NestedJSon.Version. It's not actually traversing the properties, but looking for a string literal which contains a period. In fact, if I add a property like that to your JSON like so.
[object]$QuickJson = #'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON.Version" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
}
}
I now get a value back, like so:
>$MyPSJson.$NestedJsonPath
69
The best way to get your values back is to use two separate variables, like this.
$NestedJson = "NestedJson"
$property = "Version"
>$MyPSJson.$NestedJson.$property
5.0
Or, alternatively, you could use select statements, as seen in the original answer below.
$MyPSJson | select $_.NestedJSON | select Version
Version
-------
1.2.3.4
If you use multiple Select-Object statements, they'll discard the other properties and allow you to more easily drill down to the value you'd like.
I followed Joey's filter example. However, I found it did not support accessing arrays.
Sharing the code that I got to work for this. Hopefully it will help others as well. Awesome thread!
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
foreach($node in $path){
if($node -match '.*\[\d*\]'){
$keyPieces = $node -split ('\[')
$arrayKey = $keyPieces[0]
$arrayIndex = $keyPieces[1] -replace ('\]','')
$obj = $obj.$arrayKey[$arrayIndex]
} else {
$obj = $obj.$node
}
}
$obj
}
Example usage:
$path = "nested.nestedtwo.steps[2]"
$payload | Get-DeepProperty $path
I had the same problem, so I wrote a function that does the trick.
It enables accessing any level of the json by variable path (string):
function getNestedJsonValue() {
param(
[Parameter(Mandatory = $true, ValueFromPipeline)] [PSCustomObject] $inputObj,
[Parameter(Mandatory = $true)] [string] $valuePath
)
if (($valuePath -eq $null) -or ($valuePath.length -eq 0) -or ($inputObj -eq $null)) {
return $inputObj
}
[System.Array] $nodes = "$valuePath" -split '\.'
foreach ($node in $nodes) {
if (($node -ne $null) -and ($node.length -gt 0) -and ($inputObj -ne $null)) {
$inputObj = $inputObj.$node
} else {
return $inputObj
}
}
return $inputObj
}
Usage: getNestedJsonValue -valuePath $nestedValuePath -inputObj $someJson
Pipe usage: $someJson | getNestedJsonValue -valuePath $nestedValuePath
An example nestedValuePath would be $nestedValuePath="some.nested.path"
Credit to wOxxOm for getting things on the right track.
Invoke-Expression does seem to work perfectly for this situation (if somewhat expensive, but that's fine in my personal example & situation), and it can cope with multiple levels of nesting.
So as examples for the above code snippet, the following will resolve just fine (Key point - pay attention to the initial back-tick. That caught me off guard):
Write-Host ("Single level JSON test ==> " + (iex "`$MyPSJson.$NestedJsonPath"))
Write-Host ("Double level JSON test ==> " + (iex "`$MyPSJson.$LongNestedJsonPath"))
That'll return our desired results:
Single level JSON test ==> 5.0
Double level JSON test ==> I am on the 2nd nested level
FoxDeploy's answer of using multi-level selects doesn't seem to work with 2+ levels of nesting, unfortunately for some bizarre reason.
Using:
($MyPSJson | select $_.DoubleNestedJSON | select FirstLevel)
We get the following back from Powershell:
FirstLevel
----------
... it seems that Powershell doesn't resolve nested objects in its entirety? We get a similar results if we intentionally use something that doesn't exist:
($MyPSJson | select $_.DoubleNestedJSON | select Doesnotexist)
... also simply returns:
Doesnotexist
------------
So - for now - it seems as if "Invoke-Expression" works most reliably (and most easily, as it's just a case of handing it a variable with the path'ed string).
I still can't explain the WHY of any of this so far (since I've used 'dotwalk'-ing with multiple variables through arrays quite happily), but at least there's a solution for now ... and that is Invoke-Expression !
The best (/least bad?) explanations for Invoke-Expression I've found so far are here (Microsoft's own description of the cmdlet doesn't really make a great job of hinting that it'd help in situations such as this):
http://ss64.com/ps/invoke-expression.html
https://www.adminarsenal.com/powershell/invoke-expression/

Problems parsing / accessing nested JSON / Hashtable data via variables in Powershell

I'm trying to dynamically parse & build-up a data structure of some incoming JSON files I'm to be supplied with (that'll be in non-standard structure) via Powershell to then process the data in those files & hand them over to the next step.
As part of that, I'm trying to build up the data structure of the JSON file into essentially a list of of data-paths for me to parse through & grab the data out of, so that I can cope with arrays, nested JSON objects and so on. So far so good.
Where I fall into some sort of Powershell peculiarity is in handling 2+ levels of depth via a variable. Let me give you a nice code-block to demonstrate the problem...
# Generate a Quick JSON file with different data types & levels
[object]$QuickJson = #'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
},
"DoubleNestedJSON" : {
"FirstLevel" : 1,
"DataValue" : "I am at first nested JSON level!",
"Second_JSON_Level" : {
"SecondLevel" : 2,
"SecondDataValue" : "I am on the 2nd nested level"
}
}
}
'#
# Import our JSON file into Powershell
[object]$MyPSJson = ConvertFrom-Json -InputObject $QuickJson
# Two quick string variables to access our JSON data paths
[string]$ShortJsonPath = "Name"
[string]$NestedJsonPath = "NestedJson.Version"
# Long string to access a double-nested JSON object
[string]$LongNestedJsonPath = "DoubleNestedJSON.Second_JSON_Level.SecondDataValue"
# Both of these work fine
Write-Host ("JSON Name (Direct) ==> " + $MyPSJson.Name)
Write-Host ("JSON Name (via Variable) ==> " + $MyPSJson.$ShortJsonPath)
# The following way to access a single nested Json Path works fine
Write-Host ("Nested JSON Version (via direct path) ==> " + $MyPSJson.NestedJson.Version)
# And THIS returns an empty line / is where I fall afoul of something in Powershell
Write-Host ("Nested JSON Version (via variable) ==> " + $MyPSJson.$NestedJsonPath)
# Other things I tried -- all returning an empty line / failing in effect
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson.$NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $MyPSJson.$($NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson).$($NestedJsonPath))
# Similarly, while THIS works...
$MyPSJson | select-object -Property NestedJSON
# This will fail / return me nothing
$MyPSJson | select-object -Property NestedJSON.Version
... in doing a bunch of research around this, I came across a suggestion to transform this into a Hashtable -- but that has the same problem, sadly. So with the above code-snippet, the following will transform the JSON object into a hashtable.
# Same problem with a hash-table if constructed from the JSON file...
[hashtable]$MyHash = #{}
# Populate $MyHash with the data from our quickie JSON file...
$QuickJson | get-member -MemberType NoteProperty | Where-Object{ -not [string]::IsNullOrEmpty($QuickJson."$($_.name)")} | ForEach-Object {$MyHash.add($_.name, $QuickJson."$($_.name)")}
# ... and even then -- $MyHash."$($NestedJsonPath)" -- fails, while a single level deep string works fine in the variable! :(
So it's pretty clear that I'm running into "something" of a Powershell internal logic problem, but I can't get Powershell to be overly helpful in WHY that is. Adding a '-debug' or similar in an attempt to increase verbosity hasn't helped shed light on this.
I suspect it's something akin to the items raised in this article here ( https://blogs.technet.microsoft.com/heyscriptingguy/2011/10/16/dealing-with-powershell-hash-table-quirks/ ) but just specific with variables.
I've not had any luck in finding anything obvious in the Powershell language specification (3.0 still being the latest from here as far as I can tell -- https://www.microsoft.com/en-usdownload/details.aspx?id=36389 ) either. It may be in there, I may just miss it.
Any advice in how to get Powershell to play nice with this would be greatly appreciated. I'm not sure how / why Powershell is fine with a simple string but seems to have issues with a 'something.somethingelse' type string here.
Thank you.
Further notes & addenda to the original:
It seems there are several issues to attack. One is "dealing with a single nested level". The "quick fix" for that seems to be using "Invoke-Expression" to resolve the statement, so for instance (IMPORTANT - take note of the back-tick with the first variable!):
iex "`$MyPSJson.$NestedJsonPath"
That use of Invoke-Expression also works with multi-nested situations:
iex "`$MyPSJson.$LongNestedJsonPath"
An alternative approach that was mentioned is the use of multiple select statements ... but I've not been able to get that to work with multi-nested objects (Powershell seems to not resolve those properly for some reason).
So for instance in this scenario:
($MyComp | select $_.DoubleNestedJSON | select FirstLevel)
Powershell returns
FirstLevel
----------
... instead of the actual data value. So - for now, it seems that selects won't work with multi-level nested objects due to Powershell apparently not resolving them?
When you write something like
$MyPSJson.Name
this will attempt to retrieve the member named Name from the object $MyPSJson. If there is no such member, you'll get $null.
Now, when you do that with variables for the member name:
$MyPSJson.$ShortJsonPath
this works pretty much identical in that the member with the name stored in $ShortJsonPath is looked up and its value retrieved. No surprises here.
When you try that with a member that doesn't exist on the object, such as
$MyPSJson.$NestedJsonPath
# equivalent to
# $MyPSJson.'NestedJSON.Version'
you'll get $null, as detailed before. The . operator will only ever access a member of the exact object that is the result of its left-hand-side expression. It will never go through a member hierarchy in the way you seem to expect it to do. To be frank, I'm not aware of a language that works that way.
The reason it works with Invoke-Expression is, that you effectively converting the $NestedJsonPath string into part of an expression resulting in:
$MyPSJson.NestedJSON.Version
which Invoke-Expression then evaluates.
You can, of course, define your own function that works that way (and I'd much prefer that instead of using Invoke-Expression, a cmdlet that should rarely, if ever, used (heck, it's eval for PowerShell – few languages with eval advocate its use)):
function Get-DeepProperty([object] $InputObject, [string] $Property) {
$path = $Property -split '\.'
$obj = $InputObject
$path | %{ $obj = $obj.$_ }
$obj
}
PS> Get-DeepProperty $MyPSJson NestedJson.Version
5,0
You could even make it a filter, so you can use it more naturally on the pipeline:
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
$path | %{ $obj = $obj.$_ }
$obj
}
PS> $MyPSJson | Get-DeepProperty nestedjson.version
5,0
Why this doesn't work
When you provide the properties that you'd like within a string, like this
[string]$NestedJsonPath = "NestedJson.Version"
Powershell looks for a property called NestedJSon.Version. It's not actually traversing the properties, but looking for a string literal which contains a period. In fact, if I add a property like that to your JSON like so.
[object]$QuickJson = #'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON.Version" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
}
}
I now get a value back, like so:
>$MyPSJson.$NestedJsonPath
69
The best way to get your values back is to use two separate variables, like this.
$NestedJson = "NestedJson"
$property = "Version"
>$MyPSJson.$NestedJson.$property
5.0
Or, alternatively, you could use select statements, as seen in the original answer below.
$MyPSJson | select $_.NestedJSON | select Version
Version
-------
1.2.3.4
If you use multiple Select-Object statements, they'll discard the other properties and allow you to more easily drill down to the value you'd like.
I followed Joey's filter example. However, I found it did not support accessing arrays.
Sharing the code that I got to work for this. Hopefully it will help others as well. Awesome thread!
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
foreach($node in $path){
if($node -match '.*\[\d*\]'){
$keyPieces = $node -split ('\[')
$arrayKey = $keyPieces[0]
$arrayIndex = $keyPieces[1] -replace ('\]','')
$obj = $obj.$arrayKey[$arrayIndex]
} else {
$obj = $obj.$node
}
}
$obj
}
Example usage:
$path = "nested.nestedtwo.steps[2]"
$payload | Get-DeepProperty $path
I had the same problem, so I wrote a function that does the trick.
It enables accessing any level of the json by variable path (string):
function getNestedJsonValue() {
param(
[Parameter(Mandatory = $true, ValueFromPipeline)] [PSCustomObject] $inputObj,
[Parameter(Mandatory = $true)] [string] $valuePath
)
if (($valuePath -eq $null) -or ($valuePath.length -eq 0) -or ($inputObj -eq $null)) {
return $inputObj
}
[System.Array] $nodes = "$valuePath" -split '\.'
foreach ($node in $nodes) {
if (($node -ne $null) -and ($node.length -gt 0) -and ($inputObj -ne $null)) {
$inputObj = $inputObj.$node
} else {
return $inputObj
}
}
return $inputObj
}
Usage: getNestedJsonValue -valuePath $nestedValuePath -inputObj $someJson
Pipe usage: $someJson | getNestedJsonValue -valuePath $nestedValuePath
An example nestedValuePath would be $nestedValuePath="some.nested.path"
Credit to wOxxOm for getting things on the right track.
Invoke-Expression does seem to work perfectly for this situation (if somewhat expensive, but that's fine in my personal example & situation), and it can cope with multiple levels of nesting.
So as examples for the above code snippet, the following will resolve just fine (Key point - pay attention to the initial back-tick. That caught me off guard):
Write-Host ("Single level JSON test ==> " + (iex "`$MyPSJson.$NestedJsonPath"))
Write-Host ("Double level JSON test ==> " + (iex "`$MyPSJson.$LongNestedJsonPath"))
That'll return our desired results:
Single level JSON test ==> 5.0
Double level JSON test ==> I am on the 2nd nested level
FoxDeploy's answer of using multi-level selects doesn't seem to work with 2+ levels of nesting, unfortunately for some bizarre reason.
Using:
($MyPSJson | select $_.DoubleNestedJSON | select FirstLevel)
We get the following back from Powershell:
FirstLevel
----------
... it seems that Powershell doesn't resolve nested objects in its entirety? We get a similar results if we intentionally use something that doesn't exist:
($MyPSJson | select $_.DoubleNestedJSON | select Doesnotexist)
... also simply returns:
Doesnotexist
------------
So - for now - it seems as if "Invoke-Expression" works most reliably (and most easily, as it's just a case of handing it a variable with the path'ed string).
I still can't explain the WHY of any of this so far (since I've used 'dotwalk'-ing with multiple variables through arrays quite happily), but at least there's a solution for now ... and that is Invoke-Expression !
The best (/least bad?) explanations for Invoke-Expression I've found so far are here (Microsoft's own description of the cmdlet doesn't really make a great job of hinting that it'd help in situations such as this):
http://ss64.com/ps/invoke-expression.html
https://www.adminarsenal.com/powershell/invoke-expression/

powershell piped input to export-csv different from -inputobject

In Powershell (3.0), I get different results when piping an object to Export-CSV than I do if I use the -IncludeObject parameter with the same object.
Example:
$a = Get-Process | select -first 5
$a | Export-CSV -Path '.\one.csv'
Export-CSV -InputObject $a -Path '.\two.csv'
Why are the contents of one.csv and two.csv different?
(And in case it's just me ...)
one.csv =
#TYPE System.Diagnostics.Process
"__NounName","Name","Handles","VM","WS","PM","NPM","Path","Company","CPU","FileVersion","ProductVersion","Description","Product","BasePriority","ExitCode","HasExited","ExitTime","Handle","HandleCount","Id","MachineName","MainWindowHandle","MainWindowTitle","MainModule","MaxWorkingSet","MinWorkingSet","Modules","NonpagedSystemMemorySize","NonpagedSystemMemorySize64","PagedMemorySize","PagedMemorySize64","PagedSystemMemorySize","PagedSystemMemorySize64","PeakPagedMemorySize","PeakPagedMemorySize64","PeakWorkingSet","PeakWorkingSet64","PeakVirtualMemorySize","PeakVirtualMemorySize64","PriorityBoostEnabled","PriorityClass","PrivateMemorySize","PrivateMemorySize64","PrivilegedProcessorTime","ProcessName","ProcessorAffinity","Responding","SessionId","StartInfo","StartTime","SynchronizingObject","Threads","TotalProcessorTime","UserProcessorTime","VirtualMemorySize","VirtualMemorySize64","EnableRaisingEvents","StandardInput","StandardOutput","StandardError","WorkingSet","WorkingSet64","Site","Container"
"Process","AATray","390","156721152","29769728","10678272","27600","C:\Program Files\IBM\ISAM ESSO\AA\AATray.exe","IBM Corporation","42.4166719","8.2.1.1143","8.2.1.1143","AccessAgent Tray Icon: Component of ISAM ESSO AccessAgent","ISAM ESSO AccessAgent","8",,"False",,"4844","390","7784",".","0","","System.Diagnostics.ProcessModule (AATray.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","27600","27600","10678272","10678272","257536","257536","63672320","63672320","29806592","29806592","194101248","194101248","True","Normal","10678272","10678272","00:00:32.8070103","AATray","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:30 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:42.4166719","00:00:09.6096616","156721152","156721152","False",,,,"29769728","29769728",,
"Process","ac.activclient.gui.scagent","547","155099136","22593536","8478720","33184","C:\Program Files\ActivIdentity\ActivClient\ac.activclient.gui.scagent.exe","HID Global Identity Assurance","2.2932147","7,0,5,17","7,0","ActivClient Agent","${release.product.name}","8",,"False",,"3648","547","7872",".","0","","System.Diagnostics.ProcessModule (ac.activclient.gui.scagent.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","33184","33184","8478720","8478720","274408","274408","62431232","62431232","22659072","22659072","168542208","168542208","True","Normal","8478720","8478720","00:00:01.8876121","ac.activclient.gui.scagent","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:30 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:02.2932147","00:00:00.4056026","155099136","155099136","False",,,,"22593536","22593536",,
"Process","accagt","166","508174336","17592320","25407488","26116",,,,,,,,"8",,,,,"166","2480",".","0","",,,,,"26116","26116","25407488","25407488","159440","159440","25657344","25657344","17694720","17694720","509747200","509747200",,,"25407488","25407488",,"accagt",,"True","0","System.Diagnostics.ProcessStartInfo",,,"System.Diagnostics.ProcessThreadCollection",,,"508174336","508174336","False",,,,"17592320","17592320",,
"Process","acevents","506","140414976","22474752","8048640","30856","C:\Program Files\ActivIdentity\ActivClient\acevents.exe","HID Global Identity Assurance","42.6350733","5,0,4,4","5,0","ActivIdentity Event Service","ActivClient Services","8",,"False",,"3872","506","8256",".","0","","System.Diagnostics.ProcessModule (acevents.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","30856","30856","8048640","8048640","249632","249632","61378560","61378560","22528000","22528000","157003776","157003776","True","Normal","8048640","8048640","00:00:24.6013577","acevents","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:34 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:42.6350733","00:00:18.0337156","140414976","140414976","False",,,,"22474752","22474752",,
"Process","acnamagent","395","98971648","14561280","6586368","28296",,,,,,,,"8",,,,,"395","2012",".","0","",,,,,"28296","28296","6586368","6586368","125784","125784","6647808","6647808","14594048","14594048","101597184","101597184",,,"6586368","6586368",,"acnamagent",,"True","0","System.Diagnostics.ProcessStartInfo",,,"System.Diagnostics.ProcessThreadCollection",,,"98971648","98971648","False",,,,"14561280","14561280",,
two.csv =
#TYPE System.Object[]
"Count","Length","LongLength","Rank","SyncRoot","IsReadOnly","IsFixedSize","IsSynchronized"
"5","5","5","1","System.Object[]","False","True","False"
For context, I'm trying to splat my parameters to Export-CSV, but I run into this when I pass -InputObject, and I can't pipe the input and then splat the rest of the parameters.
Thanks.
This is the expected behavior.
When you pipe in through the pipeline, arrays, collections, enumerable stuff, etc. gets processed item by item. This is usually what you want.
When you use -InputObject, it accepts the array as a single object.
The best way to see this is to use Get-Member:
$a = Get-Process | select -first 5
$a | Get-Member
Get-Member -InputObject $a
In the first invocation, you'll see the data type and members of each element. In the second invocation you'll see the type and members of the collection object.
Depending on the cmdlet, you may not notice difference at all because it's handling both cases (see the pipeline function at the end of my answer).
But in the case of Export-Csv, or ConvertTo-Json, or other serialization type cmdlets, you want this difference; otherwise it's very difficult to serialize the array explicitly when you want to.
Another way to demonstrate it:
$sb = {
$_
Write-Verbose $_.Count -Verbose
}
$a | ForEach-Object -Process $sb
ForEach-Object -Process $sb -InputObject $a
When writing your own pipeline functions, a common way to work around the different ways of receiving the object is to use the Process {} block along with foreach:
function Test-Pipeline {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
$MyVal
)
Process {
Write-Verbose $MyVal.Count -Verbose
foreach($v in $MyVal) {
$v
}
}
}
$a | Test-Pipeline
# Process block gets called once for each element
Test-Pipeline -MyVal $a
# Process block gets called once total, with the variable being an array
This works well because foreach doesn't fail if you give it a single non-array object, it just executes once.