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' `