How do/can I use ValueFromPipeLine on CustomObject methods

by willsteele at 2012-08-16 14:02:35

I have the following CustomObject generated from New-Module:

$objectdef = {
Export-ModuleMember

function Explore
{
[CmdletBinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$true)]
[System.Int32]
$number
)

ForEach-Object -InputObject $number <br> -Begin {<br> &quot;Starting to work with the collection&#46; It has $($number&#46;count) members&#46;&quot;<br> }
-Process {
"Seeing them, one at a time."
$number
} <br> -End {<br> &quot;Done for now&#46;&quot;<br> } <br> }<br> Export-ModuleMember -Function Explore<br>}</code><br><br><br>The following creates an error, as I would expect,<br><br><code>$calculator = New-Module -AsCustomObject -ScriptBlock $objectDef<br>1,2,3 | $calculator&#46;Explore()<br><br>Expressions are only allowed as the first element of a pipeline&#46;<br>At C&#58;\Users\wsteele\AppData\Local\Temp\afc69680-97fc-49b4-9745-3385113c03cc&#46;ps1&#58;line&#58;29 char&#58;30<br>+ 1,2,3 | $calculator&#46;Explore() &lt;&lt;&lt;&lt; </code><br><br>I guess I had in mind using this approach, like I used to before tacking the method on to an object. Actually this doesn't even work, as it only displays the last member in the collection, but, you get the idea.<br><br><code>function Explore<br>{<br> &#91;CmdletBinding()&#93;<br> param(<br> &#91;Parameter(Position=0,ValueFromPipeline=$true)&#93;<br> &#91;System&#46;Int32&#91;&#93;&#93;<br> $number<br> )<br> <br> ForEach-Object -InputObject $number
-Begin {
"Starting to work with the collection. It has $($number.count) members."
} <br> -Process {<br> &quot;Seeing them, one at a time&#46;&quot;<br> $number<br> }
-End {
"Done for now."
}
}
1,2,3 | Explore


Alternatively, if I do this,

Explore -number @(1,2,3)

it works. When I try to pass object via pipeline, it seems I have to use a ForEach loop to process pipelined items. Is there a way to simple reference the method without iterating over pipelined objects?
by poshoholic at 2012-08-16 14:21:12
You can’t pipe directly into any method in PowerShell, no matter where that method comes from. Methods are not the same as PowerShell commands, and this is one key difference among many.

You did define your function parameter as an array though, so you should be able to pass an array into it. But you need to have the syntax right. It would look like this:

$calculator.Explore(@(1,2,3))

That’s how you would process an array. If you want to use a pipeline, you will have to use ForEach-Object so that you can call the method and pass parameters directly into it. But in that scenario, you probably don’t want to use $_ because you’ll be passing in individual items, not a collection of all objects that are coming down the pipeline.

There is an exception to this. You could call your function like this:

1..5 | & {$obj.Explore(@($input))}

What that does is iterate over the items 1 through 5 and pass them down the pipeline. In the next state, you’re essentially invoking an end block, so you can’t reference the pipeline input using $. Instead you need $input. But $input is an enumerator, not an array, so you need to wrap it and that can be passed in to the Explore method in a single call. In practice I haven’t come across this technique being used, and I would argue that a better practice would be to make the call directly and pass the collection in to the method as a single parameter. Besides, it will end up being easier to read, and it will run faster (avoiding pipelines is a good thing if you want better performance).
by willsteele at 2012-08-16 14:33:11
Ok, I understand. The $collection | % { $obj.Method($)} is my typical approach. I was just curious if there was any magic I didn’t know about. That being the case, I’d probably stick with a single object instead of the array. I guess, in my head, I had this idea of the internal foreach begin/process/end blocks parsing a collection of objects…not necessarily a collection of objects passed to the function via iteration and a loop.