[How-To] Get information about the conhost window, update its mode settings (PowerShell hybrid)
Posted: 29 Oct 2022 09:19
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
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
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::