1

I am hashing all the files in one location, an origin folder, and writing the hashes to a variable and then doing the same to all the files in another location, a destination folder:

$origin = Get-ChildItem .\Test1 | Get-FileHash | Format-Table -Property Hash -HideTableHeaders
$destination = Get-ChildItem .\Test2 | Get-FileHash | Format-Table -Property Hash -HideTableHeaders

Then I am comparing them with Compare-Object like so:

Compare-Object $origin $destination

Now in my test I purposefully have deviations, so when the above code returned no differences I knew I had a problem.

Then I found out that if I do the following, that the hash values arn't there:

PS> Write-Host "$origin"
Microsoft.PowerShell.Commands.Internal.Format.FormatStartData Microsoft.PowerShell.Commands.Internal.Format.GroupStartData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData Microsoft.PowerShell.Commands.Internal.Format.GroupEndData Microsoft.PowerShell.Commands.Internal.Format.FormatEndData

However, if I just type the following and press enter, then the hash values are present (like I want):

PS> $origin

6B86B273FF34FCE19D6B804EFF5A3F5747ADA4EAA22F1D49C01E52DDB7875B4B
D4735E3A265E16EEE03F59718B9B5D03019C07D8B6C51F90DA3A666EEC13AB35
4E07408562BEDB8B60CE05C1DECFE3AD16B72230967DE01F640B7E4729B49FCE

I am assuming when I use Compare-Object, that my variables are not presenting the hash values like I expected.

Does anyone know what is going on or have any recommendations? This is being used to ensure files are moved from an origin location to a destination location (this is one check in a script I'm working on). I am keeping this purely PowerShell, which means no xcopy or robocopy.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Ken
  • 125
  • 1
  • 12
  • Leave out the `Format-Table` because that is only for outputting the stuff in a certain way on console. – Theo Nov 12 '19 at 20:10
  • Specify `Compare-Object $origin.Hash $destination.Hash`. Otherwise, the variables are hash-tables which are identical. At least I assume so. – Alex_P Nov 12 '19 at 20:14
  • 1
    `$xyz` is short for `write-output $xyz`. – js2010 Nov 12 '19 at 20:49
  • 1
    @Alex_P: Actually, reference types (in the absence of `-Property`) are compared by their `.ToString()` values, and in the case at hand that would invariably yield string `'Microsoft.PowerShell.Commands.FileHashInfo'` (the full type name only, irrespective of property values), which indeed would cause all objects to be considered equal. – mklement0 Nov 12 '19 at 20:52

1 Answers1

3

Re use of Format-Table to create the input collections for Compare-Object:

Only ever use Format-* cmdlets for display formatting; never use them if data must be programmatically processed.

Format-* cmdlets output formatting instructions, not data - see this answer.

Therefore:

  • Omit the Format-Table calls from your input-collection definition commands:
$origin=Get-ChildItem .\Test1 | Get-FileHash
$destination=Get-ChildItem .\Test2 | Get-FileHash
  • Then pass the names of the properties to compare the objects by to Compare-Object:
Compare-Object $origin $destination -Property Path, Hash

Note the need to compare by both path and hash, to make sure that only files of the same name are compared.

As an aside: If you didn't specify -Property, the objects would by default be compared by their .ToString() value - and since the Microsoft.PowerShell.Commands.FileHashInfo instances output by Get-FileHash only ever stringify to that very type name (irrespective of their specific property values), no differences would be found.


As for $origin vs. Write-Host $orgin:

  • Just executing $origin is implicitly like executing Write-Output $origin - it writes to the success output stream (see about_Redirection), which by default goes to the console.

  • Write-Host, by contrast, serves a different purpose than Write-Output:

    • It writes directly to the console[1], bypassing PowerShell's success output stream and thereby also its usual formatting. Its primary purpose is to write status messages, interactive prompt messages, ... to the display - as opposed to outputting data.

    • Write-Host itself applies output formatting, but only by simple .ToString() stringification, which often yields unhelpful (type name-only) representations, as in your case.

See this answer for more information about the differences between Write-Output and Write-Host.


[1] Technically, since PowerShell version 5, Write-Host output reaches the console via the information output stream (number 6), but its primary purpose is still to write to the display as opposed to outputting data.

mklement0
  • 382,024
  • 64
  • 607
  • 775