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, useSelect-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.