2

Because the -Exclude parameter of Get-ChildItem is not filtering on subfolders when using the -Recurse flag, see among other unable-to-exclude-directory-using-get-childitem-exclude-parameter-in- powershell

But the -Exclude parameter can be used to filter out folders on the root level

I wrote my own recursive function:

function Get-ChildItem-Recurse() {
    [cmdletbinding()]
    Param(
      [parameter(ValueFromPipelineByPropertyName = $true)]
      [alias('FullName')]
      [string[]] $Path,
      [string] $Filter,
      [string[]] $Exclude,
      [string[]] $Include,
      [switch] $Recurse = $true,
      [switch] $File = $false
    )

    Process {
      ForEach ( $P in $Path ) {
        Get-ChildItem -Path $P -Filter $Filter -Include $Include -Exclude $Exclude | ForEach-Object {
        if ( -not ( $File -and $_.PSIsContainer ) ) {
          $_
        }
        if ( $Recurse -and $_.PSIsContainer ) {
          $_ | Get-ChildItem-Recurse -Filter $Filter -Exclude $Exclude -Include $Include -Recurse:$Recurse
        }
      }
    }
  }
}

When I pipe the result to a ForEach-Object to Copy the result to a different destination, all works fine, and the items except those that match the exclude parameter are copied

$source = 'D:\Temp\'
$destination = 'D:\Temp_Copy\'

Get-ChildItem-Recurse -Path $source -Exclude @( '*NotThis*', '*NotThat*' ) | ForEach-Object {
  $_ | Copy-Item -Destination ( "$($destination)$($_.FullName.Substring($source.Length))" ) -Force 
}

When I pipe it directly to the Copy-Item commandlet, I get a null-valued error, because .Substring() is called on $_.FullName that is apparently null

Get-ChildItem-Recurse -Path $source -Exclude @( '*NotThis*', '*NotThat*' ) |
  Copy-Item -Destination ( "$($destination)$($_.FullName.Substring($source.Length))" ) -Force

Because the native commandlet Get-ChildItem does allow me to pipe its result to Copy-Item, I like my own custom function also to be able to do that. But I can't figure out why it's not working.

Bartel
  • 198
  • 1
  • 7

2 Answers2

6

Use a scriptblock to dynamically bind a piped input value to the parameter:

Get-ChildItem ... |Copy-Item -Destination { "$($destination)$($_.FullName.Substring($source.Length))" }

The following answer by mklement0 has extensive details on this kind of dynamic binding (retroactively named "delay-bind scriptblocks", or colloquially "pipeline-bound scriptblocks"):
For PowerShell cmdlets, can I always pass a script block to a string parameter?

Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • 1
    This method works for several other cmdlets such as `Rename-Item` . When available, I always prefer this way than `ForEach-Object` – CFou Dec 08 '21 at 12:57
  • 2
    @CFou It works for any parameter with `ValueFromPipeline` set :) – Mathias R. Jessen Dec 08 '21 at 12:58
  • Very nice to know regarding the `ValueFromPipeline` attribute! Didn't know this. – Abraham Zinala Dec 08 '21 at 13:42
  • 1
    Thanks for updating, Mathias. Just to leave no doubt: the feature works with both `ValueFromPipeline` and `ValueFromPipelineByPropertyName` parameters (sometimes a single parameter is both). To discover a given cmdlet's pipeline-binding parameters, use, for instance, `Get-Help Copy-Item -Parameter * | Where pipelineInput -like True*` - see [this answer](https://stackoverflow.com/a/61232707/45375) for more information. /cc @AbrahamZinala. – mklement0 Dec 08 '21 at 16:48
0

Usually you pipe the source to copy:

$source = 'D:\Temp\'
$destination = 'D:\Temp_Copy\'

get-childitem $source | copy-item -destination $destination -whatif
js2010
  • 23,033
  • 6
  • 64
  • 66