I'm working on some code that still needs Powershell 5, and I'm making use of some .NET tools, including [System.BitConverter]::GetBytes(). This function always returns a byte array... it has to, as .NET uses strongly-typed functions. However, sometimes when I call it the resulting array only has one element. When I do this, Powershell seems to unroll the array and give me the bare first element as a simple byte value (no array).
I end up working around it with code like this:
$bytes = [System.BitConverter]::GetBytes($data)
if ($bytes -isnot [System.Array]) {
$bytes = @($bytes)
}
I know if I were looking at this from the other direction I could force an array in various ways, and there are several questions already here about that. But I didn't see anything helping me understand what is going on with this case, where I know I already had an array, and now have to do extra work to get it back.
So the question: is there a better work-around than the -isnot [System.Array] conditional expression, and what is actually going on here?
I expect this is a "feature" that is actually useful in pipeline situations, but since I'm calling the .NET function directly and not piping anything I wonder if there's a way to tell PS to skip that feature here.
The longer form of what I'm actually doing involves certificate automation. We have a pfx file with a private key from a windows certificate store, and we want to transform it into cert and key pairs suitable to use with linux/apache. This code is part of a piece to export the primary key. Part of the key involves encoding the data length.
The original code I found looked like this:
function Encode-Asn1 {
param($type, $data)
# Common Type-Length-Value encoding
$encoded = New-Object System.IO.MemoryStream
# Type
$encoded.WriteByte($type)
# Length
$length = $data.Length
if ($length -lt 0x80) { # if the length is less than 128, write the length part of the structure as a single byte
$encoded.WriteByte($length)
} else { # Otherwise we need the longer encoding
# Handle multi-byte length fields for large data
$lengthBytes = [System.BitConverter]::GetBytes($length).Reverse() | Where-Object { $_ -ne 0 }
$encoded.WriteByte(0x80 + $lengthBytes.Length)
$encoded.Write($lengthBytes, 0, $lengthBytes.Length)
}
# Value
$encoded.Write($data, 0, $data.Length)
return $encoded.ToArray()
}
(Longer version available on GitHub)
The above function DEFINITELY put up errors where there was an attempt to call Reverse() as a method from a single byte, instead of a byte array. I don't know how that is possible in the first place, since $length should be an integer that always creates a 4 byte array, but I watched it happen and pulled hair over it for a while.
The fix that actually works looks like this:
# Otherwise we need the longer encoding
# Handle multi-byte length fields for large data
$bytes = [System.BitConverter]::GetBytes($length)
if ($bytes -isnot [System.Array]) {
$bytes = @($bytes)
} # If the array is one item long, no need to reverse
[Array]::Reverse($bytes)
$lengthBytes = $bytes | Where-Object { $_ -ne 0 }
That is, taking the method result as a variable with a conditional check to make a corrective action if we didn't get what we expect actually fixed things.
I ended up with [Array].Reverse($bytes), but I believe $bytes = $bytes.Reverse() or just $bytes.Reverse() | Where-Object ... has the same positive result. The point is it's not that change that fixed things.
The above works in testing so far, where the original did not. In fact, it has created the key for one certificate I've moved on the production and now seen served from a public web server. I had a version of this where I added an else block and logging to prove the conditional expression sometimes succeeds, and sometimes does not, depending on the data.
But I hate that I don't know what's going on.
(This would be much easier with either Powershell 7 or openssl available, but I've been asked to attempt this without taking the additional dependency).
$bytes -isnot [System.Array]is not a condition that would be met in any situation in the context of your question. If you're assigning the output of a method to a variable no unrolling happens. I feel like there is something we're not seeing,operator help? (i.e., just,[BitConverter]::GetBytes($data))|between the expression andWhere-Object.|unrolls the input array unless the command immediately upstream requests otherwise (see Santiago's answer for examples), and ifWhere-Objectultimately outputs less than 2 objects they won't be implicitly wrapped in an array. Hence no array.Reverse()fails because there's no resolvable[byte[]].Reverse()method in 5.1, so PowerShell applies member-access enumeration, hence the error relating to a scalar byte