Skip to content
ConsoleMonster

Windows Update compliance

See the whole fleet's patch state in one bar, then the five hosts that need you and the two that are overdue.

Demos Reports

Use it when the monthly patch report has to answer how many servers are compliant and which ones are not.

  • Static
  • 100×24
  • PowerShell 7.4+
  • .NET 8.0+
Windows Update compliance 100×2424-bitFull

How it's built

The whole program in one file, with its sample data at the top. It drew the recording above; all three styles produce the same frame bytes.

PowerShellupdate-compliance.ps1View on GitHub
# Check monthly patch compliance across a server fleet and call out overdue hosts.
Import-Module -Name ConsoleMonster

# Data: fictional servers and a fixed report date; no update agent or WSUS server is queried.
$servers = @(
    [pscustomobject] @{ Host = 'dc-01'; Ring = 'Pilot'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-15'; Days = 5; Status = 'Pass' }
    [pscustomobject] @{ Host = 'dc-02'; Ring = 'Broad'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-16'; Days = 4; Status = 'Pass' }
    [pscustomobject] @{ Host = 'fs-01'; Ring = 'Broad'; State = 'Pending reboot'; Missing = 0
        LastPatched = '2026-10-14'; Days = 6; Status = 'Warn' }
    [pscustomobject] @{ Host = 'fs-02'; Ring = 'Broad'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-16'; Days = 4; Status = 'Pass' }
    [pscustomobject] @{ Host = 'sql-01'; Ring = 'Broad'; State = 'Missing updates'; Missing = 3
        LastPatched = '2026-09-12'; Days = 38; Status = 'Fail' }
    [pscustomobject] @{ Host = 'sql-02'; Ring = 'Broad'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-17'; Days = 3; Status = 'Pass' }
    [pscustomobject] @{ Host = 'web-01'; Ring = 'Pilot'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-15'; Days = 5; Status = 'Pass' }
    [pscustomobject] @{ Host = 'web-02'; Ring = 'Broad'; State = 'Pending reboot'; Missing = 0
        LastPatched = '2026-10-15'; Days = 5; Status = 'Warn' }
    [pscustomobject] @{ Host = 'app-01'; Ring = 'Broad'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-16'; Days = 4; Status = 'Pass' }
    [pscustomobject] @{ Host = 'app-02'; Ring = 'Broad'; State = 'Failed'; Missing = 5
        LastPatched = '2026-08-19'; Days = 62; Status = 'Fail' }
    [pscustomobject] @{ Host = 'print-01'; Ring = 'Broad'; State = 'Missing updates'; Missing = 2
        LastPatched = '2026-10-02'; Days = 18; Status = 'Warn' }
    [pscustomobject] @{ Host = 'hv-01'; Ring = 'Pilot'; State = 'Compliant'; Missing = 0
        LastPatched = '2026-10-15'; Days = 5; Status = 'Pass' }
)
$states = @(
    [pscustomobject] @{ Label = 'Compliant'; Role = 'Pass' }
    [pscustomobject] @{ Label = 'Pending reboot'; Role = 'Info' }
    [pscustomobject] @{ Label = 'Missing updates'; Role = 'Warn' }
    [pscustomobject] @{ Label = 'Failed'; Role = 'Fail' }
)

# Behaviour: count each state, keep the hosts that need work and flag anything unpatched for 30+ days.
$byState = foreach ($state in $states) {
    $count = @($servers | Where-Object -Property State -EQ -Value $state.Label).Count
    [pscustomobject] @{ Label = $state.Label; Value = $count; Role = $state.Role }
}
$attention = @($servers | Where-Object -Property State -NE -Value 'Compliant')
$overdue = @($servers | Where-Object -Property Days -GT -Value 30)

# Layout: the fleet split first, then the hosts to fix, then the overdue call-out.
$report = New-ConsoleRows -Gap 1 -Content {
    New-ConsoleRule -Title '[bold]Windows Update compliance[/] [muted]· 2026-10 cumulative · as of 2026-10-20[/]'
    New-ConsolePanel -Title "Fleet · $($servers.Count) servers" -Content {
        $byState | New-ConsoleChart -Type Stacked
    }
    New-ConsoleTable -InputObject $attention -Property Host, Ring, State, Missing, LastPatched,
        Days, Status -Expand
    New-ConsolePanel -Title 'Overdue · unpatched for more than 30 days' -Content {
        New-ConsoleStatusList -Content {
            foreach ($server in $overdue) {
                [pscustomobject] @{ Label = $server.Host; Status = 'Fail'
                    Detail = "$($server.Days) days · $($server.State.ToLowerInvariant()) · $($server.Missing) missing" }
            }
        }
    }
}

# Behaviour: print once; schedule the overdue hosts into the next maintenance window.
$report | Show-Console
C#Program.csView on GitHub
// Check monthly patch compliance across a server fleet and call out overdue hosts.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: fictional servers and a fixed report date; no update agent or WSUS server is queried.
var servers = new Server[]
{
    new("dc-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass"),
    new("dc-02", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("fs-01", "Broad", "Pending reboot", 0, "2026-10-14", 6, "Warn"),
    new("fs-02", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("sql-01", "Broad", "Missing updates", 3, "2026-09-12", 38, "Fail"),
    new("sql-02", "Broad", "Compliant", 0, "2026-10-17", 3, "Pass"),
    new("web-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass"),
    new("web-02", "Broad", "Pending reboot", 0, "2026-10-15", 5, "Warn"),
    new("app-01", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("app-02", "Broad", "Failed", 5, "2026-08-19", 62, "Fail"),
    new("print-01", "Broad", "Missing updates", 2, "2026-10-02", 18, "Warn"),
    new("hv-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass")
};
var states = new State[]
{
    new("Compliant", ThemeToken.Pass),
    new("Pending reboot", ThemeToken.Info),
    new("Missing updates", ThemeToken.Warn),
    new("Failed", ThemeToken.Fail)
};

// Behaviour: count each state, keep the hosts that need work and flag anything unpatched for 30+ days.
var byState = new StackedBar();
foreach (var state in states)
    byState.Add(state.Label, servers.Count(server => server.State == state.Label), state.Role);
var attention = servers.Where(server => server.State != "Compliant").ToArray();
var overdue = new StatusList();
foreach (var server in servers.Where(server => server.Days > 30))
    overdue.Add(server.Host, StatusKind.Fail,
        $"{server.Days} days · {server.State.ToLowerInvariant()} · {server.Missing} missing");

// Layout: the fleet split first, then the hosts to fix, then the overdue call-out.
var table = Table.For(attention)
    .Column(row => row.Host)
    .Column(row => row.Ring)
    .Column(row => row.State)
    .Column(row => row.Missing)
    .Column(row => row.LastPatched)
    .Column(row => row.Days)
    .Column(row => row.Status, column => column.Presenter = CellPresenters.StatusWord);
table.Expand = true;

var report = new Rows(
    new Rule("[bold]Windows Update compliance[/] [muted]· 2026-10 cumulative · as of 2026-10-20[/]"),
    new Panel(byState, $"Fleet · {servers.Length} servers"),
    table,
    new Panel(overdue, "Overdue · unpatched for more than 30 days"))
{
    Gap = 1
};

// Behaviour: print once; schedule the overdue hosts into the next maintenance window.
Terminal.Write(report);

record Server(string Host, string Ring, string State, int Missing, string LastPatched, int Days,
    string Status);

record State(string Label, ThemeToken Role);
C# fluentProgram.Fluent.csView on GitHub
// Check monthly patch compliance across a server fleet and call out overdue hosts.
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Presenters;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;

// Data: fictional servers and a fixed report date; no update agent or WSUS server is queried.
var servers = new Server[]
{
    new("dc-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass"),
    new("dc-02", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("fs-01", "Broad", "Pending reboot", 0, "2026-10-14", 6, "Warn"),
    new("fs-02", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("sql-01", "Broad", "Missing updates", 3, "2026-09-12", 38, "Fail"),
    new("sql-02", "Broad", "Compliant", 0, "2026-10-17", 3, "Pass"),
    new("web-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass"),
    new("web-02", "Broad", "Pending reboot", 0, "2026-10-15", 5, "Warn"),
    new("app-01", "Broad", "Compliant", 0, "2026-10-16", 4, "Pass"),
    new("app-02", "Broad", "Failed", 5, "2026-08-19", 62, "Fail"),
    new("print-01", "Broad", "Missing updates", 2, "2026-10-02", 18, "Warn"),
    new("hv-01", "Pilot", "Compliant", 0, "2026-10-15", 5, "Pass")
};
var states = new State[]
{
    new("Compliant", ThemeToken.Pass),
    new("Pending reboot", ThemeToken.Info),
    new("Missing updates", ThemeToken.Warn),
    new("Failed", ThemeToken.Fail)
};

// Behaviour: count each state, keep the hosts that need work and flag anything unpatched for 30+ days.
var byState = (StackedBar)Ui.Chart().Type(ChartType.Stacked).Build();
foreach (var state in states)
    byState.Add(state.Label, servers.Count(server => server.State == state.Label), state.Role);
var attention = servers.Where(server => server.State != "Compliant").ToArray();
var overdue = Ui.StatusList();
foreach (var server in servers.Where(server => server.Days > 30))
    overdue.Add(server.Host, StatusKind.Fail,
        $"{server.Days} days · {server.State.ToLowerInvariant()} · {server.Missing} missing");

// Layout: the fleet split first, then the hosts to fix, then the overdue call-out.
var table = Ui.Table(attention)
    .Column(row => row.Host)
    .Column(row => row.Ring)
    .Column(row => row.State)
    .Column(row => row.Missing)
    .Column(row => row.LastPatched)
    .Column(row => row.Days)
    .Column(row => row.Status, column => column.Presenter(CellPresenters.StatusWord))
    .Expand();
var report = Ui.Rows().Gap(1).Content(c2 =>
{
    c2.Rule("[bold]Windows Update compliance[/] [muted]· 2026-10 cumulative · as of 2026-10-20[/]");
    c2.Panel(p => p.Content(content =>
    {
        content.Add(byState);
    }).Title($"Fleet · {servers.Length} servers"));
    c2.Add(table);
    c2.Panel(p => p.Title("Overdue · unpatched for more than 30 days").Content(c =>
    {
        c.Add(overdue);
    }));
});

// Behaviour: print once; schedule the overdue hosts into the next maintenance window.
report.Show();

record Server(string Host, string Ring, string State, int Missing, string LastPatched, int Days,
    string Status);

record State(string Label, ThemeToken Role);

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 update-compliance.ps1.

  3. Run it

    From the folder you saved it in.

    ./update-compliance.ps1
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n UpdateCompliance; cd UpdateCompliance
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run
  1. Create a project

    A plain console app; .NET 8 or later.

    dotnet new console -n UpdateCompliance; cd UpdateCompliance
  2. Save the project

    The project selects one C# style at a time and restores ConsoleMonster.

  3. Save both programs

    Replace Program.cs and save Program.Fluent.cs beside it.

  4. Run it

    dotnet run --property:DemoStyle=Fluent

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

Show-ConsoleDemo update-compliance
Export-ConsoleDemo update-compliance -Path ./update-compliance.ps1
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo update-compliance -n UpdateCompliance
dotnet new install ConsoleMonster.Templates
dotnet new consolemonster --demo update-compliance --DemoStyle Fluent -n UpdateCompliance

Make it yours

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

  1. Fill the server rows from your update tooling's export (WSUS, Configuration Manager or Intune) instead of the inline list.

  2. Change the 30-day overdue rule and the ring names to match your patch policy.

  3. Add a column for the maintenance window so the overdue list doubles as a schedule.

More reports

All demos