1

Probably an easy answer but can't find it online.

I have a .bat script to open some programs and also wants user input (Y[N]) which it all works fine.

My question.

How can I keep the script window on top of the newly opened windows so the user can see the the script is looking for some input.

@ECHO OFF
REM  2016-06-17 GMAN

REM Start Zookeeper
cd zookeeper-3.4.8 
CALL StartZookeeper.bat

REM Give Zookeeper a chance to start
sleep 4

REM Start Kafka
cd ..\kafka_2.11-0.10.0.0 
CALL StartKafka.bat

REM Give Kafka a chance to start
sleep 2

setlocal
:PROMPT
SET /p prodCons="Open a CMD for Producer and Consumer: (Y/[N]"?)
echo You entered: %prodCons%
sleep 1

IF /I "%prodCons%" NEQ "Y" GOTO END

REM Producer Terminal
start cmd.exe /k "TITLE Producer && cd .\kafka_2.11-0.10.0.0\bin\windows"

REM Consumer Terminal
start cmd.exe /k "TITLE Consumer && cd .\kafka_2.11-0.10.0.0\bin\windows"   

:END
endlocal

My script window is hidden behind the new CMD so user can see to enter Y or N.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
Gman
  • 2,433
  • 3
  • 26
  • 36
  • By the way, `'sleep' is not recognized as an internal or external command, operable program or batch file.` Did you mean `timeout /t 4 /nobreak >NUL`? – rojo Jun 19 '16 at 23:18

2 Answers2

0

One way to set the Always On Top flag is to import the SetWindowPos() function from user32.dll and invoke it with arguments which:

  • set the window parent to HWND_TOPMOST (-1)
  • skip resizing / moving the window with SWP_NOMOVE (0x01) and SWP_NOSIZE (0x02) flags

There's no way to do this in pure batch, but you can have PowerShell do the heavy lifting. Here's a bat + PowerShell hybrid script template into which you can paste your batch code to make the console always on top:

<# : AlwaysOnTop.bat -- http://stackoverflow.com/a/37912693/1683264
@echo off & setlocal

rem // Relaunch self in PowerShell to run this window Always On Top
powershell -noprofile "iex (${%~f0} | out-string)"

rem /* ###############################
rem    Your main batch code goes here.
rem    ############################### */

goto :EOF
rem // end batch / begin PowerShell hybrid code #>

# // Solution based on http://stackoverflow.com/a/34703664/1683264
add-type user32_dll @'
    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
        int x, int y, int cx, int cy, uint uFlags);
'@ -namespace System

# // Walk up the process tree until we find a window handle
$id = $PID
do {
    $id = (gwmi win32_process -filter "ProcessID='$id'").ParentProcessID
    $hwnd = (ps -id $id).MainWindowHandle
} while (-not $hwnd)

$rootWin = [IntPtr](-1)
$topmostFlags = 0x0003
[void][user32_dll]::SetWindowPos($hwnd, $rootWin, 0, 0, 0, 0, $topmostFlags)

One can also use GetWindowLong() to query extended window styles to determine whether the window is already set always on top -- then toggle it.

<# : AlwaysOnTop2.bat -- http://stackoverflow.com/a/37912693/1683264
@echo off & setlocal

call :toggleAlwaysOnTop

rem /* ###############################
rem    Your main batch code goes here.
rem    ############################### */

goto :EOF

:toggleAlwaysOnTop
powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF
rem // end batch / begin PowerShell hybrid code #>

add-type user32_dll @'
    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
        int x, int y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll")]
    public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
'@ -namespace System

$id = $PID
do {
    $id = (gwmi win32_process -filter "ProcessID='$id'").ParentProcessID
    $hwnd = (ps -id $id).MainWindowHandle
} while (-not $hwnd)

$style = [user32_dll]::GetWindowLong($hwnd, -20)
# // If flag 0x08 is set, make parent HWND -2 to unset it.  Otherwise, HWND -1 to set it.
[IntPtr]$rootWin = ($style -band 0x08) / -8 - 1
[void][user32_dll]::SetWindowPos($hwnd, $rootWin, 0, 0, 0, 0, 0x03)
rojo
  • 24,000
  • 5
  • 55
  • 101
  • This was working for me but now it is giving me this. Any ideas what is causing this? `Not all parse errors were reported. Correct the reported errors and try again. At line:1 char:1 + iex (${C:\Users\IT Command\Desktop\Mychat\chat.bat} | out-string) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ParserError: (:) [Invoke-Expression], ParseExcep tion + FullyQualifiedErrorId : UnexpectedToken,Microsoft.PowerShell.Commands.In vokeExpressionCommand` (Im using it in a file called chat.bat) – Mark Deven Sep 30 '18 at 02:20
  • @MarkDodsons That's a generic error saying PowerShell errored when attempting to execute the `iex` line. The error could be anywhere. One possible explanation is that maybe you saved chat.bat as UTF-8 rather than ANSI. Maybe? If that doesn't help, try renaming your .bat script to chat.ps1, then do `powershell -ex remotesigned .\chat.ps1`. It'll either set the console window as always on top, or you'll see a more specific line number and explanation for the error. In any case, AlwaysOnTop2.bat works fine for me on Win10 1803. If none of this helps, you should create a new question post. – rojo Sep 30 '18 at 22:39
  • I got it working. Turns out I had an extra `#>` in the code throwing it off, and the error is normal and does not effect it from working. – Mark Deven Oct 01 '18 at 18:19
0

To clarify I'm trying to make it always on top in the middle of a script, not at the very bottom. Does this change how it may work? I.E. when a user selects a setting in the option menu it switches to Always on top.

Mark Deven
  • 550
  • 1
  • 9
  • 21