Search code examples
c#powershellpinvoke

Set value in a c# struct with PowerShell


I have translated some of the DHCP Win32 Api's to C# so I can use the from PowerShell:

$definition = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Net;

namespace Dhcpsapi
{

    public enum DHCP_SEARCH_INFO_TYPE : int
    {
        DhcpClientIpAddress = 0,
        DhcpClientHardwareAddress = 1,
        DhcpClientName = 2
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct DHCP_BINARY_DATA
    {
        public uint DataLength;
        public IntPtr Data;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct DHCP_IP_ADDRESS
    {
        public UInt32 IPAddress;
    }

    [StructLayout(LayoutKind.Explicit, Size=8)]
    public struct SearchInfo
    {
        [FieldOffset(0)]
        public DHCP_IP_ADDRESS ClientIpAddress;
        [FieldOffset(0)]
        public DHCP_BINARY_DATA ClientHardwareAddress;
        [FieldOffset(0)]
        public IntPtr ClientName; //LPWSTR
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DHCP_SEARCH_INFO
    {
        public DHCP_SEARCH_INFO_TYPE SearchType;
        public SearchInfo SearchInfo;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct DATE_TIME
    {
        public UInt32 dwLowDateTime;
        public UInt32 dwHighDateTime;

        public DateTime ToDateTime()
        {
            if (dwHighDateTime == 0 && dwLowDateTime == 0)
            {
                return DateTime.MinValue;
            }
            if (dwHighDateTime == int.MaxValue && dwLowDateTime == UInt32.MaxValue)
            {
                return DateTime.MaxValue;
            }
            return DateTime.FromFileTime((((long)dwHighDateTime) << 32) | dwLowDateTime);
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct DHCP_HOST_INFO
    {
        public uint IpAddress;
        public string NetBiosName;
        public string HostName;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct DHCP_CLIENT_INFO
    {
        public uint ClientIpAddress;
        public uint SubnetMask;
        public DHCP_BINARY_DATA ClientHardwareAddress;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string ClientName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string ClientComment;
        public DATE_TIME ClientLeaseExpires;
        public DHCP_HOST_INFO OwnerHost;
    }

    public static class PS
    {

        [DllImport("Dhcpsapi.dll")]
        public static extern uint DhcpGetClientInfo(
            [MarshalAs(UnmanagedType.LPWStr)]
            string ServerIpAddress,
            [MarshalAs(UnmanagedType.Struct)]
            ref DHCP_SEARCH_INFO SearchInfo,
            out IntPtr ClientInfo);

        [DllImport("dhcpsapi.dll", SetLastError=true)]
        public static extern void DhcpRpcFreeMemory(IntPtr BufferPointer);
    }
}
"@

Add-Type -TypeDefinition $definition -PassThru

Now I try to use this from PowerShell:

$dsi = New-Object dhcpsapi.DHCP_SEARCH_INFO
$dsi.SearchType = [Dhcpsapi.DHCP_SEARCH_INFO_TYPE]::DhcpClientIpAddress
$ipa = [System.Net.IPAddress]::Parse("10.4.3.101")
$ip = [UInt32][System.Net.IPAddress]::NetworkToHostOrder([int][System.BitConverter]::ToUInt32($ipa.GetAddressBytes(), 0))

# $ip is now 168035173
$dsi.SearchInfo.ClientIpAddress.IPAddress = $ip

#however $dsi.SearchInfo.ClientIpAddress.IPAddress is 0
$dsi.SearchInfo.ClientIpAddress.IPAddress

After running that $dsi.SearchInfo.ClientIpAddress.IPAddress is 0 instead of 168035173 as I expect. Why does this happen (it works as expected from C#) and how to get this working?


Solution

  • SearchInfo is a struct, so when you do this:

    #however $dsi.SearchInfo.ClientIpAddress.IPAddress is 0
    $dsi.SearchInfo.ClientIpAddress.IPAddress
    

    then you are creating a copy of SearchInfo, and the value is being mutated on that copy. For each struct, you need to mutate the struct, then assign it back:

    $clientIpAddress = $dsi.SearchInfo.ClientIpAddress
    $clientIpAddress.IPAddress = $ip
    
    $searchInfo = $dsi.SearchInfo
    $searchInfo.ClientIpAddress = $clientIpAddress
    
    $dsi.SearchInfo = $searchInfo