
For a while, I’ve used Microsoft’s DHCP server in my home lab. Within the last year though, I’ve started having issues where my Unifi WAP’s would have trouble getting an IP address. If I rebooted the WAP’s, everything was fine. Because of this, I don’t really think its a problem with my DHCP server or configuration, but I wanted to be absolutely sure and rule it out.
I had a slight problem. Although I use DHCP, I use DHCP reservations for almost everything on my network. The only things I don’t reserve are guest devices. I have an excel spreadsheet that I maintain that has all my devices and their reservations. I needed a way to easily and automatically import these reservations into pfSense for when I make additions and/or changes, just like I do for Microsoft’s DHCP server currently.
I already have a powershell script to do this for Microsoft DHCP, so I used that to get started. It wasn’t as difficult as I thought it would be. A few lines of powershell later, and I have a working powershell script that takes my excel file and dumps it into pfSense’s DHCP configuration, then restarts the service.
# 1) Install-Module ImportExcel -Scope CurrentUser
# 2) Save this script in the same directory as your spreadsheet, or adjust the relevant lines
Clear
Set-Location $PSScriptRoot
$pfSenseUrl = "https://x.x.x.x"
$excelFile = "Home Network.xlsx"
# Prompt for credentials to login to pfsense
# The only permission this user needs is "WebCfg - Diagnostics: Backup & Restore"
# If you want the script to be able to restart the dhcp service, then you 
# also need:
#   Allow access to the 'Services: DHCP Server' page.
#   and
#   Allow access to the 'Status: Services' page.
if ( $cred -eq $null ) {
    $cred = (Get-Credential)
}
# get csrf token for request
$lastRequest = (Invoke-WebRequest $pfSenseUrl/diag_backup.php -SessionVariable pfSenseSession)
$csrf = $lastRequest.Forms.Fields["__csrf_magic"]
# login
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($cred.Password)
$postParams = @{login='Login';usernamefld=$cred.UserName;passwordfld=([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr));__csrf_magic=$csrf}
$lastRequest = (Invoke-WebRequest $pfSenseUrl/diag_backup.php -WebSession $pfSenseSession -Method Post -Body $postParams)
# get csrf token for next request
$lastRequest = (Invoke-WebRequest $pfSenseUrl/diag_backup.php -WebSession $pfSenseSession)
$csrf = $lastRequest.Forms.Fields["__csrf_magic"]
# download current dhcp server configuration
$postParams = @{download='download';donotbackuprrd='yes';__csrf_magic=$csrf;backuparea='dhcpd';}
$lastRequest = (Invoke-WebRequest $pfSenseUrl/diag_backup.php -WebSession $pfSenseSession -Method Post -Body $postParams)
#remove all current static mappings
[System.Xml.XmlDocument]$xml = [System.Xml.XmlDocument]::new()
$xml.LoadXml([System.Text.ASCIIEncoding]::UTF8.GetString($lastRequest.Content))
$xml.dhcpd.lan.SelectNodes("staticmap") | ForEach-Object {
    [void]$_.ParentNode.RemoveChild($_)
}
$lastRequest = $null
# convert the excel file to csv for easy manipulation in powershell
Import-Excel $excelFile | Export-Csv -Path "./$($excelFile).csv" -NoTypeInformation
# parse the generated csv file and add mappings to the xml document in memory
Import-Csv "$($excelFile).csv" | ForEach-Object {
    $mac = $_.UniqueID
    
    if ( $mac.Length -gt 0 ) {
        $desc = $_.HostName
        if ($_.Description.Length -gt 0) {
            $desc = $_.Description
        }
        
        if ($r -eq $null) {
            Write-Host Adding reservation for $_
            $reservation = $xml.CreateElement("staticmap")
            $macEle = $xml.CreateElement("mac")
            $macEle.InnerText = $mac
            $descEle = $xml.CreateElement("descr")
            $descEle.InnerText = $desc
            $ipEle = $xml.CreateElement("ipaddr")
            $ipEle.InnerText = $_.IP
            $hostEle = $xml.CreateElement("hostname")
            $hostEle.InnerText = $_.HostName
            [void]$reservation.AppendChild($macEle)
            [void]$reservation.AppendChild($ipEle)
            [void]$reservation.AppendChild($hostEle)
            [void]$reservation.AppendChild($descEle)
            [void]$xml.dhcpd.lan.AppendChild($reservation)
        }
    }
}
$LF = "`r`n"
$boundary = [System.Guid]::NewGuid().ToString()
$contentType = "multipart/form-data; boundary=`"$boundary`""
# generate the multipart/form-data body with required form variables and the config "file"
$bodyLines = (
 "--$boundary",
 "Content-Disposition: form-data; name=`"__csrf_magic`"$LF",
 $csrf,
 "--$boundary",
 "Content-Disposition: form-data; name=`"restorearea`"$LF",
 "dhcpd",
  "--$boundary",
 "Content-Disposition: form-data; name=`"conffile`"; filename=`"dhcpd.xml`"",
 "Content-Type: text/xml$LF",
 $xml.OuterXml,
 
 "--$boundary",
 "Content-Disposition: form-data; name=`"restore`"$LF",
 "Restore Configuration"
 ) -join $LF
 try {
    # restore the dhcpd configuration to pfSense
    $lastRequest = ( Invoke-WebRequest $pfSenseUrl/diag_backup.php -ContentType $contentType -WebSession $pfSenseSession -Method Post -Body $bodyLines -ErrorAction SilentlyContinue)
} catch {}
if ( $lastRequest.StatusCode -eq 200 ) {
    Write-Host
    Write-Host Success! -ForegroundColor Green
    Write-Host
    Write-Host Restarting DHCP service
    # get csrf token for request
    $csrf = $lastRequest.Forms.Fields["__csrf_magic"]
    #restart dhcp service
    $postParams = @{ajax='ajax';mode='restartservice';__csrf_magic=$csrf;service='dhcpd';}
    $lastRequest = (Invoke-WebRequest $pfSenseUrl/status_services.php -Method Post -WebSession $pfSenseSession -Body $postParams)
}
else { 
    Write-Host Failure! -ForegroundColor Red
}
$bstr = $null
$postParams = $null
$lastRequest = $null

11 comments on “Scripting pfSense DHCP static assignments”
fgfgfghey, I happened to find this script and I wanna use it to populate a bunch of DHCP static mappings on my newly configured PfSense install but before I run your script, can you post a screenshot (or the column headers) for your csv file? I assume it’s mac, descr, ipaddr, hostname but wanna be sure.
You wouldn’t believe how many people would find this useful.
thanks for writing it!
The columns that matter are:
HostName
Description
UniqueID
IP
They’re referenced in the script as properties on the automatic variable $_.
For example, $_.HostName, $.Description, $_.UniqueID, and $_.IP
The other columns are just columns I’m using for my own personal organization.
Glad it is helpful.
Sorry to bug you on this but I am getting an error. I am stepping through this with powershell ISE and
line 41 is giving me trouble.. producing the following error:
$lastRequest = $null
At line:1 char:14
+ $xml = ::new()
+ ~
An expression was expected after ‘(‘.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ExpectedExpression
It looks like your $xml declaration might be incorrect.
Try “[xml]$xml = [xml]::new()” instead of “$xml = ::new()”
That worked!
Thanks for your help!
I posted a link to your site and your blog post on the pfSense reddit.
https://www.reddit.com/r/PFSENSE/comments/j2s0ih/static_mapping_dhcp_reservation_in_pfsense_with/
Thanks for the reddit post.
After reading it, I went and checked my original post, and the syntax is correct in the post, but it appears that the syntax highlighter doesn’t like it and it’s getting removed there :/
Edit: Changed it from the shortcut [xml] to [System.Xml.XmlDocument] and it seems to have fixed the formatting.
Hello
to skip the ssl error i included
add-type @”
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
“@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
so it looks like this
$pfSenseUrl = “https://x.x.x.x”
$excelFile = “Home Network.xlsx”
add-type @”
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
“@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
what this dose it tells the script to trust all certs so the selfsigned cert will not complain ๐
Hello
can you also make this work if you have multiple dns servers in pfsense for vlans? ๐
like a sheet per vlan with just the vlan id or the name of the dhcp server ๐
My pfSense setup is pretty basic and don’t have multiple vlans/dns servers. It still should be relatively easy since I’m just using a backup/restore process to do it. Can you send me a sample backup that contains just your dns area?
Hello
no idea how to do that..
can you tell me how to do it and il gladly do it ๐
update: with the release of 22.01 it seems like this scrip is no longer working.
i get this error
:37 char:17
+ … tRequest = (Invoke-WebRequest $pfSenseUrl/diag_backup.php -SessionVar …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Cannot index into a null array.
At C:\Users\TS\Dokument\pfsense\pfsensenetworkimport.ps1:38 char:1
+ $csrf = $lastRequest.Forms.Fields[“__csrf_magic”]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray