Scripting pfSense DHCP static assignments

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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

  • Patrick says:

    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!

    • Chad McCune says:

      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.

  • Patrick says:

    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

  • Chad McCune says:

    It looks like your $xml declaration might be incorrect.

    Try “[xml]$xml = [xml]::new()” instead of “$xml = ::new()”

  • Chad McCune says:

    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.

  • Tobias Sorensson says:

    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 🙂

  • Tobias Sorensson says:

    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 🙂

  • Chad McCune says:

    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?

    • Tobias Sorensson says:

      Hello

      no idea how to do that..

      can you tell me how to do it and il gladly do it 🙂

  • Tobias Sorensson says:

    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

Leave a Reply to Chad McCune Cancel reply

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