powershellsyntaxwhere-objectpowershell-7

Apply Where-Object shorthand operators to entire object


Where-Object's params providing shorthands for builtin operators, one can skip the scriptblock and write:

Get-ChildItem | Where-Object { $_.Name -match '\.(txt|md)$' }

as:

Get-ChildItem | Where-Object Name -match '\.(txt|md)$'

Is there some way to apply the operator to entire object instead of one of its properties? I.e. how can I write similar shorthand to the following:

Get-ChildItem | ForEach-Object Name | Where-Object { $_ -match '\.(txt|md)$' }

A simple Where-Object -match '\.(txt|md)$' fails with:

Where-Object: The specified operator requires both the -Property and -Value parameters. Provide values for both parameters, and then try the command again.

The -Value is a red herring, since it positionally matches the regex. Looking at help, the -Property for -Match is not specified as optional, but I thought maybe some magic like * or _ would work - alas they do not.


Solution

  • Indeed, as of PowerShell (Core) 7 v7.5.x, using simplified syntax with Where-Object syntactically requires a property-name argument,[1] i.e. an argument that binds, typically positionally, to the -Property parameter, in addition to requiring a switch named for a comparison operator (e.g., -match) and a comparison value, i.e. an argument that binds, typically positionally, to the -Value parameter.

    Since you can therefore not omit a property name and given that there is no placeholder value that represents the input object as a whole, applying an operation to an object as a whole is not currently possible with simplified syntax, so the more verbose script block-based syntax ({ ... }), via the typically positionally bound -FilterScript parameter, must be used, where the current pipeline input object must be referred to via the automatic $_ variable or its alias, $PSItem.

    Indeed, it would be helpful if simplified syntax allowed omission of a -Property argument to imply the intent to perform the comparison on the object as a whole, as it would allow simplifying this (script-block syntax):

    # Note: -Name makes Get-ChildItem output file names only, i.e. *strings*
    Get-ChildItem -Name | Where-Object { $_ -match '\.(txt|md)$' }
    

    to (simplified syntax):

    # WISHFUL THINKING as of PowerShell 7.5.x:
    # Omitting the LHS (-Property argument) implies use of the whole object
    Get-ChildItem -Name | Where-Object -match '\.(txt|md)$' 
    

    This enhancement (feature request) was first proposed in in GitHub issue #8357 in 2018. Alas, the discussion has stalled.


    [1] You can verify this with Get-Command -Syntax Where-Object: [-Property] <string>, a notation described in the conceptual about_Command_Syntax help topic, indicates that while specifying the parameter name (-Property) is optional - indicated by the [...] enclosure - the parameter value (of type <string>) is not, i.e. it is mandatory.
    Note that even though the -Value parameter is in effect mandatory, technically it is not, as the [...] enclosure around both the parameter name and value type indicates: [[-Value] <Object>]. Instead, the presence of a -Value argument is enforced inside the command, producing the error shown in your question in its absence. Apparently, this approach was preferred to prompting for a missing value, which is what would happen if the parameter were formally marked as mandatory (you can see the prompt in action for -Property if you provide neither a -Property nor a -Value argument; e.g. Where-Object -eq).
    It's not clear to me why this implementation was chosen; the only operator not requiring (or even accepting) a value is the inherently unary -Not operator, but it is in a separate parameter set that simply doesn't include the -Value parameter.