WriteConsoleW fails under WSL #119

Closed
opened 2026-01-30 21:42:53 +00:00 by claunia · 8 comments
Owner

Originally created by @bitcrazed on GitHub (Feb 16, 2018).

From @mqudsi on July 14, 2017 18:0

Under Microsoft Windows [Version 10.0.15063], attempting to use the WriteConsoleW low-level API to output to the console results in no text being printed under WSL.

Simple test case:

#include <Windows.h>

void Write(const wchar_t *text, DWORD length = -1)
{
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	if (hOut == INVALID_HANDLE_VALUE || hOut == nullptr)
	{
		ExitProcess((UINT)-1);
	}
	DWORD charsWritten = -1;
	int result = WriteConsoleW(hOut, text, length != -1 ? length : lstrlen(text), &charsWritten, 0);
	if (result == 0)
	{
		ExitProcess((UINT)GetLastError());
	}
	CloseHandle(hOut);
}

int main()
{
	Write(L"Testing\n");
	return 0;
}

The above runs successfully under cmd.exe and prints Testing, but under bash.exe the WriteConsoleW call returns 0 and GetLastError() returns 1 (ERROR_INVALID_FUNCTION).

The only thing I can think of is that WSL is redirecting output to a file which is then redirected to the bash stdout, because the documentation for WriteConsole says:

WriteConsole fails if it is used with a standard handle that is redirected to a file. If an application processes multilingual output that can be redirected, determine whether the output handle is a console handle (one method is to call the GetConsoleMode function and check whether it succeeds). If the handle is a console handle, call WriteConsole. If the handle is not a console handle, the output is redirected and you should call WriteFile to perform the I/O. Be sure to prefix a Unicode plain text file with a byte order mark. For more information, see Using Byte Order Marks.

If that's the case, is there anything WSL could do to make this silently work as the intention behind WriteConsole failing when redirected to a file doesn't translate over too well when dealing with virtualized consoles.

Copied from original issue: Microsoft/WSL#2331

Originally created by @bitcrazed on GitHub (Feb 16, 2018). _From @mqudsi on July 14, 2017 18:0_ Under `Microsoft Windows [Version 10.0.15063]`, attempting to use the `WriteConsoleW` low-level API to output to the console results in no text being printed under WSL. Simple test case: ``` #include <Windows.h> void Write(const wchar_t *text, DWORD length = -1) { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE || hOut == nullptr) { ExitProcess((UINT)-1); } DWORD charsWritten = -1; int result = WriteConsoleW(hOut, text, length != -1 ? length : lstrlen(text), &charsWritten, 0); if (result == 0) { ExitProcess((UINT)GetLastError()); } CloseHandle(hOut); } int main() { Write(L"Testing\n"); return 0; } ``` The above runs successfully under cmd.exe and prints `Testing`, but under bash.exe the `WriteConsoleW` call returns 0 and `GetLastError()` returns `1` (`ERROR_INVALID_FUNCTION`). The only thing I can think of is that WSL is redirecting output to a file which is then redirected to the bash stdout, because the documentation for `WriteConsole` says: > WriteConsole fails if it is used with a standard handle that is redirected to a file. If an application processes multilingual output that can be redirected, determine whether the output handle is a console handle (one method is to call the GetConsoleMode function and check whether it succeeds). If the handle is a console handle, call WriteConsole. If the handle is not a console handle, the output is redirected and you should call WriteFile to perform the I/O. Be sure to prefix a Unicode plain text file with a byte order mark. For more information, see Using Byte Order Marks. If that's the case, is there anything WSL could do to make this silently work as the intention behind `WriteConsole` failing when redirected to a file doesn't translate over too well when dealing with virtualized consoles. _Copied from original issue: Microsoft/WSL#2331_
claunia added the Resolution-Fix-AvailableProduct-WSL labels 2026-01-30 21:42:53 +00:00
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @mqudsi on July 14, 2017 22:17

It is indeed an issue with WSL faking stdout by redirecting to a file, a workaround until this is fixed is as follows:

void Write(const wchar_t *text, DWORD outputHandle = STD_OUTPUT_HANDLE, DWORD length = -1)
{
	length = length != -1 ? length : lstrlen(text);

	HANDLE hOut = GetStdHandle(outputHandle);
	if (hOut == INVALID_HANDLE_VALUE || hOut == nullptr)
	{
		ExitProcess((UINT)-1);
	}
	DWORD consoleMode;
	bool isConsole = GetConsoleMode(hOut, &consoleMode) != 0;

	DWORD result = 0;
	DWORD charsWritten = -1;
	if (isConsole)
	{
		result = WriteConsoleW(hOut, text, length, &charsWritten, nullptr);
	}
	else
	{
		//WSL fakes the console, and requires UTF8 output
		DWORD utf8ByteCount = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text, length + 1, nullptr, 0, nullptr, nullptr); //include null
		auto utf8Bytes = _malloc<char>(utf8ByteCount);
		WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text, -1, utf8Bytes, utf8ByteCount, nullptr, nullptr);
		result = WriteFile(hOut, utf8Bytes, utf8ByteCount - 1 /* remove null */, &charsWritten, nullptr);
		if (charsWritten != utf8ByteCount - 1)
		{
			ExitProcess(GetLastError());
		}
		_free(utf8Bytes);
	}

	if (result == 0)
	{
		ExitProcess((UINT)GetLastError());
	}
}
@bitcrazed commented on GitHub (Feb 16, 2018): _From @mqudsi on July 14, 2017 22:17_ It is indeed an issue with WSL faking stdout by redirecting to a file, a workaround until this is fixed is as follows: ``` void Write(const wchar_t *text, DWORD outputHandle = STD_OUTPUT_HANDLE, DWORD length = -1) { length = length != -1 ? length : lstrlen(text); HANDLE hOut = GetStdHandle(outputHandle); if (hOut == INVALID_HANDLE_VALUE || hOut == nullptr) { ExitProcess((UINT)-1); } DWORD consoleMode; bool isConsole = GetConsoleMode(hOut, &consoleMode) != 0; DWORD result = 0; DWORD charsWritten = -1; if (isConsole) { result = WriteConsoleW(hOut, text, length, &charsWritten, nullptr); } else { //WSL fakes the console, and requires UTF8 output DWORD utf8ByteCount = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text, length + 1, nullptr, 0, nullptr, nullptr); //include null auto utf8Bytes = _malloc<char>(utf8ByteCount); WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, text, -1, utf8Bytes, utf8ByteCount, nullptr, nullptr); result = WriteFile(hOut, utf8Bytes, utf8ByteCount - 1 /* remove null */, &charsWritten, nullptr); if (charsWritten != utf8ByteCount - 1) { ExitProcess(GetLastError()); } _free(utf8Bytes); } if (result == 0) { ExitProcess((UINT)GetLastError()); } } ```
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @benhillis on July 15, 2017 0:2

@mqudsi - There is no guarantee that GetStandardHandle(STD_OUTPUT_HANDLE) will return a console handle. For example, run your Windows application while redirecting output to a file. What happens when you run this in cmd?

example.exe > out.txt

@bitcrazed commented on GitHub (Feb 16, 2018): _From @benhillis on July 15, 2017 0:2_ @mqudsi - There is no guarantee that GetStandardHandle(STD_OUTPUT_HANDLE) will return a console handle. For example, run your Windows application while redirecting output to a file. What happens when you run this in cmd? `example.exe > out.txt`
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @mqudsi on July 15, 2017 0:6

@benhillis I know, that's what I was trying to convey (very poorly) when I said

as the intention behind WriteConsole failing when redirected to a file doesn't translate over too well when dealing with virtualized consoles.

I'm imagining WriteConsole to be used in the same vein as is_tty() under *nix; where it will tell you whether or not there's an interactive session with a user "at the terminal" at the time the command is being run.

I guess what I'm trying to say is that bash is a console, is it not? And, as such, GetStandardHandle(STD_OUTPUT_HANDLE) should return a valid console handle for the bash session or else the handle returned should act as if it were a valid console handle for usage with APIs like WriteConsoleW?

@bitcrazed commented on GitHub (Feb 16, 2018): _From @mqudsi on July 15, 2017 0:6_ @benhillis I know, that's what I was trying to convey (very poorly) when I said > as the intention behind WriteConsole failing when redirected to a file doesn't translate over too well when dealing with virtualized consoles. I'm imagining `WriteConsole` to be used in the same vein as `is_tty()` under *nix; where it will tell you whether or not there's an interactive session with a user "at the terminal" at the time the command is being run. I guess what I'm trying to say is that `bash` is a console, is it not? And, as such, `GetStandardHandle(STD_OUTPUT_HANDLE)` should return a valid console handle for the bash session or else the handle returned should act as if it were a valid console handle for usage with APIs like `WriteConsoleW`?
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @benhillis on July 15, 2017 0:8

@mqudsi - It's a console, but it's the WSL driver's implementation of tty and not a standard windows console handle (which the WriteConsole api requires). We are hoping to improve console / tty interaction future versions of windows.

@bitcrazed commented on GitHub (Feb 16, 2018): _From @benhillis on July 15, 2017 0:8_ @mqudsi - It's a console, but it's the WSL driver's implementation of tty and not a standard windows console handle (which the WriteConsole api requires). We are hoping to improve console / tty interaction future versions of windows.
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @sunilmut on July 15, 2017 0:25

@mqudsi - This is good feedback. I would also expect what you are doing to work. But, unfortunately currently the way redirection is implemented, bash handles are not console handles and consequently WriteConsoleW does not know what to do with it.

Adding few others for visibility @zadjii-msft @miniksa

@bitcrazed commented on GitHub (Feb 16, 2018): _From @sunilmut on July 15, 2017 0:25_ @mqudsi - This is good feedback. I would also expect what you are doing to work. But, unfortunately currently the way redirection is implemented, bash handles are not console handles and consequently `WriteConsoleW` does not know what to do with it. Adding few others for visibility @zadjii-msft @miniksa
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @mqudsi on July 15, 2017 15:57

@sunilmut thanks. FWIW, I think that was a pretty ingenius way of implementing the pseudo-tty.

Is there any way to know what encoding to use with WriteFile(Ex) to the STD_OUT handle? Is it a bug that writing wide characters (UTF-16) via WriteFileW to the bash stdout handle causes corrupt output (UTF-16 interpreted as ASCII or UTF-8, can't tell which)?

In the workaround above, I hardcoded UTF-8 as the target.

@bitcrazed commented on GitHub (Feb 16, 2018): _From @mqudsi on July 15, 2017 15:57_ @sunilmut thanks. FWIW, I think that was a pretty ingenius way of implementing the pseudo-tty. Is there any way to know what encoding to use with WriteFile(Ex) to the STD_OUT handle? Is it a bug that writing wide characters (UTF-16) via WriteFileW to the bash stdout handle causes corrupt output (UTF-16 interpreted as ASCII or UTF-8, can't tell which)? In the workaround above, I hardcoded UTF-8 as the target.
Author
Owner

@bitcrazed commented on GitHub (Feb 16, 2018):

From @poizan42 on July 16, 2017 20:21

@mqudsi When you are attached to a real console it is GetConsoleOutputCP. But when you don't have a console then it is up to you to form a contract between the producer and consumer. Unfortunately there isn't really a standard agreed upon way of communicating the expected code page on Windows when it differs from the user default code page, but you could use the posix LANG/LC_* environment variables.

Also if you wanted to write UTF-16 directly to the console then you should in principle call SetConsoleOutputCP with codepage 1200, but for some reason this is not supported[1]. But of course as long as the bash tty isn't a proper console handle that point is moot - but maybe something happens if you try to change your LANG/LC_* environment variables to a UTF-16 locale?

[1]: The Code Page listing says "available only to managed applications" - seemingly these are just not recognized by the win32 api at all.

@bitcrazed commented on GitHub (Feb 16, 2018): _From @poizan42 on July 16, 2017 20:21_ @mqudsi When you are attached to a real console it is GetConsoleOutputCP. But when you don't have a console then it is up to you to form a contract between the producer and consumer. Unfortunately there isn't really a standard agreed upon way of communicating the expected code page on Windows when it differs from the user default code page, but you could use the posix LANG/LC_* environment variables. Also if you wanted to write UTF-16 directly to the console then you should in principle call SetConsoleOutputCP with codepage 1200, but for some reason this is not supported[1]. But of course as long as the bash tty isn't a proper console handle that point is moot - but maybe something happens if you try to change your LANG/LC_* environment variables to a UTF-16 locale? [1]: [The Code Page listing](https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx) says "available only to managed applications" - seemingly these are just not recognized by the win32 api at all.
Author
Owner

@zadjii-msft commented on GitHub (Mar 12, 2018):

This should be fixed in the latest Insider's flight

@zadjii-msft commented on GitHub (Mar 12, 2018): This should be fixed in the latest Insider's flight
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#119