问题
I\'m looking at the document of Rename-Item
and there is an example like this.
PS C:\\>Get-ChildItem *.txt | Rename-Item -NewName { $_.name -Replace \'\\.txt\',\'.log\' }
This example shows how to use the Replace operator to rename multiple files, even though the NewName parameter does not accept wildcard characters.
This command renames all of the .txt files in the current directory to .log.
The command uses the Get-ChildItem cmdlet to get all of the files in the current folder that have a .txt file name extension. Then, it uses the pipeline operator (|) to send those files to Rename-Item .
The value of NewName is a script block that runs before the value is submitted to the NewName parameter.
Note the last sentence:
The value of NewName is a script block that runs before the value is submitted to the NewName parameter.
Actually NewName
is a string:
[-NewName] <String>
So does that means I can always use a script block when the required parameter type is a string?
回答1:
Delay-bind script-block arguments are 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[pscustomobject]
too)- (no type specified), which is effectively the same as
[object]
whether such parameters accept pipeline input by value (
ValueFromPipelineBy
) or by property name (ValueFromPipelineByPropertyName
), is irrelevant.
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.
- This enables techniques such as the following call to
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, use a[ref]
-typed variable declared in the caller's scope 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 this GitHub issue.
回答2:
So does that means I can always use a script block when the required parameter type is a string? : NO
Here the technique is called Delay Binding, which is very useful this scenario.
What happens when you do delay binding ?
PowerShell ParameteBinder will understand the usage of delay binding and will execute the ScriptBlock first and the output is then converted to respective parameter's expected type, here it is string.
Below is an example.
#Working one
'Path'|Join-Path -Path {$_} -ChildPath 'File'
#Not working one
Join-Path -Path {'path'} -ChildPath 'File'
Join-Path : Cannot evaluate parameter 'Path' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input.
To know more about ParameterBinding, you can do a Trace-Command
like below.
Trace-Command ParameterBinding -Expression {'Path'|Join-Path -Path {$_} -ChildPath 'File'} -PSHost
回答3:
With Delay Binding, the parameter can receive a value from the pipeline using a scriptblock instead of the actual data type of the parameter.
In the scriptblock, $_ stands for the piped value.
It is only available when there is input coming on the pipeline.
来源:https://stackoverflow.com/questions/52805820/for-powershell-cmdlets-can-i-always-pass-a-script-block-to-a-string-parameter