3

I've got a batch that has a sub-section which iterates through lines of a file for EXEs to try running, and then the batch sorts the EXEs based on their exit codes.

For some reason, the ANSI SGR seems to break or echo the literal text after setting the graphics rendition of the previous one instead of re-rendering it.

I went back to re-reference this question and the original documentation, but I'm not sure why this specific area of my batches is mangling the ANSI coloration inside the console after the first line is echoed.

I swapped my tool with just Notepad, which you can manually close for a zero-exit, or use the Control Panel to end the process to get a non-zero exit.

The contents of test_map.log shouldn't matter too much since you're actually using just Notepad and sending it some args. This is what mine are set to:

C:\temp\qt_selftest.exe
C:\temp\sub_test.exe
C:\temp\cmd_module_test.exe
C:\temp\failing_qt_test.exe
C:\temp\passing_qt_test.exe
C:\temp\random_qt_test.exe
C:\temp\fail_module.exe
C:\temp\pass_module.exe

And as you can see from the screenshot, the lines are being treated literally. Within the actual batch that I pulled this from, it does go back to working again... but within that block and only in that block, it's broken.

enter image description here

Any idea where I might be messing this up?

I can't share the code directly due to ESC sequences being converted, so here is the gist: https://gist.github.com/the-nose-knows/1bebce2719e020188c6307cff736f951

If you need to re-add them before the [, use the alt-code of 027, as alt 0 2 7

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • 1
    cmd disables the console's virtual terminal mode when it runs an external program and restores VT mode when it regains control as the foreground process -- if that helps you any. It's difficult to say more without example code to reproduce the problem. – Eryk Sun Jul 03 '17 at 22:51
  • That awkward moment when you thought you had everything before submitting the question xD Thanks, @eryksun! – kayleeFrye_onDeck Jul 03 '17 at 23:48
  • What's interesting about your commentary is that the checker tool that I swapped out with Notepad uses `bool process_created = CreateProcess(obj.process_name.c_str(), parameters, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, p_startup_info, p_proc_info);`, and I haven't done many updates to that tool lately, but I might have triggered something that changed its behavior within batch files. Maybe the fact that I'm now always filling in the cmd-params with at least the process to create has made it considered as **"external"**. – kayleeFrye_onDeck Jul 04 '17 at 00:04
  • That's the batch's output from the gist. Some of the color codes might not be 1:1, but that's because I had re-make the isolated test-batch. – kayleeFrye_onDeck Jul 04 '17 at 00:07
  • 1
    This is a bug in cmd.exe. At startup it saves the original console output mode, which has virtual terminal mode disabled. It restores this mode when it runs an external program. After the program exits it resets its own mode that has virtual terminal mode enabled. The bug is that in the `for` loop statement, once it restores the startup console mode to execute notepad.exe, it never resets to its own mode setting until after the loop exits. Apparently it depends solely on the top-level batch script evaluation loop that resets the console mode between statements. – Eryk Sun Jul 04 '17 at 01:11
  • 2
    As a workaround, you could run cmd.exe from a simple console program that enables VT mode. Then when cmd restores the original mode to run the external program, VT mode will still be enabled. – Eryk Sun Jul 04 '17 at 01:15
  • I'd be happy to accept the combination of your last two comments an actual answer. What an annoying cosmetic bug! :( – kayleeFrye_onDeck Jul 04 '17 at 01:42

1 Answers1

3

As erykson said, this can be solved by making sure Virtual Terminal Mode is enabled. If you only care about PowerShell's color, you can add the /A switch to your call to CMD.exe, otherwise you're going to want a small process that handles this kind of like a shim, but one that also makes sure VTM is enabled. This isn't wholly a bad thing, as this abstraction layer could come in handy for future use cases and bugs.

The only "weird" part of this snippet would be my rel-path usage. This snippet is a process shim from a sub-directory to run a batch shim from up one directory.

The important parts of the code are the headers to include and enabling VTM for StdOut after getting the console handle.

#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <Windows.h>

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

bool uses_whitespace(std::string test_string)
{
    size_t path_white_space_query = test_string.find(' ', 0);

    if (path_white_space_query != std::string::npos)
    {
        return true;
    }

    return false;
}

int main(int argc, char* argv[])
{
    std::string this_app_path = std::string(argv[0]);

    auto it = this_app_path.find_last_of("\\", std::string::npos);

    std::string path(this_app_path, 0, it);

    // Just forwarding the args that were sent to this shim to a batch in a known location,
    // making sure whitespace arguments keep their quotes when forwarded.

    // CMD.exe will need proper quote-handling, or the call will get mangled.
    std::string str = "C:\\Windows\\System32\\cmd.exe /C \"\"" + path + "\\..\\app_shim.bat\"";
    std::vector<std::string> args;
    std::copy(argv + 1, argv + argc, std::back_inserter(args));

    for (auto const& arg : args)
    {
        if (uses_whitespace(arg))
        {
            str += (" \"" + arg + "\"");
        }
        else
        {
            str += (" " + arg);
        }
    }

    // end-of-CMD-call final wrapping quote
    str += "\"";

    HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    DWORD mode = 0;
    GetConsoleMode(stdOutHandle, &mode);
    SetConsoleMode(stdOutHandle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);

    int exit_code = system(str.c_str());

    CloseHandle(stdOutHandle);

    return exit_code;
}
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • 2
    This should call `GetConsoleMode` and OR the VT flag into the current mode to set via `SetConsoleMode`. Also, you shouldn't need to do this for both `StandardOutput` and `StandardError`; it would be a rare situation in which these are handles for separate console screen buffers. – Eryk Sun Aug 20 '17 at 14:05
  • Hm, that's a great point about about `STDERR`; thanks for mentioning that. None of my batch echoes would be going to `STDERR`, so that should in fact be irrelevant for my use-case, as none of the apps or scripts used that I know of use ANSI coloration via `STDERR` (if that's even possible, _directly_), that _I_ know of xD The more I learn, the more I know I don't know, but it is fun to learn! ^_^ Thank you, @eryksun. – kayleeFrye_onDeck Aug 21 '17 at 07:51
  • @eryksun does the latest edit reflect what you meant in your comment? I think I understood you, and it seems to work fine, but just making sure. – kayleeFrye_onDeck Aug 21 '17 at 22:38
  • 1
    Yes, that's what I meant. – Eryk Sun Aug 22 '17 at 23:53