I am trying to pipe an array of strings to write-host and explicitly use $_ to write those strings:
'foo', 'bar', 'baz' | write-host $_
However, it fails with:
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.
This error message makes no sense to me because I am perfectly able to write
'foo', 'bar', 'baz' | write-host
I would have expected both pipelines to be equivalent. Apparently, they're not. So, what's the difference?
tl;dr
The automatic
$_variable and its alias,$PSItem, only ever have meaningful values inside script blocks ({ ... }), in specific contexts.The bottom section lists all relevant contexts.
They're not:
It is the pipeline-based equivalent of the following (equivalent in ultimate effect, not technically):
That is, in your command
Write-Hostreceives input from the pipeline that implicitly binds to its-Objectparameter for each input object, by virtue of parameter-Objectbeing declared as accepting pipeline input via attribute[Parameter(ValueFromPipeline=$true)]Before pipeline processing begins, arguments -
$_in your case - are bound to parameters first:Since
$_isn't preceded by a parameter name, it binds positionally to the - implied --Objectparameter.Then, when pipeline processing begins, pipeline parameter binding finds no pipeline-binding
Write-Hostparameter to bind to anymore, given that the only such parameter,-Objecthas already been bound, namely by an argument$_.In other words: your command mistakenly tries to bind the
-Objectparameter twice; unfortunately, the error message doesn't exactly make that clear.The larger point is that using
$_only ever makes sense inside a script block ({ ... }) that is evaluated for each input object.Outside that context,
$_(or its alias,$PSItem) typically has no value and shouldn't be used - see the bottom section for an overview of all contexts in which$_/$PSIteminside a script block is meaningfully supported.While
$_is most typically used in the script blocks passed to theForEach-ObjectandWhere-Objectcmdlets, there are other useful applications, most typically seen with theRename-Itemcmdlet: a delay-bind script-block argument:That is, instead of passing a static new name to
Rename-Item, you pass a script block that is evaluated for each input object - with the input object bound to$_, as usual - which enables dynamic behavior.As explained in the linked answer, however, this technique only works with parameters that are both (a) pipeline-binding and (b) not
[object]or[scriptblock]typed; therefore, given thatWrite-Object's-Objectparameter is[object]typed, the technique does not work:Therefore, a pipeline-based solution requires the use of
ForEach-Objectin this case:Contexts in which
$_(and its alias,$PSItem) is meaningfully defined:What these contexts have in common is that the
$_/$PSItemreference must be made inside a script block ({ ... }), namely one passed to / used in:... the
ForEach-ObjectandWhere-Objectcmdlets; e.g.:... the intrinsic
.ForEach()and intrinsic.Where()methods; e.g.:... a parameter, assuming that parameter allows a script block to act as a delay-bind script-block parameter; e.g.:
... conditionals and associated script blocks inside a
switchstatement; e.g.:... simple
functionandfilters; e.g.:... direct subscriptions to an object's event (n/a to the script block that is passed to the
-Actionparameter of aRegister-ObjectEventcall); e.g::Tip of the hat to Santiago Squarzon.... the
[ValidateScript()]attribute in parameter declarations; note that for array-valued parameters the script block is called for each element; e.g.,PowerShell (Core) only: ... the substitution operand of the
-replaceoperator; e.g.:... in the context of
<ScriptBlock>elements in formatting files (but not in the context of script block-based ETS members, where$thisis used instead).... in the context of using PowerShell SDK methods such as
.InvokeWithContext()