'Check if the first n bytes of a file are specific in Powershell
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"
}
Solution 1:[1]
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!
}
Solution 2:[2]
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.
Solution 3:[3]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Mathias R. Jessen |
| Solution 2 | mklement0 |
| Solution 3 |
