diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs
index 136b7a26ef5..17cbae65894 100644
--- a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs
+++ b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs
@@ -552,7 +552,8 @@ private void ProcessOutOfBandPayload(FormatEntryData fed)
}
else
{
- _lo.WriteLine(rte.text);
+ // Write out raw text without any changes to it.
+ _lo.WriteRawText(rte.text);
}
return;
diff --git a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs
index a1f373a5dd5..3c200664636 100644
--- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs
+++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs
@@ -250,6 +250,13 @@ internal virtual void ExecuteBufferPlayBack(DoPlayBackCall playback) { }
///
internal abstract void WriteLine(string s);
+ ///
+ /// Write a line of string as raw text to the output device, with no change to the string.
+ /// For example, keeping VT escape sequences intact in it.
+ ///
+ /// The raw text to be written to the device.
+ internal virtual void WriteRawText(string s) => WriteLine(s);
+
internal WriteStreamType WriteStream
{
get;
@@ -431,7 +438,7 @@ private void WriteLineInternal(string val, int cols)
/// Implementation of the ILineOutput interface accepting an instance of a
/// TextWriter abstract class.
///
- internal class TextWriterLineOutput : LineOutput
+ internal sealed class TextWriterLineOutput : LineOutput
{
#region ILineOutput methods
@@ -467,9 +474,17 @@ internal override int RowNumber
///
internal override void WriteLine(string s)
{
- CheckStopProcessing();
+ WriteRawText(PSHostUserInterface.GetOutputString(s, isHost: false));
+ }
- s = PSHostUserInterface.GetOutputString(s, isHost: false);
+ ///
+ /// Write a raw text by delegating to the writer underneath, with no change to the text.
+ /// For example, keeping VT escape sequences intact in it.
+ ///
+ /// The raw text to be written to the device.
+ internal override void WriteRawText(string s)
+ {
+ CheckStopProcessing();
if (_suppressNewline)
{
@@ -480,6 +495,7 @@ internal override void WriteLine(string s)
_writer.WriteLine(s);
}
}
+
#endregion
///
diff --git a/test/powershell/engine/Formatting/ErrorView.Tests.ps1 b/test/powershell/engine/Formatting/ErrorView.Tests.ps1
index 7de4f9d431d..18173354a65 100644
--- a/test/powershell/engine/Formatting/ErrorView.Tests.ps1
+++ b/test/powershell/engine/Formatting/ErrorView.Tests.ps1
@@ -115,7 +115,8 @@ Describe 'Tests for $ErrorView' -Tag CI {
It "Error shows for advanced function" {
# need to have it virtually interactive so that InvocationInfo.MyCommand is empty
- $e = '[cmdletbinding()]param()$pscmdlet.writeerror([System.Management.Automation.ErrorRecord]::new(([System.NotImplementedException]::new("myTest")),"stub","notimplemented","command"))' | pwsh -noprofile -file - 2>&1 | Out-String
+ $e = '[cmdletbinding()]param()$pscmdlet.writeerror([System.Management.Automation.ErrorRecord]::new(([System.NotImplementedException]::new("myTest")),"stub","notimplemented","command"))' | pwsh -noprofile -file - 2>&1
+ $e = $e | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] } | Out-String
$e | Should -Not -BeNullOrEmpty
# need to see if ANSI escape sequences are in the output as ANSI is disabled for CI
diff --git a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1
index 53732a2bd6a..7b67f71233c 100644
--- a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1
+++ b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1
@@ -39,7 +39,7 @@ Describe 'OutputRendering tests' {
param($outputRendering, $ansi)
$PSStyle.OutputRendering = $outputRendering
- $out = "$($PSStyle.Foreground.Green)hello" | Out-String
+ $out = [pscustomobject] @{ key = "$($PSStyle.Foreground.Green)hello" } | Out-String
if ($ansi) {
$out | Should -BeLike "*`e*" -Because ($out | Format-Hex | Out-String)
diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1
index 4fc8087a904..e232ebe6d57 100644
--- a/test/powershell/engine/Formatting/PSStyle.Tests.ps1
+++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1
@@ -286,6 +286,77 @@ Describe 'Tests for $PSStyle automatic variable' {
$strDec.ContentLength | Should -Be $word.Length
$strDec.ToString("PlainText") | Should -Be $word
}
+
+ It "String intput to Out-String should be intact with OutputRendering=''" -TestCases @(
+ @{ OutputRendering = 'Ansi'; ContainsAnsi = $true }
+ @{ OutputRendering = 'Host'; ContainsAnsi = $false }
+ @{ OutputRendering = 'PlainText'; ContainsAnsi = $false }
+ ) {
+ param($OutputRendering, $ContainsAnsi)
+
+ $oldRender = $PSStyle.OutputRendering
+ $testStr = "`e[31mABC`e[0m"
+
+ try {
+ $PSStyle.OutputRendering = $OutputRendering
+ ## For input that actually goes through formatting, Out-String should remove VT sequences
+ ## from the formatting output based on the output rendering option that is in effect.
+ (Get-Verb -Verb Get | Out-String).Contains("`e[") | Should -Be $ContainsAnsi
+ ## For string input, since no formatting is applied, Out-String should keep the string intact.
+ ($testStr | Out-String).Trim() | Should -BeExactly $testStr
+ }
+ finally {
+ $PSStyle.OutputRendering = $oldRender
+ }
+ }
+
+ It "String input to Out-File should be intact with OutputRendering=''" -TestCases @(
+ @{ OutputRendering = 'Ansi'; }
+ @{ OutputRendering = 'Host'; }
+ @{ OutputRendering = 'PlainText'; }
+ ) {
+ param($OutputRendering)
+
+ $oldRender = $PSStyle.OutputRendering
+ $content = "Read-Host -Prompt '`e[33mEnter your device code`e[0m'"
+ Set-Content -Path $TestDrive\test.ps1 -Value $content -Encoding utf8NoBOM
+
+ try {
+ $PSStyle.OutputRendering = $OutputRendering
+ Get-Content $TestDrive\test.ps1 > $TestDrive\copy.ps1
+ (Get-Content $TestDrive\copy.ps1 -Raw).Trim() | Should -BeExactly $content
+ }
+ finally {
+ $PSStyle.OutputRendering = $oldRender
+ Remove-Item $TestDrive\test.ps1 -Force
+ Remove-Item $TestDrive\copy.ps1 -Force
+ }
+ }
+
+ It "Comment based help works with `$PSStyle when OutputRendering=''" -TestCases @(
+ @{ OutputRendering = 'Ansi'; ContainsAnsi = $true }
+ @{ OutputRendering = 'Host'; ContainsAnsi = $false }
+ @{ OutputRendering = 'PlainText'; ContainsAnsi = $false }
+ ) {
+ param($OutputRendering, $ContainsAnsi)
+
+ $oldRender = $PSStyle.OutputRendering
+
+ function Test-PSStyle {
+ <#
+ .Description
+ Get-Function [31mdisplays[0m the name and syntax of all functions in the session.
+ #>
+ }
+
+ try {
+ $PSStyle.OutputRendering = $OutputRendering
+ (Get-Help Test-PSStyle | Out-String).Contains("`e[31mdisplays`e[0m") | Should -Be $ContainsAnsi
+ }
+ finally {
+ $PSStyle.OutputRendering = $oldRender
+ }
+ }
}
Describe 'Handle strings with escape sequences in formatting' {