Colored text output in PowerShell console using ANSI / VT100 codes

Note:

  • The following applies to regular console windows on Windows (provided by conhost.exe), which are used by default, including when a console application is launched from a GUI application.

  • By contrast, the console windows (terminals) provided by Windows Terminal as well as Visual Studio Code’s integrated terminal provide support for VT / ANSI escape sequences by default, for all console applications.


While console windows in Windows 10 do support VT (Virtual Terminal) / ANSI escape sequences in principle, support is turned OFF by default.

You have three options:

  • (a) Activate support globally by default, persistently, via the registry, as detailed in this SU answer.

    • In short: In registry key [HKEY_CURRENT_USER\Console], create or set the VirtualTerminalLevel DWORD value to 1
      • From PowerShell, you can do this programmatically as follows:
        Set-ItemProperty HKCU:\Console VirtualTerminalLevel -Type DWORD 1
      • From cmd.exe (also works from PowerShell):
        reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1
    • Open a new console window for changes to take effect.
    • See caveats below.
  • (b) Activate support from inside your program, for that program (process) only, with a call to the SetConsoleMode() Windows API function.

    • See details below.
  • (c) Ad-hoc workaround, from PowerShell:

    • PowerShell (Core) 7+: Enclose external-program calls in (...) (invariably collects all output first before printing):

      • (.\test.exe)
    • Streaming Windows PowerShell-only alternative: Pipe output from external programs to Write-Host

      • .\test.exe | Out-Host
    • See details below.


Re (a):

The registry-based approach invariably activates VT support globally, i.e., for all console windows, irrespective of what shell / program runs in them:

  • Individual executables / shells can still deactivate support for themselves, if desired, using method (b).

  • Conversely, however, this means that the output of any program that doesn’t explicitly control VT support will be subject to interpretation of VT sequences; while this is generally desirable, hypothetically this could lead to misinterpretation of output from programs that accidentally produce output with VT-like sequences.

Note:

  • While there is a mechanism that allows console-window settings to be scoped by startup executable / window title, via subkeys of [HKEY_CURRENT_USR\Console], the VirtualTerminalLevel value seems not to be supported there.

  • Even if it were, however, it wouldn’t be a robust solution, because opening a console window via a shortcut file (*.lnk) (e.g. from the Start Menu or Task Bar) wouldn’t respect these settings, because *.lnk files have settings built into them; while you can modify these built-in settings via the Properties GUI dialog, as of this writing the VirtualTerminalLevel setting is not surfaced in that GUI.


Re (b):

Calling the SetConsoleMode() Windows API function from inside the program (process), as shown here, is cumbersome even in C# (due to requiring P/Invoke declarations), and may not be an option:

  • for programs written in languages from which calling the Windows API is not supported.

  • if you have a preexisting executable that you cannot modify.

In that event, option (c) (from PowerShell), discussed next, may work for you.


Re (c):

PowerShell automatically activates VT (virtual terminal) support for itself when it starts (in recent releases of Windows 10 this applies to both Windows PowerShell and PowerShell (Core) 7+) – but that does not extend to external programs called from PowerShell, in either edition, as of v7.3.2.

  • Separately, in v7.2+ there is the $PSStyle.OutputRendering preference variable, which controls whether PowerShell commands produce colored output via the formatting system, such as the colored headers of Get-ChildItem output. However, this setting has no effect on (direct) output from external programs. $PSStyle.OutputRendering defaults to Host, meaning that only formatted output that prints to the terminal (console) is colored. $PSStyle.OutputRendering = 'PlainText' disables coloring, and $PSStyle.OutputRendering = 'Ansi' makes it unconditional; see this answer for more information.

However, as a workaround you can relay an external program’s (stdout) output via PowerShell’s (success) output stream, in which case VT sequences are recognized:

  • As of PowerShell (Core) 7.3.2, this only works either by enclosing the call in (...) or by using Out-String, but note that all output is invariably collected first before it is printed.[1]

    (.\test.exe)
    
  • In Windows PowerShell, in addition to the above, streaming the relayed output is possible too, by piping to Write-Host (Out-Host, Write-Output or Out-String -Stream would work too)

    .\test.exe | Write-Host
    
  • Note: You need these techniques only if you want to print to the console. If, by contrast, you want to capture the external program’s output (including the escape sequences), use $capturedOutput = .\test.exe

Character-encoding caveat: Windows PowerShell by default expects output from external programs to use the OEM code page, as defined by the legacy system locale (e.g., 437 on US-English systems) and as reflected in [console]::OutputEncoding.
.NET console programs respect that setting automatically, but for non-.NET programs (e.g., Python scripts) that use a different encoding (and produce not just pure ASCII output (in the 7-bit range)), you must (at least temporarily) specify that encoding by assigning to [console]::OutputEncoding; e.g., for UTF-8:
[console]::OutputEncoding = [Text.Encoding]::Utf8.
Note that this is not only necessary for the VT-sequences workaround, but generally necessary for PowerShell to interpret non-ASCII characters correctly.

PowerShell Core (v6+), unfortunately, as of v7.3.2, still defaults to the OEM code page too, but that should be considered a bug (see GitHub issue #7233), given that it otherwise defaults to UTF-8 without BOM.


[1] Using Out-String -Stream or its built-in wrapper function, oss, is tempting in order to achieve streaming output, but this no longer works as of PowerShell 7.3.2, possibly due to the optimization implemented in GitHub PR #16612.

Leave a Comment