I wrote a program which prints a string, which contains ANSI escape sequences to make the text colored. But it doesn't work as expected in the default Windows 10 console, as you can see in the screenshot.
The program output appears with the escape sequences as printed characters. If I feed that string to PowerShell via a variable or piping, the output appears as intended (red text).
How can I achieve that the program prints colored text without any workarounds?
This is my program source (Haskell) - but the language is not relevant, just so you can see how the escape sequences are written.
main = do
let red = "\ESC[31m"
let reset = "\ESC[39m"
putStrLn $ red ++ "RED" ++ reset
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.
[HKEY_CURRENT_USER\Console]
, create or set the VirtualTerminalLevel
DWORD value to 1
Set-ItemProperty HKCU:\Console VirtualTerminalLevel -Type DWORD 1
cmd.exe
(also works from PowerShell):reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1
(b) Activate support from inside your program, for that program (process) only, with a call to the SetConsoleMode()
Windows API function.
(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.
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.
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.
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.
$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.