Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use ANSI escape sequences with CSCRIPT on Windows 10?

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.

enter image description here

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.

like image 362
dbenham Avatar asked Aug 05 '16 00:08

dbenham


People also ask

Does command prompt on Windows 10 support ANSI escape sequences?

I'm trying to make a fortan code for displaying a colored ASCII art of 2D graphics in command prompt on Windows 10 systems, like a code distributed in the website below. I have heard that command prompt on Windows 10 partially support ANSI escape sequences, and it is available if we enable the virtual terminal processing option.

Is it possible to enable ANSI escape sequences by Fortran code?

I have heard that command prompt on Windows 10 partially support ANSI escape sequences, and it is available if we enable the virtual terminal processing option. There are some examples in C code, but I want to make a function or subroutine to enable ANSI escape sequences by Fortran code.

How do I programmatically put the Windows Console in ANSI escape mode?

This is because the Windows console needs to be programmatically put in ANSI escape mode. I wrote two simple helper functions to setup the console and restore it before the caller program ends. In short, you need to get the output handle for the console and set ENABLE_VIRTUAL_TERMINAL_PROCESSING. Put the next code in a file named ansi_escapes.c:

What are the advantages of using ANSI escape codes?

The advantage of using ANSI escape codes is that, today, these are available on most operating systems, including Windows, and you don’t need to install third party libraries. These are well suited for simple command line applications. If you need to do complex text graphics check the ncurses library.


2 Answers

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

Output capture of test session

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

Output capture of redirected cmd.exe

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.

like image 139
MC ND Avatar answered Sep 20 '22 12:09

MC ND


Updated Answer to part2: How to make it work in CSCRIPT

I finally found a mechanism to enable VT-100 sequences within CSCRIPT at this SuperUser answer. I copied the relevant text from the answer and posted it below.

Fortunately, the global default can be changed from opt-in to opt-out. The registry key at HKEY_CURRENT_USER\Console\VirtualTerminalLevel sets the global default behavior for processing ANSI escape sequences. Create a DWORD key (if necessary) and set its value to 1 to globally enable (or 0 to disable`) ANSI processing by default.

[HKEY_CURRENT_USER\Console]
"VirtualTerminalLevel"=dword:00000001

Note that this registry setting controls a default, meaning that it only affects console apps which don't explicitly manipulate the console mode by calling SetConsoleMode(...). It follows that, while the registry value may help enable ANSI for console-mode-oblivious apps, it will have no effect on any console-mode-savvy app which (for some reason) may explicitly disable ANSI.

Note that the change only affects newly launched console windows - it will not enable VT-100 for already existing console windows.


Within this thread, DosTips user aGerman discovered you can enable the escape sequences by asynchronously launching PowerShell from within your script. PowerShell configures the output to support the escape sequences, and that support remains even after PowerShell exits, for as long as your CSCRIPT process remains active.

For example, here is some JScript code that will enable the sequences

var ps = WScript.CreateObject("WScript.Shell").Exec("powershell.exe -nop -ep Bypass -c \"exit\"");
while (ps.Status == 0) WScript.Sleep(50);


My original answer to part 1: Why doesn't it work in CSCRIPT

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:

enter image description here

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.

like image 45
dbenham Avatar answered Sep 18 '22 12:09

dbenham