diff --git a/PiHoleShell/PiHoleShell.psm1 b/PiHoleShell/PiHoleShell.psm1
index 6482163..3be29b1 100644
--- a/PiHoleShell/PiHoleShell.psm1
+++ b/PiHoleShell/PiHoleShell.psm1
@@ -18,7 +18,7 @@ Export-ModuleMember -Function @(
#Actions
'Update-PiHoleActionsGravity', 'Invoke-PiHoleFlushNetwork', 'Invoke-PiHoleFlushLogs', 'Restart-PiHoleDnsService' `
#Authentication
- 'Remove-PiHoleCurrentAuthSession' , 'Get-PiHoleCurrentAuthSession', 'Remove-PiHoleAuthSession', `
+ 'Remove-PiHoleCurrentAuthSession' , 'Get-PiHoleCurrentAuthSession', 'Remove-PiHoleAuthSession', 'Get-PiHoleAuthStatus', 'Get-PiHoleAuthTotp', `
#GroupManagement
'Get-PiHoleGroup', 'New-PiHoleGroup', 'Update-PiHoleGroup', 'Remove-PiHoleGroup', `
#DnsControl
@@ -44,5 +44,7 @@ Export-ModuleMember -Function @(
#ClientManagement
'Get-PiHoleClient', 'New-PiHoleClient', 'Update-PiHoleClient', 'Remove-PiHoleClient', 'Get-PiHoleClientSuggestion', `
#NetworkInformation
- 'Get-PiHoleNetworkGateway', 'Get-PiHoleNetworkRoute', 'Get-PiHoleNetworkInterface', 'Get-PiHoleNetworkDevice', 'Remove-PiHoleNetworkDevice'
+ 'Get-PiHoleNetworkGateway', 'Get-PiHoleNetworkRoute', 'Get-PiHoleNetworkInterface', 'Get-PiHoleNetworkDevice', 'Remove-PiHoleNetworkDevice', `
+ #DHCP
+ 'Get-PiHoleDhcpLease', 'Remove-PiHoleDhcpLease'
)
\ No newline at end of file
diff --git a/PiHoleShell/Public/Authentication/Get-PiHoleAuthStatus.ps1 b/PiHoleShell/Public/Authentication/Get-PiHoleAuthStatus.ps1
new file mode 100644
index 0000000..3666bb7
--- /dev/null
+++ b/PiHoleShell/Public/Authentication/Get-PiHoleAuthStatus.ps1
@@ -0,0 +1,82 @@
+function Get-PiHoleAuthStatus {
+ <#
+.SYNOPSIS
+Check if authentication is required
+
+.DESCRIPTION
+Checks whether your Pi-hole requires a login for the calling client. Some Pi-hole
+configurations skip authentication entirely for trusted local clients, in which case this
+reports a valid session without needing a password at all. Pass -Password to instead check
+the status of a real login with that password.
+
+.PARAMETER PiHoleServer
+The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100"
+
+.PARAMETER Password
+The API Password you generated from your PiHole server. Optional - omit it to check whether
+your Pi-hole requires a login at all for this client, without authenticating.
+
+.PARAMETER IgnoreSsl
+Set to $true to skip SSL certificate validation
+
+.PARAMETER RawOutput
+This will dump the response instead of the formatted object
+
+.EXAMPLE
+Get-PiHoleAuthStatus -PiHoleServer "http://pihole.domain.com:8080"
+
+.EXAMPLE
+Get-PiHoleAuthStatus -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"
+ #>
+ [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/auth')]
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.URI]$PiHoleServer,
+ [string]$Password,
+ [bool]$IgnoreSsl = $false,
+ [bool]$RawOutput = $false
+ )
+ try {
+ if ($PSBoundParameters.ContainsKey('Password')) {
+ $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl
+ }
+
+ $Params = @{
+ Uri = "$($PiHoleServer.OriginalString)/api/auth"
+ Method = "Get"
+ SkipCertificateCheck = $IgnoreSsl
+ ContentType = "application/json"
+ }
+ if ($Sid) {
+ $Params.Headers = @{sid = $($Sid) }
+ }
+
+ $Response = Invoke-RestMethod @Params
+
+ if ($RawOutput) {
+ Write-Output $Response
+ }
+ else {
+ $Object = [PSCustomObject]@{
+ Valid = $Response.session.valid
+ Totp = $Response.session.totp
+ Sid = $Response.session.sid
+ Csrf = $Response.session.csrf
+ Validity = $Response.session.validity
+ Message = $Response.session.message
+ }
+ Write-Output $Object
+ }
+ }
+
+ catch {
+ Write-Error -Message $_.Exception.Message
+ }
+
+ finally {
+ if ($Sid) {
+ Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
+ }
+ }
+}
diff --git a/PiHoleShell/Public/Authentication/Get-PiHoleAuthTotp.ps1 b/PiHoleShell/Public/Authentication/Get-PiHoleAuthTotp.ps1
new file mode 100644
index 0000000..427248c
--- /dev/null
+++ b/PiHoleShell/Public/Authentication/Get-PiHoleAuthTotp.ps1
@@ -0,0 +1,76 @@
+function Get-PiHoleAuthTotp {
+ <#
+.SYNOPSIS
+Suggest new TOTP credentials
+
+.DESCRIPTION
+Suggests new TOTP credentials for setting up two-factor authentication (2FA) on your Pi-hole.
+This only suggests a secret; it does not enable 2FA by itself.
+
+.PARAMETER PiHoleServer
+The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100"
+
+.PARAMETER Password
+The API Password you generated from your PiHole server
+
+.PARAMETER IgnoreSsl
+Set to $true to skip SSL certificate validation
+
+.PARAMETER RawOutput
+This will dump the response instead of the formatted object
+
+.EXAMPLE
+Get-PiHoleAuthTotp -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"
+ #>
+ [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/auth/totp')]
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.URI]$PiHoleServer,
+ [Parameter(Mandatory = $true)]
+ [string]$Password,
+ [bool]$IgnoreSsl = $false,
+ [bool]$RawOutput = $false
+ )
+ try {
+ $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl
+
+ $Params = @{
+ Headers = @{sid = $($Sid) }
+ Uri = "$($PiHoleServer.OriginalString)/api/auth/totp"
+ Method = "Get"
+ SkipCertificateCheck = $IgnoreSsl
+ ContentType = "application/json"
+ }
+
+ $Response = Invoke-RestMethod @Params
+
+ if ($RawOutput) {
+ Write-Output $Response
+ }
+ else {
+ $Object = [PSCustomObject]@{
+ Type = $Response.totp.type
+ Account = $Response.totp.account
+ Issuer = $Response.totp.issuer
+ Algorithm = $Response.totp.algorithm
+ Digits = $Response.totp.digits
+ Period = $Response.totp.period
+ Offset = $Response.totp.offset
+ Secret = $Response.totp.secret
+ Codes = $Response.totp.codes
+ }
+ Write-Output $Object
+ }
+ }
+
+ catch {
+ Write-Error -Message $_.Exception.Message
+ }
+
+ finally {
+ if ($Sid) {
+ Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
+ }
+ }
+}
diff --git a/PiHoleShell/Public/DHCP/Get-PiHoleDhcpLease.ps1 b/PiHoleShell/Public/DHCP/Get-PiHoleDhcpLease.ps1
new file mode 100644
index 0000000..ee60678
--- /dev/null
+++ b/PiHoleShell/Public/DHCP/Get-PiHoleDhcpLease.ps1
@@ -0,0 +1,81 @@
+function Get-PiHoleDhcpLease {
+ <#
+.SYNOPSIS
+Get currently active DHCP leases
+
+.DESCRIPTION
+Returns the currently active DHCP leases handed out by Pi-hole's DHCP server.
+
+.PARAMETER PiHoleServer
+The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100"
+
+.PARAMETER Password
+The API Password you generated from your PiHole server
+
+.PARAMETER IgnoreSsl
+Set to $true to skip SSL certificate validation
+
+.PARAMETER RawOutput
+This will dump the response instead of the formatted object
+
+.EXAMPLE
+Get-PiHoleDhcpLease -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"
+ #>
+ [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/dhcp/leases')]
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.URI]$PiHoleServer,
+ [Parameter(Mandatory = $true)]
+ [string]$Password,
+ [bool]$IgnoreSsl = $false,
+ [bool]$RawOutput = $false
+ )
+ try {
+ $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl
+
+ $Params = @{
+ Headers = @{sid = $($Sid) }
+ Uri = "$($PiHoleServer.OriginalString)/api/dhcp/leases"
+ Method = "Get"
+ SkipCertificateCheck = $IgnoreSsl
+ ContentType = "application/json"
+ }
+
+ $Response = Invoke-RestMethod @Params
+
+ if ($RawOutput) {
+ Write-Output $Response
+ }
+ else {
+ $ObjectFinal = foreach ($Item in $Response.leases) {
+ # A lease's expires value of 0 means the lease is infinite and never expires.
+ if ($Item.expires -eq 0) {
+ $Expires = $null
+ }
+ else {
+ $Expires = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.expires).LocalTime
+ }
+
+ [PSCustomObject]@{
+ Name = $Item.name
+ Ip = $Item.ip
+ HwAddr = $Item.hwaddr
+ ClientId = $Item.clientid
+ Expires = $Expires
+ }
+ }
+ Write-Output $ObjectFinal
+ }
+ }
+
+ catch {
+ Write-Error -Message $_.Exception.Message
+ }
+
+ finally {
+ if ($Sid) {
+ Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
+ }
+ }
+}
diff --git a/PiHoleShell/Public/DHCP/Remove-PiHoleDhcpLease.ps1 b/PiHoleShell/Public/DHCP/Remove-PiHoleDhcpLease.ps1
new file mode 100644
index 0000000..19f43e0
--- /dev/null
+++ b/PiHoleShell/Public/DHCP/Remove-PiHoleDhcpLease.ps1
@@ -0,0 +1,80 @@
+function Remove-PiHoleDhcpLease {
+ <#
+.SYNOPSIS
+Remove a DHCP lease
+
+.DESCRIPTION
+Removes a currently active DHCP lease. Managing DHCP leases is only possible when the DHCP
+server is enabled.
+
+.PARAMETER PiHoleServer
+The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100"
+
+.PARAMETER Password
+The API Password you generated from your PiHole server
+
+.PARAMETER Ip
+The IP address of the lease to remove, as shown by Get-PiHoleDhcpLease
+
+.PARAMETER IgnoreSsl
+Set to $true to skip SSL certificate validation
+
+.PARAMETER RawOutput
+This will dump the response instead of the formatted object
+
+.EXAMPLE
+Remove-PiHoleDhcpLease -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Ip "192.168.1.50"
+ #>
+ [CmdletBinding(SupportsShouldProcess = $true, HelpUri = 'https://ftl.pi-hole.net/master/docs/#delete-/dhcp/leases/-ip-')]
+ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.URI]$PiHoleServer,
+ [Parameter(Mandatory = $true)]
+ [string]$Password,
+ [Parameter(Mandatory = $true)]
+ [string]$Ip,
+ [bool]$IgnoreSsl = $false,
+ [bool]$RawOutput = $false
+ )
+ try {
+ $Target = "Pi-Hole DHCP lease $Ip"
+ if ($PSCmdlet.ShouldProcess($Target, "Remove DHCP lease")) {
+ $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl
+
+ $Params = @{
+ Headers = @{sid = $($Sid) }
+ Uri = "$($PiHoleServer.OriginalString)/api/dhcp/leases/$Ip"
+ Method = "Delete"
+ SkipCertificateCheck = $IgnoreSsl
+ ContentType = "application/json"
+ }
+
+ $Response = Invoke-RestMethod @Params
+
+ if ($RawOutput) {
+ Write-Output $Response
+ }
+
+ else {
+ # A successful delete returns 204 No Content, so there's no response body to
+ # build a rich object from.
+ $Object = [PSCustomObject]@{
+ Ip = $Ip
+ Status = "Removed"
+ }
+ Write-Output $Object
+ }
+ }
+ }
+
+ catch {
+ Write-Error -Message $_.Exception.Message
+ }
+
+ finally {
+ if ($Sid) {
+ Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
+ }
+ }
+}
diff --git a/README.md b/README.md
index 1a29f29..ac3af28 100644
--- a/README.md
+++ b/README.md
@@ -165,6 +165,7 @@ See [docs/EXAMPLES.md](docs/EXAMPLES.md) for real, captured output from every fu
| `Add-PiHoleConfigArrayItem` | Add config array item |
| `Get-PiHoleConfig` | Get current configuration of Pi-hole |
| `Get-PiHoleConfigProperty` | Get special properties of your Pi-hole configuration |
+| `Get-PiHoleDhcpLease` | Get currently active DHCP leases |
| `Get-PiHoleHistory` | Get activity graph data |
| `Get-PiHoleHistoryClient` | Get per-client activity graph data |
| `Get-PiHoleHistoryDatabase` | Get activity graph data (long-term data) |
@@ -190,6 +191,7 @@ See [docs/EXAMPLES.md](docs/EXAMPLES.md) for real, captured output from every fu
| `Get-PiHolePadd` | Get summarized data for PADD |
| `Get-PiHoleTeleporterDownload` | Export Pi-hole settings |
| `Remove-PiHoleConfigArrayItem` | Delete config array item |
+| `Remove-PiHoleDhcpLease` | Remove a DHCP lease |
| `Remove-PiHoleInfoMessage` | Delete a Pi-hole diagnosis message |
| `Remove-PiHoleNetworkDevice` | Delete a device from the network table |
| `Set-PiHoleConfig` | Change configuration of your Pi-hole |
@@ -200,6 +202,8 @@ Session handling is automatic for every command above, but these are available f
| Function | Description |
|---|---|
+| `Get-PiHoleAuthStatus` | Check if authentication is required |
+| `Get-PiHoleAuthTotp` | Suggest new TOTP credentials |
| `Get-PiHoleCurrentAuthSession` | List of all current sessions including their validity and further information about the client such as the IP address and user agent. |
| `Remove-PiHoleAuthSession` | Using this endpoint, a session can be deleted by its ID. |
diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md
index 95fd82e..a701363 100644
--- a/docs/EXAMPLES.md
+++ b/docs/EXAMPLES.md
@@ -99,9 +99,9 @@ New-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -GroupName "PiHo
Name : PiHoleShellDocsExampleGroup
Comment : Example group
Enabled : True
-Id : 53
-DateAdded : 9/30/2026 9:12:05 AM
-DateModified : 9/30/2026 9:12:05 AM
+Id : 57
+DateAdded : 10/1/2026 3:13:46 PM
+DateModified : 10/1/2026 3:13:46 PM
```
### Get-PiHoleGroup
@@ -143,9 +143,9 @@ DateModified : 9/30/2026 8:48:26 AM
Name : PiHoleShellDocsExampleGroup
Comment : Example group
Enabled : True
-Id : 53
-DateAdded : 9/30/2026 9:12:05 AM
-DateModified : 9/30/2026 9:12:05 AM
+Id : 57
+DateAdded : 10/1/2026 3:13:46 PM
+DateModified : 10/1/2026 3:13:46 PM
```
### Update-PiHoleGroup
@@ -159,9 +159,9 @@ Update-PiHoleGroup -PiHoleServer $PiHoleServer -Password $Password -GroupName "P
Name : PiHoleShellDocsExampleGroup
Comment : Example group
Enabled : False
-Id : 53
-DateAdded : 9/30/2026 9:12:05 AM
-DateModified : 9/30/2026 9:12:24 AM
+Id : 57
+DateAdded : 10/1/2026 3:13:46 PM
+DateModified : 10/1/2026 3:14:04 PM
```
### Remove-PiHoleGroup
@@ -190,9 +190,9 @@ Address : https://blocklistproject.github.io/Lists/alt-version/ransomware
Comment : Example list
Groups : {Default}
Enabled : True
-Id : 80
-DateAdded : 9/30/2026 9:13:02 AM
-DateModified : 9/30/2026 9:13:02 AM
+Id : 71
+DateAdded : 10/1/2026 3:14:41 PM
+DateModified : 10/1/2026 3:14:41 PM
Type : Block
DateUpdated :
Number : 0
@@ -246,7 +246,7 @@ DateAdded : 7/6/2025 2:20:46 AM
DateModified : 7/6/2025 2:20:46 AM
Type : block
DateUpdated : 7/6/2025 2:20:46 AM
-Number : 175219
+Number : 175912
InvalidDomains : 0
AbpEntries : 0
Status : 2
@@ -294,9 +294,9 @@ Address : https://blocklistproject.github.io/Lists/alt-version/ransomware
Comment : Example list
Groups : {Default}
Enabled : False
-Id : 80
-DateAdded : 9/30/2026 9:13:02 AM
-DateModified : 9/30/2026 9:13:36 AM
+Id : 71
+DateAdded : 10/1/2026 3:14:41 PM
+DateModified : 10/1/2026 3:15:14 PM
Type : Block
DateUpdated :
Number : 0
@@ -352,8 +352,8 @@ Type : block
Groups : {0}
DateAdded : 7/6/2025 2:20:46 AM
DateModified : 7/6/2025 2:20:46 AM
-DateUpdated : 9/28/2026 3:20:00 PM
-Number : 175219
+DateUpdated : 9/30/2026 10:20:00 AM
+Number : 175912
InvalidDomains : 0
AbpEntries : 0
Status : 2
@@ -421,9 +421,9 @@ Kind : Exact
Comment : Example domain
Groups : {Default}
Enabled : True
-Id : 18
-DateAdded : 9/30/2026 9:14:17 AM
-DateModified : 9/30/2026 9:14:17 AM
+Id : 22
+DateAdded : 10/1/2026 3:15:52 PM
+DateModified : 10/1/2026 3:15:52 PM
```
### Get-PiHoleDomain
@@ -441,9 +441,9 @@ Kind : exact
Comment : Example domain
Groups : {Default}
Enabled : True
-Id : 18
-DateAdded : 9/30/2026 9:14:17 AM
-DateModified : 9/30/2026 9:14:17 AM
+Id : 22
+DateAdded : 10/1/2026 3:15:52 PM
+DateModified : 10/1/2026 3:15:52 PM
```
### Update-PiHoleDomain
@@ -461,9 +461,9 @@ Kind : Exact
Comment : Example domain
Groups : {Default}
Enabled : False
-Id : 18
-DateAdded : 9/30/2026 9:14:17 AM
-DateModified : 9/30/2026 9:14:50 AM
+Id : 22
+DateAdded : 10/1/2026 3:15:52 PM
+DateModified : 10/1/2026 3:16:25 PM
```
### Remove-PiHoleDomain
@@ -494,9 +494,9 @@ Client : 192.168.99.99
Name :
Comment : Example client
Groups : {Default}
-Id : 20
-DateAdded : 9/30/2026 9:15:21 AM
-DateModified : 9/30/2026 9:15:21 AM
+Id : 22
+DateAdded : 10/1/2026 3:16:56 PM
+DateModified : 10/1/2026 3:16:56 PM
```
### Get-PiHoleClient
@@ -511,9 +511,9 @@ Client : 192.168.99.99
Name :
Comment : Example client
Groups : {Default}
-Id : 20
-DateAdded : 9/30/2026 9:15:21 AM
-DateModified : 9/30/2026 9:15:21 AM
+Id : 22
+DateAdded : 10/1/2026 3:16:56 PM
+DateModified : 10/1/2026 3:16:56 PM
```
### Get-PiHoleClientSuggestion
@@ -526,13 +526,13 @@ Get-PiHoleClientSuggestion -PiHoleServer $PiHoleServer -Password $Password
HwAddr : ip-127.0.0.1
MacVendor :
-LastQuery : 9/30/2026 9:00:01 AM
+LastQuery : 10/1/2026 3:00:00 PM
Addresses : 127.0.0.1
Names : localhost
HwAddr : 74:56:3c:bb:f4:0b
MacVendor : Giga-Byte Technology Co.,Ltd.
-LastQuery : 9/30/2026 7:49:13 AM
+LastQuery : 10/1/2026 9:03:33 AM
Addresses : 192.168.1.162
Names :
```
@@ -549,9 +549,9 @@ Client : 192.168.99.99
Name :
Comment : Updated comment
Groups : {Default}
-Id : 20
-DateAdded : 9/30/2026 9:15:21 AM
-DateModified : 9/30/2026 9:16:00 AM
+Id : 22
+DateAdded : 10/1/2026 3:16:56 PM
+DateModified : 10/1/2026 3:17:35 PM
```
### Remove-PiHoleClient
@@ -576,18 +576,18 @@ Get-PiHoleStatsSummary -PiHoleServer $PiHoleServer -Password $Password
```
-Total : 287
-Blocked : 93
-PercentBlocked : 32.404182434082
-UniqueDomains : 22
-Forwarded : 33
-Cached : 161
+Total : 52
+Blocked : 10
+PercentBlocked : 19.230770111084
+UniqueDomains : 15
+Forwarded : 27
+Cached : 15
Frequency : 0
-Types : @{A=136; AAAA=139; ANY=0; SRV=0; SOA=0; PTR=12; TXT=0; NAPTR=0; MX=0; DS=0; RRSIG=0; DNSKEY=0; NS=0; SVCB=0; HTTPS=0; OTHER=0}
-Status : @{Unknown=0; Gravity=93; Forwarded=33; Cache=122; Regex=0; DenyList=0; ExternalBlockedIp=0; ExternalBlockedNull=0; ExternalBlockedNxra=0; GravityCname=0; RegexCname=0; DenyListCname=0; Retired=0; RetiredDnssec=0; InProgress=0; Dbbusy=0; SpecialDomain=0; CacheStale=39; ExternalBlockedEde15=0}
-Replies : @{Unknown=7; Nodata=17; Nxdomain=3; Cname=0; Ip=251; Domain=9; Rrname=0; ServFail=0; Refused=0; Notimp=0; Other=0; Dnssec=0; None=0; Blob=0}
+Types : @{A=20; AAAA=19; ANY=0; SRV=0; SOA=0; PTR=13; TXT=0; NAPTR=0; MX=0; DS=0; RRSIG=0; DNSKEY=0; NS=0; SVCB=0; HTTPS=0; OTHER=0}
+Status : @{Unknown=0; Gravity=10; Forwarded=27; Cache=15; Regex=0; DenyList=0; ExternalBlockedIp=0; ExternalBlockedNull=0; ExternalBlockedNxra=0; GravityCname=0; RegexCname=0; DenyListCname=0; Retired=0; RetiredDnssec=0; InProgress=0; Dbbusy=0; SpecialDomain=0; CacheStale=0; ExternalBlockedEde15=0}
+Replies : @{Unknown=10; Nodata=3; Nxdomain=3; Cname=0; Ip=26; Domain=10; Rrname=0; ServFail=0; Refused=0; Notimp=0; Other=0; Dnssec=0; None=0; Blob=0}
Clients : @{Active=2; Total=2}
-Gravity : @{DomainsBeingBlocked=494568; LastUpdate=1790769668}
+Gravity : @{DomainsBeingBlocked=495564; LastUpdate=1790861541}
```
### Get-PiHoleStatsRecentBlocked
@@ -598,7 +598,7 @@ Get-PiHoleStatsRecentBlocked -PiHoleServer $PiHoleServer -Password $Password
```
-Blocked : googlesyndication.com
+Blocked : googleadservices.com
```
### Get-PiHoleStatsQueryType
@@ -610,10 +610,10 @@ Get-PiHoleStatsQueryType -PiHoleServer $PiHoleServer -Password $Password
```
Type : A
-Count : 136
+Count : 20
Type : AAAA
-Count : 139
+Count : 19
Type : ANY
Count : 0
@@ -635,20 +635,20 @@ Get-PiHoleStatsTopDomain -PiHoleServer $PiHoleServer -Password $Password
```
-Domain : bbc.com
-Count : 22
-
-Domain : microsoft.com
-Count : 22
+Domain : 1.0.0.127.in-addr.arpa
+Count : 7
Domain : example.com
-Count : 20
-
-Domain : apple.com
-Count : 20
+Count : 4
Domain : cloudflare.com
-Count : 16
+Count : 4
+
+Domain : reddit.com
+Count : 4
+
+Domain : bbc.com
+Count : 4
_(showing 5 of 10 results)_
```
@@ -663,11 +663,11 @@ Get-PiHoleStatsTopClient -PiHoleServer $PiHoleServer -Password $Password
IP : 192.168.1.162
Name :
-Count : 275
+Count : 39
IP : 127.0.0.1
Name : localhost
-Count : 12
+Count : 13
```
### Get-PiHoleStatsUpstream
@@ -678,9 +678,9 @@ Get-PiHoleStatsUpstream -PiHoleServer $PiHoleServer -Password $Password
```
-TotalQueries : 287
-ForwardedQueries : 33
-Upstreams : {@{Ip=blocklist; Name=blocklist; Port=-1; Count=93; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=161; ResponseTime=0; Variance=0}, @{Ip=8.8.4.4; Name=dns.google; Port=53; Count=18; ResponseTime=0.0253777056932449; Variance=0.00323175294284653}, @{Ip=8.8.8.8; Name=dns.google; Port=53; Count=15; ResponseTime=0.0273200273513794; Variance=0.00394722979890426}}
+TotalQueries : 52
+ForwardedQueries : 27
+Upstreams : {@{Ip=blocklist; Name=blocklist; Port=-1; Count=10; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=15; ResponseTime=0; Variance=0}, @{Ip=8.8.8.8; Name=dns.google; Port=53; Count=27; ResponseTime=0.0202921777963638; Variance=0.00264818341859654}}
```
### Get-PiHoleStatsQuerySuggestions
@@ -691,10 +691,10 @@ Get-PiHoleStatsQuerySuggestions -PiHoleServer $PiHoleServer -Password $Password
```
-Domain : {bbc.com, microsoft.com, example.com, apple.com…}
+Domain : {1.0.0.127.in-addr.arpa, example.com, cloudflare.com, reddit.com…}
ClientIp : {192.168.1.162, 127.0.0.1}
ClientName : {localhost}
-Upstream : {blocklist, cache, 8.8.4.4#53 (dns.google), 8.8.8.8#53 (dns.google)…}
+Upstream : {blocklist, cache, 8.8.8.8#53 (dns.google), permitted}
Type : {A, AAAA, ANY, SRV…}
Status : {UNKNOWN, GRAVITY, FORWARDED, CACHE…}
Reply : {UNKNOWN, NODATA, NXDOMAIN, CNAME…}
@@ -711,9 +711,9 @@ Get-PiHoleStatsDatabaseSummary -PiHoleServer $PiHoleServer -Password $Password
```
-SumQueries : 287
-SumBlocked : 93
-PercentBlocked : 32.404182434082
+SumQueries : 52
+SumBlocked : 10
+PercentBlocked : 19.230770111084
TotalClients : 3
```
@@ -728,7 +728,7 @@ Get-PiHoleStatsDatabaseQueryType -PiHoleServer $PiHoleServer -Password $Password
```
Type : A
-Count : 139
+Count : 19
Type : AAAA
Count : 0
@@ -740,7 +740,7 @@ Type : SRV
Count : 0
Type : SOA
-Count : 12
+Count : 13
_(showing 5 of 16 results)_
```
@@ -755,20 +755,20 @@ Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $PiHoleServer -Password $Password
```
+Domain : 1.0.0.127.in-addr.arpa
+Count : 7
+
Domain : bbc.com
-Count : 22
+Count : 4
-Domain : microsoft.com
-Count : 22
+Domain : wikipedia.org
+Count : 4
Domain : example.com
-Count : 20
-
-Domain : apple.com
-Count : 20
+Count : 4
-Domain : cloudflare.com
-Count : 16
+Domain : stackoverflow.com
+Count : 4
_(showing 5 of 10 results)_
```
@@ -785,15 +785,15 @@ Get-PiHoleStatsDatabaseTopClient -PiHoleServer $PiHoleServer -Password $Password
IP : 192.168.1.162
Name :
-Count : 182
+Count : 29
IP : 127.0.0.1
Name : localhost
-Count : 9
+Count : 11
IP : 127.0.0.1
Name :
-Count : 3
+Count : 2
```
### Get-PiHoleStatsDatabaseUpstream
@@ -806,9 +806,9 @@ Get-PiHoleStatsDatabaseUpstream -PiHoleServer $PiHoleServer -Password $Password
```
-TotalQueries : 287
-ForwardedQueries : 33
-Upstreams : {@{Ip=1; Name=; Port=4294967295; Count=22; ResponseTime=0; Variance=0}, @{Ip=2; Name=; Port=4294967295; Count=11; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=122; ResponseTime=0; Variance=0}, @{Ip=blocklist; Name=blocklist; Port=-1; Count=132; ResponseTime=0; Variance=0}}
+TotalQueries : 52
+ForwardedQueries : 27
+Upstreams : {@{Ip=2; Name=; Port=4294967295; Count=27; ResponseTime=0; Variance=0}, @{Ip=cache; Name=cache; Port=-1; Count=15; ResponseTime=0; Variance=0}, @{Ip=blocklist; Name=blocklist; Port=-1; Count=10; ResponseTime=0; Variance=0}}
```
### Get-PiHoleQuery
@@ -821,67 +821,67 @@ Get-PiHoleQuery -PiHoleServer $PiHoleServer -Password $Password -Length 5
```
-Id : 286
-Time : 9/30/2026 9:00:01 AM
+Id : 51
+Time : 10/1/2026 3:00:00 PM
Type : PTR
-Domain : 4.4.8.8.in-addr.arpa
+Domain : 1.0.0.127.in-addr.arpa
Cname :
Status : CACHE
Client : @{Ip=127.0.0.1; Name=localhost}
Dnssec : UNKNOWN
-Reply : @{Type=DOMAIN; Time=0.0011289119720459}
+Reply : @{Type=DOMAIN; Time=0.000154972076416016}
ListId :
Upstream :
Ede : @{Code=-1; Text=}
-Id : 285
-Time : 9/30/2026 9:00:01 AM
+Id : 50
+Time : 10/1/2026 2:00:01 PM
Type : PTR
-Domain : 8.8.8.8.in-addr.arpa
+Domain : 1.0.0.127.in-addr.arpa
Cname :
Status : CACHE
Client : @{Ip=127.0.0.1; Name=localhost}
Dnssec : UNKNOWN
-Reply : @{Type=DOMAIN; Time=0.00119900703430176}
+Reply : @{Type=DOMAIN; Time=0.000155925750732422}
ListId :
Upstream :
Ede : @{Code=-1; Text=}
-Id : 284
-Time : 9/30/2026 9:00:01 AM
+Id : 49
+Time : 10/1/2026 1:00:00 PM
Type : PTR
Domain : 1.0.0.127.in-addr.arpa
Cname :
Status : CACHE
Client : @{Ip=127.0.0.1; Name=localhost}
Dnssec : UNKNOWN
-Reply : @{Type=DOMAIN; Time=0.0010991096496582}
+Reply : @{Type=DOMAIN; Time=0.000154018402099609}
ListId :
Upstream :
Ede : @{Code=-1; Text=}
-Id : 283
-Time : 9/30/2026 9:00:01 AM
+Id : 48
+Time : 10/1/2026 12:00:00 PM
Type : PTR
-Domain : 162.1.168.192.in-addr.arpa
+Domain : 1.0.0.127.in-addr.arpa
Cname :
Status : CACHE
Client : @{Ip=127.0.0.1; Name=localhost}
Dnssec : UNKNOWN
-Reply : @{Type=NXDOMAIN; Time=0.0204389095306396}
+Reply : @{Type=DOMAIN; Time=0.000212192535400391}
ListId :
Upstream :
Ede : @{Code=-1; Text=}
-Id : 282
-Time : 9/30/2026 8:00:00 AM
+Id : 47
+Time : 10/1/2026 11:00:01 AM
Type : PTR
-Domain : 4.4.8.8.in-addr.arpa
+Domain : 8.8.8.8.in-addr.arpa
Cname :
Status : CACHE
Client : @{Ip=127.0.0.1; Name=localhost}
Dnssec : UNKNOWN
-Reply : @{Type=DOMAIN; Time=0.00113701820373535}
+Reply : @{Type=DOMAIN; Time=0.00014805793762207}
ListId :
Upstream :
Ede : @{Code=-1; Text=}
@@ -978,20 +978,20 @@ CpuPercent : 0
MemoryPercent : 0
ActiveClients : 2
Blocking : enabled
-Cache : @{Size=10000; Inserted=157; Evicted=0}
+Cache : @{Size=10000; Inserted=35; Evicted=0}
Config : @{DhcpActive=False; DhcpStart=; DhcpEnd=; DhcpIpv6=False; DnsDnssec=False; DnsDomain=lan; DnsNumUpstreams=2; DnsPort=53; DnsrevServerAactive=False; PrivacyLevel=0}
-GravitySize : 494568
+GravitySize : 495564
HostModel : Raspberry Pi Zero W Rev 1.1
IFace : @{v4=; v6=}
NodeName : dns3.localdomain
Pid : 388
-Queries : @{Total=287; Blocked=93; PercentBlocked=32.404182434082; QueryFrequency=0}
-RecentBlocked : googlesyndication.com
-Sensors : @{CpuTemp=40.622; HotLimit=60; Unit=C}
-System : @{Uptime=127777; Memory=; Procs=73; Cpu=; Ftl=}
-TopBlocked : ads.pubmatic.com
+Queries : @{Total=52; Blocked=10; PercentBlocked=19.230770111084; QueryFrequency=0}
+RecentBlocked : googleadservices.com
+Sensors : @{CpuTemp=39.546; HotLimit=60; Unit=C}
+System : @{Uptime=235882; Memory=; Procs=72; Cpu=; Ftl=}
+TopBlocked : googleadservices.com
TopClient : 192.168.1.162
-TopDomain : bbc.com
+TopDomain : 1.0.0.127.in-addr.arpa
Version : @{Core=; Web=; Ftl=; Docker=}
```
@@ -1028,11 +1028,11 @@ Get-PiHoleInfoSystem -PiHoleServer $PiHoleServer -Password $Password
```
-Uptime : 127787
+Uptime : 235892
Memory : @{Ram=; Swap=}
-Procs : 73
-Cpu : @{NumProcessors=1; PercentCpu=95.8000030517578; Load=}
-Ftl : @{PercentMemory=2.42485022544861; PercentCpu=95.5}
+Procs : 74
+Cpu : @{NumProcessors=1; PercentCpu=97.1999969482422; Load=}
+Ftl : @{PercentMemory=2.52266621589661; PercentCpu=96.4000015258789}
```
### Get-PiHoleInfoFtl
@@ -1043,16 +1043,16 @@ Get-PiHoleInfoFtl -PiHoleServer $PiHoleServer -Password $Password
```
-Database : @{Gravity=494568; Antigravity=0; Groups=4; Lists=14; Clients=0; Domains=; Regex=}
+Database : @{Gravity=495564; Antigravity=0; Groups=4; Lists=14; Clients=0; Domains=; Regex=}
PrivacyLevel : 0
QueryFrequency : 0
Clients : @{Total=2; Active=2}
Pid : 388
-Uptime : 7963466.771567
-PercentMemory : 2.42485022544861
-PercentCpu : 96.5999984741211
+Uptime : 24254906.207769
+PercentMemory : 2.52266621589661
+PercentCpu : 96.4000015258789
AllowDestructive : True
-Dnsmasq : @{DnsCacheInserted=157; DnsCacheLiveFreed=0; DnsQueriesForwarded=72; DnsAuthAnswered=0; DnsLocalAnswered=215; DnsStaleAnswered=39; DnsUnanswered=0; DnssecMaxCryptoUse=0; DnssecMaxSigFail=0; DnssecMaxWork=0; Bootp=0; Pxe=0; DhcpAck=0; DhcpDecline=0; DhcpDiscover=0; DhcpInform=0; DhcpNak=0; DhcpOffer=0; DhcpRelease=0; DhcpRequest=0; Noanswer=0; LeasesAllocated4=0; LeasesPruned4=0; LeasesAllocated6=0; LeasesPruned6=0; TcpConnections=0; DhcpLeasequery=0; DhcpLeaseUnassigned=0; DhcpLeaseActve=0; DhcpLeaseUnknown=0}
+Dnsmasq : @{DnsCacheInserted=35; DnsCacheLiveFreed=0; DnsQueriesForwarded=27; DnsAuthAnswered=0; DnsLocalAnswered=25; DnsStaleAnswered=0; DnsUnanswered=0; DnssecMaxCryptoUse=0; DnssecMaxSigFail=0; DnssecMaxWork=0; Bootp=0; Pxe=0; DhcpAck=0; DhcpDecline=0; DhcpDiscover=0; DhcpInform=0; DhcpNak=0; DhcpOffer=0; DhcpRelease=0; DhcpRequest=0; Noanswer=0; LeasesAllocated4=0; LeasesPruned4=0; LeasesAllocated6=0; LeasesPruned6=0; TcpConnections=0; DhcpLeasequery=0; DhcpLeaseUnassigned=0; DhcpLeaseActve=0; DhcpLeaseUnknown=0}
```
### Get-PiHoleInfoSensors
@@ -1064,7 +1064,7 @@ Get-PiHoleInfoSensors -PiHoleServer $PiHoleServer -Password $Password
```
List : {@{Name=cpu_thermal; Path=hwmon0; Source=devices/virtual/thermal/thermal_zone0; Temps=}, @{Name=rpi_volt; Path=hwmon1; Source=devices/platform/soc/soc:firmware/raspberrypi-hwmon; Temps=}}
-CpuTemp : 40.622
+CpuTemp : 40.084
HotLimit : 60
Unit : C
```
@@ -1081,12 +1081,12 @@ Size : 13983744
Type : Regular file
Mode : rw-r-----
AccessTime : 6/25/2025 12:32:44 AM
-ModifiedTime : 9/30/2026 6:58:06 AM
-ChangeTime : 9/30/2026 6:58:06 AM
+ModifiedTime : 10/1/2026 2:06:00 PM
+ChangeTime : 10/1/2026 2:06:00 PM
Owner : @{User=; Group=}
-Queries : 287
-EarliestTimestamp : 9/29/2026 9:10:00 AM
-QueriesDisk : 287
+Queries : 52
+EarliestTimestamp : 9/30/2026 3:10:00 PM
+QueriesDisk : 52
EarliestTimestampDisk :
SqliteVersion : 3.53.1
```
@@ -1156,7 +1156,13 @@ Get-PiHoleInfoMessage -PiHoleServer $PiHoleServer -Password $Password
```
Id : 1
-Timestamp : 9/30/2026 7:38:07 AM
+Timestamp : 10/1/2026 8:28:21 AM
+Type : LIST
+Plain : List with ID 63 (https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/domains/light.txt) was inaccessible during last gravity run
+Html : List with ID 63 (https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/domains/light.txt) was inaccessible during last gravity run
+
+Id : 2
+Timestamp : 10/1/2026 9:38:21 AM
Type : LOAD
Plain : Long-term load (15min avg) larger than number of processors: 1.0 > 1
Html : Long-term load (15min avg) larger than number of processors: 1.0 > 1
This may slow down DNS resolution and can cause bottlenecks.
@@ -1170,7 +1176,7 @@ Get-PiHoleInfoMessageCount -PiHoleServer $PiHoleServer -Password $Password
```
-Count : 1
+Count : 2
```
### Remove-PiHoleInfoMessage
@@ -1194,8 +1200,8 @@ Get-PiHoleLogWebserver -PiHoleServer $PiHoleServer -Password $Password
```
-Log : {@{Timestamp=9/30/2026 6:58:08 AM; Message=Initializing HTTP server on ports "8089,8489s"; Priority=}, @{Timestamp=9/30/2026 7:16:07 AM; Message=ACCESS: 192.168.1.162 - - [30/Sep/2026:12:16:06 +0000] "PATCH /api/config?restart=false HTTP/1.1" 200 4590 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}, @{Timestamp=9/30/2026 7:16:08 AM; Message=ACCESS: 192.168.1.162 - - [30/Sep/2026:12:16:07 +0000] "DELETE /api/auth HTTP/1.1" 204 558 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}, @{Timestamp=9/30/2026 7:16:12 AM; Message=ACCESS: 192.168.1.162 - - [30/Sep/2026:12:16:07 +0000] "POST /api/auth HTTP/1.1" 200 808 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}…}
-NextID : 13
+Log : {@{Timestamp=10/1/2026 8:28:22 AM; Message=Initializing HTTP server on ports "8089,8489s"; Priority=}, @{Timestamp=10/1/2026 8:31:16 AM; Message=ACCESS: 192.168.1.162 - - [01/Oct/2026:13:31:15 +0000] "PATCH /api/config?restart=false HTTP/1.1" 200 4588 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}, @{Timestamp=10/1/2026 8:31:17 AM; Message=ACCESS: 192.168.1.162 - - [01/Oct/2026:13:31:16 +0000] "DELETE /api/auth HTTP/1.1" 204 558 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}, @{Timestamp=10/1/2026 8:31:26 AM; Message=ACCESS: 192.168.1.162 - - [01/Oct/2026:13:31:17 +0000] "POST /api/auth HTTP/1.1" 200 807 - Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6; Priority=}…}
+NextID : 31
Pid : 388
File : /var/log/pihole/webserver.log
```
@@ -1208,8 +1214,8 @@ Get-PiHoleLogDnsmasq -PiHoleServer $PiHoleServer -Password $Password
```
-Log : {@{Timestamp=9/30/2026 6:58:08 AM; Message=started, version pi-hole-v2.93 cachesize 10000; Priority=}, @{Timestamp=9/30/2026 6:58:08 AM; Message=DNS service limited to local subnets; Priority=}, @{Timestamp=9/30/2026 6:58:08 AM; Message=compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n IDN2 DHCP DHCPv6 Lua TFTP no-conntrack ipset no-nftset auth DNSSEC loop-detect inotify dumpfile; Priority=}, @{Timestamp=9/30/2026 6:58:08 AM; Message=using nameserver 8.8.8.8#53; Priority=}…}
-NextID : 57
+Log : {@{Timestamp=10/1/2026 8:28:22 AM; Message=started, version pi-hole-v2.93 cachesize 10000; Priority=}, @{Timestamp=10/1/2026 8:28:22 AM; Message=DNS service limited to local subnets; Priority=}, @{Timestamp=10/1/2026 8:28:22 AM; Message=compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n IDN2 DHCP DHCPv6 Lua TFTP no-conntrack ipset no-nftset auth DNSSEC loop-detect inotify dumpfile; Priority=}, @{Timestamp=10/1/2026 8:28:22 AM; Message=using nameserver 8.8.8.8#53; Priority=}…}
+NextID : 69
Pid : 388
File : /var/log/pihole/pihole.log
```
@@ -1222,8 +1228,8 @@ Get-PiHoleLogFtl -PiHoleServer $PiHoleServer -Password $Password
```
-Log : {@{Timestamp=9/30/2026 6:58:08 AM; Message= -> Unknown DNS queries: 0; Priority=INFO}, @{Timestamp=9/30/2026 6:58:08 AM; Message= -> Unique domains: 0; Priority=INFO}, @{Timestamp=9/30/2026 6:58:08 AM; Message= -> Unique clients: 0; Priority=INFO}, @{Timestamp=9/30/2026 6:58:08 AM; Message= -> DNS cache records: 0; Priority=INFO}…}
-NextID : 535
+Log : {@{Timestamp=10/1/2026 8:54:42 AM; Message=Wrote config file:; Priority=INFO}, @{Timestamp=10/1/2026 8:54:42 AM; Message= - 167 total entries; Priority=INFO}, @{Timestamp=10/1/2026 8:54:42 AM; Message= - 157 entries are default; Priority=INFO}, @{Timestamp=10/1/2026 8:54:42 AM; Message= - 10 entries are modified; Priority=INFO}…}
+NextID : 797
Pid : 388
File : /var/log/pihole/FTL.log
```
@@ -1252,31 +1258,31 @@ Get-PiHoleHistory -PiHoleServer $PiHoleServer -Password $Password
```
-Timestamp : 9/29/2026 9:15:00 AM
+Timestamp : 9/30/2026 3:15:00 PM
Total : 0
Cached : 0
Blocked : 0
Forwarded : 0
-Timestamp : 9/29/2026 9:25:00 AM
+Timestamp : 9/30/2026 3:25:00 PM
Total : 0
Cached : 0
Blocked : 0
Forwarded : 0
-Timestamp : 9/29/2026 9:35:00 AM
+Timestamp : 9/30/2026 3:35:00 PM
Total : 0
Cached : 0
Blocked : 0
Forwarded : 0
-Timestamp : 9/29/2026 9:45:00 AM
+Timestamp : 9/30/2026 3:45:00 PM
Total : 0
Cached : 0
Blocked : 0
Forwarded : 0
-Timestamp : 9/29/2026 9:55:00 AM
+Timestamp : 9/30/2026 3:55:00 PM
Total : 0
Cached : 0
Blocked : 0
@@ -1293,19 +1299,19 @@ Get-PiHoleHistoryClient -PiHoleServer $PiHoleServer -Password $Password
```
-Timestamp : 9/29/2026 9:15:00 AM
+Timestamp : 9/30/2026 3:15:00 PM
Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}}
-Timestamp : 9/29/2026 9:25:00 AM
+Timestamp : 9/30/2026 3:25:00 PM
Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}}
-Timestamp : 9/29/2026 9:35:00 AM
+Timestamp : 9/30/2026 3:35:00 PM
Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}}
-Timestamp : 9/29/2026 9:45:00 AM
+Timestamp : 9/30/2026 3:45:00 PM
Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}}
-Timestamp : 9/29/2026 9:55:00 AM
+Timestamp : 9/30/2026 3:55:00 PM
Clients : {@{IP=192.168.1.162; Name=; Count=0}, @{IP=others; Name=; Count=0}}
_(showing 5 of 145 results)_
@@ -1321,29 +1327,37 @@ Get-PiHoleHistoryDatabase -PiHoleServer $PiHoleServer -Password $Password
```
-Timestamp : 9/30/2026 7:30:00 AM
-Total : 47
-Cached : 12
-Blocked : 9
-Forwarded : 26
+Timestamp : 10/1/2026 9:00:00 AM
+Total : 42
+Cached : 5
+Blocked : 10
+Forwarded : 27
-Timestamp : 9/30/2026 7:40:00 AM
-Total : 232
-Cached : 141
-Blocked : 84
-Forwarded : 7
+Timestamp : 10/1/2026 10:00:00 AM
+Total : 3
+Cached : 3
+Blocked : 0
+Forwarded : 0
+
+Timestamp : 10/1/2026 11:00:00 AM
+Total : 3
+Cached : 3
+Blocked : 0
+Forwarded : 0
-Timestamp : 9/30/2026 8:00:00 AM
-Total : 4
-Cached : 4
+Timestamp : 10/1/2026 12:00:00 PM
+Total : 1
+Cached : 1
Blocked : 0
Forwarded : 0
-Timestamp : 9/30/2026 9:00:00 AM
-Total : 4
-Cached : 4
+Timestamp : 10/1/2026 1:00:00 PM
+Total : 1
+Cached : 1
Blocked : 0
Forwarded : 0
+
+_(showing 5 of 7 results)_
```
### Get-PiHoleHistoryDatabaseClient
@@ -1356,17 +1370,22 @@ Get-PiHoleHistoryDatabaseClient -PiHoleServer $PiHoleServer -Password $Password
```
-Timestamp : 9/30/2026 9:00:00 AM
+Timestamp : 10/1/2026 3:00:00 PM
Clients : @{ClientId=3; Count=1}
-Timestamp : 9/30/2026 8:00:00 AM
+Timestamp : 10/1/2026 2:00:00 PM
Clients : @{ClientId=3; Count=1}
-Timestamp : 9/30/2026 7:40:00 AM
-Clients : @{ClientId=5; Count=1}
+Timestamp : 10/1/2026 1:00:00 PM
+Clients : @{ClientId=3; Count=1}
-Timestamp : 9/30/2026 7:30:00 AM
-Clients : {@{ClientId=3; Count=1}, @{ClientId=2; Count=1}, @{ClientId=5; Count=1}}
+Timestamp : 10/1/2026 12:00:00 PM
+Clients : @{ClientId=3; Count=1}
+
+Timestamp : 10/1/2026 11:00:00 AM
+Clients : @{ClientId=3; Count=1}
+
+_(showing 5 of 7 results)_
```
### Get-PiHoleNetworkGateway
@@ -1475,7 +1494,7 @@ Address : b8:27:eb:11:a4:d5
Broadcast : ff:ff:ff:ff:ff:ff
PermAddress : b8:27:eb:11:a4:d5
Stats : @{RxBytes=; TxBytes=; Bits=64}
-Addresses : {@{Family=inet; Scope=universe; Flags=; Prefixlen=24; Address=192.168.1.248; AddressType=private; Local=192.168.1.248; LocalType=private; Broadcast=192.168.1.255; BroadcastType=private; Label=wlan0; Prefered=71284; Valid=71284; Cstamp=1790649687.41; Tstamp=1790762750.49}, @{Family=inet6; Scope=link; Flags=permanent; Prefixlen=64; Address=fe80::ba27:ebff:fe11:a4d5; AddressType=link-local (LL); Prefered=4294967295; Valid=4294967295; Cstamp=1790649682.83; Tstamp=1790649682.83}}
+Addresses : {@{Family=inet; Scope=universe; Flags=; Prefixlen=24; Address=192.168.1.248; AddressType=private; Local=192.168.1.248; LocalType=private; Broadcast=192.168.1.255; BroadcastType=private; Label=wlan0; Prefered=63266; Valid=63266; Cstamp=1790649687.41; Tstamp=1790862827.1}, @{Family=inet6; Scope=link; Flags=permanent; Prefixlen=64; Address=fe80::ba27:ebff:fe11:a4d5; AddressType=link-local (LL); Prefered=4294967295; Valid=4294967295; Cstamp=1790649682.83; Tstamp=1790649682.83}}
```
### Get-PiHoleNetworkDevice
@@ -1491,20 +1510,20 @@ Get-PiHoleNetworkDevice -PiHoleServer $PiHoleServer -Password $Password
Id : 2
HwAddr : ip-127.0.0.1
Interface : lo
-FirstSeen : 9/30/2026 7:34:38 AM
-LastQuery : 9/30/2026 9:00:01 AM
-NumQueries : 12
+FirstSeen : 10/1/2026 9:03:33 AM
+LastQuery : 10/1/2026 3:00:00 PM
+NumQueries : 13
MacVendor :
-Ips : @{Ip=127.0.0.1; Name=localhost; LastSeen=9/30/2026 9:17:00 AM; NameUpdated=9/30/2026 9:17:00 AM}
+Ips : @{Ip=127.0.0.1; Name=localhost; LastSeen=10/1/2026 3:19:00 PM; NameUpdated=10/1/2026 3:19:00 PM}
Id : 1
HwAddr : 74:56:3c:bb:f4:0b
Interface : wlan0
-FirstSeen : 9/30/2026 7:34:37 AM
-LastQuery : 9/30/2026 7:49:13 AM
-NumQueries : 275
+FirstSeen : 10/1/2026 9:03:32 AM
+LastQuery : 10/1/2026 9:03:33 AM
+NumQueries : 39
MacVendor : Giga-Byte Technology Co.,Ltd.
-Ips : @{Ip=192.168.1.162; Name=; LastSeen=9/30/2026 9:17:00 AM; NameUpdated=12/31/1969 6:00:00 PM}
+Ips : @{Ip=192.168.1.162; Name=; LastSeen=10/1/2026 3:19:00 PM; NameUpdated=12/31/1969 6:00:00 PM}
```
### Remove-PiHoleNetworkDevice
@@ -1520,8 +1539,68 @@ Remove-PiHoleNetworkDevice -PiHoleServer $PiHoleServer -Password $Password -Devi
Error : Response status code does not indicate success: 404 (Not Found).
```
+### Get-PiHoleDhcpLease
+
+```powershell
+Get-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $Password
+```
+
+```
+(no output)
+```
+
+### Remove-PiHoleDhcpLease
+
+_DHCP leases arise from Pi-hole genuinely handing one out and cannot be manufactured on demand, so this example shows the error for an IP address with no active lease rather than a fabricated success._
+
+```powershell
+Remove-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $Password -Ip "192.168.1.50"
+```
+
+```
+
+Error : Response status code does not indicate success: 400 (Bad Request).
+```
+
## Authentication
+### Get-PiHoleAuthStatus
+
+_Password is optional - omit it to check whether your Pi-hole requires a login at all for this client, without authenticating. This test server does require login, so the result below is from a real authenticated check._
+
+```powershell
+Get-PiHoleAuthStatus -PiHoleServer $PiHoleServer -Password $Password
+```
+
+```
+
+Valid : True
+Totp : False
+Sid : 8sHZ0t+C5DwLRgIcKIM7/g=
+Csrf : 1xmNq2FNUPFo7bnCRoUzIQ=
+Validity : 1800
+Message : correct password
+```
+
+### Get-PiHoleAuthTotp
+
+```powershell
+Get-PiHoleAuthTotp -PiHoleServer $PiHoleServer -Password $Password
+```
+
+```
+
+Type : totp
+Account : pi.hole
+Issuer : Pi-hole%20API
+Algorithm : SHA1
+Digits : 6
+Period : 30
+Offset : 0
+Secret : XP4YEGF2S75KCDBN7BSW37AEA65AQZBQ
+Codes : {414882, 273855, 226959, 897753…}
+```
+
### Get-PiHoleCurrentAuthSession
```powershell
@@ -1535,9 +1614,9 @@ CurrentSession : True
Valid : True
TlsLogin : True
TlsMixed : False
-LoginAt : 9/30/2026 9:09:06 AM
-LastActive : 9/30/2026 9:09:11 AM
-ValidUntil : 9/30/2026 9:39:11 AM
+LoginAt : 10/1/2026 3:10:59 PM
+LastActive : 10/1/2026 3:11:03 PM
+ValidUntil : 10/1/2026 3:41:03 PM
RemoteAddress : 192.168.1.162
UserAgent : Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26200; en-US) PowerShell/7.6.6
XForwardedFor :
diff --git a/tests/Authentication/Get-PiHoleAuthStatus.Integration.Tests.ps1 b/tests/Authentication/Get-PiHoleAuthStatus.Integration.Tests.ps1
new file mode 100644
index 0000000..26adfa0
--- /dev/null
+++ b/tests/Authentication/Get-PiHoleAuthStatus.Integration.Tests.ps1
@@ -0,0 +1,51 @@
+# Requires -Module Pester
+#
+# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1
+# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically
+# if that file is missing.
+
+$script:ConfigAvailable = Test-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')
+
+Describe 'Get-PiHoleAuthStatus (Integration)' -Tag 'Integration' {
+ BeforeAll {
+ Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force
+
+ $configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
+ if (Test-Path $configPath) {
+ . $configPath
+ $script:PiHoleServer = $PiHoleServer
+ $script:PiHoleToken = $PiHoleToken
+ $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
+ }
+ }
+
+ It 'returns a valid session when given a real password' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthStatus -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl
+ $result | Format-List | Out-String | Write-Host
+
+ $result | Should -Not -BeNullOrEmpty
+ $result.Valid | Should -BeTrue
+ }
+
+ # This test server requires a login for API clients, so calling with no password at all is
+ # expected to fail - this still exercises the real anonymous-check code path. A Pi-hole
+ # configured to skip auth for trusted local clients would instead return a valid session here.
+ It 'errors when called with no password and the server requires login' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthStatus -PiHoleServer $script:PiHoleServer -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+
+ It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthStatus -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true
+ $result | Format-List | Out-String | Write-Host
+
+ $result.PSObject.Properties.Name | Should -Contain 'session'
+ }
+
+ It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthStatus -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+}
diff --git a/tests/Authentication/Get-PiHoleAuthTotp.Integration.Tests.ps1 b/tests/Authentication/Get-PiHoleAuthTotp.Integration.Tests.ps1
new file mode 100644
index 0000000..90ac40d
--- /dev/null
+++ b/tests/Authentication/Get-PiHoleAuthTotp.Integration.Tests.ps1
@@ -0,0 +1,42 @@
+# Requires -Module Pester
+#
+# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1
+# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically
+# if that file is missing.
+
+$script:ConfigAvailable = Test-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')
+
+Describe 'Get-PiHoleAuthTotp (Integration)' -Tag 'Integration' {
+ BeforeAll {
+ Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force
+
+ $configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
+ if (Test-Path $configPath) {
+ . $configPath
+ $script:PiHoleServer = $PiHoleServer
+ $script:PiHoleToken = $PiHoleToken
+ $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
+ }
+ }
+
+ It 'returns a suggested TOTP secret as a formatted object' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthTotp -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl
+ $result | Format-List | Out-String | Write-Host
+
+ $result | Should -Not -BeNullOrEmpty
+ $result.Secret | Should -Not -BeNullOrEmpty
+ }
+
+ It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthTotp -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true
+ $result | Format-List | Out-String | Write-Host
+
+ $result.PSObject.Properties.Name | Should -Contain 'totp'
+ }
+
+ It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleAuthTotp -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+}
diff --git a/tests/DHCP/Get-PiHoleDhcpLease.Integration.Tests.ps1 b/tests/DHCP/Get-PiHoleDhcpLease.Integration.Tests.ps1
new file mode 100644
index 0000000..ebddf73
--- /dev/null
+++ b/tests/DHCP/Get-PiHoleDhcpLease.Integration.Tests.ps1
@@ -0,0 +1,40 @@
+# Requires -Module Pester
+#
+# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1
+# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically
+# if that file is missing.
+
+$script:ConfigAvailable = Test-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')
+
+Describe 'Get-PiHoleDhcpLease (Integration)' -Tag 'Integration' {
+ BeforeAll {
+ Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force
+
+ $configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
+ if (Test-Path $configPath) {
+ . $configPath
+ $script:PiHoleServer = $PiHoleServer
+ $script:PiHoleToken = $PiHoleToken
+ $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
+ }
+ }
+
+ # The test server's DHCP server may not be enabled or may have no active leases, so this only
+ # confirms the call succeeds rather than asserting specific leases are present.
+ It 'succeeds and returns without throwing, even with no leases active' -Skip:(-not $script:ConfigAvailable) {
+ { Get-PiHoleDhcpLease -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl } | Should -Not -Throw
+ }
+
+ It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleDhcpLease -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true
+ $result | Format-List | Out-String | Write-Host
+
+ $result | Should -Not -BeNullOrEmpty
+ }
+
+ It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
+ $result = Get-PiHoleDhcpLease -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+}
diff --git a/tests/DHCP/Remove-PiHoleDhcpLease.Integration.Tests.ps1 b/tests/DHCP/Remove-PiHoleDhcpLease.Integration.Tests.ps1
new file mode 100644
index 0000000..de3c3c5
--- /dev/null
+++ b/tests/DHCP/Remove-PiHoleDhcpLease.Integration.Tests.ps1
@@ -0,0 +1,37 @@
+# Requires -Module Pester
+#
+# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1
+# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically
+# if that file is missing.
+#
+# NOTE: DHCP leases arise from Pi-hole's DHCP server genuinely handing one out and can't be
+# manufactured on demand, so there's no reliable way to test a genuine successful delete here.
+# These tests instead verify the error paths, which still exercise the real request/auth flow.
+
+$script:ConfigAvailable = Test-Path (Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1')
+
+Describe 'Remove-PiHoleDhcpLease (Integration)' -Tag 'Integration' {
+ BeforeAll {
+ Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force
+
+ $configPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'IntegrationConfig.local.ps1'
+ if (Test-Path $configPath) {
+ . $configPath
+ $script:PiHoleServer = $PiHoleServer
+ $script:PiHoleToken = $PiHoleToken
+ $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl
+ }
+ }
+
+ It 'errors when the lease does not exist' -Skip:(-not $script:ConfigAvailable) {
+ $result = Remove-PiHoleDhcpLease -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -Ip '192.0.2.123' -Confirm:$false -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+
+ It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) {
+ $result = Remove-PiHoleDhcpLease -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -Ip '192.0.2.123' -Confirm:$false -ErrorVariable errOut -ErrorAction SilentlyContinue
+
+ $errOut | Should -Not -BeNullOrEmpty
+ }
+}
diff --git a/tools/Update-ExampleOutput.ps1 b/tools/Update-ExampleOutput.ps1
index c58b1ad..865609d 100644
--- a/tools/Update-ExampleOutput.ps1
+++ b/tools/Update-ExampleOutput.ps1
@@ -108,6 +108,15 @@ function Invoke-Quietly {
Write-Host "Capturing example output against $PiHoleServer ..."
#region Authentication
+Add-Example -Category Authentication -FunctionName 'Get-PiHoleAuthStatus' `
+ -Invocation 'Get-PiHoleAuthStatus -PiHoleServer $PiHoleServer -Password $Password' `
+ -Note 'Password is optional - omit it to check whether your Pi-hole requires a login at all for this client, without authenticating. This test server does require login, so the result below is from a real authenticated check.' `
+ -Result (Get-PiHoleAuthStatus -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl)
+
+Add-Example -Category Authentication -FunctionName 'Get-PiHoleAuthTotp' `
+ -Invocation 'Get-PiHoleAuthTotp -PiHoleServer $PiHoleServer -Password $Password' `
+ -Result (Get-PiHoleAuthTotp -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl)
+
Add-Example -Category Authentication -FunctionName 'Get-PiHoleCurrentAuthSession' `
-Invocation 'Get-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Password $Password' `
-Result (Get-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl)
@@ -428,6 +437,21 @@ Add-Example -Category Config -FunctionName 'Remove-PiHoleNetworkDevice' `
-Result $(if ($removeDeviceError) { [PSCustomObject]@{ Error = $removeDeviceError[-1].Exception.Message } })
#endregion
+#region DHCP
+# The DHCP folder isn't its own README category either - same fallback-folding reasoning as
+# History/NetworkInformation above.
+Add-Example -Category Config -FunctionName 'Get-PiHoleDhcpLease' `
+ -Invocation 'Get-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $Password' `
+ -Result (Get-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl)
+
+$dummyDhcpIp = '192.0.2.123'
+Remove-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $PiHoleToken -IgnoreSsl $PiHoleIgnoreSsl -Ip $dummyDhcpIp -Confirm:$false -ErrorVariable removeLeaseError -ErrorAction SilentlyContinue | Out-Null
+Add-Example -Category Config -FunctionName 'Remove-PiHoleDhcpLease' `
+ -Invocation 'Remove-PiHoleDhcpLease -PiHoleServer $PiHoleServer -Password $Password -Ip "192.168.1.50"' `
+ -Note 'DHCP leases arise from Pi-hole genuinely handing one out and cannot be manufactured on demand, so this example shows the error for an IP address with no active lease rather than a fabricated success.' `
+ -Result $(if ($removeLeaseError) { [PSCustomObject]@{ Error = $removeLeaseError[-1].Exception.Message } })
+#endregion
+
#region Actions
if ($IncludeDisruptive) {
Add-Example -Category Actions -FunctionName 'Invoke-PiHoleFlushNetwork' `