Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PiHoleShell/PiHoleShell.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Export-ModuleMember -Function @(
'Get-PiHolePadd', `
#Metrics
'Get-PiHoleStatsRecentBlocked', 'Get-PiHoleStatsQueryType', 'Get-PiHoleStatsTopDomain', 'Get-PiHoleStatsSummary', 'Get-PiHoleStatsTopClient', 'Get-PiHoleStatsQuerySuggestions', `
'Get-PiHoleStatsUpstream', 'Get-PiHoleStatsDatabaseUpstream', 'Get-PiHoleStatsDatabaseSummary', 'Get-PiHoleStatsDatabaseTopDomain', 'Get-PiHoleStatsDatabaseTopClient', 'Get-PiHoleStatsDatabaseQueryType' `
'Get-PiHoleStatsUpstream', 'Get-PiHoleStatsDatabaseUpstream', 'Get-PiHoleStatsDatabaseSummary', 'Get-PiHoleStatsDatabaseTopDomain', 'Get-PiHoleStatsDatabaseTopClient', 'Get-PiHoleStatsDatabaseQueryType', 'Get-PiHoleQuery', `
#ListManagement
'Get-PiHoleList', 'Search-PiHoleListDomain', 'Add-PiHoleList', 'Remove-PiHoleList', 'Update-PiHoleList', `
#FTLInformation
Expand Down
175 changes: 175 additions & 0 deletions PiHoleShell/Public/Metrics/Get-PiHoleQuery.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
function Get-PiHoleQuery {
<#
.SYNOPSIS
Get queries

.DESCRIPTION
Request individual query log entries, with optional filtering. Returns the most recent 100
queries by default; use -Length for more, and -Cursor (the Id of the oldest query already
retrieved) to page further back. Use Get-PiHoleStatsQuerySuggestions to discover valid values
for the filter parameters.

.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 From
Only return queries from this Unix timestamp onward

.PARAMETER Until
Only return queries up to this Unix timestamp

.PARAMETER Length
Number of results to return. Defaults to 100

.PARAMETER Start
Offset from the first record

.PARAMETER Cursor
Database Id of the most recent query to start from - pass the previous call's oldest returned
Id here to page further back in time

.PARAMETER Domain
Only return queries for this domain. Wildcards ("*") are supported

.PARAMETER ClientIp
Only return queries from this client IP address. Wildcards ("*") are supported

.PARAMETER ClientName
Only return queries from this client hostname. Wildcards ("*") are supported

.PARAMETER Upstream
Only return queries sent to this upstream (or "cache", "blocklist", "permitted"). Wildcards
("*") are supported

.PARAMETER Type
Only return queries of this type (e.g. "A", "AAAA")

.PARAMETER Status
Only return queries with this status (e.g. "GRAVITY", "FORWARDED")

.PARAMETER Reply
Only return queries with this reply type (e.g. "NODATA", "NXDOMAIN")

.PARAMETER Dnssec
Only return queries with this DNSSEC status (e.g. "SECURE", "INSECURE")

.PARAMETER Disk
Set to $true to load queries from the on-disk long-term database instead of the in-memory one

.PARAMETER IgnoreSsl
Set to $true to skip SSL certificate validation

.PARAMETER RawOutput
This will dump the response instead of the formatted object - includes the pagination cursor
and total/filtered record counts alongside the queries array

.EXAMPLE
Get-PiHoleQuery -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password"

.EXAMPLE
Get-PiHoleQuery -PiHoleServer "http://pihole.domain.com:8080" -Password "your-app-password" -Domain "doubleclick.net" -Length 20
#>
[CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/queries')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")]
param (
[Parameter(Mandatory = $true)]
[System.URI]$PiHoleServer,
[Parameter(Mandatory = $true)]
[string]$Password,
[Nullable[int]]$From,
[Nullable[int]]$Until,
[Nullable[int]]$Length,
[Nullable[int]]$Start,
[Nullable[int]]$Cursor,
[string]$Domain,
[string]$ClientIp,
[string]$ClientName,
[string]$Upstream,
[string]$Type,
[string]$Status,
[string]$Reply,
[string]$Dnssec,
[Nullable[bool]]$Disk,
[bool]$IgnoreSsl = $false,
[bool]$RawOutput = $false
)
try {
$Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl

$QueryParams = [System.Collections.ArrayList]@()
if ($PSBoundParameters.ContainsKey('From')) { $QueryParams.Add("from=$From") | Out-Null }
if ($PSBoundParameters.ContainsKey('Until')) { $QueryParams.Add("until=$Until") | Out-Null }
if ($PSBoundParameters.ContainsKey('Length')) { $QueryParams.Add("length=$Length") | Out-Null }
if ($PSBoundParameters.ContainsKey('Start')) { $QueryParams.Add("start=$Start") | Out-Null }
if ($PSBoundParameters.ContainsKey('Cursor')) { $QueryParams.Add("cursor=$Cursor") | Out-Null }
if ($Domain) { $QueryParams.Add("domain=$([System.Uri]::EscapeDataString($Domain))") | Out-Null }
if ($ClientIp) { $QueryParams.Add("client_ip=$([System.Uri]::EscapeDataString($ClientIp))") | Out-Null }
if ($ClientName) { $QueryParams.Add("client_name=$([System.Uri]::EscapeDataString($ClientName))") | Out-Null }
if ($Upstream) { $QueryParams.Add("upstream=$([System.Uri]::EscapeDataString($Upstream))") | Out-Null }
if ($Type) { $QueryParams.Add("type=$([System.Uri]::EscapeDataString($Type))") | Out-Null }
if ($Status) { $QueryParams.Add("status=$([System.Uri]::EscapeDataString($Status))") | Out-Null }
if ($Reply) { $QueryParams.Add("reply=$([System.Uri]::EscapeDataString($Reply))") | Out-Null }
if ($Dnssec) { $QueryParams.Add("dnssec=$([System.Uri]::EscapeDataString($Dnssec))") | Out-Null }
if ($PSBoundParameters.ContainsKey('Disk')) { $QueryParams.Add("disk=$($Disk.ToString().ToLower())") | Out-Null }

$Uri = "$($PiHoleServer.OriginalString)/api/queries"
if ($QueryParams.Count -gt 0) {
$Uri += "?" + ($QueryParams -join '&')
}

$Params = @{
Headers = @{sid = $($Sid) }
Uri = $Uri
Method = "Get"
SkipCertificateCheck = $IgnoreSsl
ContentType = "application/json"
}

$Response = Invoke-RestMethod @Params

if ($RawOutput) {
Write-Output $Response
}
else {
$ObjectFinal = foreach ($Item in $Response.queries) {
[PSCustomObject]@{
Id = $Item.id
Time = (Convert-PiHoleUnixTimeToLocalTime -UnixTime $Item.time).LocalTime
Type = $Item.type
Domain = $Item.domain
Cname = $Item.cname
Status = $Item.status
Client = [PSCustomObject]@{
Ip = $Item.client.ip
Name = $Item.client.name
}
Dnssec = $Item.dnssec
Reply = [PSCustomObject]@{
Type = $Item.reply.type
Time = $Item.reply.time
}
ListId = $Item.list_id
Upstream = $Item.upstream
Ede = [PSCustomObject]@{
Code = $Item.ede.code
Text = $Item.ede.text
}
}
}
Write-Output $ObjectFinal
}
}

catch {
Write-Error -Message $_.Exception.Message
}

finally {
if ($Sid) {
Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl
}
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ See [docs/EXAMPLES.md](docs/EXAMPLES.md) for real, captured output from every fu

| Function | Description |
|---|---|
| `Get-PiHoleQuery` | Get queries |
| `Get-PiHoleStatsDatabaseQueryType` | Get query types (long-term database) |
| `Get-PiHoleStatsDatabaseSummary` | Get database content details |
| `Get-PiHoleStatsDatabaseTopClient` | Get top clients (long-term database) |
Expand Down
Loading
Loading