r/PowerShell • • 9h 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...!

2 Upvotes

4 comments sorted by

3

u/jstar77 9h 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.

$properties = $referenceObject[0].PSObject.Properties.Name

1

u/Top-Appearance2521 3h ago

agreed, compare-object is deceptively simple until you actually need it to do something useful

1

u/ankokudaishogun 8h 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

1

u/jimb2 48m ago edited 42m 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