The console itself obviously doesn't have this limitation, but SetCurrentConsoleFontEx cannot set the font to fonts with 16 or more characters in the font name (note: the True Type name, nothing to do with the file name). The built-in "Lucida Sans Typewriter" and the open source "Fira Code Retina" work fine from the property dialog, but not from the API.
In fact, GetCurrentConsoleFontEx doesn't return the full name for fonts with more than 16 characters, and adds a garbage character to 16 character font names (looks like a "whatever was in memory there" bug).
I'd love to hear that I've implemented the PInvoke struct wrong, but I'm pretty sure that's not the case (I can define it several different ways, but none of them work any better than this does).
Microsoft Windows [Version 10.0.17760.1]
I have copied PInvoke code from various places, particularly the PowerShell console host, and the Microsoft Docs
Note that the relevant docs for CONSOLE_FONT_INFOEX and SetCurrentConsoleFontEx do not mention any restrictions on length, and the struct defines the font face as a WCHAR field of size 32...
However, in the API, the font name has to have less than 16 characters in the name in order to work.
using System;
using System.Runtime.InteropServices;
public static class ConsoleHelper
{
private const int FixedWidthTrueType = 54;
private const int StandardOutputHandle = -11;
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int nStdHandle);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool GetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);
private static readonly IntPtr ConsoleOutputHandle = GetStdHandle(StandardOutputHandle);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct FontInfo
{
internal int cbSize;
internal int FontIndex;
internal short FontWidth;
public short FontSize;
public int FontFamily;
public int FontWeight;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
//[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.wc, SizeConst = 32)]
public string FontName;
}
public static FontInfo[] SetCurrentFont(string font, short fontSize = 0)
{
Console.WriteLine("Set Current Font: " + font);
FontInfo before = new FontInfo
{
cbSize = Marshal.SizeOf<FontInfo>()
};
if (GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref before))
{
FontInfo set = new FontInfo
{
cbSize = Marshal.SizeOf<FontInfo>(),
FontIndex = 0,
FontFamily = FixedWidthTrueType,
FontName = font,
FontWeight = 400,
FontSize = fontSize > 0 ? fontSize : before.FontSize
};
// Get some settings from current font.
if (!SetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref set))
{
var ex = Marshal.GetLastWin32Error();
Console.WriteLine("Set error " + ex);
throw new System.ComponentModel.Win32Exception(ex);
}
FontInfo after = new FontInfo
{
cbSize = Marshal.SizeOf<FontInfo>()
};
GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref after);
return new[] { before, set, after };
}
else
{
var er = Marshal.GetLastWin32Error();
Console.WriteLine("Get error " + er);
throw new System.ComponentModel.Win32Exception(er);
}
}
}
I played with it in PowerShell window by using Add-Type with that code, and then doing something like this:
[ConsoleHelper]::SetCurrentFont("Consolas", 16)
[ConsoleHelper]::SetCurrentFont("Lucida Console", 12)
You can try setting "Lucida Sans Typewriter" but it won't take.
Try this: set the font to Lucida Sans Typewriter via the property dialog. Then Get it and Set it back, or just try to change the font size, without touching anything (else) and it will fail -- the font changes to the original Courier New....
[ConsoleHelper]::SetCurrentFont("Lucida Sans Typewriter", 12)
Will give you output like this (showing the three settings: before, what we tried, and what we got):
Set Current Font: Lucida Sans Typewriter
FontSize FontFamily FontWeight FontName
-------- ---------- ---------- --------
14 54 400 Lucida Sans Type蕡
12 54 400 Lucida Sans Typewriter
12 54 400 Courier New
You see that weird character on the end of the "before" value? That's happens whenever the font is longer than 16 characters -- I assume the garbage data is the result of something is truncating the name (and not null-terminating the string).
For what it's worth, I discovered this problem with Fira Code Retina, a font that has exactly 16 characters in the name -- and I have a little bit more code than what's above in a gist here if you care to experiment.
What an excellent writeup.
I'm really curious though - you're like the 3rd person to independently come across this bug this week. I'm pretty sure the bug has existed for WAY longer than that, but what gives? Why are all these people suddenly trying to programmatically change the font? Did @bitcrazed send you? Or was it @lzybkr? Is there some change that's going in to powershell core that's causing a bunch of people to hit this suddenly? Or is it just a crazy coincidence?
Ramblings of a madman aside, this issue is already a bug on our radar for this next release :).
P.S. shoutout to @lzybkr, who actually also found the exact line of code in the console that causes this issue.
Joel pinged me on Twitter, pointing to his writeup on Stack Overflow. I investigated and asked him to file a bug over here since this is where the Console dev's hang out.
That @lzybkr found it at the same time is just icing on the cake of the collections of minutely small possibilities that is this life we lead 馃槣
No coincidences - @jaykul found the bug and I thought it'd be an easy fix so I took a peek.
@jaykul and I both share the desire to configure our consoles in our PowerShell profile. Default settings are annoying to update. I got him started by pointing out some useful Win32 console apis, he just went further than I did.
And @zadjii-msft - it turns out to be 2 lines of code, not 1. I'd submit a PR but I can only read the code now, no pushes.
Yeah, @zadjii-msft that's all my fault, I reported this on twitter and asked for help fixing my code via StackOverflow before I finally decided it wasn't _my fault_ and filed the bug 馃槆
It looks like this was resolved and is on its way up as of 9/26/2018.
This should have gone out in insider build 18267.
Most helpful comment
What an excellent writeup.
I'm really curious though - you're like the 3rd person to independently come across this bug this week. I'm pretty sure the bug has existed for WAY longer than that, but what gives? Why are all these people suddenly trying to programmatically change the font? Did @bitcrazed send you? Or was it @lzybkr? Is there some change that's going in to powershell core that's causing a bunch of people to hit this suddenly? Or is it just a crazy coincidence?
Ramblings of a madman aside, this issue is already a bug on our radar for this next release :).
P.S. shoutout to @lzybkr, who actually also found the exact line of code in the console that causes this issue.