| name | pwshspectreconsole-terminal-ui |
| description | Build polished, maintainable PowerShell 7 terminal user interfaces with the PwshSpectreConsole module, including Spectre.Console prompts, tables, markup, status spinners, progress bars, demo discovery, and safe fallbacks. |
PwshSpectreConsole Terminal UI Skill
When to use this skill
Use this skill when the user wants to create, refactor, or review a PowerShell script or module that uses PwshSpectreConsole for terminal user interfaces.
Good matches include requests involving:
- Interactive PowerShell menus, selection prompts, confirmations, and text prompts.
- Terminal dashboards, tables, panels, rules, trees, grids, charts, calendars, or rich formatted output.
- Progress bars, status spinners, long-running task feedback, or background job visualization.
- Replacing
Write-Host, Read-Host, manual ANSI escape sequences, or plain text menus with cleaner Spectre.Console wrappers.
- Creating a demo script that showcases module capabilities using
Get-SpectreDemoFeatures, Start-SpectreDemo, Get-SpectreDemoColors, or related demo commands.
- Making PowerShell tooling feel more like a modern CLI/TUI without turning it into a full GUI.
Do not use this skill when the script must run silently in CI/CD, scheduled tasks, Intune remediation, ConfigMgr baselines, Azure Automation, or other non-interactive contexts unless you also build a non-interactive fallback path.
Source of truth
Prefer the official docs and local module help over memory.
Primary references:
- Official docs:
https://pwshspectreconsole.com/
- Demo feature reference:
https://pwshspectreconsole.com/reference/demo/get-spectredemofeatures/
- GitHub repo:
https://github.com/ShaunLawrie/PwshSpectreConsole
- Spectre.Console markup reference:
https://spectreconsole.net/markup
- Spectre.Console emoji reference:
https://spectreconsole.net/appendix/emojis
Useful local discovery commands:
Get-Command -Module PwshSpectreConsole
Get-Help Start-SpectreDemo -Full
Get-Help Get-SpectreDemoFeatures -Full
Get-Help Read-SpectreSelection -Full
Get-Help Format-SpectreTable -Full
Get-Help Invoke-SpectreCommandWithProgress -Full
Use Get-SpectreDemoFeatures when you need to show or validate the range of Spectre.Console visual features available through the module. Use Start-SpectreDemo when the user wants an interactive walkthrough with examples.
Baseline assumptions
PwshSpectreConsole is intended for PowerShell 7+.
Install with:
Install-Module PwshSpectreConsole -Scope CurrentUser
For best rendering, assume the user should use a modern terminal, UTF-8 output encoding, and a font with broad glyph support, especially when using emoji, icons, or line drawing.
Recommended profile line:
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
Core design principles
Use PwshSpectreConsole to improve clarity, not to decorate every line.
Keep rich terminal UI purposeful:
- Use prompts for meaningful choices, not for values that can be inferred.
- Use tables for structured output, not for a single object.
- Use status spinners for short unknown-duration work.
- Use progress bars for known or measurable work.
- Use panels/rules/figlet text sparingly for sectioning and demos.
- Use markup only when it improves scanability.
- Escape dynamic user or external data before inserting it into Spectre markup.
Prefer readable, testable PowerShell over clever terminal effects.
Standard implementation workflow
When adding PwshSpectreConsole to a script:
- Identify whether the script is interactive or automation-oriented.
- Add a guarded module import.
- Create a small wrapper layer for output and prompts.
- Keep business logic separate from UI rendering.
- Add fallback behavior for non-interactive execution.
- Use
-WhatIf, ShouldProcess, or confirmation prompts for destructive actions.
- Avoid breaking pipeline output unless the command is explicitly designed as an interactive command.
- Test in Windows Terminal, VS Code integrated terminal, and a basic non-interactive shell if the script will be reused broadly.
Recommended script scaffold
#requires -Version 7.0
[CmdletBinding(SupportsShouldProcess)]
param(
[switch]$NonInteractive
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
try {
Import-Module PwshSpectreConsole -ErrorAction Stop
}
catch {
throw "PwshSpectreConsole is required. Install it with: Install-Module PwshSpectreConsole -Scope CurrentUser"
}
$script:IsInteractive = -not $NonInteractive -and [Environment]::UserInteractive
function Write-UiMessage {
param(
[Parameter(Mandatory)]
[string]$Message,
[ValidateSet('Info', 'Success', 'Warning', 'Error')]
[string]$Level = 'Info'
)
if (-not $script:IsInteractive) {
Write-Information $Message -InformationAction Continue
return
}
$safeMessage = Get-SpectreEscapedText -Text $Message
switch ($Level) {
'Success' { Write-SpectreHost "[green]$safeMessage[/]" }
'Warning' { Write-SpectreHost "[yellow]$safeMessage[/]" }
'Error' { Write-SpectreHost "[red]$safeMessage[/]" }
default { Write-SpectreHost "[grey]$safeMessage[/]" }
}
}
Prompt patterns
Single selection
Use Read-SpectreSelection when the user must choose one item from a list.
$choices = @('Audit only', 'Preview changes', 'Apply changes')
$mode = Read-SpectreSelection `
-Message 'Choose an execution mode' `
-Choices $choices `
-Color 'Cyan1' `
-EnableSearch
switch ($mode) {
'Audit only' { $Apply = $false; $Preview = $false }
'Preview changes' { $Apply = $false; $Preview = $true }
'Apply changes' { $Apply = $true; $Preview = $false }
}
For complex objects, use -ChoiceLabelProperty so the prompt displays a friendly label while returning the selected object.
$selectedDevice = $devices | Read-SpectreSelection `
-Message 'Select a device' `
-ChoiceLabelProperty { "$($_.DeviceName) | $($_.OperatingSystem) | $($_.ComplianceState)" } `
-EnableSearch
Multi-selection
Use Read-SpectreMultiSelection when multiple items can be chosen.
$selectedApps = $apps | Read-SpectreMultiSelection `
-Message 'Select apps to include' `
-ChoiceLabelProperty { "$($_.DisplayName) [$($_.Id)]" } `
-PageSize 10
Use -AllowEmpty only when an empty selection is a valid outcome.
Confirmation
Use Read-SpectreConfirm for destructive or expensive actions.
$confirmed = Read-SpectreConfirm `
-Message 'Continue with deleting stale assignments?' `
-DefaultAnswer 'n' `
-ConfirmSuccess '[green]Continuing...[/]' `
-ConfirmFailure '[yellow]Cancelled.[/]'
if (-not $confirmed) {
return
}
Text input
Use Read-SpectreText for short validated strings. For sensitive values, use native secure input instead of rich text prompts unless the module explicitly supports the needed security behavior.
$groupPrefix = Read-SpectreText -Message 'Enter the group prefix'
if ([string]::IsNullOrWhiteSpace($groupPrefix)) {
throw 'Group prefix cannot be empty.'
}
Output patterns
Rich text output
Use Write-SpectreHost for human-facing messages with markup.
Write-SpectreHost '[bold cyan]Intune Hydration Kit[/] [grey]starting...[/]'
Write-SpectreHost '[green]Completed successfully[/]'
Write-SpectreHost '[yellow]No matching devices found[/]'
Write-SpectreHost '[red]Failed to connect to Microsoft Graph[/]'
Never place raw user input, API data, file paths, or exception messages directly inside Spectre markup. Escape dynamic text first.
$safeName = Get-SpectreEscapedText -Text $DeviceName
Write-SpectreHost "Processing [cyan]$safeName[/]"
Tables
Use Format-SpectreTable for structured result sets.
$devices |
Select-Object DeviceName, OperatingSystem, ComplianceState, LastSyncDateTime |
Format-SpectreTable `
-Title 'Managed Devices' `
-Border Rounded `
-Color Cyan1 `
-ShowRowSeparators
Use calculated properties when values need formatting or markup.
$properties = @(
@{ Name = 'Device'; Expression = { Get-SpectreEscapedText -Text $_.DeviceName } }
@{ Name = 'OS'; Expression = { $_.OperatingSystem } }
@{ Name = 'Compliance'; Expression = {
switch ($_.ComplianceState) {
'compliant' { '[green]Compliant[/]' }
'noncompliant' { '[red]Non-compliant[/]' }
default { '[yellow]Unknown[/]' }
}
}}
)
$devices | Format-SpectreTable -Property $properties -AllowMarkup -Title 'Compliance Summary'
Only use -AllowMarkup when you intentionally include Spectre markup in table cells.
Rules and section headers
Use rules to split major phases.
Write-SpectreRule -Title 'Discovery' -Color Cyan1
Write-SpectreRule -Title 'Remediation' -Color Yellow
Write-SpectreRule -Title 'Summary' -Color Green
Panels
Use panels for summaries, warnings, and final status blocks.
$summary = @"
Devices scanned: $ScannedCount
Issues found: $IssueCount
Actions taken: $ActionCount
"@
Format-SpectrePanel -Data $summary -Title 'Run Summary' -Color Cyan1
Live feedback patterns
Status spinner
Use Invoke-SpectreCommandWithStatus for unknown-duration tasks where a spinner is enough.
$result = Invoke-SpectreCommandWithStatus `
-Title 'Querying Microsoft Graph...' `
-Spinner Dots2 `
-Color Cyan1 `
-ScriptBlock {
Get-MgDeviceManagementManagedDevice -All
}
Write-SpectreHost "Retrieved [green]$($result.Count)[/] devices."
Use Write-SpectreHost inside the status block for log messages that should not break the spinner.
Progress bar
Use Invoke-SpectreCommandWithProgress when work can be measured.
Invoke-SpectreCommandWithProgress -ScriptBlock {
param(
[Spectre.Console.ProgressContext]$Context
)
$task = $Context.AddTask('Processing devices')
$total = [math]::Max($devices.Count, 1)
foreach ($device in $devices) {
# Do work here.
Start-Sleep -Milliseconds 100
$task.Increment(100 / $total)
}
if ($task.Percentage -lt 100) {
$task.Value = 100
}
}
Use an indeterminate task when the total amount of work is unknown.
Invoke-SpectreCommandWithProgress -ScriptBlock {
param([Spectre.Console.ProgressContext]$Context)
$task = $Context.AddTask('Waiting for external operation')
$task.IsIndeterminate = $true
Invoke-SomeLongRunningOperation
$task.Value = 100
}
Demo and exploration commands
Use these commands when the task is to discover module features, create examples, or build training/demo content.
Start-SpectreDemo
Get-SpectreDemoFeatures
Get-SpectreDemoColors
Get-SpectreDemoEmoji
Open-PwshSpectreConsoleHelp
Get-SpectreDemoFeatures is specifically useful when the user asks what Spectre.Console can visually do from PowerShell. Treat it as a feature showcase rather than a production dependency.
Recommended demo script structure:
#requires -Version 7.0
Import-Module PwshSpectreConsole -ErrorAction Stop
Write-SpectreRule -Title 'PwshSpectreConsole Feature Demo' -Color Cyan1
Write-SpectreHost '[bold]Feature showcase[/]'
Get-SpectreDemoFeatures
Read-SpectrePause -Message 'Press enter to continue to the interactive demo.'
Start-SpectreDemo
Error handling
Prefer normal PowerShell error handling. Use Spectre formatting only for human-readable display.
try {
Invoke-SomethingRisky
}
catch {
if ($script:IsInteractive) {
Format-SpectreException -Exception $_.Exception
}
else {
Write-Error $_
}
}
Do not hide exceptions behind pretty messages. Preserve enough detail for troubleshooting.
Non-interactive fallback pattern
Do not force prompts in automation contexts. Add parameters so the same tool can run unattended.
[CmdletBinding()]
param(
[ValidateSet('Audit', 'Preview', 'Apply')]
[string]$Mode,
[switch]$NonInteractive
)
if (-not $Mode) {
if ($NonInteractive -or -not [Environment]::UserInteractive) {
$Mode = 'Audit'
}
else {
$Mode = Read-SpectreSelection `
-Message 'Choose mode' `
-Choices @('Audit', 'Preview', 'Apply') `
-EnableSearch
}
}
For module functions, keep objects flowing through the pipeline. Put rich UI behind an explicit switch such as -Interactive, -ShowUi, or -AsTable.
Testing checklist
Before considering the implementation complete:
- The script imports
PwshSpectreConsole explicitly and fails clearly if missing.
- PowerShell 7+ is documented or enforced.
- Interactive prompts have non-interactive alternatives.
- Destructive choices require confirmation or support
ShouldProcess.
- Dynamic text inserted into markup is escaped.
- Tables are used for structured output and do not destroy object output unless intended.
- Progress bars do not exceed 100 percent and handle zero-item collections.
- The script behaves acceptably in terminals without Nerd Font or full emoji support.
- The UX helps the user make decisions faster; it is not just decoration.
Common mistakes to avoid
Avoid these patterns:
# Bad: raw external text can break markup.
Write-SpectreHost "[green]$UserSuppliedText[/]"
# Bad: prompt blocks automation with no override.
$choice = Read-SpectreSelection -Choices $items -Message 'Choose one'
# Bad: table formatting too early destroys objects needed later.
$devices = Get-Devices | Format-SpectreTable
# Bad: every log line is high-contrast markup.
Write-SpectreHost '[bold red underline blink]Starting[/]'
Prefer these patterns:
# Good: escape dynamic text.
$safeText = Get-SpectreEscapedText -Text $UserSuppliedText
Write-SpectreHost "[green]$safeText[/]"
# Good: parameterized fallback.
if (-not $PSBoundParameters.ContainsKey('Choice')) {
$Choice = if ($NonInteractive) { 'Default' } else { Read-SpectreSelection -Choices $items -Message 'Choose one' }
}
# Good: keep data as data until display time.
$devices = Get-Devices
$devices | Format-SpectreTable -Title 'Devices'
Review rubric
When reviewing generated code, check:
- Does the terminal UI clarify the workflow?
- Is business logic separated from presentation?
- Can the script run without interaction?
- Are prompts searchable when choice lists are long?
- Are destructive actions protected?
- Is dynamic Spectre markup escaped?
- Are errors preserved, not hidden?
- Are docs or local help consulted for command-specific syntax?
Minimal example: interactive device cleanup shell
#requires -Version 7.0
[CmdletBinding(SupportsShouldProcess)]
param(
[ValidateSet('Audit', 'Preview', 'Apply')]
[string]$Mode,
[switch]$NonInteractive
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module PwshSpectreConsole -ErrorAction Stop
$isInteractive = -not $NonInteractive -and [Environment]::UserInteractive
if (-not $Mode) {
$Mode = if ($isInteractive) {
Read-SpectreSelection `
-Message 'Choose cleanup mode' `
-Choices @('Audit', 'Preview', 'Apply') `
-EnableSearch `
-Color Cyan1
}
else {
'Audit'
}
}
if ($isInteractive) {
Write-SpectreRule -Title 'Discovery' -Color Cyan1
}
$devices = Invoke-SpectreCommandWithStatus `
-Title 'Collecting stale devices...' `
-Spinner Dots2 `
-Color Cyan1 `
-ScriptBlock {
@(
[pscustomobject]@{ DeviceName = 'LAPTOP-001'; LastSeenDays = 91; Action = 'Review' }
[pscustomobject]@{ DeviceName = 'LAPTOP-002'; LastSeenDays = 144; Action = 'Remove' }
)
}
if ($isInteractive) {
$devices | Format-SpectreTable -Title 'Stale Devices' -Border Rounded -Color Cyan1
}
else {
$devices
}
if ($Mode -eq 'Apply') {
$confirmed = if ($isInteractive) {
Read-SpectreConfirm -Message 'Apply cleanup actions?' -DefaultAnswer n
}
else {
$true
}
if ($confirmed -and $PSCmdlet.ShouldProcess('stale devices', 'Apply cleanup')) {
Invoke-SpectreCommandWithProgress -ScriptBlock {
param([Spectre.Console.ProgressContext]$Context)
$task = $Context.AddTask('Applying cleanup')
$total = [math]::Max($devices.Count, 1)
foreach ($device in $devices) {
Start-Sleep -Milliseconds 150
$task.Increment(100 / $total)
}
$task.Value = 100
}
}
}
Final instruction to the coding agent
When using this skill, produce PowerShell that is useful first and beautiful second. PwshSpectreConsole should make the script easier to understand, safer to operate, and more pleasant to use. Do not add terminal effects that obscure control flow, break automation, or make troubleshooting harder.