0

Looking for a way to assign byte array to an object - tried using -as operator with no luck. (Targeting PowerShell version 5.1)

$CSharpCode = @"
using System;
namespace TestStructure
{
    public struct TestStructure3
    {
       public Byte Field1;
       public Byte Field2;
       public UInt32 Field3;
       public UInt32 Field4;
    }

    public struct TestStructure2
    {
       public UInt32 Field1;
       public UInt32 Field2;
    }

    public struct TestStructure1
    {
       public UInt16 Field1;
       public UInt16 Field2;
       public TestStructure2 Field3;
       public TestStructure3 Field4;
    }
}
"@

Add-Type -TypeDefinition $CSharpCode

[byte[]]$BytesArray = 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22

$MyObject = $BytesArray -as [TestStructure.TestStructure1]
  • that last call fails, $MyObject is $null
ssa
  • 35
  • 1
  • 5
  • 1
    `[byte[]]$BytesArray = 1, ...` works fine. Is your real question "How do I create an instance of my struct type from a binary array"? – Mathias R. Jessen Mar 28 '22 at 15:51
  • Create an object of `TestStructure1`, then enumerate its properties: `$obj = [TestStructure.TestStructure1]::new(); $obj.PSObject.Properties.ForEach{ $_ }`. Assign bytes to property values based on their size. – zett42 Mar 28 '22 at 15:52
  • @MathiasR.Jessen - yes, my question is "How do I create an instance of my struct type from a binary array"? I was able to construct something using code from https://stackoverflow.com/questions/2871/reading-a-c-c-data-structure-in-c-sharp-from-a-byte-array - added ByteArrayToNewStuff() method and calling it with $BytesArray - not sure if this is correct. – ssa Mar 28 '22 at 16:28
  • @ssa that should work too, I've posted an answer below that also uses `PtrToStructure()`. – Mathias R. Jessen Mar 28 '22 at 16:51

1 Answers1

3

PowerShell doesn't natively translate between byte arrays and initialized instances of blittable types, but you can write a small utility function to take care of it like this:

function ConvertTo-Struct
{
  # Only accept struct types (sealed value types that are neither primitive nor enum)
  param(
    [Parameter(Mandatory = $true)]
    [ValidateScript({ $_.IsValueType -and $_.IsSealed -and -not($_.IsPrimitive -or $_.IsEnum) })]
    [Type]$TargetType,

    [Parameter(Mandatory = $true)]
    [byte[]]$BinaryData
  )

  # Start by calculating minimum size of the underlying memory allocation for the new struct
  $memSize = [System.Runtime.InteropServices.Marshal]::SizeOf([type]$TargetType)

  # Make sure user actually passed enough data to initialize struct
  if($memSize -gt $BinaryData.Length){
    Write-Error "Not enough binary data to create an instance of [$($TargetType.FullName)]"
    return
  }

  # now we just need some unmanaged memory in which to create our struct instance
  $memPtr = [IntPtr]::Zero
  try {
    $memPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($memSize)

    # copy byte array to allocated unmanaged memory from previous step
    [System.Runtime.InteropServices.Marshal]::Copy($BinaryData, 0, $memPtr, $memSize)

    # then marshal the new memory region as a struct and return
    return [System.Runtime.InteropServices.Marshal]::PtrToStructure($memPtr, [type]$TargetType)
  }
  finally {
    # and finally remember to clean up the allocated memory
    if($memPtr -ne [IntPtr]::Zero){
      [System.Runtime.InteropServices.Marshal]::FreeHGlobal($memPtr)
    }
  }
}

Now that we've gotten that squared away, let's put it to use:

$testStructure1 = ConvertTo-Struct -TargetType ([TestStructure.TestStructure1]) -BinaryData (1..24 -as [byte[]])
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • Thank you, this works. What is the license for this code? (Can it be used without attribution?) – ssa Mar 28 '22 at 16:55
  • @ssa I just wrote up this example for this post, so [CC BY-SA 4.0](https://stackoverflow.com/help/licensing) (share and derive as you see fit, but attribute to this post if used as-is) :) – Mathias R. Jessen Mar 28 '22 at 17:14