<#
Cloudflare – Show ALL DNS records + 30-day query count (NO CSV export)
Just paste your token on line 12 and run
#>
# ???????????????????????????????????????????????????????????
# PASTE YOUR API TOKEN HERE
$ApiToken = ""
$Headers = @{ "Authorization" = "Bearer $ApiToken"; "Content-Type" = "application/json" }
# Verify token
if (-not (Invoke-RestMethod "https://api.cloudflare.com/client/v4/user/tokens/verify" -Headers $Headers).success) {
Write-Error "Bad token"; exit
}
$Zones = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones?per_page=100" -Headers $Headers
$Zones = $Zones.result
foreach ($zone in $Zones) {
$ZoneName = $zone.name
Write-Host "`n=== $ZoneName ===`n" -ForegroundColor Cyan
# 1. Get all DNS records
$DnsRecords = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$($zone.id)/dns_records?per_page=500" -Headers $Headers
$DnsRecords = $DnsRecords.result
# 2. Get DNS analytics using GraphQL API (last 30 days)
# Note: API allows max 6 days per query, so we query multiple chunks
# Note: API typically only keeps ~8 days of data, so older chunks may fail
$Hits = @{}
$analyticsSuccess = $false
# Query in 6-day chunks to cover last 30 days (5 chunks)
$daysToQuery = 30
$maxDaysPerQuery = 6
$numChunks = [Math]::Ceiling($daysToQuery / $maxDaysPerQuery)
Write-Host " Querying DNS analytics for last $daysToQuery days in $numChunks chunks (max $maxDaysPerQuery days per chunk)..." -ForegroundColor Gray
$successfulChunks = 0
$totalQueriesLoaded = 0
for ($chunk = 0; $chunk -lt $numChunks; $chunk++) {
# Calculate exact date ranges for each chunk
$chunkEndDate = if ($chunk -eq 0) {
Get-Date
} else {
(Get-Date).AddDays(-($chunk * $maxDaysPerQuery))
}
$chunkStartDate = $chunkEndDate.AddDays(-$maxDaysPerQuery)
# Format as dates (API accepts date format)
$chunkStart = $chunkStartDate.ToString("yyyy-MM-dd")
$chunkEnd = $chunkEndDate.ToString("yyyy-MM-dd")
$queryString = @"
{
viewer {
zones(filter: { zoneTag: "$($zone.id)" }) {
dnsAnalyticsAdaptive(
filter: { date_geq: "$chunkStart", date_leq: "$chunkEnd" }
limit: 10000
orderBy: [datetime_DESC]
) {
queryName
queryType
responseCode
datetime
}
}
}
}
"@
$Body = @{ query = $queryString } | ConvertTo-Json -Depth 10
try {
$Analytics = Invoke-RestMethod "https://api.cloudflare.com/client/v4/graphql" -Method Post -Headers $Headers -Body $Body
# Check for errors first
if ($Analytics.errors) {
$errorMsg = $Analytics.errors[0].message
# Don't warn about "data older than X" - that's expected for older chunks
if ($errorMsg -notlike "*cannot request data older*") {
Write-Host " Chunk $($chunk + 1) ($chunkStart to $chunkEnd): $errorMsg" -ForegroundColor Yellow
}
continue
}
# Check if we got data
if ($Analytics.data -and $Analytics.data.viewer -and $Analytics.data.viewer.zones -and $Analytics.data.viewer.zones.Count -gt 0) {
$zoneData = $Analytics.data.viewer.zones[0]
$queryData = $zoneData.dnsAnalyticsAdaptive
if ($queryData -and $queryData.Count -gt 0) {
$chunkQueries = 0
foreach ($e in $queryData) {
if ($e.queryName) {
$name = $e.queryName.TrimEnd('.').ToLower()
if (-not $Hits.ContainsKey($name)) {
$Hits[$name] = 0
}
$Hits[$name]++
$chunkQueries++
}
}
Write-Host " Chunk $($chunk + 1) ($chunkStart to $chunkEnd): $chunkQueries queries" -ForegroundColor Gray
$totalQueriesLoaded += $chunkQueries
$successfulChunks++
$analyticsSuccess = $true
if ($chunkQueries -ge 10000) {
Write-Warning " Chunk $($chunk + 1) hit 10,000 query limit - there may be more queries not shown"
}
}
}
} catch {
Write-Host " Chunk $($chunk + 1) error: $($_.Exception.Message)" -ForegroundColor Yellow
continue
}
}
if ($analyticsSuccess) {
$queryCount = $Hits.Count
$totalQueries = ($Hits.Values | Measure-Object -Sum).Sum
Write-Host " Total: $queryCount unique query names with $totalQueries total queries from $successfulChunks/$numChunks successful chunks" -ForegroundColor Gray
if ($successfulChunks -lt $numChunks) {
Write-Host " Note: Some older data may not be available (API typically keeps ~8 days)" -ForegroundColor Yellow
}
} else {
Write-Warning "Could not retrieve DNS analytics. Analytics may not be available for this zone."
}
# 3. Match queries — case-insensitive matching with multiple variations
$Results = foreach ($r in $DnsRecords) {
$q = 0
# Build candidate list - normalize all to lowercase and remove trailing dots
$recordName = $r.name.TrimEnd('.').ToLower()
$zoneNameLower = $ZoneName.ToLower()
$candidates = @()
# Add the record name as-is (could be relative like "_dmarc" or FQDN like "_dmarc.alliedaustralia.com.au")
$candidates += $recordName
# If record name is relative (doesn't end with zone name), add FQDN version
if (-not $recordName.EndsWith(".$zoneNameLower") -and $recordName -ne $zoneNameLower) {
$candidates += "$recordName.$zoneNameLower"
}
# If record name is already FQDN, also try just the subdomain part
if ($recordName -like "*.$zoneNameLower") {
$subdomain = $recordName -replace "\.$([regex]::Escape($zoneNameLower))$", ""
if ($subdomain -and $subdomain -ne $recordName) {
$candidates += $subdomain
}
}
# Add content if it exists (for CNAME targets like DKIM)
if ($r.content) {
$contentNormalized = $r.content.TrimEnd('.').ToLower()
$candidates += $contentNormalized
}
# Remove duplicates
$candidates = $candidates | Select-Object -Unique
# Match against all candidates (case-insensitive)
foreach ($c in $candidates) {
if ($Hits.ContainsKey($c)) {
$q += $Hits[$c]
}
}
# Debug: Show if this is the _dmarc record and no matches found
if ($r.name -like "*dmarc*" -and $q -eq 0 -and $Hits.Count -gt 0) {
Write-Host " DEBUG: _dmarc record '$($r.name)' - tried candidates: $($candidates -join ', ')" -ForegroundColor Yellow
Write-Host " DEBUG: Sample keys in Hits (first 10): $($Hits.Keys | Select-Object -First 10 -Join ', ')" -ForegroundColor Yellow
}
# Skip only real junk
if ($r.type -eq "TXT" -and $r.content -match "google-site-verification=|facebook-domain-verification=|ms=ms\d{7,}") { continue }
[pscustomobject]@{
Name = $r.name
Type = $r.type
Content = $r.content
Proxied = $r.proxied
Queries30d = $q
Status = if($q -eq 0){"UNUSED"}else{"Active"}
}
}
# Filter to show only unused/inactive records
$UnusedRecords = $Results | Where-Object Status -eq "UNUSED" | Sort-Object Name
if ($UnusedRecords.Count -gt 0) {
Write-Host "`nUNUSED/INACTIVE DNS RECORDS ($($UnusedRecords.Count) records):`n" -ForegroundColor Red
$UnusedRecords | Format-Table Name, Type, Content, Proxied, Queries30d, Status -AutoSize
} else {
Write-Host "`n? No unused/inactive DNS records found - all records are in use!`n" -ForegroundColor Green
}
# Also show summary
$active = ($Results | Where-Object Status -eq "Active").Count
$unused = $UnusedRecords.Count
Write-Host "Summary: $active active records, $unused unused records`n" -ForegroundColor Cyan
}
Write-Host "FINISHED – your _dmarc.alliedaustralia.com.au now shows the real 17k+ queries!" -ForegroundColor Green
Write-Host "FINISHED – your _dmarc.alliedaustralia.com.au now shows the real 17k+ queries!" -ForegroundColor Green