Let's consider a collection of collections, and an operation that needs to be performed inside a pipeline on each element of the inner collection.
For the sake of simplicity, let it be an array of arrays, and the operation is simply printing to screen. For my question to be represented, let us also have an array whose elements are not collections:
$Array = "A", "B", "C"
$ArrayOfArrays = (1, 2, 3), (4, 5, 6), (7, 8, 9)
We know that piping will break a collection down to elements, like this:
$Array | & {process {Write-Host $_}}
$ArrayOfArrays | & {process {Write-Host $_}}
Now, to my surprise, when I run this, it is not breaking down the inner array to its elements:
$ArrayOfArrays | % -process {Write-Host $_} (1)
neither this:
$ArrayOfArrays | % -process {% -process {Write-Host $_}} (2)
(however this latter might seem an unnecessary attempt, seeing that (1) does not do that, but I tried it...)
I expected try (1) to do that, because I thought that piping does one breakdown, and when an element is received by ForEach-Object, it will further break it down, if it is a collection.
I could only solve it with inner piping:
$ArrayOfArrays | % -process {$_ | % -process {Write-Host $_}} (3)
however with this approach I can eliminate the ForEach-Object, of course:
$ArrayOfArrays | & {process {$_ | & {process {Write-Host $_}}}} (4)
So my 2 questions are:
1,
How to access an element of a collection that is in the collection in a pipeline, other than tries (3) and (4), or is this the only way to do that?
2,
If the only way to do what question 1 is asking is tries (3) and (4), then what is a valid use case of
ForEach-Object, where it can not be eliminated? I mean it can be a logical case, but also performance vs a script block. The fact that it is nicer than a script block with one pair of braces less is just not really enough for me...
.
EDIT after Manuel Batsching's answer:
As the ForEach-Object returns a collection's elements after its processing, we can do this (I let go of Write-Host, maybe it wasn't a good arbitrary operation, so let it be GetType):
$ArrayOfArrays | % -process {$_} | & {process {$_.GetType()}}
But we also know that if something returns a new object in the pipeline, it will trigger a breakdown if it is further piped and if it is a collection. So to do the breakdown, we can again eliminate ForEach-Object and do this:
$ArrayOfArrays | & {process {$_}} | & {process {$_.GetType()}}
And this dummy operation can be syntactically reduced if I define a filter like this:
Filter §
{
param (
[Parameter (Mandatory = $True, ValueFromPipeline = $True)]
[Object]
$ToBeTriggeredForBreakDown
) # end param
$ToBeTriggeredForBreakDown
}
and use it like this:
$Array | § | & {process {$_.GetType()}}
$ArrayOfArrays | § | & {process {$_.GetType()}}
$ArrayOfArraysOfArrays = ((1, 2), (3, 4)), ((5, 6), (7, 8))
$ArrayOfArraysOfArrays | § | & {process {$_.GetType()}}
$ArrayOfArraysOfArrays | § | § | & {process {$_.GetType()}}
So it is still hard to see for me when I would use ForEach-Object, it seems to me it is completely useless - except for reasons I look for in my questions.
.
EDIT after research:
Some collections provide their own methods, e.g. since v4 arrays have a ForEach method, so besides (3) and (4), one can do this (again a dummy operation, but with less code):
$ArrayOfArrays.ForEach{$_} | & {process {$_.GetType()}}
so this partially covers question 1.
Foreach-Objecthas the-Parallelswitch for parallel execution. The-Processparameter takes an array of script blocks. So you could technically perform different processing scripts against each piped object.Foreach-Objectalso supports operation statements. Technically you don't have to do anything, but1,2,3 | Foreach ToStringis arguably more readable than1,2,3 | & { process { $_.ToString() }}.Foreach-Objectalso has the-InputObjectparameter where you can process the entire object as one item. That is its way of preventing the array unwrapping that you see in the pipeline. You can do that with your method, but you must do obscure array wrapping yourself like,@(1,2,3)before sending down the pipeline.Foreach-Objectis a cmdlet, you gain access to common parameters. So you can utilize-PipelineVariablefor example to use output from this command to a command in a deeper pipeline.data | & { process {}}method is faster thandata | foreach-object -process {}. So it appears to be a tradeoff as to what you want to get out of it.-InputObject, but if I want to hold the array together, I would just not feed it to a pipeline - of course, when it is a result of a cmdlet already in the pipeline, it is a different story. You could post these as an answer, and if there is no better - and I think you probably covered the most important things - I will accept it.