Using cmd-Errorcodes with System-restriction - cmd

System restrictions explanation:
I am using a plc-functionblock NT_StartProcess to use cmd.exe, but due to using the functionblock have some restricting factors: Needs to call one executable (which could call others like cmd.exe), maximum stringsize of command of 255 and the command needs to be in one single line/can't be split up into 2 commands. If either cmd.exe or powershell.exe are called, they need to be closed (for example with /C in cmd) after execution.
I am calling an executable, and storing the returned Error-codes in a .txt document.
I want to replicate following behaviour of 2 commands:
C:\RootFolder> Testexecutable.exe --Testparameters
...Testexecutable.exe is doing sth
C:\RootFolder> echo %ERRORLEVEL% > Testfile.txt
This always has correct and expected behaviour.
Now, due to the system restrictions, the 2 commands have to be combined. The executable-path, that im giving to the functionblock could be cmd or powershell, but can't be of Testexecutable.exe, because the only parameters, that can be given to Testexecutable.exe change the behaviour, but can't handle the output, therefore i couldn't access the Error-codes.
I haven't been able to use something like
cmd.exe /C "Testexecutable.exe --Testparameters & echo %ERRORLEVEL% > Testfile.txt"
because the previous value of %ERRORLEVEL% before cmd.exe gets called is saved.
E.g:
C:RootFolder> cd
C:RootFolder>
C:RootFolder> echo %ERRORLEVEL%
0
C:RootFolder> cmd.exe /C "Testexecutable.exe --Testparameters & echo %ERRORLEVEL% > Testfile.txt"
In this example, 0 is saved in Testfile.txt. Other example:
C:RootFolder> cd t
Can't find path...
C:RootFolder> echo %ERRORLEVEL%
1
C:RootFolder> cmd.exe /C "Testexecutable.exe --Testparameters & echo %ERRORLEVEL% > Testfile.txt"
In this example 1 gets saved in Testfile.exe.
-> %ERRORLEVEL% gets replaced by its integer value before Testexecutable.exe is called.
The supposed output in the example is always 5.
Is there a way to "delay" the replacement of %ERRORLEVEL% to after the previous command is executed? Is there another way to get the Error-codes of an executable?
I previously assumed this as a timing problem (previous question) before the behaviour of the replacement of %ERRORLEVEL% being pointed out by Mofi.

Related

Clear or reset ERRORLEVEL in a Windows batch script [duplicate]

I have a post-build event that runs some commands for a c# project. The last command would sometimes cause the ERRORLEVEL value not equals to zero and then the build fails.
I want to append an extra line of command to always set the ERRORLEVEL value to zero. What is the most convenient way to do that?
if you use exit /b 0 you can return an errorlevel 0 from within a child batch script without also exiting the parent.
Seems to do the trick:
ver > nul
Not everything works, and it is not clear why. For example, the following do not:
echo. > nul
cls > nul
In a pre- or post-build event, if the return code of an executable is greater than zero, and the call to the executable is not the last line of the pre- or post-build event, a quick way to mute it and avoid triggering a check for a non-zero errorlevel is to follow the failing line with a line that explicitly returns zero:
cmd /c "exit /b 0"
This is essentially a generic combination of the previously-mentioned solutions that will work with more than just the last line of a pre- or post-build event.
I personally use this:
cd .
Works even in unix shell.
But, this one might be a bit faster:
type nul>nul
Because Process Monitor shows QueryDirectory calls on cd .
PS:
cd . has another nice side effect in the unix shell. It does restore recreated working directory in the terminal if it has been opened before the erase.
Update:
And that is a bit more faster:
call;
Any windows command to find if a file is in use or not ? :https://www.dostips.com/forum/viewtopic.php?t=5542
I found that "exit 0" looks like a good way to deal with this problem.
Usage Example:
NET STOP UnderDevService /Y
exit 0
if the UnderDevService service is not started.
I use VERIFY or VERIFY > nul
If this is a snippet like "Post-build Event" etc., then you'll be fine appending:
(...) || ver > nul
at the end of the last command.
Alternatively
cmd /c "exit /b 0"
is very clean and non-idiomatic -- a reader who knows Windows shell will know what's going on, and what was your intent.
However, if you're in a batch script, you may want to use subrotines, which are a lightweight equivalent of the "child batch script" from akf's answer.
Have a subroutine:
:reset_error
exit /b 0
and then just
call :reset_error
wherever you need it.
Here's a complete example:
#echo off
rem *** main ***
call :raise_error
echo After :raise_error ERRORLEVEL = %ERRORLEVEL%
call :empty
echo After :empty ERRORLEVEL = %ERRORLEVEL%
call :reset_error
echo After :reset_error ERRORLEVEL = %ERRORLEVEL%
:: this is needed at the end of the main body of the script
goto:eof
rem *** subroutines ***
:empty
goto:eof
:raise_error
exit /b 1
:reset_error
exit /b 0
Which outputs:
After :raise_error ERRORLEVEL = 1
After :empty ERRORLEVEL = 1
After :reset_error ERRORLEVEL = 0
As you see - just calling and returning via goto:eof is not enough.
The following works in modern Windows (NT-based) systems that feature cmd.exe:
rem /* This clears `ErrorLevel`; the SPACE can actually be replaced by an
rem arbitrary sequence of SPACE, TAB, `,`, `;`, `=`, NBSP, VTAB, FF: */
(call )
The SPACE (or more precisely, an arbitrary sequence of one or more standard token separators, which are SPACE (code 0x20), TAB (code 0x09), ,, ;, =, NBSP (code 0xFF), VTAB (code 0x0B) and FF (code 0x0C)) is mandatory; if you omit it the ErrorLevel becomes set instead:
rem // This sets `ErrorLevel` to `1`:
(call)
There is a nice thread on DosTips.com where this technique came up.
Here is an alternative method, but which accesses the file system and might therefore be a bit slower:
dir > nul
rem /* Perhaps this is a little faster as a specific file is given rather
rem than just the current directory (`.` implicitly) like above: */
dir /B "%ComSpec%" > nul
Here are some other ways to reset the ErrorLevel state, which even work in MS-DOS (at least for version 6.22):
more < nul > nul
rem // The `> nul` part can be omitted in Windows but is needed in MS-DOS to avoid a line-break to be returned:
sort < nul > nul
The following methods work in MS-DOS only:
command /? > nul
fc nul nul > nul
keyb > nul
For the sake of completeness, this sets the ErrorLevel state to 1, valid for both Windows and MS-DOS:
< nul find ""
After reviewing all of the other answers, I decided to find which way was the most efficient for resetting the ERRORLEVEL. I made a quick script that recorded the time to execute each of these:
"cmd /c "exit /b 0"", "cd .", "ver", "type nul", and "VERIFY"
here is the output:
cmd /v:on /c set ^"q=^"^" & timeit.cmd "cmd /c ^!q^!exit /b 0^!q^!" "cd ." "ver" "type nul" "VERIFY"
cmd /c "exit /b 0" took 0:0:0.02 (0.02s total)
cd . took 0:0:0.00 (0.00s total)
Microsoft Windows [Version 10.0.18362.836]
ver took 0:0:0.00 (0.00s total)
type nul took 0:0:0.00 (0.00s total)
VERIFY is off.
VERIFY took 0:0:0.00 (0.00s total)
This took 0:0:0.06 (0.06s total)
after reviewing with Measure-Command {command} in Powershell, I found that it only really accepted cd . and cmd /c "exit /b 0" --am I doing something wrong?
I'd recommend either cd . or type nul since neither have a footprint on the output of the console, nor are they slow in any measure.
yeah I'm pretty bored
Add >nul after each command that's likely to fail - this seems to prevent the build from failing.
You can still check the result of the command by examining %errorlevel%.
For example:
findstr "foo" c:\temp.txt>nul & if %errorlevel% EQU 0 (echo found it) else (echo didn't find it)
I'm using this:
ping localhost -n 1 >null
I always just used;
set ERRORLEVEL=0
I've been using it for donkey's years.

Why the return value of "echo" is always 1

After invoking the CMD "echo xxx" the %errorlevel% is always 1.
Even though "echo xxx" is executed successfully.
Yes, echo something has absolutely no effect on the error level and will not change errorlevel to 0 as that will just always succeed.
For example:
running a echo something > c:\somefile.txt, which will succeed actually creating the file, but not change errorlevel to 0.
c:\>copy nil c:
The system cannot find the file specified.
c:\>echo %errorlevel%
1
c:\>echo this.works > c:\test.txt
c:\>echo %errorlevel%
1
type c:\test.txt
this.works
External commands like FINDSTR and XCOPY are actually separate programs (FINDSTR.EXE, XCOPY.EXE). External commands set the ERRORLEVEL upon both success and failure. By convention, 0 indicates success, and non-zero indicates an error. But some programs may not follow that convention.
ECHO is an internal command, meaning that the command is built into the CMD.EXE program itself. No additional program is needed. Internal commands behave differently.
If used on the command line, or within a batch script with a .BAT extension, then most internal commands set ERRORLEVEL upon failure, but do nothing to the ERRORLEVEL upon success. However, there are some exceptions. Both VER and VOL do set the ERRORLEVEL to 0 upon success.
When used within a batch script with a .CMD extension, all internal commands set the ERRORLEVEL upon both success and failure, just like external commands.
The ERRORLEVEL of 1 that you see after ECHO must have come from a prior command that failed. I've never seen ECHO fail. The only way I can imagine it could fail is if stdout is successfully redirected to a file, but the storage device cannot be written to for some reason such as if the device is full.
echo is a curious command. Let's see how it behaves
When echo command works
If errorlevel is 0 before echo, after echo, errorlevel will be 0 (the obvious case)
If errorlevel is 1 before echo, after echo, errorlevel will be 1. Echo does not change errorlevel
When echo command "fails"
Can echo fail? Let's create a case where it "fails". Open two command windows on the same directory. In first one run pause > file.txt to generate a file and place a lock on it while the pause command is waiting a keypress. In second command window run echo something > file.txt. In this case, the echo command will fail, as the first command window hold a lock on the file, so the second one is not able to write to the file. Properly talking the echo has not failed, but the redirection does, but just to see what happens
If errorlevel is 1 before running echo, it is still 1 after the echo (the obvious case)
If errolevel is 0 before running echo, it is still 0 after the echo
So, it seems that the echo command behaves identically in the two cases
BUT if we change the way echo is executed to
echo something && echo works || echo fails
then the behaviour changes a bit
When echo command works
No difference. errorlevel will not change, keeping the value it had before running the echo command.
When echo command "fails"
Using the echo something > file && echo works || echo fails then, if errorlevel is 1 before running echo, it keeps its value.
But if errorlevel is 0 and the echo command fails, in this case, with this construct of the command, errorlevel will show the failure and change its value to 1

Detect if bat file is running via double click or from cmd window

I have a bat file that does a bunch of things and closes the cmd window which is fine when user double clicks the bat file from explorer. But if I run the bat file from a already open cmd window as in cmd>c:\myfile.bat then I do not want the bat file to close the cmd window (END) since I need to do other things. I need bat dos command code that will do something like
if (initiated_from_explorer) then
else
endif
Is this possible ? thanks
mousio's solution is nice but I did not manage to make it work in an "IF" statement because of the double quotes in the value of %cmdcmdline% (with or without double quotes around %cmdcmdline%).
In constrast, the solution using %0 works fine. I used the following block statement and it works like a charm:
IF %0 == "%~0" pause
The following solution, which expands %~0 to a fully qualified path, might also work if the previous does not (cf. Alex Essilfie's comment):
IF %0 EQU "%~dpnx0" PAUSE
However, note that this solution with %~dpnx0 fails when
the .bat file is located somewhere in the %USERPROFILE% directory, and
your %USERNAME% contains one or more uppercase characters
because... wait for it... the d in %~dpnx0 forces your %USERPROFILE% username to lowercase, while plain %0 does not. So they're never equal if your username contains an uppercase character. ¯\_(ツ)_/¯
[Edit 18 June 2021 - thanks to JasonXA]
You can solve this lowercase issue with case-insensitive comparison (magic /I):
IF /I %0 EQU "%~dpnx0" PAUSE
This might be the best solution of all!
%cmdcmdline% gives the exact command line used to start the current Cmd.exe.
When launched from a command console, this var is "%SystemRoot%\system32\cmd.exe".
When launched from explorer this var is cmd /c ""{full_path_to_the_bat_file}" ";
this implicates that you might also check the %0 variable in your bat file, for in this case it is always the full path to the bat file, and always enclosed in double quotes.
Personally, I would go for the %cmdcmdline% approach (not %O), but be aware that both start commands can be overridden in the registry…
A consolidated answer, derived from much of the information found on this page:
:pauseIfDoubleClicked
setlocal enabledelayedexpansion
set testl=%cmdcmdline:"=%
set testr=!testl:%~nx0=!
if not "%testl%" == "%testr%" pause
The variable "testl" gets the full line of the cmd processor call (as per mousio), stripping out all of the pesky double quotes.
The variable "testr" takes "testl" and further strips outs the name of the current batch file name if present (which it will be if the batch file was invoked with a double-click).
The if statement sees if "testl" and "testr" are different. If yes, batch was double-clicked, so pause; if no, batch was typed in on command line, go on.
Naturally, if you want to do something else if you detect a double-click, you can change the pause.
Thanks everyone.
Less code, more robust:
Build upon the other answers, I find the most robust approach to:
Replace quotes with x to enable text comparison without breaking the IF statement.
Recreate the expected cmdcmdline exactly (well, with '"' replaced by x).
Test for case-insensitive equality.
The result is:
set "dclickcmdx=%comspec% /c xx%~0x x"
set "actualcmdx=%cmdcmdline:"=x%"
set isdoubleclicked=0
if /I "%dclickcmdx%" EQU "%actualcmdx%" (
set isdoubleclicked=1
)
This adds more robustness against general cmd /c calls, since Explorer adds an awkward extra space before the last quote/x (in our favor). If cmdcmdline isn't found, it correctly renders isdoubleclicked=0.
Addendum:
Similarly to the above method, the following one-liner will pause a script if it was double-clicked from explorer. I add it to the end of my scripts to keep the command-line window open:
(Edit 2022-01-12, fixed quote mismatching from this discussion)
if /i "%comspec% /c ``%~0` `" equ "%cmdcmdline:"=`%" pause
if /i "%comspec% /c %~0 " equ "%cmdcmdline:"=%" pause
Use exit /b 0, not exit
The former will exit all the way if launched from Windows Explorer, but return to the console if launched from the command line.
You can add a command line parameter when running from a CMD window that won't exist when the file is double-clicked. If there is no parameter, close the window. If there is, don't close it. You can test the parameter using %1
It's not only possible, but your desired behavior is the normal behavior of batch file execution, unless you do something 'special':
when executing a batch file by double-clicking it in Explorer, the cmd window will close when it's done;
when the batch file is executed from the command line, it simply returns to the command line prompt when complete - the window is not closed;
So I think the question that needs to be answered is what are you doing in the batch file that causes the command window to close when you execute it by the command line?
Like #anishsane I too wanted a pause statement if launched from explorer, but not when launched from a command window.
Here's what worked for me, based upon #mousio's answer above:
#SET cmdcmdline|FINDSTR /b "cmdcmdline="|FINDSTR /i pushd >nul
#IF ERRORLEVEL 1 (
#echo.
#echo Press ENTER when done
#pause > nul
)
(Nothing original here, just providing a working example)
Paste this at the beginning of your BAT or CMD script and maybe change what happens in the 'if' clause:
:: To leave command window open if script run from Windows explorer.
#setlocal
#set x=%cmdcmdline:"=%
#set x=%x: =%
#set y=%x:cmd/c=%
#if "%x%" neq "%y%" cmd /k %0 %* && exit || exit
#endlocal
What this does, is if the user either double-clicks or calls this script using "cmd /c" it will re-launch with "cmd /k" which will leave the session open after the command finishes. This allows the user to EXIT or maybe do something else.
The reason for doing it this way rather than the other ways explained in this answer is because I've found situations that still even with using the quotes or other symbols, the IF statement would barf with certain situations of the QUOTES and the /c and with spaces. So the logic first removes all QUOTES and then removes all spaces.. because SOMETIMES there is an extra space after removing the quotes.
set x=%cmdcmdline:"=% <-- removes all quotes
set x=%x: =% <-- removes all spaces
set y=%x:cmd/c=% <-- removes cmd/c from the string saving it to y
The point of the && exit || exit is so that if the ERRORLEVEL before exiting is 0 (success) it then stops running, but also if it is non 0 (some failure) it also stops running.
But you can replace this part:
cmd /k %0 %* && exit || exit
with something like
set CALLED_WITH_CMD_C=YES
and then make up your own differences in the rest of your script. You would have to then move or remove the endlocal.
The '#' symbol at front just prevents the echo, which you can have if you want to test.
Do not use echo on or echo off as it changes the setting and affects all subsequent scripts that call yours.
#dlchambers was close but set didn't work since cmdcmdline isn't a defined environment variable in some cases, but this version based on his works great for me:
echo %cmdcmdline% | findstr /i pushd >nul
if errorlevel 1 pause
after reading through the suggestions, this is what I went with:
set __cmdcmdline=%cmdcmdline%
set __cmdcmdline=%__cmdcmdline:"=%
set __cmdcmdline=%__cmdcmdline: =%
set __cmdcmdline=%__cmdcmdline:~0,5%
if "%__cmdcmdline%"=="cmd/c" set CMD_INITIATED_FROM_EXPLORER=1
set __cmdcmdline=
which conditionally sets the variable: CMD_INITIATED_FROM_EXPLORER
..and can subsequently be used as needed:
if defined CMD_INITIATED_FROM_EXPLORER (
echo.
pause
)
..but the issue regarding Powershell that #Ruben Bartelink mentions isn't solved:
running ./batch.cmd from Powershell uses cmd /c under the hood
You also can check for SESSIONNAME environment variable.
As you see here that variable typically isn't set in Explorer window. When invoking from cmd it SESSIONNAME is set to Console. I can confirm this for Windows 10.
Unfortunately behaviour seems to be changeable: https://support.microsoft.com/de-de/help/2509192/clientname-and-sessionname-enviroment-variable-may-be-missing
(Partly) Contrary and in addition to the accepted answer AToW (re %cmdcmdline%) and the top answer AToW (re if /i %0 equ "%~dpnx0") in Win10 it is:
in CMD:
in a *.cmd (here _pauseIfRunFromGUI.cmd):
"C:\Windows\System32\cmd.exe"
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
.cmd is present if entered on the cmd line, which happens if you complete with Tab.
in a *.cmd (_pauseIfRunFromGUI.cmd) that's called by a *.cmd:
"C:\Windows\System32\cmd.exe"
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
Same as above.
.cmd is present if called via call _pauseIfRunFromGUI.cmd.
In any way the comparison evaluates to false which is intended.
from GUI:
(Explorer and link on Desktop)
in a *.cmd (here _pauseIfRunFromGUI.cmd) that's launched from the GUI:
C:\WINDOWS\system32\cmd.exe /c ""C:\Users\Geri\_pauseIfRunFromGUI.cmd" "
if /I "C:\Users\Geri\_pauseIfRunFromGUI.cmd" EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
This one is different to the accepted answer AToW which says just cmd /c ""..." "_!
The comparison evaluates to true which is intended.
in a *.cmd (_pauseIfRunFromGUI.cmd) that's called by a *.cmd (here calling.cmd) that's launched from the GUI:
C:\WINDOWS\system32\cmd.exe /c ""C:\Users\Geri\calling.cmd" "
if /I _pauseIfRunFromGUI[.cmd] EQU "C:\Users\Geri\_pauseIfRunFromGUI.cmd"
Different to above, since calling .cmd is in cmdcmdline, of course, not the one in which it is evaluated (_pauseIfRunFromGUI.cmd).
.cmd is present if called via call _pauseIfRunFromGUI.cmd within calling.cmd.
The comparison evaluates to false which is not intended!
If the comparison is changed to:
if /i "%cmdcmdline:~0,31%"=="C:\WINDOWS\system32\cmd.exe /c " echo: & pause
everything works as expected.

What is the easiest way to reset ERRORLEVEL to zero?

I have a post-build event that runs some commands for a c# project. The last command would sometimes cause the ERRORLEVEL value not equals to zero and then the build fails.
I want to append an extra line of command to always set the ERRORLEVEL value to zero. What is the most convenient way to do that?
if you use exit /b 0 you can return an errorlevel 0 from within a child batch script without also exiting the parent.
Seems to do the trick:
ver > nul
Not everything works, and it is not clear why. For example, the following do not:
echo. > nul
cls > nul
In a pre- or post-build event, if the return code of an executable is greater than zero, and the call to the executable is not the last line of the pre- or post-build event, a quick way to mute it and avoid triggering a check for a non-zero errorlevel is to follow the failing line with a line that explicitly returns zero:
cmd /c "exit /b 0"
This is essentially a generic combination of the previously-mentioned solutions that will work with more than just the last line of a pre- or post-build event.
I personally use this:
cd .
Works even in unix shell.
But, this one might be a bit faster:
type nul>nul
Because Process Monitor shows QueryDirectory calls on cd .
PS:
cd . has another nice side effect in the unix shell. It does restore recreated working directory in the terminal if it has been opened before the erase.
Update:
And that is a bit more faster:
call;
Any windows command to find if a file is in use or not ? :https://www.dostips.com/forum/viewtopic.php?t=5542
I found that "exit 0" looks like a good way to deal with this problem.
Usage Example:
NET STOP UnderDevService /Y
exit 0
if the UnderDevService service is not started.
I use VERIFY or VERIFY > nul
If this is a snippet like "Post-build Event" etc., then you'll be fine appending:
(...) || ver > nul
at the end of the last command.
Alternatively
cmd /c "exit /b 0"
is very clean and non-idiomatic -- a reader who knows Windows shell will know what's going on, and what was your intent.
However, if you're in a batch script, you may want to use subrotines, which are a lightweight equivalent of the "child batch script" from akf's answer.
Have a subroutine:
:reset_error
exit /b 0
and then just
call :reset_error
wherever you need it.
Here's a complete example:
#echo off
rem *** main ***
call :raise_error
echo After :raise_error ERRORLEVEL = %ERRORLEVEL%
call :empty
echo After :empty ERRORLEVEL = %ERRORLEVEL%
call :reset_error
echo After :reset_error ERRORLEVEL = %ERRORLEVEL%
:: this is needed at the end of the main body of the script
goto:eof
rem *** subroutines ***
:empty
goto:eof
:raise_error
exit /b 1
:reset_error
exit /b 0
Which outputs:
After :raise_error ERRORLEVEL = 1
After :empty ERRORLEVEL = 1
After :reset_error ERRORLEVEL = 0
As you see - just calling and returning via goto:eof is not enough.
The following works in modern Windows (NT-based) systems that feature cmd.exe:
rem /* This clears `ErrorLevel`; the SPACE can actually be replaced by an
rem arbitrary sequence of SPACE, TAB, `,`, `;`, `=`, NBSP, VTAB, FF: */
(call )
The SPACE (or more precisely, an arbitrary sequence of one or more standard token separators, which are SPACE (code 0x20), TAB (code 0x09), ,, ;, =, NBSP (code 0xFF), VTAB (code 0x0B) and FF (code 0x0C)) is mandatory; if you omit it the ErrorLevel becomes set instead:
rem // This sets `ErrorLevel` to `1`:
(call)
There is a nice thread on DosTips.com where this technique came up.
Here is an alternative method, but which accesses the file system and might therefore be a bit slower:
dir > nul
rem /* Perhaps this is a little faster as a specific file is given rather
rem than just the current directory (`.` implicitly) like above: */
dir /B "%ComSpec%" > nul
Here are some other ways to reset the ErrorLevel state, which even work in MS-DOS (at least for version 6.22):
more < nul > nul
rem // The `> nul` part can be omitted in Windows but is needed in MS-DOS to avoid a line-break to be returned:
sort < nul > nul
The following methods work in MS-DOS only:
command /? > nul
fc nul nul > nul
keyb > nul
For the sake of completeness, this sets the ErrorLevel state to 1, valid for both Windows and MS-DOS:
< nul find ""
After reviewing all of the other answers, I decided to find which way was the most efficient for resetting the ERRORLEVEL. I made a quick script that recorded the time to execute each of these:
"cmd /c "exit /b 0"", "cd .", "ver", "type nul", and "VERIFY"
here is the output:
cmd /v:on /c set ^"q=^"^" & timeit.cmd "cmd /c ^!q^!exit /b 0^!q^!" "cd ." "ver" "type nul" "VERIFY"
cmd /c "exit /b 0" took 0:0:0.02 (0.02s total)
cd . took 0:0:0.00 (0.00s total)
Microsoft Windows [Version 10.0.18362.836]
ver took 0:0:0.00 (0.00s total)
type nul took 0:0:0.00 (0.00s total)
VERIFY is off.
VERIFY took 0:0:0.00 (0.00s total)
This took 0:0:0.06 (0.06s total)
after reviewing with Measure-Command {command} in Powershell, I found that it only really accepted cd . and cmd /c "exit /b 0" --am I doing something wrong?
I'd recommend either cd . or type nul since neither have a footprint on the output of the console, nor are they slow in any measure.
yeah I'm pretty bored
Add >nul after each command that's likely to fail - this seems to prevent the build from failing.
You can still check the result of the command by examining %errorlevel%.
For example:
findstr "foo" c:\temp.txt>nul & if %errorlevel% EQU 0 (echo found it) else (echo didn't find it)
I'm using this:
ping localhost -n 1 >null
I always just used;
set ERRORLEVEL=0
I've been using it for donkey's years.

Is there a way in a batch script to keep the console open only if invoked from Windows Manager?

I have a DOS batch script that invokes a java application which interacts with the user through the console UI. For the sake of argument, let's call it runapp.bat and its contents be
java com.example.myApp
If the batch script is invoked in a console, everything works fine. However, if the script is invoked from the Window Manager, the newly opened console closes as soon as the application finishes executing. What I want is for the console to stay open in all cases.
I know of the following tricks:
add a pause command at the end of the script. This is a bit ugly in case runapp.bat is invoked from the command line.
create a new shell using cmd /K java com.example.myApp This is the best solution I found so far, but leaves an extra shell environment when invoked from the command line, so that calling exit doesn't actually close the shell.
Is there a better way?
See this question: Detecting how a batch file was executed
This script will not pause if run from the command console, but will if double-clicked in Explorer:
#echo off
setlocal enableextensions
set SCRIPT=%0
set DQUOTE="
:: Detect how script was launched
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
:: Run your app
java com.example.myApp
:EXIT
if defined PAUSE_ON_CLOSE pause
I prefer using %cmdcmdline% as posted in the comment to Patrick's answer to the other question (which I didn't find although looked). That way, even if someone decides to use quotes to call the batch script, it won't trigger the false positive.
My final solution:
#echo off
java com.example.myApp %1 %2
REM "%SystemRoot%\system32.cmd.exe" when from console
REM cmd /c ""[d:\path\script.bat]" " when from windows explorer
#echo %cmdcmdline% | findstr /l "\"\"" >NUL
if %ERRORLEVEL% EQU 0 pause
cmd /K java com.example.myApp & pause & exit
will do the job. The & will execute the command one after another. If you use && you can break if one fails.
Include this line in a batch file and double click on the batch file in explorer:
cmd /k "script commands within these quotes seperated by &&"
For example
cmd /k "cd ../.. && dir && cd some_directory"
The full complement of options to cmd can be found here
I frequently use alternate shells (primarily TCC/LE from jpsoft.com) and subshells. I've found that this code works for a wider, more general case (and it doesn't require FINDSTR):
#echo off & setlocal
if "%CMDEXTVERSION%"=="" ( echo REQUIRES command extensions & exit /b 1 ) &:: REQUIRES command extensions for %cmdcmdline% and %~$PATH:1 syntax
call :_is_similar_command _FROM_CONSOLE "%COMSPEC%" %cmdcmdline%
if "%_PAUSE_NEEDED%"=="0" ( goto :_START )
if "%_PAUSE_NEEDED%"=="1" ( goto :_START )
set _PAUSE_NEEDED=0
if %_FROM_CONSOLE% equ 0 ( set _PAUSE_NEEDED=1 )
goto :_START
::
:_is_similar_command VARNAME FILENAME1 FILENAME2
:: NOTE: not _is_SAME_command; that would entail parsing PATHEXT and concatenating each EXT for any argument with a NULL extension
setlocal
set _RETVAL=0
:: more than 3 ARGS implies %cmdcmdline% has multiple parts (therefore, NOT direct console execution)
if NOT [%4]==[] ( goto :_is_similar_command_RETURN )
:: deal with NULL extensions (if both NULL, leave alone; otherwise, use the non-NULL extension for both)
set _EXT_2=%~x2
set _EXT_3=%~x3
if NOT "%_EXT_2%"=="%_EXT_3%" if "%_EXT_2%"=="" (
call :_is_similar_command _RETVAL "%~2%_EXT_3%" "%~3"
goto :_is_similar_command_RETURN
)
if NOT "%_EXT_2%"=="%_EXT_3%" if "%_EXT_3%"=="" (
call :_is_similar_command _RETVAL "%~2" "%~3%_EXT_2%"
goto :_is_similar_command_RETURN
)
::if /i "%~f2"=="%~f3" ( set _RETVAL=1 ) &:: FAILS for shells executed with non-fully qualified paths (eg, subshells called with 'cmd.exe' or 'tcc')
if /i "%~$PATH:2"=="%~$PATH:3" ( set _RETVAL=1 )
:_is_similar_command_RETURN
endlocal & set "%~1=%_RETVAL%"
goto :EOF
::
:_START
if %_FROM_CONSOLE% EQU 1 (
echo EXEC directly from command line
) else (
echo EXEC indirectly [from explorer, dopus, perl system call, cmd /c COMMAND, subshell with switches/ARGS, ...]
)
if %_PAUSE_NEEDED% EQU 1 ( pause )
Initially, I had used if /i "%~f2"=="%~f3" in the _is_similar_command subroutine. The change to if /i "%~$PATH:2"=="%~$PATH:3" and the additional code checking for NULL extensions allows the code to work for shells/subshells opened with non-fully qualified paths (eg, subshells called with just 'cmd.exe' or 'tcc').
For arguments without extensions, this code does not parse and use the extensions from %PATHEXT%. It essentially ignores the hierarchy of extensions that CMD.exe uses when searching for a command without extension (first attempting FOO.com, then FOO.exe, then FOO.bat, etc.). So, _is_similar_command checks for similarity, not equivalence, between the two arguments as shell commands. This could be a source of confusion/error, but will, in all likelyhood, never arise as a problem in practice for this application.
Edit: Initial code was an old version. The code is now updated to the most recent version which has: (1) a swapped %COMSPEC% and %cmdcmdline% in the initial call, (2) added a check for multiple %cmdcmdline% arguments, (3) echoed messages are more specific about what is detected, and (4) a new variable %_PAUSE_NEEDED% was added.
It should be noted that %_FROM_CONSOLE% is set based specifically on whether the batch file was excecuted directly from the console command line or indirectly through explorer or some other means. These "other means" can include a perl system() call or by executing a command such as cmd /c COMMAND.
The variable %_PAUSE_NEEDED% was added so that processes (such as perl) which execute the batch file indirectly can bypass pauses within the batch file. This would be important in cases in which output is not piped to the visible console (eg, perl -e "$o = qx{COMMAND}"). If a pause occurs in such a case, the "Press any key to continue . . ." pause prompt would never be displayed to the user and the process will hang waiting for unprompted user input. In instances where user interaction is either not possible or not allowed, the %_PAUSE_NEEDED% variable can be preset to "0" or "1" (false or true respectively). %_FROM_CONSOLE% is still set correctly by the code, but the value of %_PAUSE_NEEDED% is not subsequently set based upon %_FROM_CONSOLE%. It is just passed through.
And also note that the code will incorrectly detect execution as indirect (%_FROM_CONSOLE%=0) within a subshell if that subshell is opened with a command containing switches/options (eg, cmd /x). Generally this isn't a big problem as subshells are usually opened without extra switches and %_PAUSE_NEEDED% can be set to 0, when necessary.
Caveat codor.
#echo %CMDCMDLINE% | find /I " /c " >nul && pause

Resources