Suppose we have a PS filter X:
filter X([Parameter(ValueFromPipeline)]$o)
{
begin { Write-Host "Begin X" }
process { "|$o|" }
end { Write-Host "End X" }
}
Here is the result of running it:
C:\> 0..3 | X
Begin X
|0|
|1|
|2|
|3|
End X
C:\>
I want to write another filter Y which must invoke X internally. The desired output of running 0..3 | Y
should be:
Begin Y
Begin X
|0|
|1|
|2|
|3|
End X
End Y
Here is my current implementation:
filter Y([Parameter(ValueFromPipeline)]$o)
{
begin { Write-Host "Begin Y" }
process { $o | X }
end { Write-Host "End Y" }
}
Unfortunately, it does not work as I need:
C:\> 0..3 | Y
Begin Y
Begin X
|0|
End X
Begin X
|1|
End X
Begin X
|2|
End X
Begin X
|3|
End X
End Y
C:\>
Which is perfectly expected, because the correct implementation should be something like this:
filter Y([Parameter(ValueFromPipeline)]$o)
{
begin { Write-Host "Begin Y" ; InvokeBeginBlockOf X }
process { $o | InvokeProcessBlockOf X }
end { Write-Host "End Y" ; InvokeEndBlockOf X }
}
This is pseudo code, of course, but the point is - I must be able to invoke the begin/process/end blocks of the X filter individually and that would be the correct way to invoke filter from a filter.
So my question is - how can we invoke filter from a filter by individually invoking the begin/process/end blocks from the respective blocks in the outer filter?
CodePudding user response:
You would need a SteppablePipeline to accomplish this, as far as I know. I'm also unclear on why you're using the filter
keyword when what you're defining are actually functions.
A filter
is supposed to be a named Script Block which implicitly acts as a process
block. See this helpful answer for details.
I would advise you to change them for actual functions:
function Y {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[object] $InputObject
)
begin {
Write-Host "Begin Y"
$cmd = Get-Command X
$wrapped = { & $cmd @PSBoundParameters }
$steppablepipe = $wrapped.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablepipe.Begin($PSCmdlet)
}
process { $steppablepipe.Process($InputObject) }
end {
$steppablepipe.End()
Write-Host "End Y"
}
}
0..10 | Y
Outputs:
Begin Y
Begin X
|0|
|1|
|2|
|3|
|4|
|5|
|6|
|7|
|8|
|9|
|10|
End X
End Y
CodePudding user response:
Edit: It look like Santiago's use of steppable pipeline is better for what you want to do. I was not aware that it existed.
I am leaving my answer anyway as it provide a different take on this anyway.
A filter is a kind of function. Functions are stored in the $Function
automatic variable. You could use that to get the begin block from the AST and execute it.
Something like ([Scriptblock]::Create($function:X.Ast.Body.ProcessBlock.Extent.Text).invoke($o))
Complete example
filter X([Parameter(ValueFromPipeline)]$o) {
begin { Write-Host "Begin X" }
process { "|$o|" }
end { Write-Host "End X" }
}
filter Y([Parameter(ValueFromPipeline)]$o) {
begin {
Write-Host "Begin Y"
& ([Scriptblock]::Create($function:X.Ast.Body.BeginBlock.Extent.Text))
}
process { ([Scriptblock]::Create($function:X.Ast.Body.ProcessBlock.Extent.Text).invoke($o)) }
end {
Write-Host "End Y"
& ([Scriptblock]::Create($function:X.Ast.Body.EndBlock.Extent.Text))
}
}
Output for 0..3 | y
Begin Y
Begin X
|0|
|1|
|2|
|3|
End Y
End X
Note, when testing, I had some error for the begin block with the use of
[Scriptblock]::Create($function:X.Ast.Body.BeginBlock.Extent.Text).invoke()
# The script block cannot be invoked because it contains more than one
clause
so I switched it up with this instead.
& ([Scriptblock]::Create($function:X.Ast.Body.BeginBlock.Extent.Text))
(Not sure why yet, I'll have to investigate but in any case, this work)