"$xyz" and "Write-Host "$xyz"" giving different output
Asked Answered
S

1

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.

Stansbury answered 12/11, 2019 at 20:5 Comment(4)
Leave out the Format-Table because that is only for outputting the stuff in a certain way on console.Ruffner
Specify Compare-Object $origin.Hash $destination.Hash. Otherwise, the variables are hash-tables which are identical. At least I assume so.Bumgardner
$xyz is short for write-output $xyz.Farro
@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.Atheist
A
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.

Atheist answered 12/11, 2019 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.