- If a Windows service hangs, restart the service with PowerShell - Mon, Dec 12 2022
- Install, remove, list, and set default printer with PowerShell - Mon, Nov 7 2022
- Format time and date output of PowerShell New-TimeSpan - Wed, Nov 2 2022
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:
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 } }
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").
# 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.
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-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:
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.
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...
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 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. 😉
Do you also remove the IP from the address pool so it doesn't allocate to another?
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. 🙂
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.
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.