In what conditions does powershell unroll items in the pipeline? In what conditions does powershell unroll items in the pipeline? powershell powershell

In what conditions does powershell unroll items in the pipeline?


Empirical Test Results

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              TrueSystem.Collections.BitArray           True              TrueSystem.Collections.HashtableSystem.Collections.Queue              True              TrueSystem.Collections.SortedListSystem.Collections.Stack              True              TrueSystem.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.

Test Script

[System.Reflection.Assembly]::LoadWithPartialName('System.Collections') | Out-Null[System.Reflection.Assembly]::LoadWithPartialName('System.Collections.Generic') | Out-NullFunction 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 Cmdlet

This 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.