I am trying to create windows batch script to check the size of file after it is downloaded but it fails with this message.
The Process cannot access the file as it is used by another process
2048 was unexpected at this time.
Thanks in advance for any help.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set file="tst_file.xml"
set maxbytesize=2048
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
Consider this alteration.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set "file=tst_file.xml"
set maxbytesize=2048
if not exist "%file%" (
#echo '%file%' does not exist.
goto :SKIP_CHECK
)
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
:SKIP_CHECK
Notes
If the file doesn't exist, the FOR loop will not be able to get the file size, and the LSS comparison will blow up with no operand to compare.
If you had quoted each of the vars (e.g. if "%size%" LSS "%maxbytesize%" ...), it wouldn't have blown up. However, it would have performed a lexical comparison, which when compared to anything would evaluate to true.
In batch, if you use double quotes around only the value in a SET command, the quotes will be included in the value. Even though it looks unorthodox, put quotes around the whole <variable>=<value> expression.
If you skip tokenization and get the entire line (i.e. tokens=*), you'll be able to handle files with spaces in the name.
Related
like I told in the question I get an unexplainable Syntax error from my Code.
I've bin searching for a program which automatically backups some saves every 5 Minutes. After I hadn't found anything which belongs to my purposes I decided to do it my own.. in Batch.
Here's the code:
#echo off
set name=Backup
for /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir1= (here comes the source directory)
set dir2= (here comes the target directory)
set countvar=1
:start
For /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir2=(here comes the target directory)\%backupname%
echo Backupordner: %backupname%
ROBOCOPY %dir1% %dir2%
if "%countvar%" == "1" (
set VarDir1=%dir2%
)
if "%countvar%" == "2" (
set VarDir2=%dir2%
)
if "%countvar%" == "3" (
set VarDir3=%dir2%
rmdir /S /Q "%VarDir1%"
set VarDir1=%VarDir2%
set VarDir2=%VarDir3%
set /a countvar=%countvar%-1
)
set /a countvar=%countvar%+1
#ping -n 30 localhost> nul
goto start
What it basically does is copying the files from the source directory into a folder, which is named after the date and time, in the target directory.
Caused by the high size of the backuped files I decided to add a feature, which deletes the third oldest save, so there are two remaining, newer save-files.
This is where the problem occurs: The first two "deletes" work properly, the third "delete" causes a syntax error. Everything runs normal after it.
Does anybody have an idea where the problem could be?
Yet another example of the delayedexpansion trap.
When vardir3 is established, it has no value, so vardir2 acquires nothing on the first occasion that count=3
On the second occasion, var1 acquires that value so on the third occasion, you get a syntax error as var1 is empty.
Solution: Forget var3 entirely. In count=3, set var2 to %dir2%.
Please search SO for the many, many articles on delayed expansion.
Also, you're better off using set "var=value" for a string assignment as it does not assign any trailing spaces that may be on the line.
There are a handful of questions on SO that look similar, but I cannot figure out some behaviour and I am looking for help.
Below is a snippet from a batch file I am trying to write which will load in a set of directories and potentially replace letter substitutions with an expanded path, e.g. the properties file might look like:
location1=C:\Test
location2=[m]\Test
Where location1 points to C:\Test and location2 points to C:\Program Files(x86)\MODULE\Test, because [m] is a shorthand to C:\Program Files(x86)\MODULE.
The batch script, to this point, is simply trying to read in the list of file paths and expand/replace the [m].
SET build.dir=%~dp0%
SET progfiles=%PROGRAMFILES(X86)%
IF "%progfiles%"=="" SET progfiles=%ProgramFiles%
SET local.properties=%build.dir%local.properties
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "tokens=1* delims==" %%i IN (%local.properties%) DO (
SET local.dir=%%j
SET local.dir=!local.dir:[m]=%progfiles%\MODULE!
echo !local.dir!
)
ENDLOCAL
Running this kicks out an error:
\MODULE was unexpected at this time.
If I replace the FOR with the following instead:
set test="[m]\Proj\Dir"
set test=!test:[m]=%progfiles%\MODULE!
echo %test%
I get the desired C:\Program Files(x86)\MODULE\Proj\Dir printed out...so I'm confused why it works fine outside of the FOR loop.
My understanding about delayed expansion is that it 'expands' at runtime...which you get to happen using !! instead of %% wrapped around the variable. Furthermore, as I'm creating the local.dir variable inside the FOR loop scope, I must use delayed expansion in order to access it with the updated value for the iteration.
I feel like the problem is using %progfiles%, like there's some special syntax I need to use in order to make it work but nothing is adding up for me. When I echo %progfiles%, it prints out as C:\Program Files(x86 -- note the missing trailing ).
Any ideas? Thanks
Tested suggestion:
D:\Projects\Test\Build>test
*** "D:\Projects\Test\Build\local.properties"
*** "","C:\Program Files (x86)"
[m]=C:\Program Files (x86)\MODULE
Adding quotes around the whole expression makes it work -- can't use other characters for some reason (like []) -- and since I want to append to the path later, we can safely remove the quotes afterwards:
SET local.dir="!local.dir:[m]=%progfiles%\MODULE!"
SET local.dir=!local.dir:"=!
Test this to see if you can nut out the issue:
The double quotes are to provide robust handling in a system with long file/path names.
The () are unquoted which are a problem in a batch script, when inside a loop.
#echo off
SET "build.dir=%~dp0%"
SET "progfiles=%PROGRAMFILES(X86)%"
IF "%progfiles%"=="" "SET progfiles=%ProgramFiles%"
SET "local.properties=%build.dir%local.properties"
echo *** "%local.properties%"
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "usebackq tokens=1* delims==" %%i IN ("%local.properties%") DO (
SET "local.dir=%%j"
echo *** "!local.dir!","%progfiles%"
SET "local.dir=!local.dir:[m]=%progfiles%\MODULE!"
echo !local.dir!
)
ENDLOCAL
pause
It has to do with the () characters that end up in your progfiles string. If you take them out, the substitution seems to work fine.
My suggestion is to ditch command for this particular purpose and use one of the other standard tools that Windows comes with. While my personal preference would be Powershell (since it's so much more powerful and expressive), you may just need something quick that you can integrate into existing cmd.exe stuff.
In that case, try the following VBScript file, xlat.vbs:
set arg = wscript.arguments
wscript.echo Replace(arg(0),arg(1),arg(2))
Your batch file then becomes something like, noting the inner for /f which captures the output of the VBS script and assigns it to the variable:
#echo off
SET build.dir=%~dp0%
set progfiles=%PROGRAMFILES(X86)%
if "%progfiles%"=="" set progfiles=%ProgramFiles%
set local.properties=%build.dir%local.properties
setlocal enabledelayedexpansion
for /f "tokens=1* delims==" %%i in (%local.properties%) do (
set local.dir=%%j
for /f "delims=" %%x in ('cscript.exe //nologo xlat.vbs "!local.dir!" "[m]" "%progfiles%\MODULE"') do set local.dir=%%x
echo !local.dir!
)
endlocal
Running that, I get the output:
C:\Test
C:\Program Files (x86)\MODULE\Test
which I think is what you were after.
So I was tasked with making a batch file that does a few specific things. I've never worked with batch before, and I'm finding it hard to find tutorials on what exactly I need. (I've done basic tutorials)
I'm trying to get the most currently edited file from a directory. The only thing I've came up with (and I've noticed other people said to do) is a for loop of files in the directory sorted by date and then just get the first file and break the loop.
Some problems:
1) My loop never breaks
2) My ECHO %variable% doesn't work at the end.
#echo off
SET count=0
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF count==0 (
SET NewestFile=%%i
SET count=1
)
)
#echo on
ECHO %NewestFile%
When I run this, I get:
C:\>testing.bat
C:\>ECHO
ECHO is on.
I am 100% new to Batch. Maybe I'm doing something that this is really picky about? (Other StackOverflow questions have been solved by people just adding aa space or stuff like that)
Your condition is never met because the string count is never equal to the string 0. You need
if !count!==0 (
set NewestFile=%%i
set count=1
)
But then you also need delayed expansion (at the beginning of your batch file):
setlocal enabledelayedexpansion
The problem here is that you need to tell the batch file that there is a variable. Like foo in Perl won't magically resolve to the contents of the $foo variable count in your batch file isn't equivalent to %count% (the variable contents) or !count! (the same, but with delayed expansion).
Delayed expansion is necessary because the whole for loop is parsed at once. And cmd replaces normal (%foo%) variables with their contents during parsing so that during execution only the values remain. So once execution reaches the if there would be the condition 0==0 because that's what count's value was before the loop. Delayed expansion (using the !foo! syntax) expands the variables immediately prior to execution, so this does not happen.
For more help on delayed expansion you can read help set.
Another way would be to just use absence or presence of the count variable:
SET count=
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF not defined count (
SET NewestFile=%%i
SET count=1
)
)
This works around the problem above because there is no variable to replace during parsing. All we're doing is a run-time check whether the variable count exists.
If you supplied accurate code then you want to get the first line - and this is one way to do that.
#echo off
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO SET "NewestFile=%%i" & goto :done
:done
ECHO %NewestFile%
If you change the dir command to list the files in ascending order instead of descending order, you can use this one-liner which doesn't need any of the common bizarre cmd.exe scripting hacks. It just keeps the last line of output in the NewestFile variable (I guess it might qualify as a cmd.exe scripting hack, but I don't think it qualifies as bizarre):
for /f %%i in ('DIR Y:\ /B /O:D') do set NewestFile=%%i
How would I achieve this:
for i in *.e; do mv $i ${i%-b*.e}.e; done
in a Windows batch file? (It renames files containing "-b" to the part before "-b". Note that this is not necessarily the end of the string! e.g. "file-b-4.e" will become "file.e")
If you really want to do this in batch, this should work
#echo off
setlocal disableDelayedExpansion
for %%F in (*.e) do (
set "var=%%~F"
setlocal enableDelayedExpansion
set "var=!var:-b=.e:!"
for /f "eol=: delims=:" %%A in ("!var!") do (
endlocal
echo ren "%%F" "%%A"
)
)
Edit
The comment by panda-34 alluded to the fact that the original posted code failed if the file name begins with -b. The code above was fixed by incorporating the extension into the replacement string. (thanks panda-34 for alerting me to the problem)
panda-34 also provided an alternate solution that uses command injection with search and replace. The injected command is the REM statement.
The panda-34 solution works as long as the file name does not contain & or ^ characters, but fails if it does.
Below is a modified version of the command injection technique that should work with all valid Windows file names. There are 2 critical mods, 1) make sure the special chars in the file name are always quoted, and 2) do not pass the value as a CALL argument, otherwise ^ will be doubled to ^^.
#echo off
setlocal disableDelayedExpansion
for %%i in (*-b*.e) do (
set old="%%~ni"
call :ren_b
)
exit /b
:ren_b
set v=%old:-b=.e"&rem "%
echo ren "%old:~1,-1%.e" %v%
exit /b
Final Edit (I hope):
As baruch indicates in his comment, the solutions above remove starting with the 1st occurance, whereas the original bash command removes starting with the last occurance.
Below is a version that should be an exact equivalent of the original bash command.
#echo off
setlocal disableDelayedExpansion
set "search=-b"
for %%A in (*%search%*.e) do (
set "old=%%A"
setlocal enableDelayedExpansion
set "new=\_!old:%search%=\_!"
for %%B in ("!new!") do (
endlocal
set "new=%%~pB"
setlocal enableDelayedExpansion
set "new=!new:~2,-1!.e"
echo ren "!old!" "!new:\_=%search%!"
endlocal
)
)
Simple, really
for %%i in (*-b*.e) do call :ren_b %%~ni
goto :eof
:ren_b
set v=%*
set v="%v:-b=.e" ^& rem %
ren "%*.e" %v%
Here's a variant to keep the name till the last -b occurence
setlocal enabledelayedexpansion
for %%i in (*-b*.e) do (
set v=%%~ni
set v=!v:-b=\!
for %%j in ("\!v!") do (
set v=%%~pj
set v=!v:~1,-1!
set v=!v:\=-b!
ren "%%i" "!v!.e"
)
)
It will fail for names containing ! and starting with -b.
P.S, Didn't see, dbenham already provided the equivalent solution, probably with more provisions for terminal cases of file names.
Forget it, some convenient things cannot be done in NT scripting. What you are asking here is not possible to my knowledge. And I've written and maintained complex NT scripts bigger than 50 KiB, using all kinds of tricks. The book "Windows NT Shell Scripting" points out many of these, for the same and more see Rob van der Woude's scripting pages.
I reckon you could do part of this, but certainly not in a one-liner due to how variable expansion works in NT scripting. For example you could extract the part of the string that you expect to be -b and check whether it is -b, then extract the other parts and rename from the original name to the one that is comprised of only the extracted parts.
But you'll likely need ten to fifteen lines to achieve that. In that light, consider using a different scripting language for the purpose. Especially if this is a modern Windows version.
I realize this is not the desired answer (i.e. that this is possible and a sample), but cmd.exe is very limited compared to Bash, albeit by far not as limited as some opponents of traditional batch scripting are pointing out.
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.