1

I've read the PowerShell Team blog post Converting to Array and I've implemented the ToArray function it describes. This works. The article mentions performance could be improved by using an ArrayList instead of an array but doesn't include code for the ArrayList version. Now I'm trying to implement an ArrayList version of the function. However, I cannot get this to work.

In the End block of the pipeline function I convert the ArrayList to an array, then pass that array out. When the array is within the End block the array elements are of the right type and value. However, once the array is passed out of the End block the format of the array changes. The array returned to the calling code appears to combine an array of integer values (maybe index values?) with the array converted from the ArrayList. What am I doing wrong?

Here's the original version of the ToArray function, more-or-less a copy of the code from the blog post. It works:

function Convert-PipelineOutputToArray
{
    Begin
    {
        $output = @();
    }
    Process
    {
        $output += $_;
    }
    End
    {
        # Wraps the output in a one-element array to prevent the pipeline from unrolling the 
        # output.  In this case the pipeline will unroll the one-element wrapper array, leaving 
        # the original output array.
        return ,$output;
    }
}

$result = Get-Service | Convert-PipelineOutputToArray
Write-Host "Number of elements: $($result.Count)"
Write-Host "Resulting type: $($result.GetType().FullName)" 
Write-Host "Element type: $($result[0].GetType().FullName)"
Write-Host "First 5 elements:"
for($i = 0; $i -le 5; $i++)
{
    Write-Output $result[$i]
}

Result:

Number of elements: 353
Resulting type: System.Object[]
Element type: System.ServiceProcess.ServiceController
First 5 elements:

Status   Name               DisplayName
------   ----               -----------
Stopped  AarSvc_2e4aa3      Agent Activation Runtime_2e4aa3       
Stopped  acswgagent         Cisco AnyConnect SWG Agent
Running  acumbrellaagent    Cisco AnyConnect Umbrella Roaming Sec…
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  AJRouter           AllJoyn Router Service
Stopped  ALG                Application Layer Gateway Service

And here's the modified version of the function using an ArrayList:

function Convert-PipelineOutputToArray
{
    Begin
    {
        $arrayList = New-Object System.Collections.ArrayList;
    }
    Process
    {        
        $arrayList.Add($_);
    }
    End
    {
        $outputArray = $arrayList.ToArray();
        Write-Host "End block - number of elements: $($outputArray.Length)";
        Write-Host "End block - element type: $($outputArray[0].GetType().Name)";
        Write-Host "End block - first element value: $($outputArray[0].Name)";
        Write-Host "End block -------------";
        return ,$outputArray;
    }
}

$result = Get-Service | Convert-PipelineOutputToArray
Write-Host "Number of elements: $($result.Count)"
Write-Host "Resulting type: $($result.GetType().FullName)" 
Write-Host "Element type: $($result[0].GetType().FullName)"
Write-Host "First 5 elements:"
for($i = 0; $i -le 5; $i++)
{
    Write-Output $result[$i]
}

Result:

End block - number of elements: 353
End block - element type: ServiceController   
End block - first element value: AarSvc_2e4aa3
End block -------------
Number of elements: 354
Resulting type: System.Object[]
Element type: System.Int32
First 5 elements:
0
1
2
3
4
5

I noticed the $result array has 354 elements while the $outputArray in the End block of the function had only 353. Investigating the $result array I discovered elements [0] to [352] were integers and element [353] was a sub-array of 353 ServiceController objects - the output I wanted.

I tried modifying the return statement in the End block to leave out the leading comma: return $outputArray;. That changes the number of elements in the $result array to 706, with elements [0] to [352] being integers and elements [353] to [705] being the ServiceController objects. So it obviously unrolls the sub-array containing the ServiceController objects into the main $result array.

How can I return just the array of ServiceController objects from the End block?

Simon Elms
  • 17,832
  • 21
  • 87
  • 103
  • 2
    `.Add` from ArrayList outputs the index of the added item which if not captured `$null =` or redirected will become output of your function producing this behavior – Santiago Squarzon Aug 17 '23 at 03:10
  • 1
    @SantiagoSquarzon: Perfect, that solved the problem, thanks. If you'd like to add it as an answer I'll accept it. – Simon Elms Aug 17 '23 at 03:50
  • That's fine, I already closed your question as a duplicated, I believe you already understood the problem but if you have doubts @ me again. different than c# here you need to be mindful of any class that produces output and redirect / capture it if its unwanted output. – Santiago Squarzon Aug 17 '23 at 12:53
  • 1
    Thanks @SantiagoSquarzon. Your comment, plus the answer to the question linked in the duplicate note, explains what is going on clearly. – Simon Elms Aug 18 '23 at 00:29

0 Answers0