In this post, I'll explain how you can easily convert a DHCP reservation to a static (or manual or fixed) IP for a server's NIC. I will show you how you can automate this process with PowerShell.

The PowerShell function below can convert DHCP reservations for one or more server NICs to fixed IP configurations. Besides the IP address and subnet mask, it will also take care of other settings you would normally configure for a fixed IP, such as the default gateway and DNS servers.

The Get-IPAddressPretty function ^

Throughout this post, IP addresses will be changed and checked frequently. Whenever you do this, if you prefer to see only the relevant information, you'd run a command like this:

Get-NetIPAddress -AddressFamily IPv4 -CimSession Mgmt,DC2 `
| select PSComputerName, InterfaceAlias, IPAddress, PrefixLength, PrefixOrigin `
| Where-Object InterfaceAlias -NotLike "*Loopback*" `
| Format-Table -Wrap -AutoSize

The result is something like in the figure below:

IP address info

IP address info

This provides all the information you need for our purposes (computer name, NIC name, IP address, subnet mask, and whether it's manual or assigned via DHCP). The only problem is that it's a long command to type each time.

To avoid all the typing, I created a function that returns the same information without the need for all the things we want and don't want. I called it Get-IPAddressPretty:

function Get-IPAddressPretty {
<# 
.SYNOPSIS
Outputs a nicer, more concise version of the command Get-NetIpAddress. If an address family (IPv4 or IPv6) is not specified, it outputs the IPv4Addresses. Also, it skips the loopback address(es).
.EXAMPLE
Get-IPAddressPretty -CimSession ServerB
Displays the IPv4 Addresses of ServerB
.EXAMPLE
Get-IPAddressPretty -CimSession DC1,DC2 -AddressFamily IPv6
Displays the IPv6 Addresses of DC1 and DC2

.NOTES
Last revision on 24 Nov 2020
#>
Param (
    # Computers for which the IP address info will be retrieved
    [Parameter(Mandatory=$false,Position=0)][array]$CimSession = $env:COMPUTERNAME,
    # Toggle between IPv4 and IPv6
    [Parameter(Mandatory=$false)][ValidateSet("IPv4","IPv6")][string]$AddressFamily = "IPv4",
    # A wildcard for a NIC alias. If none is provided, information for all the relevant NICs is displayed
    [Parameter(Mandatory=$false)][string]$InterfaceAlias = "*"
)

# IPv4 is the default option.
if ($AddressFamily -eq "IPv4") {
    Get-NetIPAddress -CimSession $CimSession -AddressFamily $AddressFamily | 
        Where-Object { $_.InterfaceAlias -like "*$InterfaceAlias*" } | 
        Where-Object { $_.PrefixOrigin -eq "Manual" -or $_.PrefixOrigin -eq "Dhcp" } | 
        Select-Object PSComputerName,InterfaceAlias,IPAddress,PrefixLength,PrefixOrigin | 
        Sort-Object PSComputerName,InterfaceAlias |
        Format-Table -AutoSize -Wrap
    }
# Otherwise, retrieve the IPv6 information
else {
    Get-NetIPAddress -CimSession $CimSession -AddressFamily $AddressFamily | 
        Where-Object { $_.InterfaceAlias -like "*$InterfaceAlias*" } | 
        select PSComputerName,InterfaceAlias,IPAddress,PrefixLength,PrefixOrigin | 
        Sort-Object PSComputerName,InterfaceAlias |
        Format-Table -AutoSize -Wrap
    }
} 
Get IPAddressPretty in action

Get IPAddressPretty in action

As you see, the result is the same as above, but the command is much shorter. By default, it displays the IPv4 addresses of all the NICs on the computers. You may get more granular if you want to.

Armed with this, let's get busy!

Convert a DHCP reservation to a static IP ^

First things first: this will work with Microsoft DHCP servers. It won't work on a Cisco switch that's also configured to be a DHCP server for a small subnet or for other flavors of DHCP servers. However, the function/commands may be adapted if those other flavors support PowerShell.

With this small disclaimer out of the way, let's look first at the logic of the function that creates the reservation.

  • You provide the computer name and the network interface for which you want to create a reservation. If it is a computer with multiple NICs, you may choose to convert the current DHCP reservations for all its NICs to fixed IP configurations.
  • You also need to provide a DHCP server on which to create the reservation. The function provides auto-completion for the server name (use Tab or Ctrl+Space).
  • If a particular NIC is provided, the function validates the name of the NIC on the specific server. If a valid name is not provided, the function throws a warning to ask you for a valid NIC name and stops.
  • Once the NIC is validated, the function retrieves the MAC address for that NIC.
  • Based on the MAC address, the function checks whether there is a reservation on the selected DHCP server. If there isn't any, the function informs the user about this and stops.
  • Also, if there is more than one reservation (two or more in different scopes, for example), the function warns the user about this and stops. Normally, this is a sign of a misconfiguration (somebody didn't clean up old reservations before creating new ones).
  • Assuming a reservation for that NIC is found, the function configures the NIC with the IP address defined in the reservation. It also assigns the subnet mask based on the DHCP scope.
  • For the default gateway, the function looks for these settings from the most granular level to the broadest:
    • It first checks the reservation options.
    • It then checks the scope options (if nothing is configured at the reservation level).
    • Finally, it checks the server options (if nothing is defined at the reservation and scope level).
    • If the DHCP server has no options defined for the default gateway at any level, the NIC is not configured with a default gateway.
  • The same logic (for the default gateway) is followed for DNS: check from the most granular settings to the broadest (reservation > scope > server).
  • Once the IP address, subnet mask, and other options have been configured, DHCP is disabled for the NIC.
  • As a last step, the reservation for the NIC is removed from the DHCP server.

The Convert-DhcpV4ReservationToStaticIp function ^

Below you see the code that makes it all happen:

function Convert-DhcpV4ReservationToStaticIp {

[cmdletbinding()]

#region Function Parameters
Param (
    # Computers for which the IP address info is retrieved
    [Parameter(Mandatory=$false,Position=0)][string]$ComputerName = $env:COMPUTERNAME,
    # List of authorized DHCP servers in AD
    [ArgumentCompleter({ (Get-DhcpServerInDC).DnsName })][string]$DhcpServer,
    # Name of the NIC for which the reservation is created
    [Parameter(Mandatory)][string]$InterfaceAlias
)
#endregion

#region Additional Functions

# This function converts subnet masks (such as "255.0.0.0" to prefix length)
function Convert-SubnetMaskToPrefixLength {

Param (
    # Subnet mask to be converted to prefix length
    [Parameter(Mandatory)][IPAddress]$SubnetMask
)

# Initialize an empty string
[String]$BinaryMask=""
$SubnetMask.GetAddressBytes() | Foreach {
      # Convert to binary
      $BinaryMask+=[Convert]::ToString($_, 2)
      }
# The prefix length is the length of the string (from which the 0s have been removed)
[int]$PrefixLength = $BinaryMask.TrimEnd('0').Length

# The function returns the prefix length (this might not be relevant for IP addresses, but it is for subnet masks)
return $PrefixLength
}

#endregion

#region Validate and retrieve the NIC info

# Check whether the interface alias provided for the specific computer is valid, and stop if it isn't
[string]$Nic = (Get-NetAdapter -CimSession $ComputerName | Where-Object InterfaceAlias -Like "$InterfaceAlias").InterfaceAlias
if (!($Nic)) {
    Write-Warning "The Nic name you provided (`'$Nic`') is not valid for the computer name `"$ComputerName`". Please provide a valid Interface Alias"
    break
    }

Write-Verbose "The NIC `'$Nic`' for the computer `'$ComputerName`' is valid, continuing."

# Get the MAC address of the NIC
$MacAddress = (Get-NetAdapter -CimSession $ComputerName -InterfaceAlias $InterfaceAlias).MacAddress
Write-Verbose "MAC address for the reservation: `'$MacAddress`'"
#endregion

#region Query the DHCP scopes for reservations for the $MacAddress

# Retrieve the list of scopes on the DHCP server
$DhcpScopes = (Get-DhcpServerv4Scope -ComputerName $DhcpServer)

# Check for reservations through all the scopes
Write-Verbose "Check for reservations for `'$MacAddress`' on server `'$DhcpServer`'"
[array]$Reservations = foreach ($s in $DhcpScopes) {
    Get-DhcpServerv4Reservation -ComputerName $DhcpServer -ScopeId $s.ScopeId | Where-Object ClientId -EQ $MacAddress
    }

#endregion

#region Check if there are fewer or more than one reservation, warn the user accordingly, and exit

# Display a warning that there is no reservation for the selected interface and exit the function
if ($Reservations.Count -eq 0) {
    Write-Warning "There are no reservations for the computer `'$ComputerName`' (for the NIC `'$InterfaceAlias`') on the DHCP server `'$DhcpServer`'"
    break
    }

# Display a warning that there are two or more reservations for the selected interface and exit
if ($Reservations.Count -ge 2) {
    Write-Warning "There are $($Reservations.Count) reservations for the computer `'$ComputerName`' (for the NIC `'$InterfaceAlias`') on the DHCP server `'$DhcpServer`'. Review/ clean the obsolete reservations and try again"
    break
    }
#endregion

#region Retrieve IP address and subnet mask for the NIC

# IP address retrieved from the reservation
[string]$IpAddress = $Reservations.IpAddress
Write-Verbose "The IP Address for the NIC's static configuration is $IpAddress"

# Subnet mask retrieved from the scope properties
[string]$SubnetMask = (Get-DhcpServerv4Scope -ComputerName $DhcpServer -ScopeId $($Reservations.ScopeId)).SubnetMask
Write-Verbose "The subnet mask for the NIC's static configuration is $SubnetMask"

# Prefix length of the subnet mask
$PrefixLength = Convert-SubnetMaskToPrefixLength -SubnetMask $SubnetMask
#endregion

#region Retrieve default gateway setting for the NIC

# Retrieve the default gateway settings from the reservation options, if any

# Check at the reservation level
if ($ResGw = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer -ReservedIP $($Reservations.IPAddress) | Where-Object OptionId -EQ 3) {
    [string]$Gateway = $ResGw.Value
    Write-Verbose "Default Gateway $Gateway retrieved from the DHCP reservation options"
    }
# Check at the scope level
elseif ($ScopeGw = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer -ScopeId $($Reservations.ScopeId) | Where-Object OptionId -EQ 3) {
    [string]$Gateway = $ScopeGw.Value
    Write-Verbose "Default Gateway $Gateway retrieved from the DHCP scope options"
    }
# Check at the server level
elseif ($ServerGw = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer | Where-Object OptionId -EQ 3) {
    [string]$Gateway = $ServerGw.Value
    Write-Verbose "Default Gateway $Gateway retrieved from the DHCP server options"
    }
# Inform the user that no gateway will be defined for the NIC
else {
    Write-Verbose "There was no setting for Default Gateway found at DHCP reservation, scope or server level. No Default Gateway will be defined for the NIC `'$InterfaceAlias`'"
    }

#endregion

#region Retrieve DNS settings for the NIC

# Retrieve the DNS server settings from the reservation options, if any

# Check at the reservation level
if ($ResDns = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer -ReservedIP $($Reservations.IPAddress) | Where-Object OptionId -EQ 6) {
    [array]$Dns = $ResDns.Value
    Write-Verbose "DNS servers $Dns retrieved from the DHCP reservation options"
    }
# Check at the scope level
elseif ($ScopeDns = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer -ScopeId $($Reservations.ScopeId) | Where-Object OptionId -EQ 6) {
    [array]$Dns = $ScopeDns.Value
    Write-Verbose "DNS servers $Dns retrieved from the DHCP scope options"
    }
# Check at the server level
elseif ($ServerDns = Get-DhcpServerv4OptionValue -ComputerName $DhcpServer | Where-Object OptionId -EQ 6) {
    [array]$Dns = $ServerDns.Value
    Write-Verbose "DNS servers $Dns retrieved from the DHCP server options"
    }
# Inform the user that no gateway will be defined for the NIC
else {
    Write-Verbose "There was no setting for DNS servers found at DHCP reservation, scope or server level. No DNS servers will be defined for the NIC `'$InterfaceAlias`'"
    }

# Convert the list of DNS servers to an array (if any list of DNS servers was found)
if ($Dns) {
    # Go through each DNS server (they are separated by a space)
    foreach ($d in ($Dns -split " ")) {
        [array]$DnsServers += $d
        }
    }

#endregion

#region Configure the NIC with the gathered information

#region Set the IP address, subnet mask, and default gateway (if determined from DHCP)
# Commands to run if a gateway was identified in the DHCP reservation/scope/server options
if ($Gateway) {
    $ScriptBlock = {
        Remove-NetIPAddress -InterfaceAlias $using:InterfaceAlias -Confirm:$false
        Remove-NetRoute -InterfaceAlias $using:InterfaceAlias -Confirm:$false
        New-NetIPAddress -InterfaceAlias $using:InterfaceAlias -IPAddress $using:IpAddress -PrefixLength $using:PrefixLength -DefaultGateway $using:Gateway | Out-Null
        }
    }
# Only set the IP address and subnet mask if no default gateway was found in the DHCP reservation
else {
        $ScriptBlock = {
        Remove-NetIPAddress -InterfaceAlias $using:InterfaceAlias -Confirm:$false
        New-NetIPAddress -InterfaceAlias $using:InterfaceAlias -IPAddress $using:IpAddress -PrefixLength $using:PrefixLength | Out-Null
        }
    }
    
Write-Verbose "Set the IP Address on `'$ComputerName`' on the interface `'$InterfaceAlias`'"
Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock
Write-Verbose "Waiting..."
Start-Sleep -Seconds 5
#endregion

#region Set the DNS servers, if any were found in the DHCP reservation/scope/server options
if ($DnsServers) {
    Write-Verbose "Set the DNS servers on `'$ComputerName`' on the interface `'$InterfaceAlias`'"
    Set-DnsClientServerAddress -CimSession $ComputerName -InterfaceAlias $InterfaceAlias -ServerAddresses $DnsServers
    }
else {
    Write-Verbose "No DNS servers will be configured on `'$ComputerName`' on the interface `'$InterfaceAlias`'"
    }

#endregion

#endregion

#region Disable DHCP on the NIC

Write-Verbose "Disable DHCP on  `'$ComputerName`' on the interface `'$InterfaceAlias`'"
Set-NetIPInterface -CimSession $ComputerName -InterfaceAlias $InterfaceAlias -Dhcp Disabled

#endregion

#region Remove the reservation from the DHCP server

Write-Verbose "Remove the DHCP reservation for `'$MacAddress`' from the DHCP Server `'$DhcpServer`'"
Remove-DhcpServerv4Reservation -ComputerName $DhcpServer -ScopeId $Reservations.ScopeId -ClientId $Reservations.ClientId

#endregion

#endregion

} 

Now that you know what Convert-DhcpV4ReservationToStaticIp does, let's see it in action.

Step-by-step examples ^

First, let's look at the IP configuration of a client computer (called "Mgmt").

Current IP address before any changes. Notice the PrefixOrigin is DHCP

Current IP address before any changes. Notice the PrefixOrigin is DHCP

# Get the current IP address
Get-IPAddressPretty -CimSession Mgmt 

Notice that the PrefixOrigin of the address for the NIC "Private" is DHCP. Let's see if there is a reservation for the computer Mgmt on the DHCP server.

Viewing DHCP reservation

Viewing DHCP reservation

Indeed, there is a reservation on the DHCP server for our computer (Mgmt). Now let's convert this reservation to a static IP, using Convert-DhcpV4ReservationToStaticIp. We'll use Verbose mode to see more details about what the function does.

Convert the DHCP reservation to static IP

Convert the DHCP reservation to static IP

Convert the DHCP reservation to static IP

Convert-DhcpV4ReservationToStaticIp -ComputerName Mgmt -InterfaceAlias Private -DhcpServer dc2.ha.lab -Verbose 

As you can see in the screenshot, Verbose mode describes all the steps the function performed. Now let's check the IP configuration for our computer:

Checking IP configuration

Checking IP configuration

Get-IPAddressPretty -CimSession Mgmt 

The difference between the first time we run Get-IPAddressPretty and now is that the Prefix Origin for the NIC is Manual instead of DHCP. Let's take a look at the NIC properties as well.

The IP address subnet mask and DNS settings are static

The IP address subnet mask and DNS settings are static

As you can see, the IP and DNS configurations are static. They are the same as defined in the DHCP reservation (as you can see in the screenshot above, from the DHCP management console).

Speaking of the DHCP reservation...

No more DHCP reservations for our computer

No more DHCP reservations for our computer

The reservation was deleted from the DHCP server.

At this point, if we try to run the command again, we'll get an error saying that there is no reservation for the specified NIC for our computer.

The command warns that no reservation has been found for the ServerNIC

The command warns that no reservation has been found for the ServerNIC

The command warns that no reservation has been found for the Server/NIC

Convert-DhcpV4ReservationToStaticIp -ComputerName Mgmt -InterfaceAlias Private -DhcpServer dc2.ha.lab -Verbose 

Running the command now throws a warning saying that no reservation was found for our Computer/NIC.

Conclusion ^

Using the command Convert-DhcpV4ReservationToStaticIp can be an easy way to decommission a DHCP server without impacting your environment; the servers will have the same IP addresses as before, but without relying on a DHCP server.

Subscribe to 4sysops newsletter!

And remember, if you change your mind, you can always re-create the DHCP reservations from a static IP, using the "reverse" function described here. 😉

+2
avatar
4 Comments
  1. Tim 10 months ago

    Do you also remove the IP from the address pool so it doesn't allocate to another? 

    0

    • Author

      Hello, Tim!

      You can easily avoid the allocation using Set-DhcpServerSetting, using the switch ConflictDetectionAttempts (the default value is 0, but you can set it to 1 or up to 5).

      Of course, a more honest answer should also include that I haven't considered that, and it should also include thanks for pointing this. So, thank you for pointing this. 🙂

      0

      • Tim 10 months ago

        And I didn't think about conflict detection either, but that usually just created BAD ADDRESS entries for me so I like to try and avoid issuing a lease if I already know it's going to cause a problem. Nice job already though. My scripts evolve as people point stuff out or I run into problems so I figured I'd ask.

        0

        • Author

          I learn the same way, too. Given how many problems I run into, I should be almost a guru by now. 🙂

          Thank you for the feedback, hints and appreciation, Tim.

          0

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account