Search code examples
powershellcompareobject

Compare-Object is returning the wrong value


I'm working on a PowerShell script and the Compare-Object cmdlet is not returning what I expect. I won't put the whole script here, but this is the general idea:

$knownFiles = Import-Csv -Path "C:\knownfiles.csv"
$currentFiles = Get-ChildItem -Path "\\file\share"
$knownFileObject = $knownFiles |
                   Select-Object -Property Name, Length, LastWriteTimeUTC |
                   Sort-Object -Property Name
$currentFileObject = $currentFiles |
                     Select-Object -Property Name, Length, LastWriteTimeUTC |
                     Sort-Object -Property Name

So now, I have two sorted PSCustomObjects containing the Names, Length and Last Write Time of a set of files. Here's what I can't figure out.

If the objects are identical, Compare-Object returns nothing, as expected. However, using the example below:

$knownFileObject looks like this:

Name          Length     LastWriteTimeUtc
----          ------     ----------------
file1.dat     38988     1/1/2018 8:04:57 AM
file2.dat     7182      1/2/2017 8:03:24 AM
file4.dat     1026      1/3/2017 8:04:20 AM
file5.dat     25137     1/3/2018 8:07:51 AM

$currentFileObject looks like this:

Name          Length     LastWriteTimeUtc
----          ------     ----------------
file1.dat     38988     1/1/2018 8:04:57 AM
file2.dat     7182      1/2/2017 8:03:24 AM
file3.dat     1026      1/2/2018 8:04:30 AM
file4.dat     1026      1/3/2017 8:04:20 AM
file5.dat     25137     1/3/2018 8:07:51 AM

If I run

Compare-Object -ReferenceObject $knownFileObject -DifferenceObject $currentFileObject

it will return this:

InputObject                                                            SideIndicator
-----------                                                            -------------
@{Name=file5.dat; Length=25137; LastWriteTimeUtc=1/3/2018 8:07:51 AM}  =>

It definitely detects that there is a difference between the two objects, but it always just returns the last row, instead of the actual record that's different. If there are two changed files, it returns the last two rows. If I add a "-property Name" to the Compare-Object, it returns the correct file name, but I also need to look for differences in size and write time. I can use Export-Csv to write both of the objects back out to a file, then run Compare-Object on the Get-Content of those files, and that works great, but I don't want to have to write two files to disk, as this job will be running hundreds of times per minute on different folders.

EDIT: Per one of the suggestions, I've also tried Compare-Object ... -Property Name, Length, LastWriteTimeUtc, and when I do that, it returns every item in both objects, or a 100% mismatch.

What am I doing wrong?


Solution

  • Specify the properties by which you want to compare the objects:

    Compare-Object $knownFileObject $currentFileObject -Property Name, Length, LastWriteTimeUtc
    

    You also need to ensure that you're comparing the same thing. The problem with your approach is that the LastWriteTimeUtc property of FileInfo objects is a DateTime object, which has sub-second precision:

    PS C:\> Get-Date| Format-List *
    
    DisplayHint : DateTime
    DateTime    : Donnerstag, 25. Januar 2018 16:31:07
    Date        : 25.01.2018 00:00:00
    Day         : 25
    DayOfWeek   : Thursday
    DayOfYear   : 25
    Hour        : 16
    Kind        : Local
    Millisecond : 268          ← this here
    Minute      : 31
    Month       : 1
    Second      : 7
    Ticks       : 636524946672682859
    TimeOfDay   : 16:31:07.2682859
    Year        : 2018
    

    but the timestamps from your CSV are strings with a precision limited to seconds, thus there'll likely be a miniscule difference between original and stored timestamp.

    In your case the best approach would be to store the timestamp in a defined format (long ISO format for instance) and use the same format for the timestamp from the directory listing. Sorting the arrays isn't required, BTW, so you can drop that step.

    $saved   = Import-Csv 'C:\knownfiles.csv' |
               Select-Object Name, Length, LastWriteTimeUtc
    $current = Get-ChildItem '\\server\share' |
               Select-Object Name, Length,
                   @{n='LastWriteTimeUtc';e={$_.LastWriteTimeUtc.ToString('s')}}
    
    Compare-Object $saved $current -Property Name, Length, LastWriteTimeUtc