I think it can't be done using pure batch (or i haven't found a way yet).
You could use jscript to insert a return input (WshShell.SendKeys("{ENTER}");) and read the result (while (!WScript.StdIn.AtEndOfLine) { input += WScript.StdIn.Read(1);}). But those i/o functions are all high level iostream routines, which results in an unwanted echo of the report on the screen.
The only solution i saw is using the (c++) low level routines "_kbhit()" and "_getwch()" invoked by a c# script.
While the report-device-attributes actually aren't really needed (Microsoft provided "VT101 with No Options" only, but in theory you could use additional software to add VT400 handling to console), the report-cursor-position might be more usefull if you want to create batches using vt100 sequences and displayed user input, that must be protected from beeing overwritten,
Here is a sample "report.vt101.bat" (which creates "fibto.exe" from c# code; tested on Win 10.0.18362.657):
Code: Select all
@echo off
setlocal enableExtensions disableDelayedExpansion
if not exist "fibto.exe" call :init
if exist "fibto.pdb" del "fibto.pdb"
for /f %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"
call :getCursorPosition "$cursorPos1"
call :getDeviceAttributes "$deviceAttrib"
set "$"
<nul set /p "=__________"
call :getCursorPosition "$cursorPos2"
set "$cursorPos2"
goto :eof
:: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#query-state
:: Report Cursor Position : %ESC%[6n
:getCursorPosition
:: %~1 environment variable name to store the result into
if "%~1" == "" goto :eof
set "%~1="
<nul set /p "=%ESC%[6n"
for /f "tokens=* delims=" %%a in ('"fibto.exe"') do set "%~1=%%~a"
goto :eof
:: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#query-state
:: Report Device Attributes: %ESC%[0c
:getDeviceAttributes
:: %~1 environment variable name to store the result into
if "%~1" == "" goto :eof
set "%~1="
<nul set /p "=%ESC%[0c"
for /f "tokens=* delims=" %%a in ('"fibto.exe"') do set "%~1=%%~a"
goto :eof
:compile
for /f "tokens=1 delims=:" %%a in ('findstr /o /c:"%~1" "%~f0"') do set /a "offset=%%~a"
for /f "tokens=1 delims=:" %%a in ('findstr /n /r /c:"^.Source = @" "%~f0"') do set /a "sourceOffset=%%~a"
for %%a in ("%ComSpec%") do set "ps.exe=%%~dpaWindowsPowerShell\v1.0\powershell.exe"
if not exist "%ps.exe%" exit /b 2 ERROR_FILE_NOT_FOUND
call rem init ERROR_SUCCESS
"%ps.exe%" "-Command" "$SourceOffset = "%sourceOffset%"; Invoke-Expression $([System.IO.File]::ReadAllText('%~f0').substring(%offset%))"
del
exit /b %errorLevel%
:init
call :compile ^
#powershell
<# <nul exit /b %errorLevel%
hybrid batch function/powershell comment
#>
Function Get-CurrentLine {
$Myinvocation.ScriptlineNumber
}
$CompilerAssemblies = (
"System",
"Microsoft.CSharp"
)
$CompilerSource = @"
using System;
using System.Collections.Generic;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
namespace Penpen {
public static class Compiler {
public static void Compile(string[] assemblies, string source, string mainClass, string exeFileName) {
var options = new Dictionary<string, string> { { "CompilerVersion", "v4.0" } };
CSharpCodeProvider compiler = new CSharpCodeProvider(options);
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
parameters.OutputAssembly = exeFileName;
if (mainClass != null) {
parameters.MainClass = mainClass; // set main entry point of the executable
}
if (assemblies != null) {
for (int i = 0; i < assemblies.Length; ++i) {
parameters.ReferencedAssemblies.Add(assemblies[i] + ".dll");
}
}
parameters.GenerateExecutable = true; // true: generate exe; false: generate dll
parameters.GenerateInMemory = false; // true: memory; false: disk
parameters.IncludeDebugInformation = true; // true: debug; false: release
parameters.WarningLevel = 4; // 0: no; 1: severe; 2: +normal; 3: +less severe; 4(default): +informational
parameters.TreatWarningsAsErrors = true;
CompilerResults results = compiler.CompileAssemblyFromSource(parameters, source);
if (results.Errors.Count > 0) {
foreach(CompilerError CompErr in results.Errors) {
Console.WriteLine(
"Line number {0}, Error Number: {1}, '{2}';\r\n",
(CompErr.Line +
"@
$CompilerSource += $sourceOffset
$CompilerSource += @"
),
CompErr.ErrorNumber,
CompErr.ErrorText
);
}
}
}
}
}
"@
$Assemblies = (
"System",
"System.Runtime.InteropServices"
)
$Source = @"
using System;
using System.Runtime.InteropServices;
using wchar_t = System.Char;
namespace Penpen {
public static class FlushInBuffToOut {
[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
static extern int _kbhit();
[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl)]
static extern wchar_t _getwch();
public const wchar_t ESC = '\u001B';
public static void Main() {
wchar_t wch;
while (_kbhit() != 0) {
wch = _getwch();
if (wch == ESC) {
Console.Write("{0}", "{ESC}");
} else {
Console.Write("{0}", wch);
}
}
}
}
}
"@
#Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $Source -Language CSharp
#[Penpen.FlushInBuffToOut]::Main()
Add-Type -ReferencedAssemblies $CompilerAssemblies -TypeDefinition $CompilerSource -Language CSharp
[Penpen.Compiler]::Compile($Assemblies, $Source, "Penpen.FlushInBuffToOut", "fibto.exe")
penpen