Page 1 of 1

Set Window Topmost (Powershell hybrids)

Posted: 11 Feb 2019 12:08
by aGerman
The macros in the code below expect one argument. Pass a 1 to set the window topmost or a 0 to undo this setting.
The xtopmost macro is very restrictive because it makes closing the window quite difficult. Users might feel annoyed. You should avoid using it unless you have a real good reason.

Code: Select all

@echo off

setlocal DisableDelayedExpansion
:: setlocal EnableDelayedExpansion

call :init_topmost
call :init_xtopmost

echo(&echo  set topmost
%topmost% 1
if errorlevel 1 (echo error) else echo success
pause

echo(&echo  reset
%topmost% 0
if errorlevel 1 (echo error) else echo success
pause

echo(&echo  set topmost (extended)
%xtopmost% 1
if errorlevel 1 (echo error) else echo success
pause

echo(&echo  reset
%xtopmost% 0
if errorlevel 1 (echo error) else echo success
pause

exit /b


:: place the window above all windows and maintain this order
:init_topmost
setlocal DisableDelayedExpansion
set topmost=for %%i in (1 2) do if %%i==2 (^
%=% for /f %%j in ("^^!arg^^! x") do powershell -nop -ep Bypass -c ^"try{$c=Add-Type -Name pInv -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\")] public static extern int SetWindowPos(IntPtr wnd,IntPtr insertAfter,int x,int y,int cx,int cy,uint flags);^
%=====% [DllImport(\"kernel32.dll\")] public static extern IntPtr GetConsoleWindow();';^
%=====% $flag=0;^
%=====% if(-not [Int32]::TryParse(\"%%~j\",[ref]$flag) -or ($flag -ne 0 -and $flag -ne 1)){^
%=========% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%topmost%% flag`r`n`tflag`t1 set topmost, 0 restore defaults\");^
%=========% exit 1;}^
%=====% exit ($c::SetWindowPos($c::GetConsoleWindow(),[IntPtr]($flag-2),0,0,0,0,3) -eq 0);}catch{exit 1;}^"^&endlocal^
%=% ) else setlocal EnableDelayedExpansion^&set arg=

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


:: place the window above all windows and maintain this order; disable minimizing, maximizing, and closing of the window; no task bar icon
:init_xtopmost
setlocal DisableDelayedExpansion
set xtopmost=for %%i in (1 2) do if %%i==2 (^
%=% for /f %%j in ("^^!arg^^! x") do powershell -nop -ep Bypass -c ^"try{$c=Add-Type -Name pInv -PassThru -MemberDefinition '^
%=====% [DllImport(\"user32.dll\")] public static extern int SetWindowLong(IntPtr wnd,int idx,int newLong);^
%=====% [DllImport(\"user32.dll\")] public static extern Int64 SetWindowLongPtr(IntPtr wnd,int idx,Int64 newLong);^
%=====% [DllImport(\"user32.dll\")] public static extern int GetWindowLong(IntPtr wnd,int idx);^
%=====% [DllImport(\"user32.dll\")] public static extern Int64 GetWindowLongPtr(IntPtr wnd,int idx);^
%=====% [DllImport(\"user32.dll\")] public static extern int SetWindowPos(IntPtr wnd,IntPtr insertAfter,int x,int y,int cx,int cy,uint flags);^
%=====% [DllImport(\"user32.dll\")] public static extern IntPtr GetSystemMenu(IntPtr wnd,int revert);^
%=====% [DllImport(\"user32.dll\")] public static extern int DeleteMenu(IntPtr menu,uint id,uint flags);^
%=====% [DllImport(\"kernel32.dll\")] public static extern IntPtr GetConsoleWindow();';^
%=====% $flag=0;^
%=====% if(-not [Int32]::TryParse(\"%%~j\",[ref]$flag) -or ($flag -ne 0 -and $flag -ne 1)){^
%=========% [Console]::Error.WriteLine(\"Syntax Error`r`n Usage:`r`n%%xtopmost%% flag`r`n`tflag`t1 set topmost, 0 restore defaults\");^
%=========% exit 1;}^
%=====% $wnd=$c::GetConsoleWindow();^
%=====% if($flag -eq 1){^
%=========% if([IntPtr]::Size -eq 4){$null=$c::SetWindowLong($wnd,-16,$c::GetWindowLong($wnd,-16) -bAnd -bNot 131072 -bAnd -bNot 65536);^
%=============% $null=$c::SetWindowLong($wnd,-20,($c::GetWindowLong($wnd,-20) -bAnd -bNot 262144) -bOr 128);}^
%=========% else{$null=$c::SetWindowLongPtr($wnd,-16,$c::GetWindowLongPtr($wnd,-16) -bAnd -bNot [Int64]131072 -bAnd -bNot [Int64]65536);^
%=============% $null=$c::SetWindowLongPtr($wnd,-20,($c::GetWindowLongPtr($wnd,-20) -bAnd -bNot [Int64]262144) -bOr [Int64]128);}^
%=========% exit ($c::DeleteMenu($c::GetSystemMenu($wnd,0),61536,0) -eq 0 -or $c::SetWindowPos($wnd,[IntPtr]-1,0,0,0,0,3) -eq 0);}^
%=====% if([IntPtr]::Size -eq 4){$null=$c::SetWindowLong($wnd,-20,($c::GetWindowLong($wnd,-20) -bAnd -bNot 128) -bOr 262144);^
%=========% $null=$c::SetWindowLong($wnd,-16,$c::GetWindowLong($wnd,-16) -bOr 131072 -bOr 65536);}^
%=====% else{$null=$c::SetWindowLongPtr($wnd,-20,($c::GetWindowLongPtr($wnd,-20) -bAnd -bNot [Int64]128) -bOr [Int64]262144);^
%=========% $null=$c::SetWindowLongPtr($wnd,-16,$c::GetWindowLongPtr($wnd,-16) -bOr [Int64]131072 -bOr [Int64]65536);}^
%=====% exit ($c::GetSystemMenu($wnd,1) -ne [IntPtr]::Zero -or $c::SetWindowPos($wnd,[IntPtr]-2,0,0,0,0,3) -eq 0);}catch{exit 1;}^"^&endlocal^
%=% ) else setlocal EnableDelayedExpansion^&set arg=

endlocal&set "xtopmost=%xtopmost%"
if !!# neq # set "xtopmost=%xtopmost:^^=%"
exit /b
It's not too difficult to find the references of the API functions used, in case you want to understand the code. However, the numeric constants are not as self-explanatory as the C macro names. A little more information doesn't hurt ...

Code: Select all

:: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ magic numbers used in the code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
:: use Get/SetWindowLong for a 32 bit process, but Get/SetWindowLongPtr for a 64 bit process
::           4      size of a memory address in a 32 bit process (it's 8 bytes for a 64 bit process), the size of IntPtr reflects this fact
::
:: window styles
::         -16      GWL_STYLE                       index used to get/set style information
::       65536      WS_MAXIMIZEBOX                  used to show/hide the maximize button
::      131072      WS_MINIMIZEBOX                  used to show/hide the minimize button
::
:: extended window styles
::         -20      GWL_EXSTYLE                     index used to get/set extended style information
::         128      WS_EX_TOOLWINDOW                does not appear in the task bar
::      262144      WS_EX_APPWINDOW                 default appearance of a console window
::
:: ID to access the close menu item
::       61536      SC_CLOSE                        used to delete the close item in the context menu and to gray the X button
::
:: Z order
::  [IntPtr]-1      HWND_TOPMOST                    place the window above all non-topmost windows and maintain this order
::  [IntPtr]-2      HWND_NOTOPMOST                  default behavior of a window regarding its Z order
::           3      SWP_NOSIZE | SWP_NOMOVE         do not resize or move the window when SetWindowPos is called
Steffen

Re: Set Window Topmost (Powershell hybrids)

Posted: 12 Feb 2019 04:53
by jfl
Actually triple language Batch+PowerShell+C# macros. Impressive! :D

And the performance is actually better than what I feared. (About 0.5s per macro invocation on my system.)
This opens up infinite possibilities with all the .NET framework at hand.

Re: Set Window Topmost (Powershell hybrids)

Posted: 12 Feb 2019 06:50
by aGerman
Using those little PowerShell snippets you get almost full support for .NET. But you are right, especially if you need platform invocation (Windows API) then the performance is quite bad. The reason ist that PowerShell needs to trigger the compilation of a DLL file for the new class created by Add-Type out of the embedded C# code. It appears in the %temp% folder only for a few milliseconds. The disadvantage is that every time you call the Batch macro also this DLL file will be compiled new.

Steffen

Re: Set Window Topmost (Powershell hybrids)

Posted: 13 Feb 2019 09:38
by Aacini
You may precompile all DLL files and keep the class definitions open in a resident PowerShell code, so the posterior usage of the Windows API's is very fast. The technique is described at Read arrow keys and show color text in an efficient way.

Antonio

Re: Set Window Topmost (Powershell hybrids)

Posted: 13 Feb 2019 11:04
by aGerman
Oh yes I remember that thread and I like this possibility, too. You know I already wrote some C# hybrids which I would prefer in case performance matters and you frequently need the same functionality. Once you compiled the code to an executable, you can reuse it without the PowerShell detour. And even faster are already compiled utilities like your Assembly tools.

The aim of PowerShell macros like the above is just to have a pure scripting solution which can easily be included into any Batch code you want. And it doesn't leave any additional files. The disadvantage keeps being the lack of performance though :wink:

Steffen