Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 212
Remko Avatar asked Oct 31 '12 15:10

Remko


2 Answers

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
like image 192
dugas Avatar answered Oct 12 '22 23:10

dugas


The problem is that you are using structs, which are value types, as opposed to reference types. When accessing those members via the PowerShell '.' operator, it gives you back a copy of that struct member, and the value assignment happens on that copy and never makes it into the object you're trying to modify.

Here is a smaller example that demonstrates this:

> add-type @"
namespace TestStructs {
    public struct Inner {
        public int i;
    };
    public class Outer {
        public Inner i;
    }
}
"@
$s = new-object TestStructs.Outer

I created a class Outer that has an Inner struct as member i. If I try to assign the value, I get the behavior you are seeing, where it stays at 0:

> $s.i.i
0

> $s.i.i = 6

> $s.i.i
0

The way around this is to assign the entire struct. So, for this simple case, I can create a new struct, set the value and then assign it to the object I want to modify:

> $new_inner = new-object TestStructs.Inner
> $new_inner.i
0

> $new_inner.i = 6
> $new_inner.i
6

> $s.i = $new_inner
> $s.i.i
6

I can use some shorthand to do this:

> $s.i = new-object TestStructs.Inner -prop @{ i = 7 }
> $s.i.i
7

That may not be practical, however, if you have lots of values in your struct. So, you can also save it to a temp value, and reassign:

> $s.i = &{ $temp = $s.i; $temp.i = 10; $temp }

H:\
> $s.i.i
10
like image 23
Droj Avatar answered Oct 12 '22 23:10

Droj