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?
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With