Use the PowerShell DHCP Module to Simplify DHCP Management

Use the PowerShell DHCP Module to Simplify DHCP Management

  • Comments 5
  • Likes

Summary: Learn about using a free Windows PowerShell module to simplify working with DHCP.

Hey, Scripting Guy! Question

  Hey, Scripting Guy! I would like to know how to use Windows PowerShell to work with Microsoft DHCP servers. Can this be done?

—EJ

 

Hey, Scripting Guy! Answer Hello EJ,

Microsoft Scripting Guy, Ed Wilson, here. It is Valentine's Day for some people, and for others it is Guest Blogger Day. That’s right folks we have a guest blogger with us today. Jeremy Engel has written a DHCP module that he has shared on the Script Repository and was gracious enough to write his explanation of the process in a blog that I will now share. Here is what Jeremy had to say about his scripting background:

I started coding at the age of 10, writing games in BASIC for my lightning-fast Commodore 64. From there I ventured into the high-tech world of text-based online gaming, affectionately known as MUDs—specifically EverDark, where I learned to write C++ code. Later, I made the jump to systems engineering, and it was there that I discovered scripting. I loathe repetitive tasks and have a great love for efficiency (I’m lazy). Therefore, I can find ways to incorporate Windows PowerShell into almost every project and task I do as a systems engineer. I am also known to write the occasional application or service in C#, which is mutually reinforcing with Windows PowerShell because they are essentially different expressions of the same language. Now for today’s blog…

Hello everyone! It’s great to be here at the Hey, Scripting Guy! Blog. I’d like to take this moment to thank my mom, my elementary school bus driver, that old man down the street, and Ed for making it possible for me to be here today. Let me begin by saying that I consider myself a student of Windows PowerShell and not a master by any means. Although I have been coding and scripting since the mid-90s, I have never had formal training; therefore, I do not claim that my code is the best way to do a given task. I am still learning, so if in the course of reading this you find an assumption of mine to be incorrect, or you know of a more efficient (and by that I mean less time, CPU, and RAM) method for accomplishing a given task, please by all means, let me know. I will be very grateful.

It is frequently the case that as a systems architect and engineer, I find myself smacking into the limitations of what a given product can do. It is no different for Windows PowerShell, with one important caveat: with patience, determination, and a little creative thinking you can code yourself out of almost any problem. The perceived limitations are only set by what has been precanned; everything else is up to you to build.

However, the lack of any DHCP administrative ability outside of netsh and the GUI console is not a limitation of Windows PowerShell, but one of .NET. I can only surmise that the C++ developer for the dhcpapi.dll at Microsoft is zealously guarding his acorns and not letting anyone there build a C# .NET API for it. Apart from the amusing image of a chipmunk programmer, I can’t for the life of me make sense of this glaring hole in the framework.

Earlier this year, I converted all (several thousand) printers at my company from static to reserved DHCP addresses. This was where I first smacked my head into the lack of DHCP scriptability in Windows PowerShell. At the time I was dealing with a self-made aggressive project deadline, so rather than bemoan my bad luck, I just built a script to invoke a bunch of netsh commands in sequence.

It worked great, and I actually got the project done ahead of schedule. The problem with doing things well, though, is that people tend to come to you over “the other guy” to get future jobs done. This is often referred to as “creating a monster.” So rather than go to the designated DHCP admins, people would come to me with DHCP change requests. Even the DHCP admins were coming to me with requests! It was getting out of hand. To make matters worse, my company is perennially in acquisition mode and we are constantly converting subnets to our corporate IP addressing standard, thus building and managing new DHCP scopes.

Therefore, I set out to devise a script for the DHCP admins to import CSV files of a defined format and build these new scopes on the fly, and most importantly, without my involvement. From there evolved my idea to build an entire Windows PowerShell module for DHCP. Now by “entire,” I do not mean every function available from netsh or the console, but rather that the common administrative functions that DHCP admins perform are represented by cmdlets. The following image shows an example of how to build a new DHCP scope by using the DHCP module.

Image of code example

Because Windows PowerShell is an object-oriented scripting language, I realized that I first needed to build custom objects, or structures, in which to store all DHCP data. This was important so that I did not just output some generic string or hash table, but rather a defined and manipulated Table object that could be used in scripts that import the DHCP module. To that end, I created seven new .NET structures that use the Add-Type cmdlet. Here’s an example:

Add-Type –TypeDefinition @"

public struct DHCPOption {

public int OptionID;

public string OptionName;

public string ArrayType;

public string OptionType;

public string[] Values;

public string Level;

public override string ToString() { return OptionName; }

}

“@

As you can see, the structure is written in pure C#, not in Windows PowerShell. That’s because the code is then compiled using C# for use within a given Windows PowerShell session. You can then instantiate these objects for use within the session. Furthermore, you can use a structure to define the data type of a property in a subsequent structure, as shown here.

Add-Type –TypeDefinition @"

public struct DHCPOption {

public int OptionID;

public string OptionName;

public string ArrayType;

public string OptionType;

public string[] Values;

public string Level;

public override string ToString() { return OptionName; }

}

public struct DHCPReservation {

public string IPAddress;

public string MACAddress;

public string Scope;

public string Server;

public DHCPOption[] Options;

public override string ToString() { return IPAddress; }

}

“@

In the definition of the DHCPReservation structure, the Options property is defined as an array of DHCPOption objects.

The following screen shot shows a scope option represented by the DHCPOption object. This is actually an array of scope options, just like in the DHCPReservation structure shown previously. But since I only have one option created, that’s all we see.

Image of a scope option

Next I ascertained the various netsh commands that I wanted to have represented in the module and just went to town. It works fine for the most part, but there are some inherent limitations in netsh, mainly centered on the fact that the output is pure text and not easily manipulated. So what I had to do was a lot of output parsing.

The core of most cmdlets in this module is the invocation of a netsh command and then storing its output to a string variable.

$result = $(Invoke-Expression "cmd /c netsh dhcp server \\$Server scope $Scope <command>")

From there, the resultant string is parsed for success or errors. Here is an example of some standard parses throughout the module’s cmdlets:

if($result.Contains("completed successfully")) {

<Build and return desired DHCP object>

}

elseif($result.Contains("Server may not function properly")) {

Write-Host "ERROR: $Server is inaccessible or is not a DHCP server." -ForeGroundColor Red

}

elseif($result.Contains("The command needs a valid Scope IP Address")) {

Write-Host "ERROR: $Scope is not a valid scope on $Server." -ForeGroundColor Red

}

else { Write-Host "ERROR: $result" -ForeGroundColor Red }

The parsing gets more harrowing when trying to glean data from the netsh output, specifically when building the Get- cmdlets. For example, here is the code for parsing the mibinfo, or statistics, of a server.

$text = $(Invoke-Expression "cmd /c netsh dhcp server \\$Server show mibinfo")

if($text[2].Contains("Server may not function properly")) {

Write-Host "ERROR: $Server is inaccessible or is not a DHCP server." -ForeGroundColor Red

return

}

$serverStats = New-Object DHCPServerStatistics

$serverStats.Server = "$Server"

for($i=2;$i-lt$text.Count;$i++) {

if(!$text[$i]) { break }

$parts = $text[$i].Split("=") | %{ $_.Trim().Trim(".") }

switch($parts[0]) {

"Discovers" { $serverStats.Discovers = [int]$parts[1] }

"Offers" { $serverStats.Offers = [int]$parts[1] }

"Requests" { $serverStats.Requests = [int]$parts[1] }

"Acks" { $serverStats.Acks = [int]$parts[1] }

"Naks" { $serverStats.Naks = [int]$parts[1] }

"Declines" { $serverStats.Declines = [int]$parts[1] }

"Releases" { $serverStats.Releases = [int]$parts[1] }

"ServerStartTime" { $serverStats.StartTime = [DateTime]$parts[1] }

"Scopes" { $serverStats.Scopes = [int]$parts[1] }

"Subnet" {

$scopeStats = New-Object DHCPScopeStatistics

$scopeStats.Scope = $parts[1]

$scopeStats.Server = "$Server"

$parts = $text[++$i].Split("=") | %{ $_.Trim().Trim(".") }

$used = [int]$parts[1]

$parts = $text[++$i].Split("=") | %{ $_.Trim().Trim(".") }

$scopeStats.TotalAddresses = $used+[int]$parts[1]

$scopeStats.UsedAddresses = $used

$parts = $text[++$i].Split("=") | %{ $_.Trim().Trim(".") }

$scopeStats.PendingOffers = [int]$parts[1]

$serverStats.ScopeStatistics += $scopeStats

}

}

}

As you can see, I make ample use of Split and Trim to get the string data parsed to the desired information.

The following screenshot shows the DHCPServerStatistics and DHCPScopeStatistics objects.
Note: There are no stats because I created this DHCP server solely for the purpose of this blog.

Image of DHCP objects

Another constraint that I faced is inherent in calling netsh using the Invoke-Expression cmdlet. I could not figure out a way to hold the netsh session open between commands. That means that I have to open a new netsh session to a given DHCP server for each command I want to run. This can result in delays between running a cmdlet and getting its return value, especially if the DHCP server is running over a slow WAN link. This is compounded when running a Get- cmdlet that calls other Get- cmdlets, most notably the Get-DHCPServer cmdlet. This cmdlet invokes several netsh commands and then calls the Get-DHCPScope cmdlet for each scope (which also calls its own series of Get- cmdlets), and then it calls the Get-DHCPOption cmdlet. You should go to lunch if you plan on running the Get-DHCPServer cmdlet.

So while the module is not perfect, it has been instrumental in my ability to pass bulk DHCP administration back to the DHCP admins, and free up more of my time to …well… write more scripts. This in turn allows me to automate or hand off more admin work, which in turn allows me to write more scripts, which in turn…

This is known as the “upward spiral.”

The complete DHCP module is posted in Script Repository: PowerShell Module for DHCP.

 

EJ, that is all there is to using the DHCP module for Windows PowerShell. Thank you, Jeremy, for your hard work and for sharing this with us. Guest Blogger Week will continue tomorrow when Andy Schneider will talk about SharePoint.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Great post!  I am also assigning reserved IP addresses for my printers instead of static addresses and one problem I have is dynamically registering them in DNS.  Normally, I just check the "Dynamically update DNS A and PTR records for DHCP clients that do not request updates" box on the DNS tab for each reservation in the MMC console.  I found out that this actually sets option 81.  However, it looks like netsh cannot set this option; it can only display it using "show reservedoptionvalue."  I get the message "The specified option does not exist" whenever I tried to set option 81 with netsh.  Do you know if this is a limitation of netsh?  Am I stuck ticking off the box for each reservation after running the script?  We're using DHCP on Server 2003.  I'm upgrading our DC's and DHCP servers to 2008 in the next couple of weeks, so if it is possible to do it programatically in Server 2008 for some reason, that would be awesome.

  • Thanks Blaine for your post. I hadn't originally given much heed to the DNS update configuration for DHCP, only showing it in passing within the DHCPServer object, but having no way to set it. Well, I saw your post while at the beach this weekend, and it gave me something to do while my girls built sand-castles. So, weathering connection speeds that would make someone from 1996 groan and the disapproving looks of my wife (I love you, honey! Yes I know we're on vacation, but scripting is so much fun!), I have created a new .Net structure, DHCPDNSConfiguration, and two new cmdlets, Get-DHCPDNSConfiguration and Set-DHCPDNSConfiguration. I have updated the module in the repository (see above link), and I'm pretty sure it's all working. I'll have more time to validate and/or improve it on Monday, when I am not getting tackled by two-year olds.

    Enjoy!

  • Baine, I wanted to post again to directly answer your question. Yes, you can set the dns update configuration using netsh (netsh dhcp server \\<serverName> scope <scopeAddress> set dnsconfig <reservationAddress> <configSettings>), and thus why I was able to build a PowerShell module for it. using my PowerShell module you can do something like this:

    Set-DHCPDNSConfiguration -Owner server/scope/reservation -AllowDynamicUpdate -UpdateTrigger ClientRequest -DiscardStaleRecords -AllowLegacyClientUpdate

    And you can revert the settings to default (ie: inherit) by doing this:

    Set-DHCPDNSConfiguration -Owner server/scope/reservation -RestoreDefaults

    This command actually deletes option 81 if it's defined. As I said above, when option 81 is not defined, the object inherits its DNS update configuration. So a reservation would inherit from its parent scope. If the scope doesn't have option 81 defined, then its config is inherited from its parent server. If the server doesn't have option 81 defined, then it uses the default configuration of allowing dynamic updates by client request and discarding stale DNS records. If you're interested, this is akin to setting option 81 to a value of 5.

  • The syntax of the netsh command looks funnny.  SHould it not be using netsh -r $Server <command> rather than netsh <command> \\$Server?

    I had to replace all the netsh commands in the invoke-expression statements to get it to work.