Page 1 of 1

[How-To] Get information about the conhost window, update its mode settings (PowerShell hybrid)

Posted: 29 Oct 2022 09:19
by aGerman
The code below contains two macros.

1) The %ConsoleInfo% macro outputs a sequence of "name=value" pairs which provide information about the console window, such like its appearance, settings, and environment.

2) The %SetConsoleMode% macro updates mode settings of the console window. The mode is an integral value which represents a bitset of flags. It is initially received from the %ConsoleInfo% macro in value "cmo". In a SET /A satement, flags can be set or removed using bitwise OR (|) or the combination of bitwise AND and NOT (&~), respectively. Therefore, supported flags can be defined as environment variables by calling the :init_ModeFlags subroutine.

Steffen

Code: Select all

@echo off &setlocal

call :init_ConsoleInfo

for /f %%i in ('%ConsoleInfo%') do set /a "%%i"
echo terminal app identifier: %cta%
echo console monitor boundaries: %cml%, %cmt%, %cmr%, %cmb%
echo console window size: %cwx%, %cwy%
echo console viewport size: %cvx%, %cvy%
echo console font size: %cfx%, %cfy%
echo console charcell size: %chx%, %chy%
echo console current size: %ccx%, %ccy%
echo console largest size: %clx%, %cly%
%comspec% /c exit %cmo%
echo console I/O modes: 0x%=ExitCode:~0,4% / 0x%=ExitCode:~-4%
echo(
pause

if %cta% equ 0 exit /b

cls
call :init_ModeFlags
call :init_SetConsoleMode
echo(
echo Enable Quick Edit Mode ...
set /a "newmode1=cmo | ENABLE_QUICK_EDIT_MODE"
%SetConsoleMode% %newmode1%
echo *** Try to mark something with your mouse. ***
pause

echo(
echo Disable Quick Edit Mode ...
set /a "newmode2=newmode1 & ~ENABLE_QUICK_EDIT_MODE"
%SetConsoleMode% %newmode2%
echo *** Try again to mark something. ***
pause

goto :eof

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init_ConsoleInfo
setlocal
:: prefer PowerShell Core if installed
for %%i in ("pwsh.exe") do if "%%~$PATH:i"=="" (set "ps=powershell") else set "ps=pwsh"

:: - BRIEF -
:: The %ConsoleInfo% macro collects properties of the console window and its environment.
::  It prints a string in the following format to StdOut:
::   cta=N,cml=N,cmt=N,cmr=N,cmb=N,cwx=N,cwy=N,cvx=N,cvy=N,cfx=N,cfy=N,chx=N,chy=N,ccx=N,ccy=N,clx=N,cly=N,cmo=N
::    or
::   cta=0
::  With:
::   cta  identifier for the terminal app
::         0  not a console (perhaps Windows Terminal)
::             NOTE: all other values are removed from the output whenever the
::                    ID is 0, because they are only valid for the console host
::         1  legacy console
::         2  console V2 (Windows 10 onwards)
::   cml  left dots boundary of the monitor's workspace
::   cmt  top dots boundary of the monitor's workspace
::   cmr  right dots boundary of the monitor's workspace
::   cmb  bottom dots boundary of the monitor's workspace
::   cwx  current width of the console window as number of dots
::   cwy  current height of the console window as number of dots
::   cvx  current width of the console viewport as number of dots
::   cvy  current height of the console viewport as number of dots
::   cfx  console font width as number of pixels
::   cfy  console font height as number of pixels
::   chx  approximated console charcell width as number of dots
::   chy  approximated console charcell height as number of dots
::   ccx  current width of the console client window as number of character cells
::   ccy  current height of the console client window as number of character cells
::   clx  largest width of the console client window as number of character cells
::   cly  largest height of the console client window as number of character cells
::   cmo  bitset of console mode flags, where the 16 high order bits represent
::         the input mode while the 16 low order bits represent the output mode,
::         refer to https://learn.microsoft.com/en-us/windows/console/getconsolemode
::   N    different integral values of the above properties
:: - SYNTAX -
::  %ConsoleInfo%
:: - EXAMPLES -
::  Print the values to the screen:
::    %ConsoleInfo%
::  Define variables %cfx% .. %cmo%:
::    FOR /F %%I IN ('%ConsoleInfo%') DO SET /A "%%I"
set ConsoleInfo=%ps%.exe -nop -ep Bypass -c ^"^
%=% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void SetProcessDPIAware(^);^
%===% [DllImport(\"shcore.dll\"^)]^
%=====% public static extern void SetProcessDpiAwareness(int value^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetStdHandle(int idx^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetConsoleMode(IntPtr h, ref int mode^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern int SetConsoleMode(IntPtr h, int mode^);^
%====% [DllImport(\"user32.dll\")]^
%=====% public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr GetConsoleWindow(^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetWindowRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetClientRect(IntPtr hwnd, int[] rect^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern void GetMonitorInfoW(IntPtr hMonitor, int[] lpmi^);^
%===% [DllImport(\"user32.dll\"^)]^
%=====% public static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern IntPtr CreateFile(string name, int acc, int share, IntPtr sec, int how, int flags, IntPtr tmplt^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void GetCurrentConsoleFont(IntPtr hOut, int isMax, int[] info^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern int GetConsoleFontSize(IntPtr hOut, int nFont^);^
%===% [DllImport(\"kernel32.dll\"^)]^
%=====% public static extern void CloseHandle(IntPtr h^);^
%=% ';^
 ^
%=% $PROCESS_PER_MONITOR_DPI_AWARE=2;^
%=% try {$w::SetProcessDpiAwareness($PROCESS_PER_MONITOR_DPI_AWARE^)} catch {$w::SetProcessDPIAware(^)}^
 ^
%=% $STD_INPUT_HANDLE=-10;^
%=% $imo=0;^
%=% $hin=$w::GetStdHandle($STD_INPUT_HANDLE^);^
%=% $w::GetConsoleMode($hin, [ref]$imo^);^
 ^
%=% $NULLPTR=[IntPtr]::Zero;^
%=% $WM_GETICON=0x007F;^
%=% $ENABLE_VIRTUAL_TERMINAL_INPUT=0x0200;^
%=% $cta=0;^
%=% $hwnd=$w::GetConsoleWindow(^);^
%=% if ($w::SendMessageW($hwnd, $WM_GETICON, $NULLPTR, $NULLPTR^) -ne $NULLPTR^) {^
%===% if ($w::SetConsoleMode($hin, $imo -bOr $ENABLE_VIRTUAL_TERMINAL_INPUT^) -eq 0^) {$cta=1} else {$cta=2}^
%===% $null=$w::SetConsoleMode($hin, $imo^);^
%=% }^
 ^
%=   Get outta here if this is not a conhost window   =% ^
%=% if (-not $cta^) {'cta=0'; exit 0}^
 ^
%=   The $moninf array is a replacement for the MONITORINFO structure. The elements at index         =% ^
%=   5, 6, 7, and 8 represent left, top, right, and bottom boundaries of the monitor's work space.   =% ^
%=% $moninf=[int[]]::new(10^);^
%=% $moninf[0]=40;^
%=% $MONITOR_DEFAULTTONEAREST=2;^
%=% $w::GetMonitorInfoW($w::MonitorFromWindow($hwnd, $MONITOR_DEFAULTTONEAREST^), $moninf^);^
 ^
%=   The $wrect array is a replacement for the RECT structure. The elements at index   =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the window.   =% ^
%=% $wrect=[int[]]::new(4^);^
%=% $w::GetWindowRect($hwnd, $wrect^);^
 ^
%=   The $crect array is a replacement for the RECT structure. The elements at index          =% ^
%=   0, 1, 2, and 3 represent left, top, right, and bottom boundaries of the client window.   =% ^
%=% $crect=[int[]]::new(4^);^
%=% $w::GetClientRect($hwnd, $crect^);^
 ^
%=% $GENERIC_READ=0x80000000;^
%=% $GENERIC_WRITE=0x40000000;^
%=% $FILE_SHARE_WRITE=0x00000002;^
%=% $OPEN_EXISTING=3;^
%=   Because the StdOut stream is redirected to a pipe behind the scenes of a FOR /F loop,   =% ^
%=   we need to explicitly open a handle to the console output device (alias 'CONOUT$').     =% ^
%=% $hout=$w::CreateFile('CONOUT$', $GENERIC_READ -bOr $GENERIC_WRITE, $FILE_SHARE_WRITE, $NULLPTR, $OPEN_EXISTING, 0, $NULLPTR^);^
%=   The $fntinf array is a replacement for the CONSOLE_FONT_INFO structure.   =% ^
%=% $fntinf=[int[]]::new(2^);^
%=% $w::GetCurrentConsoleFont($hout, 0, $fntinf^);^
%=   The Microsoft docs state we shouldn't rely on the font size that we already   =% ^
%=   got from GetCurrentConsoleFont. Refer to                                      =% ^
%=   learn.microsoft.com/en-us/windows/console/console-font-info                   =% ^
%=   The 16 low order bits of the return value of GetConsoleFontSize represent     =% ^
%=   the font width, while its 16 high order bits represent the font height.       =% ^
%=% $fsize=$w::GetConsoleFontSize($hout, $fntinf[0]^);^
%=% $omo=0;^
%=% $w::GetConsoleMode($hout, [ref]$omo^);^
%=% $w::CloseHandle($hout^);^
 ^
%=% $raw=$host.UI.RawUI;^
%=% $cur=$raw.WindowSize;^
%=% $lrg=$raw.MaxPhysicalWindowSize;^
 ^
%=% 'cta={0},cml={1},cmt={2},cmr={3},cmb={4},cwx={5},cwy={6},cvx={7},cvy={8},cfx={9},cfy={10},chx={11},chy={12},ccx={13},ccy={14},clx={15},cly={16},cmo={17}' -f^
%===% $cta,^
%===% $moninf[5], $moninf[6], $moninf[7], $moninf[8],^
%===% ($wrect[2]-$wrect[0]^), ($wrect[3]-$wrect[1]^),^
%===% ($crect[2]-$crect[0]^), ($crect[3]-$crect[1]^),^
%===% ($fsize -bAnd 0xFFFF^), ($fsize -shr 16^),^
%===% [math]::Round(($crect[2] - $crect[0]^) / $cur.Width^), [math]::Round(($crect[3] - $crect[1]^) / $cur.Height^),^
%===% $cur.Width, $cur.Height,^
%===% $lrg.Width, $lrg.Height,^
%===% (($imo -shl 16^) -bOr $omo^);^
 ^"

endlocal &set "ConsoleInfo=%ConsoleInfo%"
exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init_ModeFlags
:: Flags to be used to update the cmo bitset provided by the ConsoleInfo macro
::  and passed to the SetConsoleMode macro.
:: For more information refer to:
::  https://learn.microsoft.com/en-us/windows/console/getconsolemode
:: The flags for the input mode are shifted into the 16 high order bits to be
::  able to get both the input flags and the output flags merged in one value.
:: ENABLE_AUTO_POSITION is undocumented.
:: Setting or removing ENABLE_EXTENDED_FLAGS is not effective because the
::  SetConsoleMode macro will overrule you by setting this flag by default. This
::  is necessary to make updates of ENABLE_QUICK_EDIT_MODE work.
set /a ^"^
 %= flags for the input mode =%^
  ENABLE_PROCESSED_INPUT             = 0x00010000,^
  ENABLE_LINE_INPUT                  = 0x00020000,^
  ENABLE_ECHO_INPUT                  = 0x00040000,^
  ENABLE_WINDOW_INPUT                = 0x00080000,^
  ENABLE_MOUSE_INPUT                 = 0x00100000,^
  ENABLE_INSERT_MODE                 = 0x00200000,^
  ENABLE_QUICK_EDIT_MODE             = 0x00400000,^
  ENABLE_EXTENDED_FLAGS              = 0x00800000,^
  ENABLE_AUTO_POSITION               = 0x01000000,^
  ENABLE_VIRTUAL_TERMINAL_INPUT      = 0x02000000,^
 %= flags for the output mode =%^
  ENABLE_PROCESSED_OUTPUT            = 0x00000001,^
  ENABLE_WRAP_AT_EOL_OUTPUT          = 0x00000002,^
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x00000004,^
  DISABLE_NEWLINE_AUTO_RETURN        = 0x00000008,^
  ENABLE_LVB_GRID_WORLDWIDE          = 0x00000010^"

exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init_SetConsoleMode
setlocal DisableDelayedExpansion
:: prefer PowerShell Core if installed
for %%i in ("pwsh.exe") do if "%%~$PATH:i"=="" (set "ps=powershell") else set "ps=pwsh"

:: - BRIEF -
::  Set the mode of the current console host session.
::   The mode is passed as integer which represents a bitset.
::   The bitset consists of flags that are defined in the :init_ModeFlags routine.
::   It is recommended to get the initial console mode flags from the %ConsoleInfo%
::   macro first, and only modify the flags of your interest.
:: - SYNTAX -
::  %SetConsoleMode% %cmo%
::    with %cmo% containing the new console mode flags
:: - EXAMPLES -
::  Enable quick edit mode:
::    for /f %%i in ('%ConsoleInfo%') do set /a "%%i"
::    set /a "cmo |= ENABLE_QUICK_EDIT_MODE"
::    %SetConsoleMode% %cmo%
::  Disable quick edit mode:
::    for /f %%i in ('%ConsoleInfo%') do set /a "%%i"
::    set /a "cmo &= ~ENABLE_QUICK_EDIT_MODE"
::    %SetConsoleMode% %cmo%
set SetConsoleMode=for %%- in (1 2) do if %%-==2 (for /f %%. in ("^^!arg^^! x") do^
%=% %ps%.exe -nop -ep Bypass -c ^"^
%===% $w=Add-Type -Name WAPI -PassThru -MemberDefinition '^
%=====% [DllImport(\"kernel32.dll\")]^
%=======% public static extern IntPtr GetStdHandle(int idx);^
%=====% [DllImport(\"kernel32.dll\")]^
%=======% public static extern int SetConsoleMode(IntPtr h, int mode);^
%===% ';^
%===% $mode=0;^
%===% if (-not [Int32]::TryParse('%%~.', [ref]$mode) -or $mode -lt 0) {exit 1}^
%===% $STD_INPUT_HANDLE=-10;^
%===% $STD_OUTPUT_HANDLE=-11;^
%===% $inret=$w::SetConsoleMode($w::GetStdHandle($STD_INPUT_HANDLE), ($mode -shr 16) -bOr 0x80);^
%===% $outret=$w::SetConsoleMode($w::GetStdHandle($STD_OUTPUT_HANDLE), $mode -bAnd 0xFFFF);^
%===% exit (-not $inret -or -not $outret);^
%=% ^" ^&endlocal) else setlocal EnableDelayedExpansion ^&set arg=

endlocal &set "SetConsoleMode=%SetConsoleMode%"
if !!# neq # set "SetConsoleMode=%SetConsoleMode:^^=%"
exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::