Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Powershell wrapper for netsh and dnscmd functions used for DHCP scope creation.
############################
#AUTHOR: JR Morgan
#CREATED: 20120417
#MODIFIED: 20140611
############################
<#
.Synopsis
Adds DHCP Scope to ALL specified DHCP servers. If split-scope is desired
the script uses IP Math to automatically add the desired exlcude ranges.
.Description
Creates a DHCP based on user-provided parameters. For 50/50 split-scope config,
the ordering of the DHCP Servers determines the upper/lower designation. The first
DHCP server specified will host upper scope portion, while the second DHCP server will
host the lower scope portion. Upper/Lower 'tags' are added to the description when using
the Split50 switch.
.Parameter DhcpServer
An array of DHCP Server IP names or addresses that will host the new DHCP scopes
.Parameter IPScope
The desired IP Scope
.Parameter IPMask
The desired IP mask for the scope
.Parameter Description
A brief scope description. Upper/Lower tags will
automatically be added if using the Split50 switch
.Parameter Gateway
The IP address of the router or gateway for this scope (Option 3)
.Parameter Dns
An array of DNS server IP addresses utilized by scope clients (Option 6).
.Parameter Domain
The fully-qualified domain name for scope clients (Option 15)
.Parameter StartAddress
An optional parameter used to specify the start address for distribution.
If no address is provided, the default StartAddress is the Network address +2
.Parameter EndAddress
An optional parameter used to specify the end address for distribution.
If no address is provided, the default EndAddress is the Broadcast address -1
.Parameter LeaseTime
Integer value (in seconds) for the desired address lease times associated
with the scope. Default: 691200
.Parameter State
Set the DHCP scope state to enable (1) or disable (0). Default: 0
.Parameter ExcludeAll
Excludes all addresses from distribution (Reservation required for IP distribution)
.Parameter Split50
Configure a 50/50 split-scope between specified DHCP servers.
Ony two (2) DHCP servers may be specified. Exclusions are automatically
calculated for each server.
.Parameter NoRevLookup
A switched parameter used to disable the automatic creation of a corresponding reverse lookup zone.
DNS settings are retrieved using the 'Domain' parameter. If the reverse lookup zone already exists then
creation is skipped.
.Parameter NoDynamicDNS
A switched parameter used to disable dynamic DNS updates for a specific scope.
.Parameter OptionTftp
Adds an array of TFTP servers using DHCP option 150
.Parameter Invoke
A switched parameter used to automatically invoke the netsh commands generated
for a new scope. Use cautiously!
.Notes
This script assumes you're running this as a domain admin or at least have DHCP Administrators group membership.
This script also assumes you would be using administrative credentials to actually invoke the generated
netsh commands. Command invocation will not work if you don't have the necessary privileges.
.Example
Set-Dhcp-Scope.ps1 -DhcpServer grudcpr03,grudcpr04 -IPScope 172.20.229.0 -IPMask 255.255.255.0 -Description "Description" -Gateway 172.20.229.1 -Dns 172.20.227.219,172.20.242.119 -Domain domain.com -StartAddress 172.20.229.2 -Split50 -State 1
#>
#Parameters and required validation. Alias definitions for shorthand. Positional parameter options work, too.
Param
(
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[ValidateCount(1,2)]
[Alias("dhcp")]
[String[]]
$DhcpServer
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("s")]
[String]
$IPScope
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("m")]
[String]
$IPMask
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[Alias("desc")]
[String]
$Description = ""
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("g")]
[String]
$Gateway
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("n")]
[String[]]
$Dns
,
[Parameter(ValueFromPipeline = $True, Mandatory = $True)]
[Alias("d")]
[String]
$Domain = "gruadmin.gru.com"
,
[Parameter(ValueFromPipeline = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("start")]
[String]
$StartAddress
,
[Parameter(ValueFromPipeline = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[Alias("end")]
[String]
$EndAddress
,
[Parameter(ValueFromPipeline = $True, Mandatory = $False)]
[Alias("l")]
[Int]
$LeaseTime = 691200
,
[Parameter(ValueFromPipeline = $True, Mandatory = $False)]
[ValidateRange(0,1)]
[Int]
$State = 0
,
[Parameter(ValueFromPipeline = $True)]
[Alias("Exclude")]
[Switch]
$ExcludeAll
,
[Parameter(ValueFromPipeline = $True)]
[Alias("50")]
[Switch]
$Split50
,
[Parameter(ValueFromPipeline = $True)]
[Switch]
$NoRevLookup
,
[Parameter(ValueFromPipeline = $True)]
[Switch]
$NoDynamicDNS
,
[Parameter(ValueFromPipeline = $True)]
[ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')]
[String[]]
$OptionTftp
,
[Parameter(ValueFromPipeline = $False)]
[Alias("x")]
[Switch]
$Invoke
)
#region IPMath
Function ConvertTo-BinaryIP {
<#
.Synopsis
Converts a Decimal IP address into a binary format.
.Description
ConvertTo-BinaryIP uses System.Convert to switch between decimal and binary format. The output from this function is dotted binary.
.Parameter IPAddress
An IP Address to convert.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Net.IPAddress]$IPAddress
)
Process {
Return [String]::Join('.', $( $IPAddress.GetAddressBytes() |
ForEach-Object { [Convert]::ToString($_, 2).PadLeft(8, '0') } ))
}
}
Function ConvertTo-DecimalIP {
<#
.Synopsis
Converts a Decimal IP address into a 32-bit unsigned integer.
.Description
ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
.Parameter IPAddress
An IP Address to convert.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Net.IPAddress]$IPAddress
)
Process {
$i = 3; $DecimalIP = 0;
$IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
Return $DecimalIP
}
}
Function ConvertTo-DottedDecimalIP {
<#
.Synopsis
Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
.Description
ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
.Parameter IPAddress
A string representation of an IP address from either UInt32 or dotted binary.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[String]$IPAddress
)
Process {
Switch -RegEx ($IPAddress) {
"([01]{8}\.){3}[01]{8}" {
Return [String]::Join('.', $( $IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) } ))
}
"\d" {
$IPAddress = [UInt32]$IPAddress
$DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
$Remainder = $IPAddress % [Math]::Pow(256, $i)
($IPAddress - $Remainder) / [Math]::Pow(256, $i)
$IPAddress = $Remainder
} )
Return [String]::Join('.', $DottedIP)
}
default {
Write-Error "Cannot convert this format"
}
}
}
}
Function ConvertTo-MaskLength {
<#
.Synopsis
Returns the length of a subnet mask.
.Description
ConvertTo-MaskLength accepts any IPv4 address as input, however the output value
only makes sense when using a subnet mask.
.Parameter SubnetMask
A subnet mask to convert into length
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Alias("Mask")]
[Net.IPAddress]$SubnetMask
)
Process {
$Bits = "$( $SubnetMask.GetAddressBytes() | ForEach-Object { [Convert]::ToString($_, 2) } )" -Replace '[\s0]'
Return $Bits.Length
}
}
Function ConvertTo-Mask {
<#
.Synopsis
Returns a dotted decimal subnet mask from a mask length.
.Description
ConvertTo-Mask returns a subnet mask in dotted decimal format from an integer value ranging
between 0 and 32. ConvertTo-Mask first creates a binary string from the length, converts
that to an unsigned 32-bit integer then calls ConvertTo-DottedDecimalIP to complete the operation.
.Parameter MaskLength
The number of bits which must be masked.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Alias("Length")]
[ValidateRange(0, 32)]
$MaskLength
)
Process {
Return ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(("1" * $MaskLength).PadRight(32, "0")), 2))
}
}
Function Get-NetworkAddress {
<#
.Synopsis
Takes an IP address and subnet mask then calculates the network address for the range.
.Description
Get-NetworkAddress returns the network address for a subnet by performing a bitwise AND
operation against the decimal forms of the IP address and subnet mask. Get-NetworkAddress
expects both the IP address and subnet mask in dotted decimal format.
.Parameter IPAddress
Any IP address within the network range.
.Parameter SubnetMask
The subnet mask for the network.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Net.IPAddress]$IPAddress,
[Parameter(Mandatory = $True, Position = 1)]
[Alias("Mask")]
[Net.IPAddress]$SubnetMask
)
Process {
Return ConvertTo-DottedDecimalIP ((ConvertTo-DecimalIP $IPAddress) -BAnd (ConvertTo-DecimalIP $SubnetMask))
}
}
Function Get-BroadcastAddress {
<#
.Synopsis
Takes an IP address and subnet mask then calculates the broadcast address for the range.
.Description
Get-BroadcastAddress returns the broadcast address for a subnet by performing a bitwise AND
operation against the decimal forms of the IP address and inverted subnet mask.
Get-BroadcastAddress expects both the IP address and subnet mask in dotted decimal format.
.Parameter IPAddress
Any IP address within the network range.
.Parameter SubnetMask
The subnet mask for the network.
#>
[CmdLetBinding()]
Param(
[Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
[Net.IPAddress]$IPAddress,
[Parameter(Mandatory = $True, Position = 1)]
[Alias("Mask")]
[Net.IPAddress]$SubnetMask
)
Process {
Return ConvertTo-DottedDecimalIP $((ConvertTo-DecimalIP $IPAddress) -BOr `
((-BNot (ConvertTo-DecimalIP $SubnetMask)) -BAnd [UInt32]::MaxValue))
}
}
Function Get-NetworkSummary ( [String]$IP, [String]$Mask ) {
If ($IP.Contains("/"))
{
$Temp = $IP.Split("/")
$IP = $Temp[0]
$Mask = $Temp[1]
}
If (!$Mask.Contains("."))
{
$Mask = ConvertTo-Mask $Mask
}
$DecimalIP = ConvertTo-DecimalIP $IP
$DecimalMask = ConvertTo-DecimalIP $Mask
$Network = $DecimalIP -BAnd $DecimalMask
$Broadcast = $DecimalIP -BOr
((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)
$NetworkAddress = ConvertTo-DottedDecimalIP $Network
$RangeStart = ConvertTo-DottedDecimalIP ($Network + 1)
$RangeEnd = ConvertTo-DottedDecimalIP ($Broadcast - 1)
$ScopeStart = ConvertTo-DottedDecimalIP ($Network + 2)
$ScopeEnd = ConvertTo-DottedDecimalIP ($Broadcast + 1)
$BroadcastAddress = ConvertTo-DottedDecimalIP $Broadcast
$MaskLength = ConvertTo-MaskLength $Mask
$BinaryIP = ConvertTo-BinaryIP $IP; $Private = $False
Switch -RegEx ($BinaryIP)
{
"^1111" { $Class = "E"; $SubnetBitMap = "1111" }
"^1110" { $Class = "D"; $SubnetBitMap = "1110" }
"^110" {
$Class = "C"
If ($BinaryIP -Match "^11000000.10101000") { $Private = $True } }
"^10" {
$Class = "B"
If ($BinaryIP -Match "^10101100.0001") { $Private = $True } }
"^0" {
$Class = "A"
If ($BinaryIP -Match "^00001010") { $Private = $True } }
}
$NetInfo = New-Object Object
Add-Member NoteProperty "Network" -Input $NetInfo -Value $NetworkAddress
Add-Member NoteProperty "Broadcast" -Input $NetInfo -Value $BroadcastAddress
Add-Member NoteProperty "Range" -Input $NetInfo `
-Value "$RangeStart - $RangeEnd"
Add-Member NoteProperty "Mask" -Input $NetInfo -Value $Mask
Add-Member NoteProperty "RangeStart" -Input $NetInfo -Value $RangeStart
Add-Member NoteProperty "RangeEnd" -Input $NetInfo -Value $RangeEnd
Add-Member NoteProperty "ScopeStart" -Input $NetInfo -Value $ScopeStart
Add-Member NoteProperty "ScopeEnd" -Input $NetInfo -Value $ScopeEnd
Add-Member NoteProperty "MaskLength" -Input $NetInfo -Value $MaskLength
Add-Member NoteProperty "Hosts" -Input $NetInfo `
-Value $($Broadcast - $Network - 1)
Add-Member NoteProperty "Class" -Input $NetInfo -Value $Class
Add-Member NoteProperty "IsPrivate" -Input $NetInfo -Value $Private
Return $NetInfo
}
#endregion
$c = $null
$DecimalIP = ConvertTo-DecimalIP $IPScope
$DecimalMask = ConvertTo-DecimalIP $IPMask
#Basic Calculations in the event of missing Start/End:
$Network = $DecimalIP -BAnd $DecimalMask
$Broadcast = $DecimalIP -BOr
((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)
$NetworkAddress = ConvertTo-DottedDecimalIP $Network
$RangeStart = ConvertTo-DottedDecimalIP ($Network + 2)
$RangeEnd = ConvertTo-DottedDecimalIP ($Broadcast - 1)
foreach ($s in [array]$DhcpServer)
{
$Desc = $null
#Autogenerate upper/lower descriptions:
if ($DhcpServer.count -gt 1 -and $Description -match "vlan" -and $Split50)
{
if ($s -eq $DhcpServer[0]) { $Desc = ($Description -replace "vlan","[upper] VLAN") }
if ($s -eq $DhcpServer[1]) { $Desc = ($Description -replace "vlan","[lower] VLAN") }
}
elseif ($DhcpServer.count -gt 1 -and $Split50)
{
if ($s -eq $DhcpServer[0]) { $Desc = $Description + " [upper]" }
if ($s -eq $DhcpServer[1]) { $Desc = $Description + " [lower]" }
}
else
{
$Desc = $Description
}
$c += "netsh dhcp server \\$s add scope $IPScope $IPMask `"$Desc`"`r`n"
$c += "netsh dhcp server \\$s scope $IPScope set state $State`r`n"
if ([string]::IsNullOrEmpty($StartAddress))
{
$StartAddress = $RangeStart
}
if ([string]::IsNullOrEmpty($EndAddress))
{
$EndAddress = $RangeEnd
}
$c += "netsh dhcp server \\$s scope $IPScope add iprange $StartAddress $EndAddress`r`n"
if (($Split50 -and $DhcpServer.count -gt 1) -or ($ExcludeAll -and $DhcpServer.count -gt 1))
{
#Scope Start decimal:
$ssd = ConvertTo-DecimalIP $StartAddress
#Scope End decimal:
$sed = ConvertTo-DecimalIP $EndAddress
#Math bytes:
$th = [int64]$sed - [int64]$ssd
#Halve the scope total hosts#:
$hh = $th/2
#Subtract from Scope End Decimal:
#Lower Scope End (this is the end of the lower scope range):
$lsedd = ConvertTo-DottedDecimalIP ($sed-$hh)
#Upper Scope Start (this is beginning of the upper scope range):
$ussdd = ConvertTo-DottedDecimalIP (($ssd+$hh)+1)
#Config Upper scope (exclusions for lower scope)
if ($s -eq $DhcpServer[0])
{
if (-not $ExcludeAll)
{
$c += "netsh dhcp server \\$s scope $IPScope add excluderange $StartAddress $lsedd`r`n"
}
else
{
$c += "netsh dhcp server \\$s scope $IPScope add excluderange $StartAddress $EndAddress`r`n"
}
}
#Config Lower scope (exclusions for upper scope)
if ($s -eq $DhcpServer[1])
{
if (-not $ExcludeAll)
{
$c += "netsh dhcp server \\$s scope $IPScope add excluderange $ussdd $EndAddress`r`n"
}
else
{
$c += "netsh dhcp server \\$s scope $IPScope add excluderange $StartAddress $EndAddress`r`n"
}
}
}
#Lease Time:
$c += "netsh dhcp server \\$s scope $IPScope set optionvalue 51 DWORD `"$LeaseTime`"`r`n"
#Gateway:
$c += "netsh dhcp server \\$s scope $IPScope set optionvalue 3 IPADDRESS `"$Gateway`"`r`n"
#DNS Domain Name:
$c += "netsh dhcp server \\$s scope $IPScope set optionvalue 15 STRING `"$Domain`"`r`n"
#DNS Servers:
$dnslist = $null
foreach ($d in [array]$Dns)
{
$dnslist += "`"$d`" "
}
$c += "netsh dhcp server \\$s scope $IPScope set optionvalue 6 IPADDRESS $dnslist`r`n"
$c += "`r`n"
#TFTP Servers:
$tftplist = $null
if(([array]$OptionTftp).count -gt 0)
{
foreach ($ts in [array]$OptionTftp)
{
$tftplist += "`"$ts`" "
}
$c += "netsh dhcp server \\$s scope $IPScope set optionvalue 150 IPADDRESS $tftplist`r`n"
$c += "`r`n"
}
if ($NoDynamicDNS)
{
$c += "netsh dhcp server \\$s scope $IPScope set DnsConfig 0 0 0 0"
}
}
if (-not $NoRevLookup)
{
$revname = $IPScope.Split(".")
$zones = (Invoke-Expression -Command "dnscmd $Domain /enumzones") | %{$_ -split "\s+"} | ?{ `
$_.StartsWith("$($revname[2]).$($revname[1]).$($revname[0]).in-addr.arpa") `
-or `
$_.StartsWith("$($revname[0]).$($revname[1]).$($revname[2]).in-addr.arpa")}
if (([string[]]$zones).count -lt 1)
{
$c += "dnscmd $Domain /zoneadd $($revname[2]).$($revname[1]).$($revname[0]).in-addr.arpa /dsprimary"
$c += "dnscmd $Domain /Config $($revname[2]).$($revname[1]).$($revname[0]).in-addr.arpa /AllowUpdate 2"
$c += "dnscmd $Domain /Config $($revname[2]).$($revname[1]).$($revname[0]).in-addr.arpa /Aging 1"
}
else
{
Write-Output "Skipping Reverse Lookup Zone creation. Detected pre-existing zone(s): `r`n $zones `r`n"
}
}
Write-Output "Generated the following netsh commands:`r`n"
Write-Output $c
if ($Invoke)
{
Write-Host "Received Invoke parameter; executing all commands.`r`n"
#Write-Host "These are the commands that were generated from your input:`n$($c)`n"
#$execute = Read-Host "Definitely execute these commands? (USE CARE)? (Y/N)"
#Double-check execution/invocation of netsh
#if ($execute -match "Y")
#{
Invoke-Expression $c
#}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.