How to use ANSI escape sequences with CSCRIPT on W

2019-02-17 09:40发布

问题:

I'm trying to use the new VT100 ANSI escape sequence capabilities available in the Windows 10 console with CSCRIPT (JScript). But I cannot get it to work.

Here is a really simple JScript script:

test.js

WScript.Echo('\x1B[7mReverse\x1B[0m Normal');
WScript.stdout.WriteLine('\x1B[7mReverse\x1B[0m Normal');

I've done a number of tests, and the escape sequences output by CSCRIPT are impotent when written directly to the screen, and only work if written to a file first and then TYPEed, or else captured by FOR /F and ECHOed.

I have two questions:

1) Why doesn't the direct write to the console work from CSCRIPT?
2) How can I get the direct write to work?

I would like to add text highlighting to my JREPL.BAT regular expression find/replace utility (hence the batch-file tag), but I will not implement that feature if it requires a temporary file and/or FOR /F.

回答1:

MS documentation states

The following terminal sequences are intercepted by the console host when written into the output stream if the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag is set on the screen buffer handle using the SetConsoleMode flag. You can use GetConsoleMode and SetConsoleMode flags to configure this behavior.

So, just to test, I wrote a simple C program to change the console mode and then act as a pipe or launch another process and wait (sorry, just test code).

#define _WIN32_WINNT   0x0500
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004

int _tmain(int argc, TCHAR *argv[]){

    // Console handlers
    DWORD dwOldMode, dwMode ;
    HANDLE hStdout;

    // Pipe read buffer
    int c;

    // Spawn process variables
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    // Retrieve standard output handle
    hStdout = GetStdHandle( STD_OUTPUT_HANDLE );
    if (! GetConsoleMode( hStdout, &dwOldMode ) ) {
        return 1;
    }

    // Change standard output handle
    dwMode = dwOldMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    if (! SetConsoleMode( hStdout, dwMode ) ){
        CloseHandle( hStdout );
        return 2;
    }

    if( argc < 2 ) {
        // If there is not an argument, read stdin / write stdout 
        while ( EOF != (c = getchar()) ) putchar( c );    
    } else {
        // Argument is present, create a process and wait for it to end
        ZeroMemory( &si, sizeof(si) );
        si.cb = sizeof(si);
        ZeroMemory( &pi, sizeof(pi) );
        if( !CreateProcess(NULL, argv[1], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi )){
            printf( "CreateProcess failed (%d).\n", GetLastError() );
            return 3;
        }
        WaitForSingleObject( pi.hProcess, INFINITE );
        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );    
    }

    // Restore old console mode
    SetConsoleMode( hStdout, dwOldMode );
    CloseHandle( hStdout );

    return 0;
};

Compiled to run.exe with mingw/gcc. The results are

Now, the output from cscript and findstr is processed and the escape sequences are interpreted.

Also, if instead of running the separate programs, I run cmd.exe itself

Since I have not changed the code from findstr.exe, cscript.exe or cmd.exe, only the environment where they are working it seems that

  • neither cscript nor findstr configure/change the console buffer configuration

  • some internal cmd commands change the buffer configuration (I forget to include it in the capture, but copy test.txt con and prompt also work) or, as you point, they use a different output method

  • the only requirement for an application that writes to the standard output stream is that the console output buffer mode is properly configured.

And no, I don't know how to enable it from pure batch.



回答2:

OK, I think I have a viable theory as to why it doesn't work. I believe that there must be some low level way/call/function/method (whatever) to pass stdout to the console that only a few internal commands know about. I base this on the fact that FINDSTR also cannot send functioning escape sequences to the console, as shown below:

I've already shown that both TYPE and ECHO work. I've also verified that SET /P works (not shown). So I suspect that cmd.exe was modified to support the new Windows 10 console functionality.

I would love to see some MS documentation describing the required mechanism to send escape sequences to the console.