Check whether command is available in batch file - windows

I'm writing a batch file for Windows and use the command 7z (7-Zip). I have put the location of it in the PATH. Is there a relatively easy way to check whether the command is available?

Do not execute the command to check its availability (i.e., found in the PATH environment variable). Use where instead:
where 7z.exe >nul 2>nul
IF NOT ERRORLEVEL 0 (
#echo 7z.exe not found in path.
[do something about it]
)
The >nul and 2>nul prevent displaying the result of the where command to the user. Executing the program directly has the following issues:
Not immediately obvious what the program does
Unintended side effects (change the file system, send emails, etc.)
Resource intensive, slow startup, blocking I/O, ...
You can also define a routine, which can help users ensure their system meets the requirements:
rem Ensures that the system has a specific program installed on the PATH.
:check_requirement
set "MISSING_REQUIREMENT=true"
where %1 > NUL 2>&1 && set "MISSING_REQUIREMENT=false"
IF "%MISSING_REQUIREMENT%"=="true" (
echo Download and install %2 from %3
set "MISSING_REQUIREMENTS=true"
)
exit /b
Then use it such as:
set "MISSING_REQUIREMENTS=false"
CALL :check_requirement curl cURL https://curl.haxx.se/download.html
CALL :check_requirement svn SlikSVN https://sliksvn.com/download/
CALL :check_requirement jq-win64 jq https://stedolan.github.io/jq/download/
IF "%MISSING_REQUIREMENTS%"=="true" (
exit /b
)
PowerShell:
On PowerShell, the Get-Command cmdlet can be considered to be the equivalent of cmd's where.exe.
Get-Command <cmd>
IF ($? -ne $true)
{
Write-Host "<cmd> not found in path"
# Do something about it
}

Yup:
#echo off
set found=
set program=7z.exe
for %%i in (%path%) do if exist %%i\%program% set found=%%i
echo "%found%"

An attempt to execute 7z.exe will return an %errorlevel% of 9009 if the command is not found. You can check that.
7z.exe
if %errorlevel%==9009 echo Command Not Found
Note: This solution is viable for this specific 7zip use case, and likely for plenty of others. But as a general rule, executing a command to determine whether it's present could potentially be harmful. So make sure you understand the effect of executing the command you're checking for, and use your discretion with this approach.

Yes, open a command window and type "7z" (I assume that is the name of the executable). If you get an error saying that the command or operation is not recognised then you know the path statement has a problem in it somewhere, otherwise it doesn't.

Related

How to find the name/path of a parent batch file from within another batch file RUN, but not CALLed, by the parent?

my goal is to have a general use batch file that is able to be run by name from within another batch file, which will then do meta/introspective things with the parent batch file that ran it. In order to do this, I'm hoping I can identify the parent from within my general use file. I'm aware I can supply the name/path of the parent alongside the command as an argument when the parent runs my file, but to prevent dishonest reporting by the parent file, for aesthetic reasons, and also in the interest of exploring the functionality of batch in general, I would like to avoid doing this and leave all identification to be done by my script, if that is possible.
I've looked into WMIC, and I'm sure I can get the ParentProcessID of my script's cmd.exe instance when it runs, but I haven't found a way to do anything useful with it. I've messed with the (goto) trick, and I know how to get the name/path of the parent if the parent CALLs my script, but since I'm trying to RUN it (i.e. by its bare name as a command), the (goto) trick also hasn't done me any good, since it seems to all be one call stack.
although anyone who messes with batch should already know what I've attempted when I say these things, in the interest of fluffing up this post's content, here's how I'll use the (goto) trick to inspect changes to the call stack:
#ECHO OFF
:: TOPLEVEL.BAT, note I'm not calling my script
"MYSCRIPT.BAT"
#ECHO OFF
:: MYSCRIPT.BAT
SETLOCAL
SET "_SELF_=%~dpnx0"
(
(goto) 2>NUL
ECHO this is the scope I should be in: %~dpnx0
CALL ECHO this is the scope I would break into: %%~dpnx0
(goto) 2>NUL
:: define variable that will be blank while still in nested scope
CALL SET "_COMPLETE_=%%"
IF NOT DEFINED _COMPLETE_ (
:: go one scope deeper and recurse
CALL "%_SELF_%"
) ELSE (
:: back to baseline
CALL "%ComSpec%" /c
)
)
ENDLOCAL
anyway, any wizards out there know whether this is possible or how it could be done?
When chaining batchfiles there is very little info available from previous chains.
There is however the variable %cmdcmdline%.
Depending on the way the first batchfile is started, this variable may contain the name of the first started batchfile.
If manually started from the commandline it has no usefull info, but this can be silently changed to run with cmd/c (see chain0.cmd).
If started with the start-command or started from explorer or started from commandline with cmd/c it will contain the name of the first started batchfile in %cmdcmdline%.
chain0.cmd
#echo off
cls
echo this is: %~0
echo cmdcmdline=[%cmdcmdline%]
if "%comspec%" == %cmdcmdline% cmd /c %0 & exit /b
Chain1.cmd
chain1.cmd
#echo off
echo this is: %~0
echo cmdcmdline=[%cmdcmdline%]
Chain2.cmd
Chain2.cmd
#echo off
echo this is: %~0
echo cmdcmdline=[%cmdcmdline%]
pause

How to supply console input ( yes / no ) as part of batch file on Windows.

I am writing a simple batch file (remove.bat) to remove a directory and all its subdirectories. The file contains the following command-
rmdir /S modules
where modules is the name of the non-empty directory.
I get the following message -
C:\...\bin>rmdir /S modules
modules, Are you sure (Y/N)?
How can I supply through the batch file the console input "Y" to the Y/N question above? Is there a command that can do this?
As others have pointed out, you should use the /Q option. But there is another "old school" way to do it that was used back in the day when commands did not have options to suppress confirmation messages. Simply ECHO the needed response and pipe the value into the command.
echo y|rmdir /s modules
I recommend using the /Q option instead, but the pipe technique might be important if you ever run into a command that does not provide an option to suppress confirmation messages.
Note - This technique only works if the command reads the input from stdin, as is always the case with cmd.exe internal commands. But this may not be true for some external commands.
Do rmdir /S for deleting a non-empty directory and do rmdir /Q for not prompting. Combine to rmdir /S /Q for quietly delete non-empty directories.
Use rmdir /S /Q modules
/Q suppresses the confirmation prompt.
You can do
rmdir /Q
Q is for quiet
If you are not dealing with a windows with a english/us locale you might need to retrieve the answers needed for your machine:
#echo off
setlocal
set "ans_yes="
set "ans_no="
set "ans_all="
copy /y nul # >nul
for /f "tokens=2-7 delims=[(/)]" %%a in ( '
copy /-y nul # ^<nul
' ) do if not defined ans_yes if "%%~e" == "" (
set "ans_yes=%%~a"
set "ans_no=%%~b"
set "ans_all=%%~c"
) else (
set "ans_yes=%%~a"
set "ans_no=%%~c"
set "ans_all=%%~e"
)
del /q #
set "ans_yes=%ans_yes: =%"
set "ans_no=%ans_no: =%"
set "ans_all=%ans_all: =%"
set "ans_y=%ans_yes:~0,1%"
set "ans_n=%ans_no:~0,1%"
set "ans_a=%ans_all:~0,1%"
endlocal & (
set "ans_y=%ans_y%"
set "ans_n=%ans_n%"
set "ans_a=%ans_a%"
)
echo %ans_y%|rmdir /s modules
YES.EXE
Yes is a fantastic tool that will continually answer Yes, No or whatever to any process that is asking for input.
If you run it by itself, it just outputs y + enter over and over. But that's not really what it's meant for. It's meant for piping into another program that is looking for a response to a prompt.
The simplest use case:
yes|rd temp /s
You can use yes.exe to output any argument or string: (stupid example warning):
yes hello world for a simple basic 10 PRINT "Hello World": GOTO 10
What it's really for:
It's meant for command line tools that can have a repetitive prompt but don't have a built-in /y or /n.
For example, you're copying files and keep getting the Overwrite? (Yes/No/All) prompt, you get stuck having to hammer to "N" key for No. Here's the fix:
yes n|copy * c:\stuff
How to get it?
This is just a small part of the GNU Core Utils for Windows, which provides the basic Linux commands to Windows people. VERY, VERY useful stuff if you write a lot of batch files.
If you have Git for Windows, you already have it, along with the rest of the GNU Core Utils. Check your PATH for it. It's probably in C:\Users\USERNAME\AppData\Local\Programs\Git\usr\bin
If you need to download the Windows binaries, they're available from a lot of different places, but the most popular is probably at https://cygwin.com/packages/summary/coreutils.html
I just want to add that, although not applicable to Rmdir, a force switch may also be the solution in some cases. So in a general sense you should look at your command switches for /f, /q, or some variant thereof (for example, Netdom RenameComputer uses /Force, not /f).
The echo pipe is a neat trick and very useful to keep around since you wont always find an appropriate switch. For instance, I think it's the only way to bypass this Y/N prompt...
Echo y|NETDOM COMPUTERNAME WorkComp /Add:Work-Comp
Link to nearly identical StackOverflow post

Use shebang/hashbang in Windows Command Prompt

I'm currently using the serve script to serve up directories with Node.js on Windows 7. It works well in the MSYS shell or using sh, as I've put node.exe and the serve script in my ~/bin (which is on my PATH), and typing just "serve" works because of it's Shebang (#!) directive which tells the shell to run it with node.
However, Windows Command Prompt doesn't seem to support normal files without a *.bat or *.exe extension, nor the shebang directive. Are there any registry keys or other hacks that I can get to force this behavior out of the built-in cmd.exe?
I know I could just write up a simple batch file to run it with node, but I was wondering if it could be done in a built-in fasion so I don't have to write a script for every script like this?
Update: Actually, I was thinking, is it possible to write a default handler for all 'files not found' etc. that I could automatically try executing within sh -c?
Thanks.
Yes, this is possible using the PATHEXT environment variable. Which is e.g. also used to register .vbs or .wsh scripts to be run "directly".
First you need to extend the PATHEXT variable to contain the extension of that serve script (in the following I assume that extension is .foo as I don't know Node.js)
The default values are something like this:
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
You need to change it (through the Control Panel) to look like this:
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.FOO
Using the control panel (Control Panel -> System -> Advanced System Settings -> Environment Variables is necessary to persist the value of the PATHEXT variable.
Then you need to register the correct "interpreter" with that extension using the commands FTYPE and ASSOC:
ASSOC .foo=FooScript
FTYPE FooScript=foorunner.exe %1 %*
(The above example is shamelessly taken from the help provided by ftype /?.)
ASSOC and FTYPE will write directly into the registry, so you will need an administrative account to run them.
Command prompt does not support shebang , however there are a lot hybrid techniques for different languages that you allow to combine batch and other languages syntax in one file.As your question concerns node.js here's a batch-node.js hybrid (save it with .bat or .cmd extension):
0</* :{
#echo off
node %~f0 %*
exit /b %errorlevel%
:} */0;
console.log(" ---Self called node.js script--- ");
console.log('Press any key to exit');
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', process.exit.bind(process, 0));
It is possible to be done with many other languages like Ruby,Perl,Python,PHP and etc.
Here is a simple way to force windows to support shebang however it has a caveat regarding the file naming. Copy the following text in to a batch file and follow general idea in REM comments.
#echo off
REM This batch file adds a cheesy shebang support for windows
REM Caveat is that you must use a specific extension for your script files and associate that extension in Windows with this batch program.
REM Suggested extension is .wss (Windows Shebang Script)
REM One method to still easily determine script type visually is to use double extensions. e.g. script.pl.wss
setlocal enableextensions disabledelayedexpansion
if [%1] == [] goto usage
for /f "usebackq delims=" %%a IN (%1) do (
set shebang=%%a
goto decode_shebang
)
:decode_shebang
set parser=%shebang:~2%
if NOT "#!%parser%" == "%shebang%" goto not_shebang
:execute_script
"%parser%" %*
set exit_stat=%errorlevel%
echo script return status: %exit_stat%
goto finale
:not_shebang
echo ERROR script first line %shebang% is not a valid shebang
echo maybe %1 is not a shebanged script
goto finale
:usage
echo usage: %0 'script with #! shebang' [scripts args]+
echo This batch file will inspect the shebang and extract the
echo script parser/interpreter which it will call to run the script
:finale
pause
exit /B %exit_stat%
No, there's no way to "force" the command prompt to do this.
Windows simply wasn't designed like Unix/Linux.
Is there a shell extension that does something similar?
Not that I've heard of, but that should be asked on Super User, not here.
There's no way to execute random file, unless it is an actual executable binary file. Windows CreateProcess() function just not designed for it. The only files it can execute are those with MZ magic or with extensions from %PATHEXT% list.
However, CMD itself has a limited support for custom interpreters through EXTPROC clause. The limitation is that interpreter should also support and omit this clause in its execution.
#npocmaka Thanks for the hint! After some trial and error I found the equivalent for a batch/php hybrid is as follows:
<?/** :
#echo off
C:\tools\php81\php.exe -d short_open_tag=On %~f0 %*
exit /b
*/ ?>
<?php
header('Location: example.com/');
print("<body><h1>Hello PHP!<h1></body>");
?>

How to test if an executable exists in the %PATH% from a windows batch file?

I'm looking for a simple way to test if an executable exists in the PATH environment variable from a Windows batch file.
Usage of external tools not provided by the OS is not allowed. The minimal Windows version required is Windows XP.
Windows Vista and later versions ship with a program called where.exe that searches for programs in the path. It works like this:
D:\>where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
D:\>where where
C:\Windows\System32\where.exe
For use in a batch file you can use the /q switch, which just sets ERRORLEVEL and doesn't produce any output.
where /q myapplication
IF ERRORLEVEL 1 (
ECHO The application is missing. Ensure it is installed and placed in your PATH.
EXIT /B
) ELSE (
ECHO Application exists. Let's go!
)
Or a simple (but less readable) shorthand version that prints the message and exits your app:
where /q myapplication || ECHO Cound not find app. && EXIT /B
for %%X in (myExecutable.exe) do (set FOUND=%%~$PATH:X)
if defined FOUND ...
If you need this for different extensions, just iterate over PATHEXT:
set FOUND=
for %%e in (%PATHEXT%) do (
for %%X in (myExecutable%%e) do (
if not defined FOUND (
set FOUND=%%~$PATH:X
)
)
)
Could be that where also exists already on legacy Windows versions, but I don't have access to one, so I cannot tell. On my machine the following also works:
where myExecutable
and returns with a non-zero exit code if it couldn't be found. In a batch you probably also want to redirect output to NUL, though.
Keep in mind
Parsing in batch (.bat) files and on the command line differs (because batch files have %0–%9), so you have to double the % there. On the command line this isn't necessary, so for variables are just %X.
Here is a simple solution that attempts to run the application and handles any error afterwards.
file.exe /? 2> NUL
IF NOT %ERRORLEVEL%==9009 ECHO file.exe exists in path
Error code 9009 usually means file not found.
The only downside is that file.exe is actually executed if found (which in some cases is not desiderable).
This can be accomplished via parameter substitution.
%~$PATH:1
This returns the full path of the executable filename in %1, else an empty string.
This does not work with user-defined variables. So if the executable filename is not a parameter to your script, then you need a subroutine. For example:
call :s_which app.exe
if not "%_path%" == "" (
"%_path%"
)
goto :eof
:s_which
setlocal
endlocal & set _path=%~$PATH:1
goto :eof
See http://ss64.com/nt/syntax-args.html
For those looking for a PowerShell option. You can use the Get-Command cmdlet passing two items. First give the current dir location with .\ prefixed, then give just the exe name.
(Get-Command ".\notepad", "notepad" -ErrorAction Ignore -CommandType Application) -ne $null
That will return true if found local or in system wide paths.
#echo off
set found=
set prog=cmd.exe
for %%i in (%path%) do if exist %%i\%prog% set found=%%i
echo "%found%"
if "%found%"=="" ....
Sometimes this simple solution works, where you check to see if the output matches what you expect. The first line runs the command and grabs the last line of standard output.
FOR /F "tokens=*" %%i in (' "xcopy /? 2> nul" ') do SET xcopyoutput=%%i
if "%xcopyoutput%"=="" echo xcopy not in path.
Use command : powershell Test-Path "exe which you looking for"
It will return True if its present, otherwise False.

Detecting how a batch file was executed

Assuming Windows, is there a way I can detect from within a batch file if it was launched from an open command prompt or by double-clicking? I'd like to add a pause to the end of the batch process if and only if it was double clicked, so that the window doesn't just disappear along with any useful output it may have produced.
Any clever ways to do this? I'm looking for solutions I could rely on to work on a machine that was configured more or less with default settings.
I just ran a quick test and noticed the following, which may help you:
When run from an open command prompt, the %0 variable does not have double quotes around the path. If the script resides in the current directory, the path isn't even given, just the batch file name.
When run from explorer, the %0 variable is always enclosed in double quotes and includes the full path to the batch file.
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="
#echo do something...
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
:EXIT
if defined PAUSE_ON_CLOSE pause
EDIT:
There was also some weird behavior when running from Explorer that I can't explain. Originally, rather than
#echo %SCRIPT:~0,1% | findstr /l %DQUOTE% > NUL
if %ERRORLEVEL% EQU 0 set PAUSE_ON_CLOSE=1
I tried using just an if:
if %SCRIPT:0,1% == ^" set PAUSE_ON_CLOSE=1
This would work when running from an open command prompt, but when run from Explorer it would complain that the if statement wasn't correct.
Yes. Patrick Cuff's final example almost worked, but you need to add one extra escape, '^', to make it work in all cases. This works great for me:
set zero=%0
if [^%zero:~0,1%] == [^"] pause
However, if the name of the batch file contains a space, it'll be double quoted in either case, so this solution won't work.
Don't overlook the solution of having two batch files:
abatfile.bat and abatfile-with-pause.bat
The second simply calling the first and adding a pause
Here's what I use :
rem if double clicked it will pause
for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c" pause
I use a parameter "automode" when I run my batch files from scripts.
set automode=%7
(Here automode is the seventh parameter given.)
Some code follows and when the file should pause, I do this:
if #%automode%==# pause
One easy way to do it is described here:
http://steve-jansen.github.io/guides/windows-batch-scripting/part-10-advanced-tricks.html
There is little typo in the code mentioned in the link. Here is correct code:
#ECHO OFF
SET interactive=0
ECHO %CMDCMDLINE% | FINDSTR /L /I %COMSPEC% >NUL 2>&1
IF %ERRORLEVEL%==0 SET interactive=1
ECHO do work
IF "%interactive%"==1 PAUSE
EXIT /B 0
Similar to a second batch file you could also pause if a certain parameter is not given (called via clicking).
This would mean only one batch file but having to specify a -nopause parameter or something like that when calling from the console.
crazy idea: use tasklist and parse it's results.
I've wrote in a test batch file:
tasklist > test.out
and when I double-clicked it, there was an additional "cmd.exe" process just before the tasklist process, that wasn't there when the script was run from command line (but note that might not be enough if someone opens a command line shell and then double-click the batch file)
Just add pause regardless of how it was opened? If it was opened from command prompt no harm done apart from a harmless pause. (Not a solution but just thinking whether a pause would be so harmful / annoying )

Resources