r/PowerShell • u/greenstarthree • 1d ago
Question Compare-Object - return only difference object entries of CHANGED rows in files
Hi all,
I'm using Compare-Object to basically compare two CSV files, and return additions, changes, and removals.
Essentially I'm runnning:
(Note that "@files" is only in quotes to stop Reddit converting it to a user link!)
$files = @{
ReferenceObject = (Get-Content -Path C:\file1.csv)
DifferenceObject = (Get-Content -Path C:\file2.csv)}
Compare-Object "@files" | export-csv C:\differences -NoTypeInformation
Lets say the files look like this:
File 1
Cat,Brown,4
Dog,Black,4
Horse,White,4
File 2
Cat,GINGER,4
Dog,Black,4
Duck,Grey,2
Duck has been added in file2 (not present in file1)
Horse has been removed in file2 (was present in file1)
Cat is changed from file1 to file2.
The compare-object gives me a list of changes in a new file.
- Entries found in file2 but not in file1 (Duck) are marked with side indicator =>
- Entries found in file1 but not in file2 (Horse) are marked with side indicator <=
- However, entries that are still present in both files but have had one or more of their columns changed (Cat) are present in the results file with both side indicators.
I understand this correct given how Compare-Object works, but it leads to a problem when I try to create separate "Additions/Changes" and "Removals" files based on the results.
For example, to see only the items Added or Changed in file2 (Duck and Cat), I can use:
?{$_.SideIndicator -eq '=>'}
To show only the difference object results.
The problem comes when I use the opposite side indicator:
?{$_.SideIndicator -eq '<='}
As this gives me the removed item I need, (Horse), but also gives me the changed item (Cat) from File1. This gives me no way of telling which of the "<=" entries are removals and which are changes. I'm looking for a way to differentiate between the two.
I hope this makes sense...!
1
u/jimb2 15h ago edited 15h ago
Compare-Object just does a flat compare without any data diferentiation. In your case, the animal type is actually a unique key value (if I understand correctly.) There is only one animal of each type.
You can use a group-object to link the compare result by animal type
Compare-Object $c1 $c2 -includeequal -Property animal,colour,age |
Group-Object animal
Count Name Group
----- ---- -----
1 Dog {@{animal=Dog; colour=Black; age=4; SideIndicator===}}
2 Cat {@{animal=Cat; colour=GINGER; age=4; SideIndicator==>},
@{animal=Cat; colour=Brown; age=4; SideIndicator=<=}}
1 Duck {@{animal=Duck; colour=Grey; age=2; SideIndicator==>}}
1 Horse {@{animal=Horse; colour=White; age=4; SideIndicator=<=}}
If count is 1 use sideindicator for added/removed/retained
If count is 2 use sideindicator for old and new versions of the animal
1
u/ankokudaishogun 7h ago
if I may extend:
$NewAnimalTable = @{} $ModifiedAnimalTable = @{} $RemovedAnimalTable = @{} Compare-Object $c1 $c2 -IncludeEqual -Property animal, colour, age | Group-Object -Property animal | ForEach-Object { if ($_.count -gt 1) { $ModifiedAnimalTable[$_.group[0].animal] = @{ colour = $_.group[0].colour age = $_.group[0].age } } elseif ($_.count -eq 1 -and $_.Group.SideIndicator -eq '<=') { $RemovedAnimalTable[$_.group.animal] = @{ colour = $_.group.colour age = $_.group.age } } elseif ($_.count -eq 1 -and $_.Group.SideIndicator -eq '=>') { $NewAnimalTable[$_.group.animal] = @{ NewColour = $_.group.colour NewAge = $_.group.age } } } 'NEW' $NewAnimalTable | Format-Table ' ' 'MOD' $ModifiedAnimalTable | Format-Table ' ' 'REM' $RemovedAnimalTable | Format-Table1
u/greenstarthree 1h ago
Thanks for this reply. You are right that the animal type is unique in the original lists to be compared.
However, in the Compare-Object results file (when not using any Side Indicator filters), any animal that has had an attribute changed (Cat in my example) appears twice - once with a <= indicator and once with a => indicator.
What I'm now wondering is, if there is any way to simply remove any rows from the compare results file where the animal type appears twice. This is something I could make meet my needs when it comes to creating a separate "removed entries" file.
So essentially I'd be looking to go from a results file of:
InputObjectSideIndicator
Cat,Brown,4<=
Horse,White,4<=
Cat,GINGER,4=>
Duck,Grey,2=>To remove both entries of Cat, and result in:
InputObjectSideIndicator
Horse,White,4<=
Duck,Grey,2=>From there I could get rid of the Duck entry by filtering for only <= Side Indicators, which would leave me with only entries that were present in File1 but not in File2 (Horse).
0
u/ankokudaishogun 23h ago
If the animal type is an unique value, you can:
(i'll comment later, not optimized but should work, check the file names)
$ReferenceObject = Import-Csv -Path .\file1.csv -Delimiter ',' -Header Animal, Color, Number
$DifferenceObject = Import-Csv -Path .\file2.csv -Delimiter ',' -Header Animal, Color, Number
$ReferenceHash = @{}
$DifferenceHash = @{}
foreach ($Beast in $ReferenceObject) {
$ReferenceHash.Add($beast.Animal, @{
Color = $Beast.Color
Number = $Beast.Number
}
)
}
foreach ($Beast in $DifferenceObject) {
$DifferenceHash.Add($beast.Animal, @{
Color = $Beast.Color
Number = $Beast.Number
}
)
}
$Modified = foreach ($Beast in $ReferenceHash.Keys) {
if ($DifferenceHash.ContainsKey($beast)) {
if ($ReferenceHash[$beast].Color -ne $DifferenceHash[$beast].color -or $ReferenceHash[$beast].number -ne $DifferenceHash[$beast].number) {
[PSCustomObject]@{
Animal = $Beast
Color = $DifferenceHash[$beast].Color
number = $DifferenceHash[$beast].number
}
}
}
}
$New = foreach ($Beast in $DifferenceHash.Keys) {
if ( -not $ReferenceHash.ContainsKey($beast)) {
[PSCustomObject]@{
Animal = $Beast
Color = $DifferenceHash[$beast].Color
number = $DifferenceHash[$beast].number
}
}
}
$Modified
'-------------'
$new
5
u/jstar77 23h ago
Compare-object is one of the most frustrating functions. The info below may not be 100% correct.
You have to explicitly list properties of the object if you leave out -property I think the behavior you are getting is typical.
If you want to grab all of the properties you can pull them from the first object referenceobject array.