1

I am creating a small Powershell script to overwrite all files in folder with the name by 1 source file. So this is a script:

Version 1

Get-ChildItem -Path 'C:\path\to\folder' -Recurse |
Where-Object { $PSItem.Name -eq 'target-file.ps1' } |
Copy-Item -Path $sourceFile -Destination $PSItem -Force -PassThru

This script error in:

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.

So what is the $PSItem that passed to Copy-Item cmdlet ?

Version 2

Get-ChildItem -Path 'C:\path\to\folder' -Recurse |
Where-Object { $PSItem.Name -eq 'target-file.ps1' } |
Select-Object -ExpandProperty 'FullName' |
Copy-Item -Path $sourceFile -Destination $PSItem -Force -PassThru

So here I extract the property FullName from FileInfo object that run in pipeline just until this line and then it will continue to Copy-Item cmdlet ? No ! Error is:

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.

Version 3

Let's write what's in pipeline at this moment:

Get-ChildItem -Path 'C:\path\to\folder' -Recurse |
Where-Object { $PSItem.Name -eq 'target-file.ps1' } |
Select-Object -ExpandProperty 'FullName' |
Write-Host $PSItem

It is just Write-Host at the end. Result in error:

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.

Version 4

Let's create array:

$allFiles = Get-ChildItem -Path 'C:\path\to\folder' -Recurse |
Where-Object { $PSItem.Name -eq 'target-file.ps1' } |
Select-Object -ExpandProperty 'FullName'

foreach ($item in $allFiles) {
    Write-Host $item
}

It is actually output full name to file as expected in the foreach loop.

So - why 3 versions above that did not work and result in error ?

An-dir
  • 465
  • 2
  • 13
Alex F
  • 3,180
  • 2
  • 28
  • 40
  • 2
    When piping the results of Get-ChildItem to Copy-Item, you can leave out the `-Path` parameter, just tell it where to copy the piped object to in `-Destination` In examples 1 and two, you are trying to use the piped object itself as destination.. wrong order.. Example 3: just remove `| Write-Host $PSItem` to see the result on screen. In all examples you don't need the Where-Object clause, just use the `-Filter` parameter to look for the file with that name – Theo Jul 30 '23 at 09:26
  • 1
    Probably easier to use XCopy instead of Copy-Item. From cmd.exe type >Help XCopy for more info. – jdweng Jul 30 '23 at 09:41
  • it is not a wrong order - I copy my file instead those detected in target folder (overwrite) – Alex F Jul 30 '23 at 12:25
  • 1
    What is `-Destination $PSItem` supposed to mean? Are you trying to copy the file to itself? – Santiago Squarzon Jul 30 '23 at 13:34
  • @SantiagoSquarzon why do you think so ? What actually make you think that I copy file onto itself ? Could you highlight the code that doing so ? And even so - why it is not working ? – Alex F Jul 30 '23 at 14:03
  • 1
    Didn't @Theo already explained problem clearly in his comment? First, you're trying to use `-Path` when this parameter is bound from pipeline then you're trying to use `-Destination $PSItem` when `$PSItem` represents the current object in the pipeline – Santiago Squarzon Jul 30 '23 at 14:08
  • @SantiagoSquarzon where I use `-Path` parameter bound to pipeline ? Please read the code.... Anyway - why ? why it is not working ? – Alex F Jul 30 '23 at 14:41

2 Answers2

1

Provide the full path of the destination files via a calculated property named Destination to your Copy-Item call, which makes the pipeline input bind to the -Destination parameter:

Get-ChildItem -LiteralPath 'C:\path\to\folder' -Recurse -Filter 'target-file.ps1' |
  Select-Object @{ Name='Destination'; Expression={ $_.FullName } } |  
  Copy-Item -LiteralPath $sourceFile -Force -PassThru -WhatIf

Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf and re-execute once you're sure the operation will do what you want.


As for what you tried:

  • A given cmdlet's pipeline-binding parameters must be bound either via the pipeline or via an argument; if you try both, you'll get the error you saw.

    • See the bottom section of this answer for how to discover a given cmdlet's pipeline-binding parameters.

    • Re your ... | Copy-Item -Path $sourceFile -Destination $PSItem attempts:

      • The Copy-Item call is complete without pipeline input, and the conflicting attempt to also bind parameters via the pipeline therefore fails:

        • In Version 1, the (filtered) Get-ChildItem input binds to Copy-Item's -LiteralPath parameter (see this answer), which conflicts with the bound-by-argument -Path parameter.

        • In Version 2, the string pipeline input binds to Copy-Item's -Path parameter, which conflicts with that parameter already having been bound by argument.

    • Re your ... | Write-Host $PSItem attempt:

      • In Version 3, the pipeline input binds to Write-Host's -Object parameter, which conflicts with that parameter already having been bound by argument, positionally.
    • Re your Version 4:

      • You're passing only an argument to Write-Host - and not also pipeline input - which is why the calls succeed.

      • As an aside: Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed). See also: the bottom section of this answer.

  • The automatic $PSItem variable (as well as its more commonly used alias, $_) cannot meaningfully be used outside script blocks ({ ... }) - see this answer.


A note on terminology:

  • A parameter is a formal placeholder declared by a command (cmdlet, function, or script) that represents input data to operate on at runtime.

  • Binding a parameter is the act of assigning a value to it, i.e. the act of instantiating a placeholder.

    • Note that binding fundamentally only succeeds if the value to be assigned is either of the same type as the parameter or convertible to the latter.
  • There two binding mechanisms:

    • By argument, i.e. as a whitespace-separated token following the command name or path; e.g.:

      Write-Host -Object hi!
      
      • Because the argument above (the string `'hi!') is preceded by the name of the target parameter, it is called a named argument.

      • Many commands also accept arguments without indicating the target parameter, in which case it is the argument's position among other such arguments, if any, that implies the target parameter; therefore passing values this way is called a positional argument, but note any parameters targeted this way must have been declared as positional, i.e. must be positional parameters; e.g.:

         # Target parameter -Object is positionally implied.
         Write-Host hi!
        
    • Via the pipeline, i.e. via the output of another command or expression provided as input via |, the pipeline operator; e.g.:

      # Target parameter -Object is implied by the data type
      # of the pipeline input data.
      'hi!' | Write-Host
      
      • Parameters that can be bound by pipeline input must be declared as such and are called pipeline-binding parameters. Pipeline input can be bound by value, i.e. using each input object as a whole, or by property value, i.e. by the value of the property of each input object whose name is the same as the target parameter's (or any of its aliases).

        • E.g., The -Id parameter of Get-Process is declared as accepting pipeline input by property value, which means that input objects that either have an .Id property or a .PID property (because -PID is an alias of -Id) are bound to it; e.g., the following two commands are equivalent to Get-Process -Id $PID:

           # By-property-value pipeline binding to parameter -Id
           [pscustomobject] @{ Id = $PID } | Get-Process
          
           # Ditto, via parameter alias -PID.
           [pscustomobject] @{ PID = $PID } | Get-Process
          
      • Note that binding by pipeline means that each input object supplies a value to the target parameter, which in turn means that the target command must be designed to process pipeline input object by object. In the case of functions and scripts, i.e. commands written in PowerShell, a process block is required - see the relevant section of the conceptual about_Functions help topic.

        • E.g., in terms of output, 'hi', 'there' | Write-Host is equivalent to Write-Host 'hi'; Write-Host 'there', because the -Object parameter is bound twice with pipeline binding, once for each input string.
      • As noted, this answer has instructions on how to discover a given command's pipeline-binding parameters, if any, as well as their data types.

  • Note:

    • In a given call, a given parameter can only be bound by one of the binding mechanisms. Attempting to do both results in the The input object cannot be bound to any parameters for the command ... error you saw.

    • By contrast, combining the two binding mechanism to target different parameters is a very common and useful technique.

    • Binding by argument happens first, followed by binding via the pipeline, once for each input object.

      • Only parameters that haven't already been bound by arguments are candidates for pipeline binding.
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Could you exalain this: In Version 3, the string input binds to Write-Host's -Object parameter, which conflicts with that parameter already having been bound by argument, positionally. ? What is this 'bound by argument' ? – Alex F Jul 31 '23 at 06:52
  • @AlexF, please see the new bottom section I've just added to the answer. – mklement0 Jul 31 '23 at 12:35
  • So I understand that 'binding' process happens first and then powershell will analyze my arguments ? – Alex F Jul 31 '23 at 13:13
  • So what you saying here: `In Version 3, the pipeline input binds to Write-Host's -Object parameter, which conflicts with that parameter already having been bound by argument, positionally. ` Means that Write-Host was first bind and then it was surprised by argument that I provided for the same parameter ? – Alex F Jul 31 '23 at 13:15
  • 1
    @AlexF, yes, `... | Write-Host $foo` means that _argument_ `$foo` (first) binds to `-Object`, and PowerShell then complains about the (later) attempt to bind `-Object` _again_, via the _pipeline_. – mklement0 Jul 31 '23 at 13:18
0

The command copy-item accepts pipeline inputs but it tries to automatically matches the piped object AND does use your parameters additionally. In your case, additionally the destination parameter only accepts string but you tried an object that may contain more than string even can contain an array of objects. In most cases powershell will convert an object to string to make it work, but mutltiple objects AND the auto-matching of parameters result in your problem.

the solution is quite simple with your first example:

Get-ChildItem -Path 'C:\path\to\folder' -Recurse |
Where-Object { $PSItem.Name -eq 'target-file.ps1' } |
foreach {
Copy-Item -Path $sourceFile -Destination $PSItem -Force -PassThru
}

On the following page you can read about the commandlet and see that string but NOT multiple string[] is the only input for -destination: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/copy-item?view=powershell-5.1#syntax

An-dir
  • 465
  • 2
  • 13