# Delay-bind script-block argument:
# The code inside { ... } is executed for each input object ($_) and
# the output is passed to the -NewName parameter.
... | Rename-Item -NewName { $_.Name -replace '\.txt$','.log' }
The call above shows an application of a delay-bind script-block ({ ... }
) argument, which is an implicit feature that:
-
only works with parameters that are designed to take pipeline input,
-
of any type except the following, in which case regular parameter binding happens[1]:
[scriptblock]
[object]
([psobject]
, however, does work, and therefore the equivalent[pscustomobject]
too)- (no type specified), which is effectively the same as
[object]
-
Whether such parameters accept pipeline input by value (
ValueFromPipeline
) or by property name (ValueFromPipelineByPropertyName
), is irrelevant. -
See this answer for how to discover a given cmdlet’s pipeline-binding parameters; in the simplest case, e.g.:
Get-Help Rename-Item -Parameter * | Where pipelineInput -like True*
-
-
enables per-input-object transformations via a script block passed instead of a type-appropriate argument; the script block is evaluated for each pipeline object, which is accessible inside the script block as
$_
, as usual, and the script block’s output – which is assumed to be type-appropriate for the parameter – is used as the argument.-
Since such ad-hoc script blocks by definition do not match the type of the parameter you’re targeting, you must always use the parameter name explicitly when passing them.
-
Delay-bind script blocks unconditionally provide access to the pipeline input objects, even if the parameter would ordinarily not be bound by a given pipeline object, if it is defined as
ValueFromPipelineByPropertyName
and the object lacks a property by that name. -
This enables techniques such as the following call to
Rename-Item
, where the pipeline input fromGet-Item
is – as usual – bound to the-LiteralPath
parameter, but passing a script block to-NewName
– which would ordinarily only bind to input objects with a.NewName
property – enables access to the same pipeline object and thus deriving the destination filename from the input filename:Get-Item file | Rename-Item -NewName { $_.Name + '1' } # renames 'file' to 'file1'
; input binds to both-LiteralPath
(implicitly) and the-NewName
script block.
-
Note: Unlike script blocks passed to
ForEach-Object
orWhere-Object
, for example, delay-bind script blocks run in a child variable scope[2], which means that you cannot directly modify the caller’s variables, such as incrementing a counter across input objects.
As a workaround, useGet-Variable
to gain access to a caller’s variable and access its.Value
property inside the script block – see this answer for an example.
-
[1] Error conditions:
-
If you mistakenly attempt to pass a script block to a parameter that is either not pipeline-binding or is
[scriptblock]
– or[object]
-typed (untyped), regular parameter-binding occurs:- The script block is passed once, before pipeline-input processing begins, if any.
That is, the script block is passed as a (possibly converted) value, and no evaluation happens.- For parameters of type
[object]
or[scriptblock]
/ a delegate type such asSystem.Func
that is convertible to a script block, the script block will bind as-is. - In the case of a (non-pipeline-binding)
[string]
-typed parameter, the script block’s literal contents is passed as the string value. - For all other types, parameter binding – and therefore the command as a whole – will simply fail, since conversion from a script block is not possible.
- For parameters of type
- The script block is passed once, before pipeline-input processing begins, if any.
-
If you neglect to provide pipeline input while passing a delay-bind script block to a pipeline-binding parameter that does support them, you’ll get the following error:
Cannot evaluate parameter '<name>' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input.
[2] This discrepancy is being discussed in GitHub issue #7157.