I'm currently running a ps1 script in Powershell 5, where I need two things to happen at the same time:
An example of the code used is the following:
Write-Host "Name: " -ForegroundColor "DarkMagenta" -NoNewLine
Write-Host "John" -ForegroundColor "DarkBlue"
Unfortunately, the different methods I've tried to achieve the above goals do not work, since:
Start-Transcript "C:\Log.txt"
, the -NoNewLine
are ignored and every Write-Host
command gets logged on the txt file on a separate line.& "Script.ps1" | Tee-Object -FilePath "C:\Log.txt"
or as POWERSHELL -Command "& { ""Script.ps1"" | Tee-Object ""C:\Log.txt"" }"
does not save the Write-Host
outputs, only the Write-Output
ones.POWERSHELL -File "Script.ps1" | Tee-Object -FilePath "C:\Log.txt"
does not show the colors on the console (everything is white on black, as usual, and logged correctly).Write-Output "`e[5;36mMyText`e[0m"
does not work with PS5 (or, at least, in my environment, where the raw string is simply printed).Any suggestion about how could I achieve the desired result (i.e. colored output on the console and correctly formatted log on the text file)?
Thank you!
Do use ANSI (VT) escape sequences, which Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1) also supports.
The only pitfall is that PowerShell's `e
escape sequence only exists in PowerShell (Core) 7, whereas in Windows PowerShell it becomes a verbatim e
- use $([char] 27)
or similar techniques instead; e.g.:
# Write-Output implied
'{0}[5;36mMyText{0}[0m' -f [char] 27
# Alternative, with helper variable (you can also embed $([char] 27) every time).
$e = [char] 27; "$e[5;36mMyText$e[0m"
Start-Transcript
in Windows PowerShell should then record something like the following correctly, i.e. as a single output line with the escape sequences invariably preserved (which renders as expected when you print the file's content to the terminal, e.g. with Get-Content
, but may be unwanted noise when viewing in a text editor):[1]
Write-Host ('The following is colored and blinking: {0}[5;36mMyText{0}[0m' -f [char] 27)
Start-Transcript
the escape sequences are invariably removed - you'll invariably get monochrome text; see GitHub issue #11567 for a discussion.As for the Tee-Object
attempts:
If you use (implied) Write-Output
rather than Write-Host
, your Tee-Object
approach should work as-is - and the ANSI escape sequences are invariably preserved.
If you want to stick with Write-Host
(so as not to "pollute" the success output stream with the colored messages by default), use a *>
or - information stream-specifically, 6>
- redirection to (also) capture Write-Host
messages with the escape sequences in Windows PowerShell:
& "Script.ps1" *>&1 | Tee-Object -FilePath "C:\Log.txt"
Write-Host
+ *>
or 6>
) case, removes the ANSI escape sequences by default if the redirection target is a file or, if it is &1
, i.e. merged into the success output stream, and piped to Out-File
, Tee-Object
or Out-String
; however, there is an opt-in for preserving them, namely if you first (temporarily) set the $PSStyle
preference variable's .OutputRendering
property as follows:$PSStyle.OutputRendering = 'Ansi'
Windows PowerShell:
PowerShell 7:
itself uses ANSI escape sequences to colorize and style for-display output, as part of its formatting system; e.g. outputting $PSVersionTable
to the display produces a tabular display with colored column headers.
.OutputRendering
property of the object stored in the $PSStyle
preference variable.mostly preserves ANSI escape sequences in user-supplied strings, with the following exceptions:
When a string with embedded ANSI escape sequences is passed to Write-Host
and Write-Information
and a *>
or - information stream-specifically - 6>
redirection is applied and the redirection target is either a file or, if it is &1
, i.e. merged into the success output stream, piped to Out-File
, Tee-Object
, or Out-String
, the escape sequences are only preserved if
$PSStyle.OutputRendering = 'Ansi'
is in effect.[2]
However, if you use *>&1
or 6>&1
, you can retrieve the string intact even when $PSStyle.OutputRendering = 'Host'
(the default) or $PSStyle.OutputRendering = 'PlainText'
are in effect, namely if you explicitly stringify the [System.Management.Automation.InformationRecord]
instances that Write-Host
and Write-Information
output, before sending the result to Out-File
, Tee-Object
, or Out-String
; e.g., the following always preserves the escape sequences, as evidenced by the -match "`e"
operation yielding $true
:
(
Write-Host "It ain't easy being `e[32mgreen`e[m." *>&1 |
ForEach-Object ToString # explicit stringification
) -match "`e"` # -> always $true
Note that escape sequences are always preserved in strings sent directly output to the success output stream (implicitly or as command output, as is typical, or via Write-Output
). However, with $PSStyle.OutputRendering = 'PlainText'
in effect, strings with escape sequences print to the display uncolored (due the formatting system removing the colors for display); yet, the escape sequences are still present in programmatic processing.
However, the above, in-session rules are overruled in the following scenarios:
As of v7.5.x, when Start-Transcript
is used to create a session transcript, escape sequences are never preserved, i.e. they are actively stripped, whether or not they originate from the formatting system or the success output stream; see GitHub issue #11567 for a discussion that asks for a future version to allow coloring to be preserved in some fashion, on an opt-in basis.
When outside callers receive output from pwsh
, the PowerShell (Core) 7 CLI, they receive the for-display output that the code would produce in-session as a string via stdout.
The upshot is that, when $PSStyle.OutputRendering = 'PlainText'
is in effect - possibly via having set environment variable NO_COLOR
to 1
prior to invocation - escape sequences are stripped even from strings directly output to PowerShell's success output stream.
Also, as of v7.5.x, there's arguably a bug: $PSStyle.OutputRendering = 'Host'
, which is the default, isn't honored as expected, in that even when outside callers capture or redirect a CLI call's output, the formatting system unexpectedly preserves the escape sequences: see GitHub issue #20170.
[1] If preserving the ANSI sequences is undesired, you'll have to strip (remove) them yourself in Windows PowerShell after the fact; you yourself came up with the following regex, though note that this doesn't cover all possible VT (ANSI) escape sequences:
(Get-Content -Raw C:\Log.txt) -replace '\e\[[0-9;]*m' | Set-Content -NoNewLine C:\Log.txt
Note that PowerShell 7 now offers a helper class, System.Management.Automation.Internal.StringDecorated
, to achieve the same, which (presumably) covers all escape sequences:
([System.Management.Automation.Internal.StringDecorated] (Get-Content -Raw C:\Log.txt)).ToString('PlainText') | Set-Content -NoNewLine C:\Log.txt
[2] These cmdlets use the for-display formatting system to create string representations of their input objects (in the case of Tee-Object
, this applies to use with the -FilePath
parameter, not with -Variable
). While .NET primitive types as well as strings are considered out-of-band by the formatting system and are stringified with a simple .ToString()
call, the [System.Management.Automation.InformationRecord]
instances that Write-Host
/ Write-Information
wrap their string arguments in do go through the formatting system and are therefore subject to $PSStyle.OutputRendering
.