Consider the following:
function OutputArray{
$l = @(,(10,20))
$l
}
(OutputArray) -is [collections.ienumerable]
# C:\ PS> True
(OutputArray).Count
# C:\ PS> 2
$l
is "unrolled" when it enters the pipeline. This answer states that powershell unrolls all collections. A hashtable is a collection. However, a hashtable is of course unaffected by the pipeline:
function OutputHashtable{
$h = @{nested=@{prop1=10;prop2=20}}
$h
}
(OutputHashtable) -is [collections.ienumerable]
# C:\ PS> True
(OutputHashtable).Count
# C:\ PS> 1
This comment suggests that it is all IEnumerable that are converted to object arrays. However, both array and hashtable are ienumerable:
@(,(10,20)) -is [collections.ienumerable]
#True
@{nested=@{prop1=10;prop2=20}} -is [collections.ienumerable]
#True
What, exactly, are the conditions where powershell "unrolls" objects into the pipeline?
A pipeline is a series of commands connected by pipeline operators ( | ) (ASCII 124). Each pipeline operator sends the results of the preceding command to the next command. The output of the first command can be sent for processing as input to the second command. And that output can be sent to yet another command.
The `|` character in between the two commands is the “pipe” which indicates that instead of displaying the output of the Get-Content command in the PowerShell command window, it should instead pass that data to the next script (the Measure-Object cmdlet).
Windows PowerShell has a unique pipeline variable $_ or $PSItem . Windows PowerShell's ability to pipe entire objects from one command to another is needed to represent that object traversing the pipeline. When one Windows PowerShell cmdlet pipes something to another command, that cmdlet can send an object.
The pipeline character in Windows PowerShell is the vertical bar (also called the pipe: | ). On most U.S. keyboards, it is found on the key with the backslash. You can press Shift + Backslash to get the pipe character.
I'd rather have an analytical basis for these results, but I need an answer so I can move on. So, here are the results of my stab at an empirical test to discover which collections are unrolled by powershell's pipeline, and which aren't:
True in a column indicates there's probably some unrolling occurring.
StartingType ChangedInCmdlet^ ChangedWhenEmitted**
------------ --------------- ------------------
System.String
System.Collections.ArrayList True True
System.Collections.BitArray True True
System.Collections.Hashtable
System.Collections.Queue True True
System.Collections.SortedList
System.Collections.Stack True True
System.Collections.Generic.Dictionary
System.Collections.Generic.List True True
These are results for a line of powershell that looks like this:
$result = $starting | Cmdlet
^ The ChangedInCmdlet
column indicates that the type of $starting
is different when it appears inside Cmdlet
.
** The ChangedWhenEmitted
column indicates that the type of $result
is different when it is assigned to $result from when it was emitted inside Cmdlet
.
There's probably some nuance in there for some types. That nuance can be analyzed by looking at the details of the output of the test script below. The whole test script is below.
[System.Reflection.Assembly]::LoadWithPartialName('System.Collections') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('System.Collections.Generic') | Out-Null
Function BackThroughPipeline{
[CmdletBinding()]
param([parameter(position=1)]$InputObject)
process{$InputObject}
}
Function EmitTypeName{
[CmdletBinding()]
param([parameter(ValueFromPipeline=$true)]$InputObject)
process{$InputObject.GetType().FullName}
}
$objects = (New-Object string 'TenTwentyThirty'),
([System.Collections.ArrayList]@(10,20,30)),
(New-Object System.Collections.BitArray 16),
([System.Collections.Hashtable]@{ten=10;twenty=20;thirty=30}),
([System.Collections.Queue]@(10,20,30)),
([System.Collections.SortedList]@{ten=10;twenty=20;thirty=30}),
([System.Collections.Stack]@(10,20,30)),
(& {
$d = New-Object "System.Collections.Generic.Dictionary``2[System.String,int32]"
('ten',10),('twenty',20),('thirty',30) | % {$d.Add($_[0],$_[1])}
$d
}),
(& {
$l = New-Object "System.Collections.Generic.List``1[int32]"
10,20,30 | % {$l.Add($_)}
$l
})
$objects |
% {
New-Object PSObject -Property @{
StartingType = $_.GetType().FullName
StartingCount = $_.Count
StartingItems = $_
InCmdletType = $_ | EmitTypeName
InCmdletCount = ($_ | EmitTypeName).Count
AfterCmdletType = (BackThroughPipeline $_).GetType().FullName
AfterCmdletItems = (BackThroughPipeline $_)
AfterCmdletCount = (BackThroughPipeline $_).Count
ChangedInCmdlet = if ($_.GetType().FullName -ne ($_ | EmitTypeName) ) {$true};
ChangedWhenEmitted = if (($_ | EmitTypeName) -ne (BackThroughPipeline $_).GetType().Fullname ) {$true}
}
}
Out-Collection
CmdletThis testing eventually led me to create a cmdlet that conditionally wraps collections in sacrificial arrays to (hopefully) reliably prevent loop unrolling. That cmdlet is called Out-Collection
and is in this github repository.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With