This is a project to investigate an issue where stderr output to the console is not colored.
In a c# console application, if the standard output is redirected, the standard error output is not displayed in the specified color.
- OS: WIndows 10
- IDE: Microsoft Visual Studio Community 2022 (64 bit) - Current Version 17.5.1
- Runtime: .NET6.0.14 / .NET7.0.3
- Console: Command prompt / Command prompt by Windows terminal
- It is a console application running on
.NET
, and - the foreground color has been changed with
Console.ForegroundColor
, and - the display has been made to the console by
Console.Error.Write()
/Console.Error.Write()
case.
If the above conditions are met, regardless of what value the user code sets the ForegroundColor
property to, the following will occur:
- The value of the
ForegroundColor
property isConsoleColor.Gray
. - Characters are displayed in the default foreground color in the console.
It will definitely reproduce.
Below is the sample source code.
using System;
namespace Experiment.ConsoleStandatdErrorWithColor.Experiment01
{
/// <summary>
/// A test program to check the strange behavior of the Console class.
/// </summary>
internal class Program
{
private static void Main(string[] args)
{
// Back up the current foreground color before changing the foreground color.
var defaultColor = Console.ForegroundColor;
// Change the foreground color to blue.
Console.ForegroundColor = ConsoleColor.Blue;
// Display with "Console.WriteLine(string?)".
Console.WriteLine("This text is expected to be displayed in blue. (to default (==stdout))");
// Display with "Console.Out.WriteLine(string?)"
Console.Out.WriteLine("This text is expected to be displayed in blue. (to stdout)");
// Display with "Console.Error.WriteLine(string?)"
Console.Error.WriteLine("This text is expected to be displayed in blue. (to stderr)");
// Display the foreground color of the console.
Console.WriteLine($"The current console foreground color is \"{Console.ForegroundColor}\" (to stdout).");
Console.Error.WriteLine($"The current console foreground color is \"{Console.ForegroundColor}\" (to stderr).");
// Restore the foreground color.
Console.ForegroundColor = defaultColor;
}
}
}
The complete source code of the sample program can be found at the URL below. https://github.com/rougemeilland/Experiment.ConsoleStandatdErrorWithColor/tree/main/Experiment.ConsoleStandatdErrorWithColor.Experiment01
- Build the above source files to create
experiment01.exe
. - The following results are displayed when
experiment01.exe
is executed on the console. - The following results are displayed when
experiment01.exe > foo.txt
is executed on the console. - The following results are displayed when
experiment01.exe 2> foo.txt
is executed on the console.
According to Mark Lakata's comments on a stack overflow post , the issue seems to have existed since at least December 2012.
First, I examined
the source code of the Console
class
, and found that
the ConsolePal
class
is responsible for processing the ForegroundColor property.
After that, as a result of investigating the source code of the ForegroundColor
property of
the ConsolePal
class
, the following was found.
- The
ForegoundColor
property getter determines the foreground color from the results of theGetConsoleScreenBufferInfo
Win32 API. - A call to the
GetConsoleScreenBufferInfo
API will fail if the handle (that is, any of stdin/stdout/stderr) has been redirected. - The getter calls the
GetConsoleScreenBufferInfo
API in the following order, and determines the current foreground color based on the information that was successfully obtained first.- A call to the
GetConsoleScreenBufferInfo
API for the handle to standard output - A call to the
GetConsoleScreenBufferInfo
API for the handle to standard error output - A call to the
GetConsoleScreenBufferInfo
API for the handle to standard input
- A call to the
- The
ForegoundColor
property setter sets the specified foreground color to the console by calling theSetConsoleTextAttribute
Win32 API on standard output. Even if an error occurs in the Win32 API at this time, it will be ignored. - If the stdout/stderr handle given to the
SetConsoleTextAttribute
Win32 API is a redirected one, the API will return with an error.
From the above survey results, the following can be inferred.
When user code changes the value of the ForegroundColor
property, a Win32 API is called as the change to the standard output handle.
After that, when user code prints to standard output, it will be displayed in the console with the foreground color set by the ForegroundColor
property.
This is expected behavior.
At the same time, the foreground color of the standard error output will also be the same color. The output destination for standard output is a device called "console", and the output destination for standard error output is the same device, so this is probably expected behavior.
When user code changes the value of the ForegroundColor
property, a Win32 API is called for the change to the standard output handle.
However, this Win32 API should have failed because standard output has been redirected, but the failure is ignored.
Subsequent attempts to print characters to stderr will call the Win32 API to ask for information such as the console foreground color. By the way, standard output is redirected at this time, so it's the Win32 API calls to standard error that correctly return console information. However, the Win32 API returns the foreground color before the change (that is, the default foreground color) because the foreground color has failed to be changed as described above.
As a result, the foreground color of the standard error output is the default color, regardless of what the ForegroundColor property is set to. Of course, this is behavior most users would not expect.
Since the ForegroundColor
and BackgroundColor
properties have very similar implementations, they probably have similar potential problems.
Also, it seems that it can be dealt with by the same coping method.
The current implementation of the ConsolePal
class ForegroundColor
property setter does not error check1 calls to the SetConsoleTextAttribute
Win32 API and only calls to standard output.
Would calling the SetConsoleTextAttribute
Win32 API on the setter of the ForegroundColor
property not only on stdout, but also on stderr (and stdin) solve the problem?
The current code is below.
Interop.Kernel32.SetConsoleTextAttribute(OutputHandle, attrs);
My suggested code is below.
The code below turned out to be incorrect. See Postscript (March 14, 2023)) for details.
Interop.Kernel32.SetConsoleTextAttribute(OutputHandle, attrs);
Interop.Kernel32.SetConsoleTextAttribute(ErrorHandle, attrs);
Interop.Kernel32.SetConsoleTextAttribute(InputHandle, attrs);
However, I don't know why the code was written where the SetConsoleTextAttribute
API is called only on the stdout handle, so my suggestion may be inaccurate.
At the very least, ignoring the error and calling the SetConsoleTextAttribute
Win32 API on both stdout and stderr seems to set the foreground color correctly even if one of them is redirected.2
As a result of experiments, it was found that giving a standard input handle to most APIs for manipulating the console would result in an error.
These APIs also include the SetConsoleTextAttribute
and GetConsoleScreenBufferInfo
APIs shown in the proposed fix above.
So the new proposed fix for the source code would be:
Interop.Kernel32.SetConsoleTextAttribute(OutputHandle, attrs);
Interop.Kernel32.SetConsoleTextAttribute(ErrorHandle, attrs);
By the way, the current ConsolPal
class (Windows version) source code calls the GetConsoleScreenBufferInfo
API for each of the standard output/standard error output/standard input to get the CONSOLE_SCREEN_BUFFER_INFO
structure. (see .NET code findings)
However, calling the GetConsoleScreenBufferInfo
API on a handle to standard input always fails, so I don't think it's necessary to call it.
Some console operations in the UNIX ConsolePal
class are implemented by sending ANSI escape codes to the console.
This includes changing the foreground and background colors of characters displayed in the console.
However, in the current ConsolePal
class implementation, ANSI escape codes are always printed to standard output.
Therefore, even though the standard error output is valid as a console, if the standard output is redirected, some functions of the Console
class (eg changing the foreground/background color, moving the cursor, beeping, clear screen, etc.) are not available.
Footnotes
-
Considering that an error occurs even in the common situation that "standard output is redirected", it seems rather natural that error checking is not performed. ↩
-
If the
SetConsoleTextAttribute
Win32 API fails for both standard output and standard error, it should mean that both standard output and standard error are being redirected. Considering whether you really need to care about the value of theForegroundColor
property in that situation, you may not need to call theSetConsoleTextAttribute
Win32 API on standard input. ↩