Search code examples
powershellpipelineget-childitemexclusionpath

Powershell Get-ChildItem exclusions and piping


I'm working on a Powershell script to compare directories, and I want to clean up some of the code if possible. The script as a whole works exactly like I want it to, but I feel like the code I've included below (which is only a small part of it) could be written better.

For the code below, it focuses on getting exclusions from a csv file and incorporating them into Get-ChildItem. I've found that you can't pull the excluded paths from the csv the same way as you can the files (since paths deal with FullName vs Name, plus have wildcards for folders and servers incorporated).

So what I have below works for me. But is there a way to make the Where-Object part that deals with excluding paths into a function since I have to call it twice? I've tried just making it into a function and tacking it on at the end, but that doesn't work. I've also tried putting it at the beginning as a function, but that didn't work either. And I know when you deal with functions and piping data, you have to set it up a specific way. So maybe I'm just doing something wrong. Anyways, if you have a suggestion for how to clean this up or make it more efficient, I'm open to seeing what you have.

$ExcludedPaths = @(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedPaths
$ExcludedFiles = @(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedFiles

$SourceFiles = Get-ChildItem -Recurse -Path $SourceDir -Exclude $ExcludedFiles -Force | Where-Object {
    $FullName = $_.FullName
    -not($ExcludedPaths|Where-Object {
        $FullName -like "$_*"
    })
}
$DestFiles = Get-ChildItem -Recurse -Path $DestDir -Exclude $ExcludedFiles -Force | Where-Object {
    $FullName = $_.FullName
    -not($ExcludedPaths|Where-Object {
        $FullName -like "$_*"
    })
}

Solution

  • Abstracting the logic in your script out into a separate function is pretty straightforward.

    We start by identifying the variable parts of the routine - these will be our parameters. In your case that's the -Path and -Exclude parameters passed to Get-ChildItem and the $ExcludePaths array for the inner Where-Object.

    By reusing the same parameter names as the target cmdlet (Get-ChildItem), we can easily splat the $PSBoundParameters variable, so we don't have to manually check whether the -Exclude argument was passed or not. Similarly with $ExcludePaths, no additional checking is needed, as applying -not to an empty array will always return $true.

    So we end up with something like:

    function Get-ChildItemFiltered
    {
        param(
            [Parameter(Mandatory)]
            [string[]]$Path,
            [string[]]$Exclude,
            [string[]]$ExcludePaths
        )
    
        if($PSBoundParameters.ContainsKey('ExcludePaths')){
            $PSBoundParameters.Remove('ExcludePaths')
        }
    
        Get-ChildItem -Recurse -Force @PSBoundParameters | Where-Object {
            $FullName = $_.FullName
            -not($ExcludePaths|Where-Object {
                $FullName -like "$_*"
            })
        }
    }
    

    And your script ends up being much simpler:

    $SourceFiles = Get-ChildItem -Path $SourceDir -Exclude $ExcludedFiles -ExcludePaths $ExcludedPaths
    $DestFiles   = Get-ChildItem -Path $DestDir -Exclude $ExcludedFiles -ExcludePaths $ExcludedPaths
    

    Alternatively, you could store the filter you use for Where-Object in a scriptblock:

    $ExcludedPaths = @(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedPaths
    $ExcludedFiles = @(Import-Csv -LiteralPath 'D:\ExclusionList.csv') |Select-Object -Expand ExcludedFiles
    
    $Filter = {
        $FullName = $_.FullName
        -not($ExcludedPaths|Where-Object {
            $FullName -like "$_*"
        })
    }
    
    $SourceFiles = Get-ChildItem -Recurse -Path $SourceDir -Exclude $ExcludedFiles -Force | Where-Object -FilterScript $Filter
    $DestFiles = Get-ChildItem -Recurse -Path $DestDir -Exclude $ExcludedFiles -Force | Where-Object -FilterScript $Filter