Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Getting the Console Width in PowerShell (Even When There Isn't One)

A width check that works in a real console, in VS Code, and in scheduled tasks and CI runners where there's no window to measure.

AT A GLANCEGet-ConsoleWidth.ps1
What it does
Returns the console width in characters, trying the PowerShell host, then .NET's Console class, then the COLUMNS environment variable, then a default. Never throws.
Requires
  • Windows PowerShell 5.1 or PowerShell 7+
  • No modules
Permissions
None.
Runs on
Windows, macOS, Linux, CI runners
Tested
Parse-checked and run in PowerShell 7.4 in a terminal, a non-interactive session and with only COLUMNS set

The one-liner is $Host.UI.RawUI.WindowSize.Width, and in a normal console it's all you need. I use it to draw separator lines, truncate long paths so a status line doesn't wrap, and decide whether a table fits or I should switch to a list.

Then you put that script in a scheduled task, or run it over remoting, or in a CI pipeline, and it falls apart. There's no window. Depending on the host, WindowSize comes back empty or zero, and [Console]::WindowWidth throws an IOException because there's no console handle to ask. A cosmetic helper takes down the whole script, which is a silly way for a job to fail at 2 a.m.

This version tries each source in turn, catches the failures, and falls back to a sensible default. With -Detailed it also tells you where the number came from, which is handy when you're trying to figure out why the output looks different in the pipeline than on your machine.

Get-ConsoleWidth.ps1Download
<#
.SYNOPSIS
    Returns the width of the console window in characters, with a sensible fallback when
    there isn't a real console to measure.
.DESCRIPTION
    Tries the PowerShell host first ($Host.UI.RawUI.WindowSize), then .NET ([Console]::WindowWidth),
    then the COLUMNS environment variable, and finally a default. Scheduled tasks, remoting
    sessions, CI runners and redirected output often have no window at all, and the first two
    can return 0, $null or throw. This never throws; it just tells you where the number came from.
.PARAMETER Default
    Width to use when nothing can be measured. Default: 120.
.PARAMETER Detailed
    Return an object with the width, where it came from, and whether output is redirected,
    instead of just the number.
.EXAMPLE
    .\Get-ConsoleWidth.ps1
.EXAMPLE
    '-' * ((.\Get-ConsoleWidth.ps1) - 1)
.EXAMPLE
    .\Get-ConsoleWidth.ps1 -Detailed
#>
[CmdletBinding()]
[OutputType([int])]
param(
    [ValidateRange(20, 1000)]
    [int]$Default = 120,

    [switch]$Detailed
)

$width = 0
$source = $null

# 1. The PowerShell host. Right for the real console, Windows Terminal, VS Code and ISE.
try {
    $w = $Host.UI.RawUI.WindowSize.Width
    if ($w -gt 0) { $width = $w; $source = 'Host.UI.RawUI' }
}
catch { Write-Verbose "RawUI unavailable in host '$($Host.Name)': $($_.Exception.Message)" }

# 2. .NET's view of the console. Throws when there's no console handle.
if (-not $width) {
    try {
        $w = [Console]::WindowWidth
        if ($w -gt 0) { $width = $w; $source = 'Console' }
    }
    catch { Write-Verbose "[Console]::WindowWidth unavailable: $($_.Exception.Message)" }
}

# 3. Many terminals and CI systems export COLUMNS.
if (-not $width -and $env:COLUMNS -match '^\d+$' -and [int]$env:COLUMNS -gt 0) {
    $width = [int]$env:COLUMNS
    $source = 'COLUMNS'
}

# 4. Give up gracefully.
if (-not $width) {
    $width = $Default
    $source = 'Default'
}

if ($Detailed) {
    $redirected = try { [Console]::IsOutputRedirected } catch { $null }
    [pscustomobject]@{
        Width            = $width
        Source           = $source
        HostName         = $Host.Name
        OutputRedirected = $redirected
    }
}
else {
    $width
}

Parameters

ParameterTypeDefaultWhat it's for
-Defaultint120The width to use when nothing can be measured.
-Detailedswitch—Return an object with the width, its source, the host name and whether output is redirected, instead of just the number.

Run it

Just the number.

.\Get-ConsoleWidth.ps1

A separator line that fits the window exactly (minus one, so it doesn't wrap in older consoles).

'-' * ((.\Get-ConsoleWidth.ps1) - 1)

Where did the number come from?

.\Get-ConsoleWidth.ps1 -Detailed

Make sure wide tables aren't cut off in a log file.

Get-Process | Format-Table -AutoSize | Out-String -Width ([Math]::Max(200, (.\Get-ConsoleWidth.ps1)))

What you'll see

Example outputvalues are illustrative
PS> .\Get-ConsoleWidth.ps1
142

PS> .\Get-ConsoleWidth.ps1 -Detailed

Width Source        HostName    OutputRedirected
----- ------        --------    ----------------
  142 Host.UI.RawUI ConsoleHost            False

(and in a scheduled task with no console)
Width Source  HostName    OutputRedirected
----- ------  --------    ----------------
  120 Default ConsoleHost             True

How it works

  1. Ask the PowerShell host. $Host.UI.RawUI.WindowSize.Width is the right answer in the classic console, Windows Terminal, VS Code's integrated terminal and the ISE. If it throws or comes back as zero, move on.
  2. Ask .NET. [Console]::WindowWidth talks to the console directly. It throws when there's no console attached, so it's wrapped in try.
  3. Check COLUMNS. Many terminals and CI systems set this environment variable, so it's a decent hint when the first two fail.
  4. Fall back. If nothing works, return -Default (120 unless you say otherwise). The script never throws, which is the whole point.

With -Detailed, you also get [Console]::IsOutputRedirected, so you can tell "the window is narrow" apart from "there is no window."

Take it further

  • Build a truncate helper. if ($text.Length -gt $w) { $text.Substring(0, $w - 4) + '...' } keeps progress lines on one row, which makes a long-running script far easier to watch.
  • Pick table or list automatically. Measure the table with Out-String, and if the widest line is wider than the console, use Format-List instead.
  • Use it in a profile. A prompt function that shortens the current path when the window is narrow is a nice small quality-of-life fix.

Things that'll trip you up

  • Window size is not buffer size. $Host.UI.RawUI.BufferSize.Width is how wide the scrollback buffer is, which in the old Windows console could be much wider than what you can see. For "will this line wrap on screen," you want WindowSize.
  • Resizing doesn't update your variable. The width is read once, when you call the script. If someone resizes the window during a long run, call it again right before you draw anything that depends on it.
  • Redirected output has no width. When output is going to a file or another program, there's no meaningful width at all. That's what -Default is for, and it's why Out-String -Width is the right tool for log files.
  • ISE is its own thing. The PowerShell ISE reports a width from its output pane, and [Console] calls there don't reflect it. The host check runs first for exactly this reason.