Powershell: Detect broken use of [CmdletBinding()] and parameter attributes in Filter functions at parse time

Created on 28 Mar 2018  路  1Comment  路  Source: PowerShell/PowerShell

_[Updated based on @lzybkr's feedback below.]_

Use of [CmdletBinding()] and parameter attributes in Filter functions requires special considerations, and it is easy to create functions that invariably fail at runtime, in obscure fashion.

Perhaps it's possible to detect such cases at parse time.

Note that a tempting reason to use [CmdletBinding()] is to benefit from its automatic enforcement of allowing only arguments for explicitly declared parameters to be passed.

Steps to reproduce

Filter foo1 { [CmdletBinding()] param() $_ }
Filter foo2 { param([Parameter(Position=0)] $Bar) $_ }
1 | foo1; 1 | foo2

The reasons these two Filter functions invariably fail when invoked is that - due to using [CmdletBinding()] and/or parameter attributes - at least one pipeline-binding parameter must be defined explicitly.
This requirement makes the use of these attributes in Filter functions impractical, as you may as well define a regular Function function with an explicit process block at that point, which offers the added flexibility of begin and end blocks.

Expected behavior

Ideally, report an error during the parsing stage.

Actual behavior

foo1 : The input object cannot be bound to any parameters for the command either 
because the command does not take pipeline input or the input and its properties 
do not match any of the parameters that take pipeline input.
...
foo2 : The input object cannot be bound to any parameters for the command either 
because the command does not take pipeline input or the input and its properties 
do not match any of the parameters that take pipeline input.

That is, the function definitions succeeded, but any attempt to use them invariably fails.

Note that if you use just [CmdletBinding()] and no param() block, the functions don't break, but [CmdletBinding()] is quietly ignored.

Environment data

PowerShell Core v6.0.2 on macOS 10.13.3
PowerShell Core v6.0.2 on Ubuntu 16.04.3 LTS
PowerShell Core v6.0.2 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.674 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Issue-Discussion WG-Engine WG-Language

Most helpful comment

If the body does not use named blocks, the body is a process block if you used the filter keyword or the body is an end block if you used the function keyword.

The following should all work similarly:

function f1 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    process { $InputObject }
}
filter f2 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    $InputObject
}
filter f3 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    process { $InputObject }
}

So your actual issue is that a function with a process block (named or unnamed when using filter) must specify a parameter with ValueFromPipeline if the function uses cmdlet binding.

Like many other semantic errors involving the Parameter attribute, this could be detected at parse time, but hasn't in part because resolution of types and attributes was deferred until first invocation to allow a script to load necessary assemblies.

That said, Parameter and CmdletBinding could be special cased - symbol resolution could happen in two stages - an early pass (like what is needed for classes) can be performed during parsing, and a second time upon invocation.

>All comments

If the body does not use named blocks, the body is a process block if you used the filter keyword or the body is an end block if you used the function keyword.

The following should all work similarly:

function f1 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    process { $InputObject }
}
filter f2 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    $InputObject
}
filter f3 {
    param([Parameter(ValueFromPipeline)]$InputObject)
    process { $InputObject }
}

So your actual issue is that a function with a process block (named or unnamed when using filter) must specify a parameter with ValueFromPipeline if the function uses cmdlet binding.

Like many other semantic errors involving the Parameter attribute, this could be detected at parse time, but hasn't in part because resolution of types and attributes was deferred until first invocation to allow a script to load necessary assemblies.

That said, Parameter and CmdletBinding could be special cased - symbol resolution could happen in two stages - an early pass (like what is needed for classes) can be performed during parsing, and a second time upon invocation.

Was this page helpful?
0 / 5 - 0 ratings