Certificate expiry report
Review certificate renewal deadlines, named owners and practical next actions in one concise report.
- Static
- 100×30
- 28 lines
Show-ConsoleDemo certificate-expirydotnet new consolemonster --demo certificate-expiryCombine 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.
── DNS query results · example.test ──────────────────────────────────────────────────────────────────────────────────────────────────────────── Domain example.test Resolver Edge west DNS over HTTPS (192.0.2.53) Query time 38 ms for 7 record types Records 10 DNSSEC ▴ not signed ╭─────────────────────┬──────┬────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Name │ Type │ TTL │ Data │ │ example.test │ A │ 300 │ 192.0.2.12 │ │ example.test │ A │ 300 │ 198.51.100.94 │ │ example.test │ AAAA │ 300 │ 2001:db8::12 │ │ example.test │ MX │ 3,600 │ 10 mail.example.test │ │ example.test │ NS │ 86,400 │ ns1.example.test │ │ example.test │ NS │ 86,400 │ ns2.example.test │ │ example.test │ TXT │ 3,600 │ v=spf1 include:spf.example.test include:relay.example.test ip4:203.0.113.9 -all │ │ _dmarc.example.test │ TXT │ 3,600 │ v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; fo=1 │ │ example.test │ CAA │ 3,600 │ 0 issue "ca.example.test" │ │ example.test │ SOA │ 3,600 │ ns1.example.test. hostmaster.example.test. 2026102401 10000 2400 604800 3600 │ ╰─────────────────────┴──────┴────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─ Delegation ────────────────────────────────────────────────────────╮ ╭─ Mail security ─────────────────────────────────────── example.test ─╮ │ . root │ │ ✓ SPF record present and valid 9 lookups │ │ └── test. reserved namespace │ │ ✓ DMARC policy enforced p=reject │ │ ├── ns.test 192.0.2.42 │ │ ✓ DKIM selector1 / selector2 2048-bit │ │ └── example.test. 2 name servers │ │ ▴ MTA-STS policy not published │ │ ├── ✓ ns1.example.test 198.51.100.103 · 12 ms │ │ ▴ TLS-RPT reporting not published │ │ ├── ✓ ns2.example.test 203.0.113.149 · 14 ms │ │ ✗ DNSSEC chain of trust zone not signed │ │ └── mail MX target │ │ – BIMI record optional │ │ └── ✓ mail.example.test 192.0.2.17 │ │ ──────────────────────────────────────────────────────────────────── │ │ │ │ Edge west ▆▆▆▆▆▆▆▆▆▆▆▆ 12 ms │ │ │ │ Edge east ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 18 ms │ │ │ │ Cache north ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 24 ms │ │ │ │ Cache south ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 31 ms │ │ │ │ Local resolver ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ 47 ms │ ╰─────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────────────────────────────────────────────╯
The whole program in one file, with its sample data at the top. It drew the recording above; both languages draw the same cells.
# 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
// 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);
Three steps in PowerShell, four in C#. Every command copies with one click.
Once. Windows PowerShell 5.1 or PowerShell 7 on Windows, macOS or Linux.
Install-Module ConsoleMonster -Scope CurrentUserDownload it, or copy it from above into query.ps1.
From the folder you saved it in.
./query.ps1A plain console app; .NET 8 or later.
dotnet new console -n Query; cd QueryThe only package the program needs.
dotnet add package ConsoleMonsterDownload it, or copy the program from above.
dotnet runOr skip the copying. Every demo ships with the module and the project template.
Show-ConsoleDemo queryGet-ConsoleDemo query -Path ./query.ps1dotnet new install ConsoleMonster.Templatesdotnet new consolemonster --demo query -n QueryStart with these, then read the rest of the file.
Replace the inline records with your query results.
Map your diagnostic checks to status and detail values.
Compare resolver latency with your own measurements.
Review certificate renewal deadlines, named owners and practical next actions in one concise report.
Show-ConsoleDemo certificate-expirydotnet new consolemonster --demo certificate-expiryShow storage pressure with threshold bars, a folder breakdown and a clear summary of remaining capacity.
Show-ConsoleDemo disk-usagedotnet new consolemonster --demo disk-usageFollow 332 checks across twelve targets, six workers, throughput, duration buckets and actionable findings.
Show-ConsoleDemo assessmentdotnet new consolemonster --demo assessment