Home > Software engineering >  Powershell eq operator saying hashes are different, while Write-Host is showing the opposite
Powershell eq operator saying hashes are different, while Write-Host is showing the opposite

Time:11-11

I have a script that periodically generates a list of all files in a directory, and then writes a text file of the results to a different directory.

I'd like to change this so it checks the newest text file in the output directory, and only makes a new one if there's differences. It seemed simple enough.

Here's what I tried:

First I get the most recent file in the directory, grab the hash, and write my variable values to the console:

$lastFile = gci C:\ReportOutputDir | sort LastWriteTime | select -last 1 | Select-Object -ExpandProperty FullName
$oldHash = Get-FileHash $lastFile | Select-Object Hash
Write-Host 'lastFile = '$lastFile
Write-Host 'oldHash = '$oldHash

Output:

lastFile = C:\ReportOutputDir\test1.txt
oldHash =  @{Hash=E7787C54F5BAE236100A24A6F453A5FDF6E6C7333B60ED8624610EAFADF45521}

Then I do the exact same gci on the FileList dir, and create a new file (new_test.txt), then grab the hash of this file:

gci -Path C:\FileLists -File -Recurse -Name -Depth 2 | Sort-Object | out-file C:\ReportOutputDir\new_test.txt
$newFile = gci C:\ReportOutputDir | sort LastWriteTime | select -last 1 | Select-Object -ExpandProperty FullName
$newHash = Get-FileHash $newFile | Select-Object Hash
Write-Host 'newFile = '$newFile
Write-Host 'newHash = '$newHash

Output:

newFile =  C:\ReportOutputDir\new_test.txt
newHash =  @{Hash=E7787C54F5BAE236100A24A6F453A5FDF6E6C7333B60ED8624610EAFADF45521}

Finally, I attempt my -eq operator where I'd usually simple remove the newFile since it's equal. For not, I'm just doing a simple :

if ($newHash -eq $oldHash){
'files are equal'
}
else {'files are not equal'}

And somehow, I'm getting

files are not equal

What gives? Also, for the record I was originally trying to save the gci output to a variable and comparing the contents of the last file to the gci output, but was also having trouble with the -eq operator. Fairly new to powershell stuff so I'm sure I'm doing something wrong here.

CodePudding user response:

  • Select-Object Hash creates an object with a .Hash property and it is that property that contains the hash string.

  • The object returned is of type [pscustomobject], and two instances of this type never compare as equal - even if all their property names and values are equal:

    • The reason is that reference equality is tested, because [pscustomobject] is a .NET reference type that doesn't define custom equality-testing logic.

    • Testing reference equality means that only two references to the very same instance compare as equal.

    • A quick example:

      PS> [pscustomobject] @{ foo = 1 } -eq [pscustomobject] @{ foo = 1 }
      False # !! Two distinct instances aren't equal, no matter what they contain.
      

You have two options:

  • Compare the .Hash property values, not the objects as a whole:

    if ($newHash.Hash -eq $oldHash.Hash) { # ...
    
  • If you don't need a [pscustomobject] wrapper for the hash strings, use Select-Object's -ExpandProperty parameter instead of the (possibly positionally implied) -Property parameter:

    Select-Object -ExpandProperty Hash
    

As for why the Write-Host output matched:

When you force objects to be converted to string representations - essentially, Write-Host calls .ToString() on its arguments - the string representations of distinct [pscustomobject] instances that have the same properties and values will be the same:

PS> "$([pscustomobject] @{ foo = 1 })" -eq "$([pscustomobject] @{ foo = 1 })"
True # Same as: '@{foo=1}' -eq '@{foo=1}'

However, you should not rely on these hashtable-like string representations to determine equality of [pscustomobject]s as a whole, because of the inherent limitations of these representations, which can easily yield false positives.

This answer shows how to compare [pscustomobject] instances as a whole, by comparing all of their property values, by passing all property names to Compare-Object -Property - but note that this assumes that all property values are either strings or instances of .NET value types or corresponding properties must again either reference the very same instance of a .NET reference type or be of a type that implements custom equality-comparison logic.

  • Related