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.
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:
Add a .RelativePath
property to the [pscustomobject]
instances you construct, and manually determine the relative path, by removing the root path's substring from the .FullName
property of each file.
Compare the arrays of custom objects by that property, using -Property RelativePath
. Additionally, in order to ensure that your custom objects are passed through as a whole, you must add -PassThru
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:
, @(...)
to ensure that the Get-ChildItem
output objects are output as an array as a whole (a single object) is explained in this answer.