implicit "popd" on batch exit - windows

Is there a way to undo all pushd at the end of script. What I have is:
pushd somwhere
rem doing stuff
goto end
:end
popd
goto :EOF
What I'd like to have is:
setlocal
pushd somwhere
rem doing stuff
goto :EOF
But that doesn't work, the directory stays "pushd". Another try where I can't say how many pushd will occur would be:
set CurrentDir=%CD%
rem doing a lot of pushd and popd
pushd somwhere
:POPBACK
if /i not "%CD%" == "%CurrentDir%" popd & goto POPBACK
But that looks like I can easily get stuck at :POPBACK. Adding a counter to exit that loop is a bit uncertain at which dir I end up.
In that context I'd like to know if a can see the stack of pushd directories. The only thing I found was $+ in prompt $P$+$G which adds a '+' for each pushd directory e.g. D:\CMD++>. But I can't see a way how to make use of that.
EDIT:
I just noticed there is something wrong with the $+ of prompt that confused me.
The setlocal in the example above actually makes the script return to the initial directory, but the prompt shows a '+' indicating a missing popd.
D:\CMD>test.cmd
D:\CMD>prompt $P$+$g
D:\CMD>setlocal
D:\CMD>pushd e:\DATA
e:\DATA+>goto :EOF
D:\CMD+>popd
D:\CMD>
EDIT:
Due to the hint to the difference between local environment and directory stack I expanded the example form above, pushing to C:\DATA before calling the script. the script returns to where it has been called from (C:\DATA). So far so good. The first popd shows no effect as it removes the last pushd from stack but not changing the directory, since the directory change has been undone by the (implicit) endlocal. The second popd returns to initial directory as expected.
D:\CMD>pushd C:\DATA
C:\DATA+>d:\cmd\test.cmd
C:\DATA+>prompt $P$+$g
C:\DATA+>setlocal
C:\DATA+>pushd e:\DATA
e:\DATA++>goto :EOF
C:\DATA++>popd
C:\DATA+>popd
D:\CMD>

The pushd command without any arguments lists the contents of the directory stack, which can be made use of, by writing the list with output redirection > to a (temporary) file, which is then read by a for /F loop, and to determine how many popd commands are necessary.
In a batch-file:
pushd > "tempfile.tmp" && (
for /F "usebackq delims=" %%P in ("tempfile.tmp") do popd
del "tempfile.tmp"
)
Directly in cmd:
pushd > "tempfile.tmp" && (for /F "usebackq delims=" %P in ("tempfile.tmp") do #popd) & del "tempfile.tmp"
Note, that you cannot use for /F "delims=" %P in ('pushd') do ( … ), because for /F would execute pushd in a new instance of cmd.exe, which has got its own new pushd/popd stack.
One way to prevent residues from the pushd/popd stack is to simply avoid pushd and to use cd /D instead within a localised environment (by setlocal/endlocal in a batch file), because, unlike popd, endlocal becomes implicitly executed whenever necessary (even if the batch file contains wrong syntax that leads to a fatal error that aborts execution, like rem %~). This of course only works when there are no UNC paths to be handled (that is, to be mapped to local paths).
It is advisable to use pushd/popd only in a limited code section with out any branches (by if, goto, etc.). Additionally, ensure to account for potential errors (using conditional execution here):
rem // First stack level:
pushd "D:\main" && (
rem // Second stack level:
pushd "D:\main\sub" && (
rem /* If `D:\main\sub` does not exist, `pushd` would obviously fail;
rem to avoid unintended `popd`, use conditional execution `&&`: */
popd
)
rem // We are still in directory `D:\main` here as expected.
popd
)

Two points here:
1. Setlocal/Endlocal commands save/restore current directory!
This means that when Endlocal command is executed, the current directory is returned to the point it has when the previous Setlocal command was executed. This happens no matter if the Endlocal is explicitly executed, or it is implicitly executed by the termination of the called subroutine (via exit /B or goto :EOF commands).
This behaviour could be used to solve your problem, because all changes of the current directory are reverted when endlocal command is executed. That is:
setlocal
cd somwhere
rem doing stuff
goto :EOF
However note that this behavior is not related to pushd/popd commands! You can not mix the use of this feature with pushd/popd commands. You would need to complete several tests until the mechanism of the interaction between these two features be comprehended.
This is an undocumented behavior discussed here.
2. POPD command sets the Exit Code when there is no previous PUSHD
As described below Exit Code management section at this answer, there are some commands that does not set the ErrorLevel when an error happens, but sets an internal value we could call "Exit Code". This value can be tested via the && || construct this way:
command && Then command when Exit Code == 0 || Else command when Exit code != 0
POPD is one of this type of commands (described under Table 5), so we could use this feature to execute POPD several times until there is not a matching PUSHD previously executed:
#echo off
setlocal
echo Enter in:
for /L %%i in (1,1,%1) do echo %%i & pushd ..
echo Leave out:
set "n=0"
:nextLevel
popd || goto exitLevels
set /A n+=1
echo %n%
goto nextLevel
:exitLevels
echo Done
Bottom line: a simple popd && goto POPBACK line solves your problem...

After my own reminder of exit codes in a now deleted comment and the answer posted by #Aacini, one more version
#echo off
pushd "somewhere"
pushd "somewhere else"
:pop
popd && (echo %cd% & goto :pop) || goto :EOF
Where echo %cd% will only show the each item in the stack, so can be removed and simply become:
:pop
popd && goto :pop || goto :EOF

Related

Windows batch: REN does not halt even if errorlevel 1

I'm using REN to find files with a certain naming pattern and modify them, like so:
REN "?%var1%?%var2%.S16" "?%var1%?%var3%.S16"
This finds all files like aXaY.S16, bXaY.S16, cXbY.S16 (etc) and renames them to aXaZ.S16, bXaZ.S16, cXbZ.S16 (etc). If it finds what it's looking for, it works just fine. But there's a problem: REN won't halt the operation if it encounters an error.
To prove this is the case, my script is as follows:
#echo off
set /p var1=Enter first var:
set /p var2=Enter second var:
set /p var3=Change second var to:
echo Searching for all files matching ?%var1%?%var2%.S16
REN "?%var1%?%var2%.S16" "?%var1%?%var3%.S16"
echo Errorlevel: %errorlevel%
IF ERRORLEVEL 1 goto :FAIL
echo Success!
PAUSE
goto :eof
:FAIL
echo I done goofed!
PAUSE
exit
I ran this in a folder containing a few hundred files. I searched for files matching ?0?a.S16 (of which there are ~40 results) and asked it to change the 'a' to a 'c', knowing that files with this name already exist and should create a conflict.
Here is the console output (shortened for brevity):
Enter first var: 0
Enter second var:a
Change second var to:c
Searching for all files matching ?0?a.S16
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
...(etc)...
Errorlevel: 1
I done goofed!
Press any key to continue . . .
The complaint about duplicates goes on for about 40 lines, as expected. As you can see, though, Errorlevel returns 1 at the end of the process instead of quitting at the first sign of trouble, which is what I'd rather it do.
I did consider passing this through FOR /f but I don't know how I would also pass the searchmask through it. I know FOR supports '*' wildcards, but as far as I'm aware, it doesn't support '?' the same way REN does. A possible alternative would be to use regular expressions somehow, but I can't wrap my head around them at all despite trying.
Any clues? Many thanks for taking a look.
Yes, you have documented how the REN command works - it continues to completion, even after a rename fails, and then reports ERRORLEVEL 1 if at least one rename failed.
If you want to halt processing upon the first error, then you will have to write your own loop to rename each file, one at a time. You should not use the simple FOR loop because it can begin iterating before it has scanned the entire directory, so you run the risk of renaming the same file twice. The safe thing to do is use FOR /F coupled with DIR /B /A-D instead.
#echo off
set /p var1=Enter first var:
set /p var2=Enter second var:
set /p var3=Change second var to:
echo Searching for all files matching ?%var1%?%var2%.S16
for /f "eol=: delims=" %%F in (
'dir /b /a-d "?%var1%?%var2%.S16"'
) do ren "%%F" "?%var1%?%var3%.S16" || goto :break
:break
echo Errorlevel: %errorlevel%
IF ERRORLEVEL 1 goto :FAIL
echo Success!
PAUSE
goto :eof
:FAIL
echo Errorlevel: %errorlevel%
echo I done goofed!
PAUSE
exit
Note - The wildcard rules used by REN are not at all intuitive. You should have a look at How does the Windows RENAME command interpret wildcards? to make sure you are getting the results you expect.
replace you ren command with
FOR /f "delims=" %%x IN ('dir /b /a-d "%sourcedir%\?%var1%?%var2%.S16" 2^>nul') DO REN "%sourcedir%\%%x" "?%var1%?%var3%.S16"&IF ERRORLEVEL 1 ECHO failed AT %%x&goto :EOF
note This is a direct patch of my test, where I set sourcedir to a testing directory. In your case, you'd need to make appropriate adjustments.
This makes a directory list in basic form without directories and each filename is then assigned to %%x Then rename is then attempted and the resultant errorlevel interpreted. On fail, show the name for good measure and bail out.
[edit] 2^>nul added into dir command.
This will cause dir errors (like "file not found") to be directed to nowhere. The caret (^) is required to tell cmd that the > is part of the dir command, not the for.

How do I detect if script was CALL'ed or invoked directly, in Windows CMD.EXE shell?

I need to distinguish these two situations inside script.cmd:
C:\> call script.cmd
C:\> script.cmd
How can I determine if my script.cmd was invoked directly, or invoked in the context of using a CALL?
If it matters, this is on Windows 7.
#echo off
set invoked=0
rem ---magic goes here---
if %invoked%==0 echo Script invoked directly.
if %invoked%==1 echo Script invoked by a CALL.
Anyone know the "magic goes here" which would detect having been CALL'ed and set invoked=1?
At this moment, I see no way to detect it, but as a workaround you can always force the use of the sentinel.
#echo off
setlocal enableextensions
rem If "flag" is not present, use CALL command
if not "%~1"=="_flag_" goto :useCall
rem Discard "flag"
shift /1
rem Here the main code
set /a "randomExitCode=%random% %% 2"
echo [%~1] exit with code %randomExitCode%
exit /b %randomExitCode%
goto :eof
rem Retrieve a correct full reference to the current batch file
:getBatchReference returnVar
set "%~1=%~f0" & goto :eof
rem Execute
:useCall
setlocal enableextensions disabledelayedexpansion
call :getBatchReference _f0
endlocal & call "%_f0%" _flag_ %*
This will allow you to use the indicated syntax
script.cmd first && script.cmd second && script.cmd third
The posted code ends the script with a random exit code for testing. Execution will continue when the exit code is 0
NOTE: For it to work, at least in XP, it seems the call to the batch file MUST be the last code in the batch file
Check if the script's path is in the CMDCMDLINE variable. If not, then it was probably called.
In this example I use %CMDCMDLINE:"=/% to turn the quotes into forward-slashes (the FIND command can't search for quotes) and I echo it with <NUL SET/P="" so that certain characters in the file path (like ampersands) don't break the script.
<NUL SET/P="%CMDCMDLINE:"=/%" | FIND "/%~0/">NUL || (
REM Commands to perform if script was called
GOTO:EOF
)
::AND/OR
<NUL SET/P="%CMDCMDLINE:"=/%" | FIND "/%~0/">NUL && (
REM Commands to perform if script was NOT called
GOTO:EOF
)

use cd in windows .bat file has no effect

i want to run git pull in a path for all projects, so i wrote a bat file.
#echo off
setlocal enabledelayedexpansion
set dir=%1
if "%dir%"=="" (
set dir=%~dp0
)
for /D /R "%dir%" %%i in (*) do (
if exist %%i\.git (
cd /d %%i
rem no effect
echo %cd%
rem git pull
)
)
pause
but is seems that cd in for loop does not take any effect, i don't know why.
can someone help me solve this problem?enter code here
It has effect. But, in batch files, when a block of code (the code enclosed in parenthesis) is reached (the same for a line out of a block), variable reads are replaced with the value of the variable before executing the code in the block. So, when your for command is reached, the read of the %cd% variable is replaced with the value of the %cd% variable before executing the code. This speeds and simplifies execution of the code but generate this kind of problems.
You can enable delayed expansion with setlocal enabledelayedexpansion command, and change the sintax from %cd% to !cd!. This tells cmd that that this variable read should be delayed until execution of the line.

Variable not getting set correctly in a dos batch file on XP

I am trying to write a batch file to be run in a command prompt on XP. I am trying to get a listing of files in a specific path that follow a certain naming convention. I need to copy and rename each file instance to a static name and drop it to a transmission folder.
Since it may take a little while for the file to go in the transmission folder, I need to check before I copy the next file over so that I don't overlay the previous file. I am not able to use SLEEP or TIMEOUT since I don't have the extra toolkit installed. I try to just continually loop back to a START section until the file is sent.
I noticed that if I passed the %%x value set in the for loop that if I loop back to the START section a couple of times, it seems to lose its value and it is set to nothing. So I tried to set a variable to hold the value.
I seem to be having issues with the variable not being set correctly or not cleared. Originally it kept on referencing the first file but now it doesn't seem to be set at all. The ECHO displays the correct the value but the filename variable is empty still.
Does anyone know a better way of doing this? Thanks in advance for your help as I have already wasted a whole day on this!
This is the batch file:
#ECHO "At the start of the loop"
#for %%x in (C:\OUTBOUND\customer_file*) do (
#ECHO "In the loop"
#ECHO "loop value ="
#ECHO %%x
SET filename=%%x
#ECHO "filename ="
#ECHO %filename%
#ECHO ...ARCHIVE OUTBOUND CUSTOMER FILE
archivedatafile --sourcefile="%filename%" --archivefolder="..\archivedata\customer" --retentiondays=0
IF NOT %ERRORLEVEL%==0 GOTO ERROR
PAUSE
:START
IF EXIST l:\OutputFile (
#ping 1.1.1.1 -n 1 -w 30000
GOTO START
) ELSE (
COPY %filename% l:\OutputFile /Y
IF NOT %ERRORLEVEL%==0 GOTO ERROR
PAUSE
)
)
GOTO END
:ERROR
#echo off
#ECHO *************************************************************
#ECHO * !!ERROR!! *
#ECHO *************************************************************
:END
SET filename=
foxidrive has provided a script that should work, but did not provide an explanation as to why your code fails and how he fixed the problems.
You have 2 problems:
1) Your FOR loop is aborted immediately whenever GOTO is executed within you loop. It does not matter where the GOTO target label is placed - GOTO always terminates a loop. Foxidrive's use of CALL works perfectly - the loop will continue once the CALLed routine returns.
2) You attempt to set a variable within a block of code and then reference the new value within the same block. %VAR% is expanded when the statement is parsed, and complicated commands like IF and FOR are parsed once in their entirety in one pass. Actually, any block of code within parentheses is parsed in one pass. So the values of %ERRORLEVEL% and %FILENAME% will be constant - the values that existed before the block was entered.
As Endoro has indicated, one way to solve that problem is to use delayed expansion. Delayed expansion must be enabled by using setlocal enableDelayedExpansion, and then expand the variable using !VAR!. The value is expanded at execution time instead of parse time. Type HELP SET from the command prompt for more information about delayed expansion.
But beware that delayed expansion can cause its own problems when used with a FOR loop because the delayed expansion occurs after the FOR variable expansion: %%x will be corrupted if the value contains a !. This problem can be solved by carefully toggling delayed expansion ON and OFF as needed via SETLOCAL and ENDLOCAL.
Foxidrive's code avoids the entire delayed expansion issue by using CALL. His :NEXT routine is not inside a FOR loop, so all the commands are reparsed each time it is called, so delayed expansion is not required.
This may work - it is untested:
#echo off
ECHO Starting...
for %%x in (C:\OUTBOUND\customer_file*) do call :next "%%x"
echo done
pause
goto :eof
:next
ECHO ...ARCHIVING OUTBOUND CUSTOMER FILE "%~1"
archivedatafile --sourcefile="%~1" --archivefolder="..\archivedata\customer" --retentiondays=0
IF ERRORLEVEL 1 GOTO :ERROR
:loop
echo waiting for file...
ping -n 6 localhost >nul
IF EXIST l:\OutputFile GOTO :loop
COPY "%~1" l:\OutputFile /Y
IF ERRORLEVEL 1 GOTO :ERROR
GOTO :EOF
:ERROR
ECHO *************************************************************
ECHO * !!ERROR!! in "%%x"
ECHO *************************************************************
pause
goto :EOF
try this:
#echo off&setlocal
for %%x in (C:\OUTBOUND\customer_file*) do SET "filename=%%x"
ECHO %filename%
ECHO ...ARCHIVE OUTBOUND CUSTOMER FILE
archivedatafile --sourcefile="%filename%" --archivefolder="..\archivedata\customer" --retentiondays=0
IF NOT %ERRORLEVEL%==0 GOTO:ERROR
PAUSE
:START
IF EXIST l:\OutputFile ping 1.1.1.1 -n 1 -w 30000&GOTO:START
COPY "%filename%" l:\OutputFile /Y
IF NOT %ERRORLEVEL%==0 GOTO:ERROR
PAUSE
GOTO:END
:ERROR
echo off
ECHO *************************************************************
ECHO * !!ERROR!! *
ECHO *************************************************************
:END
SET "filename="
If you use codeblocks (if and for with ( )) and variables with changing values you have to enable delayed expansion. You don't need code blocks in this code, as you can see.

Drag and drop batch file for multiple files?

I wrote a batch file to use PngCrush to optimize a .png image when I drag and drop it onto the batch file.
In the what's next section, I wrote about what I thought would be a good upgrade to the batch file.
My question is: is it possible to create a batch file like I did in the post, but capable of optimizing multiple images at once? Drag and drop multiple .png files on it? (and have the output be something like new.png, new(1).png, new(2).png, etc...
Yes, of course this is possible. When dragging multiple files on a batch file you get the list of dropped files as a space-separated list. You can verify this with the simple following batch:
#echo %*
#pause
Now you have two options:
PngCrush can already handle multiple file names given to it on the command line. In this case all you'd have to do would be to pass %* to PngCrush instead of just %1 (as you probably do now):
#pngcrush %*
%* contains all arguments to the batch file, so this is a convenient way to pass all arguments to another program. Careful with files named like PngCrush options, though. UNIX geeks will know that problem :-)
After reading your post describing your technique, however, this won't work properly as you are writing the compressed file to new.png. A bad idea if you're handling multiple files at once as there can be only one new.png :-). But I just tried out that PngCrush handles multiple files just well, so if you don't mind an in-place update of the files then putting
#pngcrush -reduce -brute %*
into your batch will do the job (following your original article).
PngCrush will not handle multiple files or you want to write each image to a new file after compression. In this case you stick with your "one file at a time" routine but you loop over the input arguments. In this case, it's easiest to just build a little loop and shift the arguments each time you process one:
#echo off
if [%1]==[] goto :eof
:loop
pngcrush -reduce -brute %1 "%~dpn1_new%~x1"
shift
if not [%1]==[] goto loop
What we're doing here is simple: First we skip the entire batch if it is run without arguments, then we define a label to jump to: loop. Inside we simply run PngCrush on the first argument, giving the compressed file a new name. You may want to read up on the path dissection syntax I used here in help call. Basically what I'm doing here is name the file exactly as before; I just stick "_new" to the end of the file name (before the extension). %~dpn1 expands to drive, path and file name (without extension), while %~x1 expands to the extension, including the dot.
ETA: Eep, I just read your desired output with new.png, new(1).png, etc. In this case we don't need any fancy path dissections but we have other problems to care about.
The easiest way would probably be to just start a counter at 0 before we process the first file and increment it each time we process another one:
#echo off
if [%1]==[] goto :eof
set n=0
:loop
if %n%==0 (
pngcrush -reduce -brute %1 new.png
) else (
pngcrush -reduce -brute %1 new^(%n%^).png
)
shift
set /a n+=1
if not [%1]==[] goto loop
%n% is our counter here and we handle the case where n is 0 by writing the result to new.png, instead of new(0).png.
This approach has problems, though. If there are already files named new.png or new(x).png then you will probably clobber them. Not nice. So we have to do something different and check whether we can actually use the file names:
rem check for new.png
if exist new.png (set n=1) else (set n=0 & goto loop)
rem check for numbered new(x).png
:checkloop
if not exist new^(%n%^).png goto loop
set /a n+=1
goto checkloop
The rest of the program stays the same, including the normal loop. But now we start at the first unused file name and avoid overwriting files that are already there.
Feel free to adapt as needed.
To do Drag & Drop in a secure way, isn't so simple with batch.
Dealing with %1, shift or %* could fail, because the explorer is not very smart, while quoting the filenames, only filenames with spaces are quoted.
But files like Cool&stuff.png are not quoted by the explorer so you get a cmdline like
pngCr.bat Cool&stuff.png
So in %1 is only Cool even in %* is only Cool, but after the batch ends, cmd.exe tries to execute a stuff.png (and will fail).
To handle this you could access the parameters with !cmdcmdline! instead of %1 .. %n,
and to bypass a potential error at the end of execution, a simple exit could help.
#echo off
setlocal ENABLEDELAYEDEXPANSION
rem Take the cmd-line, remove all until the first parameter
set "params=!cmdcmdline:~0,-1!"
set "params=!params:*" =!"
set count=0
rem Split the parameters on spaces but respect the quotes
for %%G IN (!params!) do (
set /a count+=1
set "item_!count!=%%~G"
rem echo !count! %%~G
)
rem list the parameters
for /L %%n in (1,1,!count!) DO (
echo %%n #!item_%%n!#
)
pause
REM ** The exit is important, so the cmd.ex doesn't try to execute commands after ampersands
exit
Btw. there is a line limit for drag&drop operations of ~2048 characters, in spite of the "standard" batch line limit of ~8192 characters.
As for each file the complete path is passed, this limit can be reached with few files.
FOR %%A IN (%*) DO (
REM Now your batch file handles %%A instead of %1
REM No need to use SHIFT anymore.
ECHO %%A
)
And to differentiate between dropped files and folders, you can use this:
FOR %%I IN (%*) DO (
ECHO.%%~aI | FIND "d" >NUL
IF ERRORLEVEL 1 (
REM Processing Dropped Files
CALL :_jobF "%%~fI"
) ELSE (
REM Processing Dropped Folders
CALL :_jobD "%%~fI"
)
)
This is a very late answer, Actually I was not aware of this old question and prepared an answer for this similar one where there was a discussion about handling file names with special characters because explorer only quotes file names that contain space(s). Then in the comments on that question I saw a reference to this thread, after that and not to my sureprise I realized that jeb have already covered and explained this matter very well, which is expected of him.
So without any further explanations I will contribute my solution with the main focus to cover more special cases in file names with this ,;!^ characters and also to provide a mechanism to guess if the batch file is directly launched by explorer or not, so the old fashion logic for handling batch file arguments could be used in all cases.
#echo off
setlocal DisableDelayedExpansion
if "%~1" EQU "/DontCheckDrapDrop" (
shift
) else (
call :IsDragDrop && (
call "%~f0" /DontCheckDrapDrop %%#*%%
exit
)
)
:: Process batch file arguments as you normally do
setlocal EnableDelayedExpansion
echo cmdcmdline=!cmdcmdline!
endlocal
echo,
echo %%*=%*
echo,
if defined #* echo #*=%#*%
echo,
echo %%1="%~1"
echo %%2="%~2"
echo %%3="%~3"
echo %%4="%~4"
echo %%5="%~5"
echo %%6="%~6"
echo %%7="%~7"
echo %%8="%~8"
echo %%9="%~9"
pause
exit /b
:: IsDragDrop routine
:: Checks if the batch file is directly lanched through Windows Explorer
:: then Processes batch file arguments which are passed by Drag'n'Drop,
:: rebuilds a safe variant of the arguments list suitable to be passed and processed
:: in a batch script and returns the processed args in the environment variable
:: that is specified by the caller or uses #* as default variable if non is specified.
:: ErrorLevel: 0 - If launched through explorer. 1 - Otherwise (Will not parse arguments)
:IsDragDrop [retVar=#*]
setlocal
set "Esc="
set "ParentDelayIsOff=!"
setlocal DisableDelayedExpansion
if "%~1"=="" (set "ret=#*") else set "ret=%~1"
set "Args="
set "qsub=?"
:: Used for emphasis purposes
set "SPACE= "
setlocal EnableDelayedExpansion
set "cmdline=!cmdcmdline!"
set ^"ExplorerCheck=!cmdline:%SystemRoot%\system32\cmd.exe /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" (
set ^"ExplorerCheck=!cmdline:"%SystemRoot%\system32\cmd.exe" /c ^""%~f0"=!^"
if "!cmdline!"=="!ExplorerCheck!" exit /b 1
)
set "ExplorerCheck="
set ^"cmdline=!cmdline:*"%~f0"=!^"
set "cmdline=!cmdline:~0,-1!"
if defined cmdline (
if not defined ParentDelayIsOff (
if "!cmdline!" NEQ "!cmdline:*!=!" set "Esc=1"
)
set ^"cmdline=!cmdline:"=%qsub%!"
)
(
endlocal & set "Esc=%Esc%"
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if not defined cmdline endlocal & endlocal & set "%ret%=" & exit /b 0
:IsDragDrop.ParseArgs
if "%cmdline:~0,1%"=="%qsub%" (set "dlm=%qsub%") else set "dlm= "
:: Using '%%?' as FOR /F variable to not mess with the file names that contain '%'
for /F "delims=%dlm%" %%? in ("%cmdline%") do (
set ^"Args=%Args% "%%?"^"
setlocal EnableDelayedExpansion
set "cmdline=!cmdline:*%dlm: =%%%?%dlm: =%=!"
)
(
endlocal
for /F "tokens=*" %%A in ("%SPACE% %cmdline%") do (
set "cmdline=%%A"
)
)
if defined cmdline goto :IsDragDrop.ParseArgs
if defined Esc (
set ^"Args=%Args:^=^^%^"
)
if defined Esc (
set ^"Args=%Args:!=^!%^"
)
(
endlocal & endlocal
set ^"%ret%=%Args%^"
exit /b 0
)
OUTPUT with sample files dragged and dropped onto the batch file:
cmdcmdline=C:\Windows\system32\cmd.exe /c ""Q:\DragDrop\DragDrop.cmd" Q:\DragDrop\ab.txt "Q:\DragDrop\c d.txt" Q:\DragDrop\!ab!c.txt "Q:\DragDrop\a b.txt" Q:\DragDrop\a!b.txt Q:\DragDrop\a&b.txt Q:\DragDrop\a(b&^)).txt Q:\DragDrop\a,b;c!d&e^f!!.txt Q:\DragDrop\a;b.txt"
%*=/DontCheckDrapDrop "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
#*= "Q:\DragDrop\ab.txt" "Q:\DragDrop\c d.txt" "Q:\DragDrop\!ab!c.txt" "Q:\DragDrop\a b.txt" "Q:\DragDrop\a!b.txt" "Q:\DragDrop\a&b.txt" "Q:\DragDrop\a(b&^)).txt" "Q:\DragDrop\a,b;c!d&e^f!!.txt" "Q:\DragDrop\a;b.txt"
%1="Q:\DragDrop\ab.txt"
%2="Q:\DragDrop\c d.txt"
%3="Q:\DragDrop\!ab!c.txt"
%4="Q:\DragDrop\a b.txt"
%5="Q:\DragDrop\a!b.txt"
%6="Q:\DragDrop\a&b.txt"
%7="Q:\DragDrop\a(b&^)).txt"
%8="Q:\DragDrop\a,b;c!d&e^f!!.txt"
%9="Q:\DragDrop\a;b.txt"
In :IsDragDrop routine I specially tried to minimize the assumptions about command line format and spacing between the arguments. The detection (guess) for explorer launch is based on this command line signature %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments"
So it is very possible to fool the code into thinking it has launched by double click from explorer or by drag'n'drop and that's not an issue and the batch file will function normally.
But with this particular signature it is not possible to intentionally launch batch file this way: %SystemRoot%\system32\cmd.exe /c ""FullPathToBatchFile" Arguments & SomeOtherCommand" and expect that the SomeOtherCommand to be executed, instead it will be merged into the batch file arguments.
You don't need a batch script to optimize multiple PNGs, all you need is the wildcard:
pngcrush -d "crushed" *.png
That will pngcrush all PNGs in the current dir and move them to a sub-dir named "crushed". I would add the -brute flag to likely shave off a few more bytes.
pngcrush -d "crushed" -brute *.png
I'm posting this because it doesn't seem to be well documented or widely known, and because it may be easier for you in the long run than writing and maintaining a drag and drop batch file.

Resources