Date: | February 1, 2016 / year-entry #23 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20160201-00/?p=92952 |
Comments: | 10 |
Summary: | Kicking the tires. |
Today's Little Program exercises the console alias functions.
These functions let you define console aliases which are
active when a target program reads a line of text from the console.
The alias is recognized when it is entered at the start of a line.
(Therefore, a way to defeat an alias is to put a space in front of it.)
More details about console aliases can be found in
the documentation for The program we'll write has five commands:
Let's dive in. #define UNICODE #define _UNICODE #include <windows.h> #include <iostream> #include <vector> void do_add(int argc, wchar_t **argv); void do_delete(int argc, wchar_t **argv); void do_show(int argc, wchar_t **argv); void do_showall(int argc, wchar_t **argv); void do_showexes(int argc, wchar_t **argv); int __cdecl wmain(int argc, wchar_t **argv) { auto command = argv[1]; if (wcscmp(command, L"add") == 0) { do_add(argc, argv); } else if (wcscmp(command, L"delete") == 0) { do_delete(argc, argv); } else if (wcscmp(command, L"show") == 0) { do_show(argc, argv); } else if (wcscmp(command, L"showall") == 0) { do_showall(argc, argv); } else if (wcscmp(command, L"showexes") == 0) { do_showexes(argc, argv); } return 0; } The main program looks at the first command line argument and dispatches the rest of the work to the appropriate handler function. Now let's look at each of the handlers. Remember, Little Programs do little to no error checking. void do_add(int argc, wchar_t **argv) { auto program = argv[2]; auto alias = argv[3]; auto value = argv[4]; if (AddConsoleAlias(alias, value, program)) { std::wcout << alias << L"=" << value << std::endl; } else { std::wcout << L"Failed to add alias" << std::endl; } }
To add an alias, we call scratch add cmd.exe proj "cd /D \"%USERPROFILE%\Documents\Visual Studio 2015\Projects\$*\"" (All one line; split into two for expository purposes.)
This lets you type Next is deletion: To delete an alias, you set it to a null pointer. void do_delete(int argc, wchar_t **argv) { auto program = argv[2]; auto alias = argv[3]; if (AddConsoleAlias(alias, nullptr, program)) { std::wcout << alias << L" deleted" << std::endl; } else { std::wcout << L"Failed to delete alias" << std::endl; } }
Continuing our example, if you get bored of the The next command is for showing the value of an alias, void do_show(int argc, wchar_t **argv) { auto program = argv[2]; auto alias = argv[3]; wchar_t value[8192]; if (GetConsoleAlias(alias, value, sizeof(value), program)) { std::wcout << alias << L"=" << value << std::endl; } else { std::wcout << L"Cannot show (maybe it isn't defined)" << std::endl; } }
There is no way to query the length of an alias's value,
but since
the maximum command line length supported by The last two commands are for showing all the aliases defined for a specific program, and for showing the programs that have aliases defined. The two functions are very similar, so we present them together. First, a simple version that is subtly defective: // code in italics is wrong void do_showall(int argc, wchar_t **argv) { auto program = argv[2]; auto bytes = GetConsoleAliasesLength(program); std::vector<wchar_t> buffer( (bytes + sizeof(wchar_t) + 1) / sizeof(wchar_t)); if (GetConsoleAliases(buffer.data(), bytes, program)) { for (auto current = buffer.data(); current < buffer.data() + buffer.size(); current += wcslen(current) + 1) { std::wcout << current << std::endl; } } } void do_showexes(int argc, wchar_t **argv) { auto bytes = GetConsoleAliasExesLength(); std::vector<wchar_t> buffer( (bytes + sizeof(wchar_t) + 1) / sizeof(wchar_t)); if (GetConsoleAliasExes(buffer.data(), bytes)) { for (auto current = buffer.data(); current < buffer.data() + buffer.size(); current += wcslen(current) + 1) { std::wcout << current << std::endl; } } }
One annoyance here is that the
The next annoyance is that the Not knowing when you have reached the end of the valid data is a really bad situation for a program to be in.
We can work around this problem by zeroing out the memory before we call
On the other hand, if that didn't happen, then we want to stop when we reached the end of the buffer.
The final problem is that another thread could add an alias
in between our calls to
All of the preceding issues with
Here's the resulting code that tries to solve all of the problems: template<typename GetLengthBytes, typename GetContents> void PrintAliasValue( const GetLengthBytes& getLengthBytes, const GetContents& getContents) { std::vector<wchar_t> buffer; do { auto bytes = getLengthBytes(); auto length = (bytes + sizeof(wchar_t) - 1) / sizeof(wchar_t); buffer.resize(length); ZeroMemory(buffer.data(), bytes); SetLastError(ERROR_SUCCESS); if (getContents(buffer.data(), bytes)) { for (auto current = buffer.data(); current < buffer.data() + buffer.size() && *current; current += wcslen(current) + 1) { std::wcout << current << std::endl; } } } while (GetLastError() == ERROR_MORE_DATA); } void do_showall(int argc, wchar_t **argv) { auto program = argv[2]; PrintAliasValue( [program]() { return GetConsoleAliasesLength(program); }, [program](LPTSTR buffer, DWORD length) { return GetConsoleAliases(buffer, length, program); }); } void do_showexes(int argc, wchar_t **argv) { PrintAliasValue( []() { return GetConsoleAliasExesLength(); }, [](LPTSTR buffer, DWORD length) { return GetConsoleAliasExes(buffer, length); }); } The underlying algorithm is the same: Get the byte length, allocate a vector of characters, zero-initialize the data in the vector so that we can detect that a short buffer was returned, then ask for the data. If it succeeds, then read out the data, but stop when we hit one of our preallocated zeroes, or when we reach the end of the buffer, whichever comes first. If it fails because the buffer is too small, then loop back and try again.
So there you have it.
A quick tour of the console alias functions.
Now you can write your own |
Comments (10)
Comments are closed. |
I have used doskey in the console autorun value for 15+ years and the one annoying thing about it is that doskey crashes every now and then when started from that key. Maybe this series(?) is the kick in the butt I need to fix this myself once and for all.
Is there any hope of getting the history of some of the other console stuff like full graphics support, custom menus and the other undocumented things that go back to the early days of NT?
Didn’t Microsoft beat its chest claiming API’s are no longer UCS-2 but UTF-16? (we all know that’s impossible, especially if you think of NTFS and $UpCase, but let’s play along). So potentially, you’d need 4, 6 or 8 times the amount of memory, why returning # of bytes is definitely the most sane (the opposite, returning # of glyphs, would be insane).
I think I prefer byte count over some randomized glyph-count (especially when it’s based on a lie).
Most Win32 functions return the number of TCHARs; i.e., the number of code units. There may be some special-purpose functions that return the number of code points (which I think is what you mean by glyphs), but they are the exception rather than the rule.
I don’t see that $UpCase is a problem per se, assuming that it translates the surrogates to themselves. That means anything outside the BMP plane is necessarily case-sensitive, but does that matter?
What makes this method better than adding a directory to the PATH that contains useful .bat files?
This can be used to create aliases for any app that reads command lines (such as ntsd and cdb or my fake debugger app).
>More details about console aliases can be found in the documentation for DOSKEY.
Uhh, not they aren’t. Only one occurrence of “console” on that page and it doesn’t mention aliases.
Console aliases are covered here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682057%28v=vs.85%29.asp
Hey look, that page says “For more information on the special codes that can be used in Doskey macro definitions, see the command-line help for Doskey.exe or Doskey on TechNet.”
Can you believe that the DOSKEY instructions include this?
To create a macro that performs a quick and unconditional format of a disk, type:
doskey qf=format $1 /q /u
To format a disk in drive A quickly and unconditionally, type:
qf a: