2

How can I compare bytes from a file against a literal array of values?

$path = "C:\BinaryFile.bin"
$bytes = Get-Content -LiteralPath $path -Encoding byte -TotalCount 4 -ReadCount 4
if ($bytes -eq [0 1 2 3]) { # How to compare to expected bytes?
    Write-Output "OK: $bytes"
}
Adamarla
  • 739
  • 9
  • 20

4 Answers4

4

Maybe compare-object. No output means equal. Syncwindow 0 for exact order.

$path = 'file'
[byte[]](0..3) | set-content $path -Encoding Byte
$bytes = Get-Content $path -Encoding byte -TotalCount 4
if (! (compare-object $bytes 0,1,2,3 -syncwindow 0)) { 
    "OK: $bytes"
}

Or do a string comparision:

if ("0 1 2 3" -eq $bytes) {
    "OK: $bytes"
}
js2010
  • 23,033
  • 6
  • 64
  • 66
2

PowerShell doesn't have a built-in operator for sequence equality, but you can write your own little utility function fit for this purpose:

function Test-ArrayEquals
{
    param([array]$ReferenceArray, [array]$DifferenceArray)

    if($ReferenceArray.Length -ne $DifferenceArray.Length){
        # not same length, not equal
        return $false
    }

    for($i = 0; $i -lt $ReferenceArray.Length; $i++){
        if($ReferenceArray[$i] -ne $DifferenceArray[$i]){
            # two aligned array members were found to not be equal
            return $false
        }
    }

    # we reached the end, array values must be equal
    return $true
}

Now you can do:

if (Test-ArrayEquals -ReferenceArray @(0, 1, 2, 3) -DifferenceArray $bytes) {
    # looks good!
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
0

Arrays can be compared by Compare-Object. This reads the first four (4) bytes of a file and compares it to a literal array.

In Windows PowerShell 5.1:

Compare-Object `
    -ReferenceObject (Get-Content -Path '.\file.txt' -Encoding Byte -TotalCount 4) `
    -DifferenceObject @(97,100,115,102) -SyncWindow 0

In PowerShell Core 6.x+:

Compare-Object `
    -ReferenceObject (Get-Content -Path '.\file.txt' -AsByteStream -TotalCount 4) `
    -DifferenceObject @(97,100,115,102) -SyncWindow 0
lit
  • 14,456
  • 10
  • 65
  • 119
  • Thanks for updating (you still have the redundant `[0..3]`). By far the most typical uses case are: (a) _absence_ of `-SyncWindow`, in which case elements with the same value are considered present in both collections _irrespective of their position_. (b) `-SyncWindow 0`, in which case elements are only considered equal if they are in the _same_ position in both arrays. A _nonzero_ `-SyncWindow` value limits how many elements are considered to look for equal elements, and the resulting behavior is hard to reason about. – mklement0 Feb 01 '22 at 18:06
0

The other answers offer helpful solutions that work with array of any type.

Here's a shortcut that works in your case, that compares the arrays as strings, taking advantage of how PowerShell stringifies arrays (see limitations below):

if ("$bytes" -eq '1 2 3' ) { # Be sure to use *decimal* values
    Write-Output "OK: $bytes"
}

Limitations:

PowerShell stringifies arrays by concatenating their (stringified) elements with spaces; e.g., $bytes = 0x1, 0x2, 0xA; "$bytes" becomes a string with verbatim value 1 2 10 (note how the decimal representations of the numbers are used).

Therefore, comparing arrays via their (PowerShell) string representations only works robustly if both the following conditions are met:

  • The elements have unambiguous string representations (typically: .NET primitive types, such as numbers, and strings)

  • No element's string representation has embedded spaces.

If the elements are strings and you want to compare them case-sensitively, use -ceq in lieu of -eq


LINQ-based solution

This solution, based on System.Linq.Enumerable.SequenceEqual(), works with all types.

Note the need for[byte[]] casts - which create strongly typed copies of the input arrays (which are themselves [object[]]-typed) - in order for the method call to work:

if ([Linq.Enumerable]::SequenceEqual([byte[]] $bytes, [byte[]] (1, 2, 3))) {
    Write-Output "OK: $bytes"
}

Caveat: It won't matter in this case, but in general it is worth noting that this method is stricter in terms of the element-by-element comparison than the PowerShell's equality tests as performed by Compare-Object and the -eq operator: notably, string elements are compared case-sensitively by default. If you were to choose [object[]] as the cast above - to avoid the need to create copies of the arrays - numbers with the same value would only be considered equal if they're of the exact same type; notably, array literal 1, 2, 3 creates an [object[]] array with [int] values, not [byte] values.

On the plus side, the use of LINQ has the potential to perform much better than a Compare-Object solution - see the bottom section of this answer for more information.

mklement0
  • 382,024
  • 64
  • 607
  • 775