ConsoleMonster draws directly only on a real local interactive console. Everywhere else the cmdlets write through the PowerShell host, line by line as Write-Host does, so jobs complete, transcripts record and redirection captures whole lines.
| Where the script runs | What ConsoleMonster writes |
|---|---|
| A real local console | Drawn directly, with colour and live dashboards. |
A real console with Start-Transcript running | Coloured text through the host UI; the transcript receives the same lines as plain text. |
| The ISE | Coloured text through the host UI. Prompts ask through the host. |
| Background jobs and remoting | Write-Host style information records (tag PSHOST) with foreground and background colours. |
| Redirected output, thread jobs and other hosts | One plain record per line, so files, transcripts and redirected streams get whole lines of plain text. |
NO_COLOR, CONSOLEMONSTER_COLOR=none, plain mode | No colour on any route. |
Colours on the host routes map to the 16 console colours by role: pass green, fail red, warn yellow, muted dark grey and accent cyan; the light theme uses the darker variants. A live dashboard in those hosts narrates as append-only lines.
Background jobs
A job has no console of its own, so ConsoleMonster writes its output as Write-Host style information records, tagged PSHOST, each piece with its foreground and background colour. Receive-Job replays those records in the console that receives them, so a table drawn inside the job arrives whole and in colour. The job starts in a new session, so it imports ConsoleMonster itself. The objects the job returns stay separate from the drawn table: $results = Receive-Job ... still shows the table and puts only the objects in $results.
From a background job ╭───────┬────────╮ │ Check │ Status │ ├───────┼────────┤ │ SPF │ ✓ Pass │ │ DMARC │ ✗ Fail │ ╰───────┴────────╯
From a background job ╭───────┬────────╮ │ Check │ Status │ ├───────┼────────┤ │ SPF │ ✓ Pass │ │ DMARC │ ✗ Fail │ ╰───────┴────────╯
$module = (Get-Module ConsoleMonster).Path
Start-Job -ArgumentList $module {
param($Module)
Import-Module $Module
[pscustomobject] @{ Check = 'SPF'
Status = 'Pass' }, [pscustomobject] @{ Check = 'DMARC'; Status = 'Fail' } |
Show-ConsoleTable -Title 'From a background job' -StatusProperty Status
} | Receive-Job -Wait -AutoRemoveJobFull program
Import-Module ConsoleMonster
$module = (Get-Module ConsoleMonster).Path
Start-Job -ArgumentList $module {
param($Module)
Import-Module $Module
[pscustomobject] @{ Check = 'SPF'
Status = 'Pass' }, [pscustomobject] @{ Check = 'DMARC'; Status = 'Fail' } |
Show-ConsoleTable -Title 'From a background job' -StatusProperty Status
} | Receive-Job -Wait -AutoRemoveJobCI
When a CI system is detected (CI, GitHub Actions, Azure Pipelines, Jenkins, GitLab or TeamCity) or output is redirected, live sessions switch to stream mode: one line per meaningful change, a heartbeat summary every ten seconds and [mm:ss] prefixes in CI, where log viewers often lack their own timestamps.
To use it in a pipeline:
Install the module in the job. On GitHub Actions, with PowerShell 7:
- name: Checks shell: pwsh run: | Install-Module ConsoleMonster -Scope CurrentUser -Force Import-Module ConsoleMonster ./checks.ps1Nothing else to configure: GitHub Actions sets
CI, so live output streams as lines. Other systems are detected by their own variables, andCI=trueworks for any of them.For logs with ASCII glyphs and no escape sequences, also set
CONSOLEMONSTER_MODE: plainunder the step'senv:.
Build artifacts +-----------------------------+---------+----------+--------+--------------------------------------+ | Name | Size | Built in | Signed | Path | +- Module - 2 ----------------+---------+----------+--------+--------------------------------------+ | ConsoleMonster-2.0.0.zip | 2.77 MB | 12 s | yes | D:\Builds\Console...onster-2.0.0.zip | | Docs.zip | 50.1 KB | 1.2 s | no | D:\Builds\Console...\Packed\Docs.zip | | Subtotal | 2.82 MB | 13 s | | | +- NuGet - 2 -----------------+---------+----------+--------+--------------------------------------+ | ConsoleMonster.2.0.0.nupkg | 1.41 MB | 48 s | yes | D:\Builds\Console...ster.2.0.0.nupkg | | ConsoleMonster.2.0.0.snupkg | 393 KB | 3.1 s | yes | D:\Builds\Console...ter.2.0.0.snupkg | | Subtotal | 1.8 MB | 51 s | | | +-----------------------------+---------+----------+--------+--------------------------------------+ | Total | 4.62 MB | 1 m 04 s | | | +-----------------------------+---------+----------+--------+--------------------------------------+
# Build artifacts as a module build reports them.
$artifacts = @(
[pscustomobject] @{ Name = 'ConsoleMonster.2.0.0.nupkg'; Kind = 'NuGet'; Length = 1482113
Time = [timespan]::FromSeconds(48.2); Signed = $true
Path = 'D:\Builds\ConsoleMonster\Packed\ConsoleMonster.2.0.0.nupkg' }
[pscustomobject] @{ Name = 'ConsoleMonster.2.0.0.snupkg'; Kind = 'NuGet'; Length = 402331
Time = [timespan]::FromSeconds(3.1); Signed = $true
Path = 'D:\Builds\ConsoleMonster\Packed\ConsoleMonster.2.0.0.snupkg' }
# … 6 more lines in the full program
)
$artifacts | Show-ConsoleTable -Title '[bold]Build artifacts[/]' -GroupBy Kind -Property @(
@{ Name = 'Name'; Key = $true; Overflow = 'Never' }
@{ Name = 'Length'; Header = 'Size'; Presenter = 'Bytes'; Priority = 9; Aggregate = 'Sum' }
@{ Name = 'Time'; Header = 'Built in'; Presenter = 'Duration'; Priority = 6; Aggregate = 'Sum' }
@{ Name = 'Signed'; Presenter = 'Boolean'; Priority = 5 }
@{ Name = 'Path'; Overflow = 'TruncateMiddle'; Priority = 1; MinWidth = 20 }
) -ShowTotalsFull program
Import-Module ConsoleMonster
# Build artifacts as a module build reports them.
$artifacts = @(
[pscustomobject] @{ Name = 'ConsoleMonster.2.0.0.nupkg'; Kind = 'NuGet'; Length = 1482113
Time = [timespan]::FromSeconds(48.2); Signed = $true
Path = 'D:\Builds\ConsoleMonster\Packed\ConsoleMonster.2.0.0.nupkg' }
[pscustomobject] @{ Name = 'ConsoleMonster.2.0.0.snupkg'; Kind = 'NuGet'; Length = 402331
Time = [timespan]::FromSeconds(3.1); Signed = $true
Path = 'D:\Builds\ConsoleMonster\Packed\ConsoleMonster.2.0.0.snupkg' }
[pscustomobject] @{ Name = 'ConsoleMonster-2.0.0.zip'; Kind = 'Module'; Length = 2904576
Time = [timespan]::FromSeconds(11.7); Signed = $true
Path = 'D:\Builds\ConsoleMonster\Packed\ConsoleMonster-2.0.0.zip' }
[pscustomobject] @{ Name = 'Docs.zip'; Kind = 'Module'; Length = 51302
Time = [timespan]::FromSeconds(1.2); Signed = $false
Path = 'D:\Builds\ConsoleMonster\Packed\Docs.zip' }
)
$artifacts | Show-ConsoleTable -Title '[bold]Build artifacts[/]' -GroupBy Kind -Property @(
@{ Name = 'Name'; Key = $true; Overflow = 'Never' }
@{ Name = 'Length'; Header = 'Size'; Presenter = 'Bytes'; Priority = 9; Aggregate = 'Sum' }
@{ Name = 'Time'; Header = 'Built in'; Presenter = 'Duration'; Priority = 6; Aggregate = 'Sum' }
@{ Name = 'Signed'; Presenter = 'Boolean'; Priority = 5 }
@{ Name = 'Path'; Overflow = 'TruncateMiddle'; Priority = 1; MinWidth = 20 }
) -ShowTotalsShow-ConsoleTable with CONSOLEMONSTER_MODE=plain: ASCII glyphs and no escape sequences, for logs that keep every byte.Text that reaches stream and plain output is sanitised: escape sequences and control characters in log messages, captured lines or data are removed, so plain mode never emits an escape sequence.
Native CI logs and job summaries
Groups, annotations and job summaries use the provider's own protocol when GITHUB_ACTIONS=true, TF_BUILD=true or GITLAB_CI=true. Other CI systems keep ordinary output. CONSOLEMONSTER_CI=github, azure or gitlab forces that provider; none disables provider commands. This choice is independent of colour, glyphs and stream mode. GitLab's section markers deliberately contain the control sequences required by its log viewer, including in plain mode.
In a PowerShell terminal:
Import-Module ConsoleMonster
Use-ConsoleGroup 'Build' {
dotnet build
if ($LASTEXITCODE -ne 0) {
Write-ConsoleAnnotation -Level Error -Title 'Build' -Message 'The build failed'
}
}
$annotation = @{
Level = 'Warning'
Message = 'Configuration is missing a timeout'
File = './build.ps1'
Line = 12
Column = 5
}
Write-ConsoleAnnotation @annotation
@(
[pscustomobject]@{ Check = 'Build'; Result = 'Passed' }
[pscustomobject]@{ Check = 'Tests'; Result = 'Passed' }
) | Add-ConsoleJobSummary
| Provider | Groups | Annotations | Summary |
|---|---|---|---|
| GitHub Actions | Collapsible log groups | Error, Warning and Notice, with optional title and source position | Appends Markdown to GITHUB_STEP_SUMMARY |
| Azure Pipelines | Collapsible log groups | Error and Warning with source position; Notice is an ordinary line | Uploads a UTF-8 Markdown file to the run's Extensions tab |
| GitLab CI | Sections collapsed by default | Styled log lines | Returns Markdown for the script to save |
| Terminal or other CI | Muted heading | Styled status line; source links where supported | Returns Markdown without writing a file |
Groups nest and close when an exception unwinds the script. Sequential Invoke-ConsoleRun puts each item's work and result inside a group. Parallel runs and Start-ConsoleRun group each completed item's result atomically, so groups cannot cross when workers finish out of order. Output emitted independently by parallel workers keeps its existing routing; use Use-ConsoleGroup for a serial block whose entire log should fold together.
Annotation files inside GITHUB_WORKSPACE, BUILD_SOURCESDIRECTORY or CI_PROJECT_DIR use paths relative to that checkout, with / separators. Paths outside the provider's workspace stay as supplied, with separators normalised. In a terminal, the source location appears once, relative to the current directory: src/App.cs:42. That label is a hyperlink when the host supports links and plain text otherwise. PowerShell resolves relative input from its current location.
Invoke-ConsolePester automatically annotates failing tests, with the assertion's file and line, and adds its results-by-file table and failures to the GitHub or Azure summary. -NoJobSummary turns off that summary. Pester's result object, throw policy, XML result file and exit code keep their native behaviour. Each invocation emits at most ten automatic annotations, followed by one line counting the remainder; all failures remain in the summary. With Enable-ConsoleFormatting, the Errors feature also annotates displayed script errors, including terminating errors with a script position, capped at ten per output pipeline. Explicit Write-ConsoleAnnotation calls have no ConsoleMonster limit. An annotation alone does not set an exit code.
GitHub summary writes append. Azure retains a separate consolemonster-summary-*.md file for each call in SYSTEM_DEFAULTWORKINGDIRECTORY (or the temporary directory when forced outside an agent). Keep those files available until the agent uploads them; the job workspace owns their cleanup. GitLab and terminal callers can save the returned Markdown themselves:
$env:CONSOLEMONSTER_CI = 'none'
Get-Process | Select-Object -First 5 Name, Id | Add-ConsoleJobSummary | Set-Content ./summary.md
The C# API shares those transports. AddSummary(string) accepts Markdown; the widget overload uses the existing optional Markdown exporter, without adding a dependency to the core package:
using ConsoleMonster;
using ConsoleMonster.Markdown;
using ConsoleMonster.Widgets;
using (ContinuousIntegration.Group("Build")) {
ContinuousIntegration.Annotate(
AnnotationLevel.Warning, "Review this setting", file: "build.cs", line: 12);
}
var table = new Table();
table.AddColumn("Check");
table.AddColumn("Result");
table.AddRow("Build", "Passed");
string markdown = ContinuousIntegration.AddSummary(
table, provider: new MarkdownFrameExportProvider());
// A host can override detection for one async flow, then restore the previous choice.
using (ContinuousIntegration.UseProvider(ContinuousIntegrationProvider.None)) {
ContinuousIntegration.Annotate(AnnotationLevel.Notice, "Local run");
}
The wire formats follow GitHub workflow commands, Azure logging commands and GitLab custom collapsible sections. GitHub and Azure escape their own property and message delimiters. Azure command-shaped text in headings, notices and rendered log data is neutralised before writing. ConsoleMonster owns Pester's CI annotations even when its live adapter is disabled, so the provider override and annotation limit apply to fallback runs too.
Prompts when nobody can answer
| Situation | What prompts do |
|---|---|
CI, redirected input, pwsh -NonInteractive, CONSOLEMONSTER_NONINTERACTIVE=1 | Return the default, or fail with a message that names the parameter to pass. |
| Remoting, the ISE | Ask through the host: numbered choices and PromptForChoice. |
CONSOLEMONSTER_ACCESSIBLE=1 | Plain numbered questions with no redraws; every answer is confirmed as text. |
CONSOLEMONSTER_NOMOUSE=1 | The usual prompts, with the mouse left to the terminal (selection, scrollback). InteractiveOptions.Default.Mouse = false does the same in C#. |
Known limits
- Windows Terminal does not expose its colour scheme, so its light schemes need
CONSOLEMONSTER_THEME=light. - Output that was already printed cannot re-flow on a later resize. Use
-Widthor a fixed profile when output must fit a known width. - Transcript detection, and transcript writing on 5.1, use guarded reflection into PowerShell internals; it does nothing if a future version renames those members.
Next
- Environment overrides: pin any of these decisions.
- Capability detection: how each decision is made.
- Live runs: what a run prints in CI.