Skip to content

ANSI escape handling and PowerShell #13071

Closed
@SteveL-MSFT

Description

@SteveL-MSFT

User scenario

Since we've added more ANSI escape sequences for coloring in PS7, if you pipe or redirect that text output, the embedded ANSI escape sequences are sent down the pipeline. This can make log files harder to read. Also, for those wanting to leverage ANSI escape sequences, they are difficult to create/read for scripters and also harder to use formatting presets rather than hardcoding specific colors.

Fundamental philosophy

On Linux, if you use ls --color then different file types use ANSI escape sequences as color indicators. If you pipe this output to less, then you get paging while retaining the color information. If you redirect this output to a file, that file contains the ANSI escape sequences. If you then use the cat command on the file, you see the coloring as the ANSI escape sequences are rendered by the terminal.

On macOS, the similar command is ls -G, however, piping this output to less or redirecting to a file you lose the ANSI escape sequences so it is just plain text. I believe ls on macOS is detecting if output is redirected and turning off coloring automatically.

So for PowerShell, we should have a consistent experience regardless if Windows, Linux, or macOS and interop with native commands that may output ANSI escape sequences in addition to cmdlets/formatting.

Since ANSI escape sequences are de facto standardized, we should not introduce a new intermediate format and should just embed ANSI escape sequences within strings. This will ensure compatibility with other tools that emit or handle ANSI escape sequences.

Proposed technical implementation details

A new automatic variable $PSStyle will be added:

   TypeName: System.Management.Automation.PSStyle

Name            MemberType Definition
----            ---------- ----------
Reset           Property   string AttributesOff {get;set;}
Background      Property   System.Management.Automation.PSStyle+BackgroundColor Background {get;set;}
Blink           Property   string Blink {get;set;}
BlinkOff        Property   string BlinkOff {get;set;}
Bold            Property   string Bold {get;set;}
BoldOff         Property   string BoldOff {get;set;}
Foreground      Property   System.Management.Automation.PSStyle+ForegroundColor Foreground {get;set;}
Formatting      Property   System.Management.Automation.PSStyle+FormattingData Formatting {get;set;}
Hidden          Property   string Hidden {get;set;}
HiddenOff       Property   string HiddenOff {get;set;}
OutputRendering Property   System.Management.Automation.OutputRendering OutputRendering {get;set;}
Reverse         Property   string Reverse {get;set;}
ReverseOff      Property   string ReverseOff {get;set;}
Standout        Property   string Standout {get;set;}
StandoutOff     Property   string StandoutOff {get;set;}
Underline       Property   string Underlined {get;set;}
UnderlineOff    Property   string UnderlinedOff {get;set;}

$PSStyle

The base members return ANSI escape sequences mapped to their names. These are also settable so the user can change bold to underlined, for example. This makes it easier for scripters to author decorated strings with tab completion:

"$($PSStyle.Background.LightCyan)Power$($PSStyle.Underlined)$($PSStyle.Bold)Shell$($PSStyle.Reset)"

$PSStyle.OutputRendering

This is of type System.Management.Automation.OutputRendering which is an enum with the values:

  • Automatic This is the default. If the host supports VirtualTerminal, then ANSI is always passed as-is, otherwise plaintext
  • ANSI ANSI is always passed through as-is
  • PlainText ANSI escape sequences are always stripped so that it is only plain text
  • HostOnly This would be the macOS behavior where redirected or piped output the ANSI escape sequences are removed

$PSStyle.Formatting

This effectively replaces $Host.PrivateData as the way to read or configure colors for formatting rendering. $Host.PrivateData will continue to exist for backwards compatibility, but is not connected to $PSStyle.Formatting.

   TypeName: System.Management.Automation.PSStyle+FormattingData

Name         MemberType Definition
----         ---------- ----------
Debug        Property   string Debug {get;set;}
Error        Property   string Error {get;set;}
ErrorAccent  Property   string ErrorAccent {get;set;}
FormatAccent Property   string FormatAccent {get;set;}
Verbose      Property   string Verbose {get;set;}
Warning      Property   string Warning {get;set;}

One difference here is that instead of being of type ConsoleColor, these are all strings and doesn't separate foreground and background colors. This means that a single member can have foreground and background colors defined as well as other attributes like bold, underlined, etc...

Scripters can easily leverage this:

"$($PSStyle.Formatting.ErrorAccent)Power$($PSStyle.Formatting.Verbose)Shell$($PSStyle.AttributesOff)"

$PSStyle.Foreground and $PSStyle.Background

These members contain the standard 16 console colors as well as a RGB() methods to specify 24-bit color. For the colors, the values are settable and because they are strings can be any string content and any number of ANSI escape sequences.


   TypeName: System.Management.Automation.PSStyle+ForegroundColor

Name         MemberType Definition
----         ---------- ----------
Rgb          Method     string Rgb(byte red, byte green, byte blue), string Rgb(int rgb)
Black        Property   string Black {get;set;}
Blue         Property   string Blue {get;set;}
Cyan         Property   string Cyan {get;set;}
DarkGray     Property   string DarkGray {get;set;}
Green        Property   string Green {get;set;}
LightBlue    Property   string LightBlue {get;set;}
LightCyan    Property   string LightCyan {get;set;}
LightGray    Property   string LightGray {get;set;}
LightGreen   Property   string LightGreen {get;set;}
LightMagenta Property   string LightMagenta {get;set;}
LightRed     Property   string LightRed {get;set;}
LightYellow  Property   string LightYellow {get;set;}
Magenta      Property   string Magenta {get;set;}
Red          Property   string Red {get;set;}
White        Property   string White {get;set;}
Yellow       Property   string Yellow {get;set;}

PowerShell engine changes

The formatting system, pipelining, and redirection will be updated to respect the value of $PSStyle.OutputRendering.

StringDecorated type

This is an internal type to handle ANSI escaped strings.

Constructor

Single constructor taking a string as input.

bool IsDecorated property

Returns if the string contains ANSI escape sequences based on if the string contains ESC or C1 CSI.

int Length property

Returns the length of just the text content of the string sans ANSI escape sequences.

StringDecorated Substring(int contentLength) method

Returns a substring starting at index 0 up to the contentLength for content that is not part of an ANSI escape sequence. This is needed for table formatting to truncate strings preserving ANSI escape sequences that don't take up printable character space.

string ToString() method

Returns the plaintext version of the string.

string ToString(bool Ansi) method

Returns the raw ANSI embedded string if Ansi parameter is true, otherwise returns plain text with ANSI escape sequences removed.

Out-String update

To enable scripts to easily allow plain text redirection, Out-String will have a -RemoveAnsi switch.

Select-String update

By default, it would make sense for Select-String to ignore ANSI escape sequences, perhaps a -IncludeAnsi switch should be added.

$Host.PrivateData

This will be available for legacy scripts/modules that read from it. However, the engine changes (and console host changes) would mean these settings will no longer be observed and instead use the settings from $PSStyle.

Alternate considerations

Original prototype was built on top of System.CommandLine.Rendering, however, that is still a work in progress and is not targeted to be 1.0 in time for 7.1. It is also geared towards C# developers and the user experience is not great for scripts.

Related Issues

#10811
#7744
#3611

Metadata

Metadata

Assignees

Labels

Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifIssue-Enhancementthe issue is more of a feature request than a bug

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions