Page 1 of 1

[How-To] Hide, minimize, or show a window (PowerShell hybrid)

Posted: 18 Oct 2020 12:46
by aGerman
Apparently Batch itself has little to no opportunities to interact with windows. Other scripting languages like PowerShell can support here and they can be merged into a Batch code. The below uses the macro syntax to define PowerShell code and inject conditions at runtime.
The comments and examples explain how to use it.

Code: Select all

@echo off &setlocal

:: define the show_hide_window macro variable
call :init_show_hide_window

:: create a PowerShell process with iconic window
:: pause to give you the opportunity to observe the icon on the task bar
:: maximize the window
start /min powershell.exe
pause
%show_hide_window% MAXIMIZE "$_.WINDOWTITLE -eq 'Windows PowerShell'"
echo %errorlevel%

:: create two Notepad processes with iconic window
:: pause to give you the opportunity to observe the icons on the task bar
:: maximize the first window and pause
:: hide the second window and pause (observe the disappeared icon on the task bar)
:: bring the second window to front in normal style
:: after a second minimize it to the task bar and pause
>"%temp%\test.txt" echo pick me first
start /min notepad.exe "%temp%\test.txt"
>"%temp%\another test.txt" echo leave me alone for a while
start /min notepad.exe "%temp%\another test.txt"
pause
del "%temp%\test.txt"
del "%temp%\another test.txt"
%show_hide_window% MAXIMIZE "$_.IMAGENAME -eq 'notepad.exe' -and $_.WINDOWTITLE -like 'test.txt - *'"
echo %errorlevel%
pause
%show_hide_window% HIDE "$_.IMAGENAME -eq 'notepad.exe' -and $_.WINDOWTITLE -like 'another test.txt - *'"
echo %errorlevel%
pause
%show_hide_window% SHOWNORMAL "$_.IMAGENAME -eq 'notepad.exe' -and $_.WINDOWTITLE -like 'another test.txt - *'"
echo %errorlevel%
timeout 1
%show_hide_window% MINIMIZE "$_.IMAGENAME -eq 'notepad.exe' -and $_.WINDOWTITLE -like 'another test.txt - *'"
echo %errorlevel%
pause
goto :eof

:init_show_hide_window
:: - BRIEF -
:: Hide, minimize, or bring a window into the foreground and activate it. The window appearance
::  is defined by an ID. Selection takes place by custom filters. If the filter matches more
::  than one window then the first found window will be selected.
:: - SYNTAX -
:: %show_hide_window% showcmd "filter"
::   showcmd  Command for the new window appearance. Valid arguments:
::               HIDE
::               MAXIMIZE
::               MINIMIZE
::               RESTORE
::               SHOWDEFAULT
::               SHOWNORMAL
::   filter   Conditional expression using at least one of these properties:
::               $_.PID
::               $_.IMAGENAME
::               $_.WINDOWTITLE
::              Image names and window titles have to be enclosed into single quotes.
::             Conditions are following the syntax of PowerShell comparisons:
::              https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators
::              https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logical_operators
:: - EXAMPLES -
:: Minimize the window with title "Windows PowerShell":
::   %show_hide_window% MINIMIZE "$_.WINDOWTITLE -eq 'Windows PowerShell'"
:: Hide the Notepad window whose title contains "foobar.txt":
::   %show_hide_window% HIDE "$_.IMAGENAME -eq 'notepad.exe' -and $_.WINDOWTITLE -like '*foobar.txt*'"
:: Maximize the main window of the process with process ID 1234:
::   %show_hide_window% MAXIMIZE "$_.PID -eq 1234"
setlocal DisableDelayedExpansion
set show_hide_window=for %%i in (1 2) do if %%i==2 (^
%=% for /f "tokens=1,2*" %%j in ("? ^^!arg^^!") do powershell.exe -nop -ep Bypass -c ^"try{Add-Type '^
%=% using System;using System.Text;using System.Collections.Generic;using System.Runtime.InteropServices;^
%=% public class Data{public IntPtr HWND;public int PID;public string IMAGENAME;public string WINDOWTITLE;}^
%=% public class Wnd{^
%=====% private delegate int CbPtr(IntPtr hWnd,IntPtr lPrm);^
%=====% private static List^^^^^^^<Data^^^^^^^> dataList=new List^^^^^^^<Data^^^^^^^>();^
%=====% [DllImport(\"user32.dll\")] static extern int EnumWindows(CbPtr func,IntPtr lPrm);^
%=====% [DllImport(\"user32.dll\")] static extern IntPtr GetWindow(IntPtr hWnd,int cmd);^
%=====% [DllImport(\"user32.dll\")] static extern int GetWindowText(IntPtr hWnd,StringBuilder str,int max);^
%=====% [DllImport(\"user32.dll\")] static extern int GetWindowThreadProcessId(IntPtr hWnd,ref int pid);^
%=====% [DllImport(\"kernel32.dll\")] static extern IntPtr OpenProcess(int access,int inherit,int pid);^
%=====% [DllImport(\"kernel32.dll\")] static extern int QueryFullProcessImageName(IntPtr hProc,int flags,StringBuilder exeName,ref int size);^
%=====% [DllImport(\"kernel32.dll\")] static extern int CloseHandle(IntPtr hObj);^
%=====% [DllImport(\"user32.dll\")] public static extern int ShowWindow(IntPtr hWnd,int show);^
%=====% [DllImport(\"user32.dll\")] public static extern int SetForegroundWindow(IntPtr hWnd);^
%=====% private static int CbProc(IntPtr hWnd,IntPtr lPrm){^
%=========% if (GetWindow(hWnd,4)==IntPtr.Zero){^
%=============% int pid=0,size=512;^
%=============% StringBuilder exe=new StringBuilder(size),title=new StringBuilder(size);^
%=============% int len=GetWindowText(hWnd,title,size);^
%=============% GetWindowThreadProcessId(hWnd,ref pid);^
%=============% IntPtr hProc=OpenProcess(4096,0,pid);^
%=============% QueryFullProcessImageName(hProc,0,exe,ref size);^
%=============% CloseHandle(hProc);^
%=============% if (len^^^^^^^>0^^^^^^^&^^^^^^^&size^^^^^^^>0)^
%=================% dataList.Add(new Data{HWND=hWnd,PID=pid,IMAGENAME=System.IO.Path.GetFileName(exe.ToString()),WINDOWTITLE=title.ToString()});^
%=========% }^
%=========% return 1;^
%=====% }^
%=====% public static List^^^^^^^<Data^^^^^^^> GetData(){^
%=========% EnumWindows(CbProc,IntPtr.Zero);^
%=========% return dataList;^
%=====% }^
%=% }';^
%=% $show=('HIDE','SHOWNORMAL','','MAXIMIZE','','','MINIMIZE','','','RESTORE','SHOWDEFAULT').IndexOf(\"%%~k\".ToUpper());^
%=% if(-1,2 -contains $show){exit 1;}^
%=% [Wnd]::GetData()^^^^^^^|?{%%~l;}^^^^^^^|%%{^
%=====% $e=[int]([Wnd]::ShowWindow($_.HWND,$show) -eq 0);^
%=====% if(0,6 -contains $show){exit $e;}^
%=====% exit [int]([Wnd]::SetForegroundWindow($_.HWND) -eq 0);^
%=% }}catch{exit 1;}exit 1;^"^&endlocal) else setlocal EnableDelayedExpansion^&set arg=
endlocal &set "show_hide_window=%show_hide_window%"
if !!# neq # set "show_hide_window=%show_hide_window:^^!=!%"
exit /b
For those who are wondering why I heavily used platform invocation rather than the Get-Process cmdlet:
- It's all about windows. Enumerating the top-level windows rather than the processes (that may not even have a window) is much more logical.
- However, the main reason is that the .MainWindowHandle property is null for hidden windows. Thus, unhiding windows wouldn't have been supported using Get-Process.

Steffen

Re: [How-To] Hide, minimize, or show a window (PowerShell hybrid)

Posted: 19 Oct 2020 03:05
by npocmaka_
Once I've tried something similar with a C# hybrid - https://github.com/npocmaka/batch.scrip ... owMode.bat

Re: [How-To] Hide, minimize, or show a window (PowerShell hybrid)

Posted: 19 Oct 2020 04:54
by aGerman
Nice one :) You may update it based on my code. There are two issues.
- The Get-Process cmdlet I was talking about in my addendum just wraps the .NET Process.GetProcesses() method that you used. So I suspect that windows are not found anymore, once they are hidden. Rather enumerate the windows and afterwards determine the processes they belong to.
- ShowWindow and ShowWindowAsync do not necessarily bring a window to top. That's the reason why I use the SetForegroundWindow API in addition. The disadvantage of this function is that it automatically activates the window. All the IDs for ShowWindow which don't activate the window are getting useless (which is the reason why I skipped them).

Steffen