It's kinda mind-boggling to me that after making a change to the build definition, there was no way to try it out and see if it works, other than committing the change and waiting until the next day to see if it broke the build.
My question is: how was anyone expected to make changes to the build definition if this was the only way? Wait a day to find out if it worked, and break the build for everyone if not?
My understanding from other anecdotes about Microsoft is that a lot of their internal tooling, for a very long time, remained the kind of hideous nightmare of interwoven dependencies and build times measured in hours that we try to avoid in most modern setups.
I would speculate that in most parts of the company that haven't been left to rot that's hopefully no longer true, but I have no direct anecdotes about that, I just would have assumed it to be a target for someone looking for their iteration cycles to be saner at some point, and once one group managed it, etc.
never was part of Windows, but from my limited interactions with it the old build system was a chimera of Perl scripts, passes, Makefile-like things and other tooling. build labs would run and jobs would take hours.
it underwent a Herculean effort to modernize it, converging on the actual VS build system and from sd to git. it seemed much healthier last I looked.
>how was anyone expected to make changes to the build definition if this was the only way? Wait a day to find out if it worked, and break the build for everyone if not?
You could build a subcomponent of Windows source locally, and 99% of the time, you knew which subparts of the source tree to build locally to have confidence that you wouldn't break the build.
The problem was that if I was doing something exotic nobody has tried before, I'm suddenly in the 1% case where I no longer have confidence that building locally will eliminate all issues.
At one point you say "precompiler" when I guess you mean "preprocessor"?
Also I think the C preprocessor would be relatively unhelpful with the file format you explained in the post: As soon as you reached the first unmatched, unquoted apostrophe, cpp would assume it was inside a really long character literal and refuse to substitute any macros until the next apostrophe.
cpp is great, but it does basically require a grammar that assigns broadly the expected meaning to ' " # // /* */. Curly-brace languages fine, running English text not so much.
Without running a preprocessor on the .mc file, and without adding "%d" support to `ShowError`, here's one other category of solution. These days, you could just write
if (minimumPassphraseLength > MAX_PASSPHRASE_MINIMUM) {
static_assert(MAX_PASSPHRASE_MINIMUM == 20);
ShowError(ERROR_BITLOCKER_PASSPHRASE_MINIMUM_LONGER_THAN_20);
}
so that if the value of MAX_PASSPHRASE_MINIMUM ever changed, you'd get a build failure right on this line and you'd be forced to fix it (part of which should involve updating the message to match).
You could make that fancier by trying to craft the name of the error message itself via the preprocessor — something like:
But that would just make the compiler error (when MAX_PASSPHRASE_MINIMUM changed) a lot harder to read, without changing the essential task (go find the error message and update it), so it's not a good idea.
>At one point you say "precompiler" when I guess you mean "preprocessor"?
Ah, you're right! Fixed, thanks!
>Also I think the C preprocessor would be relatively unhelpful with the file format you explained in the post: As soon as you reached the first unmatched, unquoted apostrophe, cpp would assume it was inside a really long character literal and refuse to substitute any macros until the next apostrophe.
Oh, that's a good point. I'm not sure how Visual C++ does with it, but I just tried with gcc, and it falls over on an apostrophe:
The ShowError() API they're talking about doesn't take a format string. It takes a resource ID which is an integer that's essentially an index in the resource table. So running the message compiler produces a C header that defines ERROR_BITLOCKER_PASSPHRASE_MINIMUM_TOO_LONG to an integer constant, and then ShowError(ERROR_BITLOCKER_PASSPHRASE_MINIMUM_TOO_LONG) looks up the string at that index in the resource table and displays it.
(You'll note that that page mentions that FormatMessage() does support printf-style format specifiers in the string resource. That's why I'm saying that their ShowError() specifically is the one that doesn't.)
I'd say that of course people at Microsoft thought to do this, twenty years prior at the time of OS/2 1.0 …
… except that it might have been people from IBM. OS/2 had a DosGetMessage() API function that looked up messages in a compiled message file and inserted strings taken from an application-supplied array wherever a %1 to a %9 occurred in the message.
It was regularly used to edit the applicable filename, drive letter, numeric values, or whatnot into error messages.
In a way this is more like Perl's maketext or maybe Mozilla Fluent. Reasonable translations need to be constructed using variable inputs and correct messages can vary greatly between languages with grammar agreement and such. While gettext does have some extra support for getting numeric translations and agreements right it is kind of hacked in as an add on to a text lookup procedure rather than in terms of constructing the messages based on inputs.
The Microsoft way is to do things backwards, make the possible impossible, and wrap simple, well-understood APIs behind 16 layers of abstraction with awful names that will ever so slightly munge your arguments. Surely you know this :-)
No, the group policy checks are runtime checks, not compile time checks.
It meant that when a user enables BitLocker on a drive, the BitLocker setup flow would check if group policy defined a minimum password length. If it did, BitLocker would enforce that minimum. Otherwise, BitLocker would use the default minimum.
If you have a Windows system, you can play with this easily as long as you have an admin user account for the OS. The Windows system doesn't need to be part of an org or need Active Directory. You can edit group policy by opening gpedit.msc from an elevated command prompt.
My confusion was that I thought they wanted to put a group policy value into an error message, and doing so via the preprocessor didn't make any sense.
I believe that the article is referring to a maximum which is hardcoded into Bitlocker (not user configurable). If the administrator configures via GPO a minimum key length which is longer than the hardcoded maximum, then it spits out the error in question.
Very knowledgeable, helpful, and snarky - that's the vibe I'm getting from Raymond Chen too after reading many of his blog posts.
Author here. Happy to take any feedback or questions about this post.
It's kinda mind-boggling to me that after making a change to the build definition, there was no way to try it out and see if it works, other than committing the change and waiting until the next day to see if it broke the build.
My question is: how was anyone expected to make changes to the build definition if this was the only way? Wait a day to find out if it worked, and break the build for everyone if not?
My understanding from other anecdotes about Microsoft is that a lot of their internal tooling, for a very long time, remained the kind of hideous nightmare of interwoven dependencies and build times measured in hours that we try to avoid in most modern setups.
I would speculate that in most parts of the company that haven't been left to rot that's hopefully no longer true, but I have no direct anecdotes about that, I just would have assumed it to be a target for someone looking for their iteration cycles to be saner at some point, and once one group managed it, etc.
never was part of Windows, but from my limited interactions with it the old build system was a chimera of Perl scripts, passes, Makefile-like things and other tooling. build labs would run and jobs would take hours.
it underwent a Herculean effort to modernize it, converging on the actual VS build system and from sd to git. it seemed much healthier last I looked.
>how was anyone expected to make changes to the build definition if this was the only way? Wait a day to find out if it worked, and break the build for everyone if not?
You could build a subcomponent of Windows source locally, and 99% of the time, you knew which subparts of the source tree to build locally to have confidence that you wouldn't break the build.
The problem was that if I was doing something exotic nobody has tried before, I'm suddenly in the 1% case where I no longer have confidence that building locally will eliminate all issues.
At one point you say "precompiler" when I guess you mean "preprocessor"?
Also I think the C preprocessor would be relatively unhelpful with the file format you explained in the post: As soon as you reached the first unmatched, unquoted apostrophe, cpp would assume it was inside a really long character literal and refuse to substitute any macros until the next apostrophe.
cpp is great, but it does basically require a grammar that assigns broadly the expected meaning to ' " # // /* */. Curly-brace languages fine, running English text not so much.
Without running a preprocessor on the .mc file, and without adding "%d" support to `ShowError`, here's one other category of solution. These days, you could just write
so that if the value of MAX_PASSPHRASE_MINIMUM ever changed, you'd get a build failure right on this line and you'd be forced to fix it (part of which should involve updating the message to match).You could make that fancier by trying to craft the name of the error message itself via the preprocessor — something like:
But that would just make the compiler error (when MAX_PASSPHRASE_MINIMUM changed) a lot harder to read, without changing the essential task (go find the error message and update it), so it's not a good idea.>At one point you say "precompiler" when I guess you mean "preprocessor"?
Ah, you're right! Fixed, thanks!
>Also I think the C preprocessor would be relatively unhelpful with the file format you explained in the post: As soon as you reached the first unmatched, unquoted apostrophe, cpp would assume it was inside a really long character literal and refuse to substitute any macros until the next apostrophe.
Oh, that's a good point. I'm not sure how Visual C++ does with it, but I just tried with gcc, and it falls over on an apostrophe:
very nice anecdote, thanks for sharing it!
Somehow, for me, your code samples are appearing as black text on a black background.
Everybody in gettext land uses (s)printf format specifiers. Nobody thought to do that at MS?
The ShowError() API they're talking about doesn't take a format string. It takes a resource ID which is an integer that's essentially an index in the resource table. So running the message compiler produces a C header that defines ERROR_BITLOCKER_PASSPHRASE_MINIMUM_TOO_LONG to an integer constant, and then ShowError(ERROR_BITLOCKER_PASSPHRASE_MINIMUM_TOO_LONG) looks up the string at that index in the resource table and displays it.
https://learn.microsoft.com/en-us/windows/win32/eventlog/mes...
(You'll note that that page mentions that FormatMessage() does support printf-style format specifiers in the string resource. That's why I'm saying that their ShowError() specifically is the one that doesn't.)
I'd say that of course people at Microsoft thought to do this, twenty years prior at the time of OS/2 1.0 …
… except that it might have been people from IBM. OS/2 had a DosGetMessage() API function that looked up messages in a compiled message file and inserted strings taken from an application-supplied array wherever a %1 to a %9 occurred in the message.
It was regularly used to edit the applicable filename, drive letter, numeric values, or whatnot into error messages.
In a way this is more like Perl's maketext or maybe Mozilla Fluent. Reasonable translations need to be constructed using variable inputs and correct messages can vary greatly between languages with grammar agreement and such. While gettext does have some extra support for getting numeric translations and agreements right it is kind of hacked in as an add on to a text lookup procedure rather than in terms of constructing the messages based on inputs.
The Microsoft way is to do things backwards, make the possible impossible, and wrap simple, well-understood APIs behind 16 layers of abstraction with awful names that will ever so slightly munge your arguments. Surely you know this :-)
A product I was working on had a configuration file that was a bit like a limited programming language.
"One customer" asked me to add some compile time condition based on the platform.
I told him to use the C++ preprocessor and use #if/#ifdef
How was this supposebit locker? Every time an admin changed the group policy it would trigger a recompile of bitlocker?
No, the group policy checks are runtime checks, not compile time checks.
It meant that when a user enables BitLocker on a drive, the BitLocker setup flow would check if group policy defined a minimum password length. If it did, BitLocker would enforce that minimum. Otherwise, BitLocker would use the default minimum.
If you have a Windows system, you can play with this easily as long as you have an admin user account for the OS. The Windows system doesn't need to be part of an org or need Active Directory. You can edit group policy by opening gpedit.msc from an elevated command prompt.
My confusion was that I thought they wanted to put a group policy value into an error message, and doing so via the preprocessor didn't make any sense.
Supposed to work.
I believe that the article is referring to a maximum which is hardcoded into Bitlocker (not user configurable). If the administrator configures via GPO a minimum key length which is longer than the hardcoded maximum, then it spits out the error in question.