Skip to content
ConsoleMonster

Live runs

Run a script block for many items in parallel with a live view, re-run a command with Watch-Console, and watch Pester tests as they run.

Three commands turn long-running work into a live view on an interactive console and into plain lines everywhere else: ForEach-ConsoleItem (Invoke-ConsoleRun) for many items, Watch-Console for a command you want to see change, and Invoke-ConsolePester for tests. Each writes ordinary PowerShell output when it ends, so it fits into a script like the commands it replaces.

Forty servers, eight at a time

ForEach-ConsoleItem runs a script block for every item, here in parallel with a time limit. Three servers fail and one hangs; the run keeps going, and the failures are listed when it ends.

ForEach-ConsoleItem -Parallel 100×1324-bitFull
PowerShellWebsite/examples/demos/parallel.ps1
# Forty servers patched eight at a time. Three fail and one hangs past the four-second limit;
# the view lists the failures and -ErrorVariable keeps them for the rest of the script.
$servers = 1..40 | ForEach-Object { 'web{0:d2}' -f $_ }
$results = $servers | ForEach-ConsoleItem -Parallel -ThrottleLimit 8 -TimeoutSeconds 4 -Title 'Patching the web farm' -ErrorVariable failed -ErrorAction SilentlyContinue {
    $number = [int] $_.Substring(3)
    # … 3 more lines in the full program
    "$_ patched"
}
Full program
# Forty servers patched eight at a time. Three fail and one hangs past the four-second limit;
# the view lists the failures and -ErrorVariable keeps them for the rest of the script.
$servers = 1..40 | ForEach-Object { 'web{0:d2}' -f $_ }
$results = $servers | ForEach-ConsoleItem -Parallel -ThrottleLimit 8 -TimeoutSeconds 4 -Title 'Patching the web farm' -ErrorVariable failed -ErrorAction SilentlyContinue {
    $number = [int] $_.Substring(3)
    Start-Sleep -Milliseconds (500 + ($number * 137) % 900)
    if ($number % 13 -eq 0) { throw "$_ did not come back after the reboot" }
    if ($number -eq 22) { Start-Sleep -Seconds 10 }
    "$_ patched"
}

Use it in your script

PowerShell 5.1 and 7
Install-Module ConsoleMonster -Scope CurrentUser

Reference: Invoke-ConsoleRun, ConsoleRun (C#)

What the run view shows

  • A summary bar: done of total, the percentage, the time left (estimated from the time so far), and counters for passed, failed, skipped and running items. Counters keep their order, and zeros are dimmed rather than hidden, so the line does not jump.
  • A cell map: one symbol per item in its status colour, so a run of hundreds of items still fits on a few rows.
  • The items running now, with how long each has been running.
  • The failures, listed with their messages when the run ends. An item that runs past -TimeoutSeconds is stopped and counted as failed.

Without -Parallel the items run one at a time in the caller's scope, as with ForEach-Object. With it, up to -ThrottleLimit items run at a time in their own runspaces on Windows PowerShell 5.1 and PowerShell 7; pass the caller's variables with $using:name. In redirected output, CI and jobs, the view becomes one line per finished item and a summary.

The summary bar and the cell map are widgets of their own, for your own views:

$states = @('Pass') * 28 + @('Fail') * 2 + @('Warn') + @('Running') * 4
New-ConsoleRows -Gap 1 {
    New-ConsoleSummaryBar -Done 31 -Total 40 -Elapsed 42s -Counter ([ordered] @{ passed = 28; failed = 2; warnings = 1 })
    New-ConsoleCellMap -Status $states -Count 40
} | Write-ConsoleWidget

In C#, ConsoleRun.ForEachAsync runs the same view around any async work, and SummaryBar and CellMap are in ConsoleMonster.Widgets.

A command as a live view

Watch-Console runs a script block every few seconds and shows its latest output; -Differences marks what changed since the last run.

Watch-Console -Differences 96×924-bitFull
PowerShellWebsite/examples/demos/watch.ps1
# The mail queues every 1.5 seconds; -Differences marks the cells that changed since the last run.
$script:tick = 0
Watch-Console -Title 'Mail queues' -Interval 1.5 -Count 6 -Differences {
    $script:tick++
    $servers = 'MX01', 'MX02', 'MX03', 'MX04'
    for ($i = 0; $i -lt $servers.Count; $i++) {
        $load = ($script:tick * 7 + $i * 3) % 11
        [pscustomobject] @{
            Server    = "$($servers[$i]).evotec.xyz"
            Queued    = $load * 4
            Deferred  = if ($i -eq 2) { 12 + $script:tick * 5 } else { $load % 3 }
            Delivered = 1200 + $script:tick * 85 + $load * 9
            Status    = if ($i -eq 2 -and $script:tick -gt 2) { 'Warn' } else { 'Pass' }
        }
    }
}

Use it in your script

PowerShell 5.1 and 7
Install-Module ConsoleMonster -Scope CurrentUser

Reference: Watch-Console

Watch-Console runs the script block every -Interval seconds and replaces the view with its latest output, shaped as Format-ConsoleTable shapes it. -Differences marks the cells that changed since the previous run, a run that fails shows its error and the watch goes on, and -Count ends it after that many runs. Ctrl+C ends it and leaves the last frame in the scrollback. Piped on, or without an interactive console, every run is printed instead.

Pester tests as they run

Invoke-ConsolePester takes Pester's parameters and shows the current test, a cell per test and each failure with its expected and actual values.

Invoke-ConsolePester 96×2224-bitFull
PowerShellWebsite/examples/demos/pester.ps1
# Pester 5 tests with a live view: the current test, counts so far and the failures as they happen.
Invoke-ConsolePester -Path ./fixtures -Title 'Mail domain checks'

Use it in your tests

PowerShell 5.1 and 7, Pester 5 or 6
Install-Module ConsoleMonster -Scope CurrentUser
Install-Module Pester -Scope CurrentUser -SkipPublisherCheck

Reference: Invoke-ConsolePester

Invoke-ConsolePester takes Pester's own parameters (-Path, -TagFilter, -Configuration...) and shows the run as it happens: a file list with progress, one cell per test, the test running now and each failure with its expected and actual values. It returns Pester's result object with -PassThru. When the run ends, a table of results by file, the failures and the totals are written; in redirected output and CI they are plain text.

Edit this page on GitHub