Printing Details
URL: http://www.ianatkinson.net/computing/dhcpcsharp.htm
Date: 10 Sep 2010 20:14
All content © Ian Atkinson 2000–2010, not to be re-used without permission
Querying Windows DHCP Server With C#
I was working on a project recently which gave me cause to try and extract DHCP lease information from Microsoft Server using C#.
It didn’t sound too hard and I was sure there would be a way built in to the .NET framework to do what I wanted … however it turns out there isn’t!
What you can do though is use some ‘interop’ code, i.e. make .NET use some non-native libraries. In this case the functionality is available in dhcpsapi.dll.
I managed to find a few forum posts where people had done what I wanted but the code had no comments to speak of (and chunks of it were redundant) so it took me a long time to get it doing what I wanted.
The code below is the results of my labour, it’s now neater and fully commented! If you want you can cut and paste it into your own program and just use the function. The main portion of the program is just a few lines demonstrating its use.
Code
I have tested this against Windows 2003 servers, I can’t guarantee the code on any other version!
using System;
using System.Runtime.InteropServices;
using System.Collections;
using System.Net;
namespace dhcp
{
// c# class for processed clients
public class dhcpClient
{
public string hostname { get; set; }
public string ip { get; set; }
public string mac { get; set; }
}
// structs for use with call to unmanaged code
[StructLayout(LayoutKind.Sequential)]
public struct DHCP_CLIENT_INFO_ARRAY
{
public uint NumElements;
public IntPtr Clients;
}
[StructLayout(LayoutKind.Sequential)]
public struct DHCP_CLIENT_UID
{
public uint DataLength;
public IntPtr Data;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct DHCP_CLIENT_INFO
{
public uint ip;
public uint subnet;
public DHCP_CLIENT_UID mac;
[MarshalAs(UnmanagedType.LPWStr)]
public string ClientName;
[MarshalAs(UnmanagedType.LPWStr)]
public string ClientComment;
}
// main
class Program
{
static void Main()
{
try
{
// get settings
String server, subnet;
Console.Write("Enter server : ");
server = Console.ReadLine();
Console.Write("Enter subnet : ");
subnet = Console.ReadLine();
// gather clients
ArrayList clients = findDhcpClients(server, subnet);
// output results
Console.WriteLine();
foreach (dhcpClient d in clients)
Console.WriteLine(String.Format("{0,-35} {1,-15} {2,-15}", d.hostname, d.ip, d.mac));
Console.WriteLine('\n' + clients.Count.ToString() + " lease(s) in total");
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.ReadLine();
}
static ArrayList findDhcpClients(string server, string subnet)
{
// set up container for processed clients
ArrayList foundClients = new ArrayList();
// make call to unmanaged code
uint parsedMask = StringIPAddressToUInt32(subnet);
uint resumeHandle = 0;
uint numClientsRead = 0;
uint totalClients = 0;
IntPtr info_array_ptr;
uint response = DhcpEnumSubnetClients(
server,
parsedMask,
ref resumeHandle,
65536,
out info_array_ptr,
ref numClientsRead,
ref totalClients
);
// set up client array casted to a DHCP_CLIENT_INFO_ARRAY
// using the pointer from the response object above
DHCP_CLIENT_INFO_ARRAY rawClients =
(DHCP_CLIENT_INFO_ARRAY)Marshal.PtrToStructure(info_array_ptr, typeof(DHCP_CLIENT_INFO_ARRAY));
// loop through the clients structure inside rawClients
// adding to the dchpClient collection
IntPtr current = rawClients.Clients;
for (int i = 0; i < (int)rawClients.NumElements; i++)
{
// 1. Create machine object using the struct
DHCP_CLIENT_INFO rawMachine =
(DHCP_CLIENT_INFO)Marshal.PtrToStructure(Marshal.ReadIntPtr(current), typeof(DHCP_CLIENT_INFO));
// 2. create new C# dhcpClient object and add to the
// collection (for hassle-free use elsewhere!!)
dhcpClient thisClient = new dhcpClient();
thisClient.ip = UInt32IPAddressToString(rawMachine.ip);
thisClient.hostname = rawMachine.ClientName;
thisClient.mac = String.Format("{0:x2}{1:x2}.{2:x2}{3:x2}.{4:x2}{5:x2}",
Marshal.ReadByte(rawMachine.mac.Data),
Marshal.ReadByte(rawMachine.mac.Data, 1),
Marshal.ReadByte(rawMachine.mac.Data, 2),
Marshal.ReadByte(rawMachine.mac.Data, 3),
Marshal.ReadByte(rawMachine.mac.Data, 4),
Marshal.ReadByte(rawMachine.mac.Data, 5));
foundClients.Add(thisClient);
// 3. move pointer to next machine
current = (IntPtr)((int)current + (int)Marshal.SizeOf(typeof(IntPtr)));
}
return foundClients;
}
public static uint StringIPAddressToUInt32(string ip)
{
// convert string IP to uint IP e.g. "1.2.3.4" -> 16909060
IPAddress i = System.Net.IPAddress.Parse(ip);
byte[] ipByteArray = i.GetAddressBytes();
uint ipUint = (uint)ipByteArray[0] << 24;
ipUint += (uint)ipByteArray[1] << 16;
ipUint += (uint)ipByteArray[2] << 8;
ipUint += (uint)ipByteArray[3];
return ipUint;
}
public static string UInt32IPAddressToString(uint ip)
{
// convert uint IP to string IP e.g. 16909060 -> "1.2.3.4"
IPAddress i = new IPAddress(ip);
string[] ipArray = i.ToString().Split('.');
return ipArray[3] + "." + ipArray[2] + "." + ipArray[1] + "." + ipArray[0];
}
[DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint DhcpEnumSubnetClients(
string ServerIpAddress,
uint SubnetAddress,
ref uint ResumeHandle,
uint PreferredMaximum,
out IntPtr ClientInfo,
ref uint ElementsRead,
ref uint ElementsTotal
);
}
}
Example
Here is an (abbreviated) example of the output:
H:\simpleDHCP>csc dhcp.cs Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1 for Microsoft (R) .NET Framework version 3.5 Copyright (C) Microsoft Corporation. All rights reserved. H:\simpleDHCP>dhcp.exe Enter server : 172.10.1.9 Enter subnet : 172.10.0.0 BW-WKSPRINT-02.academic.lcad 172.10.3.2 0017.f20c.2f66 BW-ADV-03.academic.lcad 172.10.3.3 0016.cbc9.7b03 <snip> BC-WKSID-12.academic.lcad 172.10.4.46 0019.bbdf.03fe BC-WKSID-15.academic.lcad 172.10.4.47 0019.bbdf.23a0 289 lease(s) in total
The subnet entered will obviously need to match whatever is set up on your DHCP server, you can see them in angle brackets after ‘Scope’ in the configuration tree.