powershellfiledirectorycompare

Comparing two folder structures with PowerShell


I am trying to compare two folder structures with PowerShell. At the end, I should see an overview of all files/folders with the full path, which are just in folder A or folder B.

I also want to implement a switch which can be toggled to exclude the file extensions. The comparison should just be done at the path level. So when a file has the same name but a different size, it should be seen as "equal".

So far, it looks like this:

# Prompt for the paths of the two folders to compare
$folder1 = Read-Host "Enter the path for Folder A"
$folder2 = Read-Host "Enter the path for Folder B"

# Prompt for whether to ignore file extensions
$ignoreExtensions = Read-Host "Ignore file extensions? (y/n)"
$ignoreExtensions = $ignoreExtensions.ToLower() -eq "y"

# Get the files in each folder and store their names and paths in arrays
$dir1Dirs = Get-ChildItem -Recurse -Name $folder1 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}
$dir2Dirs = Get-ChildItem -Recurse -Name $folder2 | ForEach-Object {
    $name = $_.Name
    if ($ignoreExtensions) {
        $name = $name -replace '\.[^.]*$'
    }
    [PSCustomObject]@{
        Name = $name
        FullName = $_.FullName
    }
}

# Compare the two arrays of file names and display the paths to files that are different
$diff = Compare-Object -ReferenceObject $dir1Dirs -DifferenceObject $dir2Dirs | Where-Object { $_.SideIndicator -eq "=>" }

if ($diff) {
    Write-Host "Files that are different:"
    $diff | Select-Object -ExpandProperty FullName
} else {
    Write-Host "No differences found."
}

but I get an error:

PS C:\Windows\system32> D:\compare4.ps1
Enter the path for Folder A: D:\folder1
Enter the path for Folder B: D:\folder2
Ignore file extensions? (y/n): y

Files that are different:
Select-Object : Property "FullName" cannot be found.
At D:\compare4.ps1:36 char:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==>}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

Select-Object : Property "FullName" cannot be found.
At D:\compare4.ps1:36 char:13
+     $diff | Select-Object -ExpandProperty FullName
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (@{InputObject=; SideIndicator==>}:PSObject) [Select-Object], PSArgumen
   tException
    + FullyQualifiedErrorId : ExpandPropertyNotFound,Microsoft.PowerShell.Commands.SelectObjectCommand

When change the compare line from above with this one, the error is gone, but no output generated:

$diff = Compare-Object $dir1Dirs $dir2Dirs -Property Name -IncludeEqual -PassThru | Where-Object { $_.SideIndicator -eq "=>" }

What am I missing here?

Thanks in advance.


Solution

  • You're trying to compare folder trees by the relative paths of the files in them.

    While Get-ChildItem's -Name parameter does output relative paths, it is all that it outputs, i.e. it emits strings rather than the usual System.IO.FileInfo (and System.IO.DirectoryInfo) instances, which means that the output won't have properties such as .FullName

    If the requirement to optionally ignore extension weren't in the picture, you would be able to reconstruct the original full paths, by inferring what root path to prepend, depending on the value of .SideIndicator in the output from Compare-Object.

    To satisfy your requirements, you'll have to:

    To put it all together:

    $folder1 = Read-Host "Enter the path for Folder A"
    $folder2 = Read-Host "Enter the path for Folder B"
    
    # Prompt for whether to ignore file extensions
    $ignoreExtensions = 'y' -eq (Read-Host 'Ignore file extensions? (y/n)')
    
    # Get the files in each folder and store their relative and full paths
    # in arrays, optionally without extensions.
    $dir1Dirs, $dir2Dirs = $folder1, $folder2 | 
      ForEach-Object {
        $fullRootPath = Convert-Path -LiteralPath $_
        # Construct the array of custom objects for the folder tree at hand
        # and *output it as a single object*, using the unary form of the 
        # array construction operator, ","  
        , @(
          Get-ChildItem -File -Recurse -LiteralPath $fullRootPath |
            ForEach-Object {
              $relativePath = $_.FullName.Substring($fullRootPath.Length + 1)
              if ($ignoreExtensions) { $relativePath = $relativePath -replace '\.[^.]*$' }
              [PSCustomObject] @{
                RelativePath = $relativePath
                FullName = $_.FullName
              }
            }
        )
      }
    
    # Compare the two arrays.
    # Note the use of -Property RelativePath and -PassThru
    # as well as the Where-Object SideIndicator -eq '=>' filter, which
    # - as in your question - only reports differences
    # from the -DifferenceObject collection.
    # To report differences from *either* collection, simply remove the filter.
    $diff = 
      Compare-Object -Property RelativePath -PassThru $dir1Dirs $dir2Dirs | 
      Where-Object SideIndicator -eq '=>'
    
    # Output the results.
    if ($diff) {
        Write-Host "Files that are different:"
        $diff | Select-Object -ExpandProperty FullName
    } else {
        Write-Host "No differences found."
    }
    

    Note: