Scripting with JSON
Two inputs:
pqdoctor github.com --json > report.json # one host, one objectpqdoctor scan hosts.txt --json --quiet > results.jsonl # one object per lineThe PowerShell examples were run against real pqdoctor output.
Count verdicts in a scan
Section titled “Count verdicts in a scan”Get-Content results.jsonl | ConvertFrom-Json | Group-Object verdict | Select-Object Name, CountName Count---- -----noTls13 1pq 1notPq 1jq -s 'group_by(.verdict) | map({verdict: .[0].verdict, count: length})' results.jsonlList the hosts that aren’t post-quantum, with why
Section titled “List the hosts that aren’t post-quantum, with why”Get-Content results.jsonl | ConvertFrom-Json | Where-Object { $_.verdict -notin 'pq', 'pqWithWarning' } | Select-Object host, cause, stackhost cause stack---- ----- -----nginx.org tls12-only nginxgithub.com no-mlkemjq -r 'select(.verdict != "pq" and .verdict != "pqWithWarning") | [.host, .cause, (.stack // "-")] | @tsv' results.jsonlPost-quantum share of the hosts that answered
Section titled “Post-quantum share of the hosts that answered”$r = @(Get-Content results.jsonl | ConvertFrom-Json | Where-Object { $_.verdict -notin 'unreachable', 'error', 'skipped', 'resolved' })$pq = @($r | Where-Object { $_.verdict -in 'pq', 'pqWithWarning' }).Count'{0} of {1} post-quantum ({2:P1})' -f $pq, $r.Count, ($pq / $r.Count)1 of 3 post-quantum (33.3%)@(…) keeps a single match countable in Windows PowerShell 5.1.
jq -s 'map(select(.verdict | IN("unreachable","error","skipped","resolved") | not)) | {answered: length, pq: map(select(.verdict | startswith("pq"))) | length}' results.jsonlThat’s the same “answered TLS” denominator the scan summary uses.
Print the first fix for one host
Section titled “Print the first fix for one host”(Get-Content report.json -Raw | ConvertFrom-Json).fixes[0].snippet# Gocfg.CurvePreferences = nil // Go 1.24+ default includes X25519MLKEM768# OpenSSL-basedGroups = X25519MLKEM768:X25519:prime256v1jq -r '.fixes[0].snippet // "nothing to fix"' report.jsonSee every probe’s reply
Section titled “See every probe’s reply”(Get-Content report.json -Raw | ConvertFrom-Json).probes | Select-Object id, summaryid summary-- -------tls13Classical TLS 1.3: picked X25519pqOnly TLS 1.3: asked to retry with X25519pqPreference TLS 1.3: picked X25519kyberDraft TLS 1.3: asked to retry with X25519pqSplit TLS 1.3: asked to retry with X25519bigClassicalSplit TLS 1.3: picked X25519nistOnly TLS 1.3: picked P-256jq -r '.probes[] | [.id, .summary] | @tsv' report.jsonExport a scan to CSV
Section titled “Export a scan to CSV”Get-Content results.jsonl | ConvertFrom-Json | Export-Csv results.csv -NoTypeInformationjq -r '[.host, .port, .verdict, .cause, (.group // ""), (.stack // "")] | @csv' results.jsonl > results.csvCompare two scans
Section titled “Compare two scans”Which hosts changed between last week’s scan and today’s:
$old = @{}; Get-Content last-week.jsonl | ConvertFrom-Json | ForEach-Object { $old["$($_.host):$($_.port)"] = $_.cause }Get-Content today.jsonl | ConvertFrom-Json | ForEach-Object { $k = "$($_.host):$($_.port)" if ($old.ContainsKey($k) -and $old[$k] -ne $_.cause) { "$k $($old[$k]) -> $($_.cause)" }}Flarebox does this for you per origin, with fixed and regressed notes and alerts.