Skip to content
ConsoleMonster

DNS and mail query report

Combine a query summary, wrapped DNS records, a delegation tree, mail checks and resolver latency.

Demos Reports Showcase

Use it when a diagnostic report needs records, hierarchy, checks and timings together.

  • Static
  • 144×48
  • PowerShell 7.4+
  • .NET 8.0+
DNS and mail query report 144×4824-bitFull

How it's built

The whole program in one file, with its sample data at the top. It drew the recording above; both languages draw the same cells.

PowerShellquery.ps1View on GitHub
# DNS and mail security: summary, wrapped records, delegation and resolver latency.
[CmdletBinding()]
param(
    [switch] $StaticSnapshot,
    [ValidateRange(0, 0)] [int] $Step = 0
)
Import-Module ConsoleMonster

# Data: reserved synthetic names and documentation addresses. No DNS requests run.
$domain = 'example.test'
$records = @(
    [pscustomobject] @{ Name = $domain; Type = 'A'; TTL = 300; Data = '192.0.2.12' }
    [pscustomobject] @{ Name = $domain; Type = 'A'; TTL = 300; Data = '198.51.100.94' }
    [pscustomobject] @{ Name = $domain; Type = 'AAAA'; TTL = 300; Data = '2001:db8::12' }
    [pscustomobject] @{ Name = $domain; Type = 'MX'; TTL = 3600; Data = '10 mail.example.test' }
    [pscustomobject] @{ Name = $domain; Type = 'NS'; TTL = 86400; Data = 'ns1.example.test' }
    [pscustomobject] @{ Name = $domain; Type = 'NS'; TTL = 86400; Data = 'ns2.example.test' }
    [pscustomobject] @{ Name = $domain; Type = 'TXT'; TTL = 3600; Data = 'v=spf1 include:spf.example.test include:relay.example.test ip4:203.0.113.9 -all' }
    [pscustomobject] @{ Name = "_dmarc.$domain"; Type = 'TXT'; TTL = 3600; Data = 'v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; fo=1' }
    [pscustomobject] @{ Name = $domain; Type = 'CAA'; TTL = 3600; Data = '0 issue "ca.example.test"' }
    [pscustomobject] @{ Name = $domain; Type = 'SOA'; TTL = 3600; Data = 'ns1.example.test. hostmaster.example.test. 2026102401 10000 2400 604800 3600' }
)
$checks = @(
    ,@('SPF record present and valid', 'Pass', '9 lookups')
    ,@('DMARC policy enforced', 'Pass', 'p=reject')
    ,@('DKIM selector1 / selector2', 'Pass', '2048-bit')
    ,@('MTA-STS policy', 'Warn', 'not published')
    ,@('TLS-RPT reporting', 'Warn', 'not published')
    ,@('DNSSEC chain of trust', 'Fail', 'zone not signed')
    ,@('BIMI record', 'Skip', 'optional')
)
$resolvers = [ordered] @{ 'Edge west' = 12; 'Edge east' = 18; 'Cache north' = 24; 'Cache south' = 31; 'Local resolver' = 47 }

# Layout: keep the dense report readable through wrapping and two balanced panels.
$report = New-ConsoleRows -Gap 1 {
    New-ConsoleRule '[bold]DNS query results[/] [muted]· example.test[/]'
    New-ConsolePadding -Padding 1, 0 {
        New-ConsoleKeyValue {
            New-ConsoleKeyValueItem Domain '[bold]example.test[/]' -Markup
            New-ConsoleKeyValueItem Resolver 'Edge west DNS over HTTPS [muted](192.0.2.53)[/]' -Markup
            New-ConsoleKeyValueItem 'Query time' '38 ms [muted]for 7 record types[/]' -Markup
            New-ConsoleKeyValueItem Records '10'
            New-ConsoleKeyValueItem DNSSEC 'not signed' -Status Warn
        }
    }
    $records | New-ConsoleTable -Property Name, Type, TTL, Data -Expand -NoHeaderSeparator
    New-ConsoleColumns {
        New-ConsolePanel -Title Delegation {
            New-ConsoleTree '[bold].[/] [muted]root[/]' {
                New-ConsoleTreeNode '[bold]test.[/]' -Detail 'reserved namespace' {
                    New-ConsoleTreeNode 'ns.test' -Detail '192.0.2.42'
                    New-ConsoleTreeNode '[bold]example.test.[/]' -Detail '2 name servers' {
                        New-ConsoleTreeNode 'ns1.example.test' -Status Pass -Detail '198.51.100.103 · 12 ms'
                        New-ConsoleTreeNode 'ns2.example.test' -Status Pass -Detail '203.0.113.149 · 14 ms'
                        New-ConsoleTreeNode mail -Detail 'MX target' {
                            New-ConsoleTreeNode 'mail.example.test' -Status Pass -Detail '192.0.2.17'
                        }
                    }
                }
            }
        }
        New-ConsolePanel -Title 'Mail security' -Caption $domain {
            New-ConsoleRows -Gap 0 {
                New-ConsoleStatusList {
                    foreach ($check in $checks) { New-ConsoleStatusItem $check[0] -Status $check[1] -Detail $check[2] }
                }
                New-ConsoleRule
                New-ConsoleBarChart -Name latency -Unit ' ms' -Data $resolvers
            }
        }
    }
}

# Behaviour: a print-once report works interactively and in redirected snapshots.
$report | Write-ConsoleWidget
C#Program.csView on GitHub
// DNS and mail security: summary, wrapped records, delegation and resolver latency.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: reserved synthetic names and documentation addresses. No DNS requests run.
string domain = "example.test";
var records = new[] {
    new { Name = domain, Type = "A", TTL = 300, Data = "192.0.2.12" },
    new { Name = domain, Type = "A", TTL = 300, Data = "198.51.100.94" },
    new { Name = domain, Type = "AAAA", TTL = 300, Data = "2001:db8::12" },
    new { Name = domain, Type = "MX", TTL = 3600, Data = "10 mail.example.test" },
    new { Name = domain, Type = "NS", TTL = 86400, Data = "ns1.example.test" },
    new { Name = domain, Type = "NS", TTL = 86400, Data = "ns2.example.test" },
    new { Name = domain, Type = "TXT", TTL = 3600, Data = "v=spf1 include:spf.example.test include:relay.example.test ip4:203.0.113.9 -all" },
    new { Name = "_dmarc." + domain, Type = "TXT", TTL = 3600, Data = "v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; fo=1" },
    new { Name = domain, Type = "CAA", TTL = 3600, Data = "0 issue \"ca.example.test\"" },
    new { Name = domain, Type = "SOA", TTL = 3600, Data = "ns1.example.test. hostmaster.example.test. 2026102401 10000 2400 604800 3600" }
};
var checks = new[] {
    ("SPF record present and valid", StatusKind.Pass, "9 lookups"),
    ("DMARC policy enforced", StatusKind.Pass, "p=reject"),
    ("DKIM selector1 / selector2", StatusKind.Pass, "2048-bit"),
    ("MTA-STS policy", StatusKind.Warn, "not published"),
    ("TLS-RPT reporting", StatusKind.Warn, "not published"),
    ("DNSSEC chain of trust", StatusKind.Fail, "zone not signed"),
    ("BIMI record", StatusKind.Skip, "optional")
};
var resolvers = new[] { ("Edge west", 12), ("Edge east", 18), ("Cache north", 24), ("Cache south", 31), ("Local resolver", 47) };
int stepAt = Array.IndexOf(args, "--step");
int step = stepAt < 0 ? 0 : int.Parse(args[stepAt + 1], System.Globalization.CultureInfo.InvariantCulture);
if (step != 0) throw new ArgumentOutOfRangeException(nameof(step));

// Layout: keep the dense report readable through wrapping and two balanced panels.
KeyValueList summary = new KeyValueList()
    .Add("Domain", "[bold]example.test[/]")
    .Add("Resolver", "Edge west DNS over HTTPS [muted](192.0.2.53)[/]")
    .Add("Query time", "38 ms [muted]for 7 record types[/]")
    .Add("Records", "10")
    .Add("DNSSEC", "not signed", StatusKind.Warn);
Table table = Table.For(records).WithExpand().WithHeaderSeparator(false)
    .Column(r => r.Name).Column(r => r.Type)
    .Column(r => r.TTL, c => c.WithHeader("TTL").WithAlignment(HorizontalAlignment.Right))
    .Column(r => r.Data, c => c.WithFlexible());
Tree delegation = new Tree("[bold].[/] [muted]root[/]");
TreeNode test = delegation.Add("[bold]test.[/]", null, "reserved namespace");
test.Add("ns.test", null, "192.0.2.42");
TreeNode zone = test.Add("[bold]example.test.[/]", null, "2 name servers");
zone.Add("ns1.example.test", StatusKind.Pass, "198.51.100.103 · 12 ms");
zone.Add("ns2.example.test", StatusKind.Pass, "203.0.113.149 · 14 ms");
zone.Add("mail", null, "MX target").Add("mail.example.test", StatusKind.Pass, "192.0.2.17");
StatusList mail = new StatusList();
foreach (var (label, status, detail) in checks) mail.Add(label, status, detail);
BarChart latency = new BarChart("latency") { Unit = " ms" };
foreach (var (label, value) in resolvers) latency.Add(label, value);
IWidget report = new Rows(
    new Rule("[bold]DNS query results[/] [muted]· example.test[/]"),
    new Padding(summary, 1, 0, 1, 0), table,
    new Columns(new Panel(delegation, "Delegation"),
        new Panel(new Rows(mail, new Rule(), latency) { Gap = 0 }, "Mail security") { Caption = domain })) { Gap = 1 };

// Behaviour: a print-once report works interactively and in redirected snapshots.
Terminal.Write(report);

Run it yourself

Three steps in PowerShell, four in C#. Every command copies with one click.

  1. Install the module

    Once. Windows PowerShell 5.1 or PowerShell 7 on Windows, macOS or Linux.

    Install-Module ConsoleMonster -Scope CurrentUser
  2. Save the program

    Download it, or copy it from above into query.ps1.

  3. Run it

    From the folder you saved it in.

    ./query.ps1
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n Query; cd Query
  2. Add ConsoleMonster

    The only package the program needs.

    dotnet add package ConsoleMonster
  3. Replace Program.cs

    Download it, or copy the program from above.

  4. Run it

    dotnet run

Or skip the copying. Every demo ships with the module and the project template.

Show-ConsoleDemo query
Get-ConsoleDemo query -Path ./query.ps1
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo query -n Query

Make it yours

Start with these, then read the rest of the file.

  1. Replace the inline records with your query results.

  2. Map your diagnostic checks to status and detail values.

  3. Compare resolver latency with your own measurements.

More reports

All demos