Batch Script to execute more than 30 oracle sql queries - oracle

We have more than 30 Oracle query files which contain stored procedures and SQL queries.
I have tried to automate the process by creating a batch script which calls a text file "driverssql.txt" containing the file names. Below is the batch script:
#echo off
setlocal enabledelayedexpansion
>output.txt
( for /f %%a in (driversql.txt) do (
sqlplus uname/pwd#"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=XXXXXX) (PORT=1521)))(CONNECT_DATA=(SID=XXXXX)))" "%%a"
))
The script above executes well but the issue is every time the script loops through, it creates a connection which is a waste and not the best way to handle this.
When I put the connection outside the loop, the connection is successful but after that the SQLPlus window is in a hung state and does not execute the queries. Please suggest a better way to achieve this.

#ECHO OFF
SETLOCAL
SET "list="
FOR /f %%i IN (driversql.txt) DO (
CALL SET list=%%list%% "%%i"
)
ECHO sqlplus uname/pwd#"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=XXXXXX) (PORT=1521)))(CONNECT_DATA=(SID=XXXXX)))"%list%
I believe what we'd need to know is how SQLPLUS operates.
What the above code does is accumulate all of the filenames into one list, space-separated and quoted. It might work - all depends on how you would cascade operations sql1.sql then sql2.sql ... using one SQLPLUS command
Well, How about being able to control which [set of] SQL files gets executed, and in what order?
I'll assume that you can simply concatenate .SQL files into one If this isn't the case, then stop reading now...
#ECHO OFF
SETLOCAL
:tloop
SET tempfile=%RANDOM%
IF EXIST "%temp%\%tempfile%*" GOTO tloop
COPY NUL "%temp%\%tempfile%.1" >nul
FOR /f %%s IN (sqlsched.txt) DO (
FOR /f "tokens=1,2" %%i IN (%%s) DO (
FINDSTR /i /b /e /c:"%%i" "%temp%\%tempfile%.1" >nul
IF ERRORLEVEL 1 (SET "excludeme=") ELSE (SET excludeme=Y)
IF /i "%%j"=="again" (SET "excludeme=")
IF NOT DEFINED excludeme (>>"%temp%\%tempfile%.1" ECHO %%i)
)
)
TYPE "%temp%\%tempfile%.1"
(
FOR /f "delims=" %%i IN ('type "%temp%\%tempfile%.1"') DO type %%i
)>humungous.sql
DEL "%temp%\%tempfile%.1"
Suppose we have a file SQLSCHED.TXT, contents
daily1.txt
special.txt
daily2.txt
weekly1.txt
and for example daily2.txt contains
D0S9.sql
D2S1.sql
D0S0.sql
D2S1.sql
D0S9.sql again
Then this batch will first read sqlsched.txt and then process each of the files daily1.txt,special.txt,daily2.txt,weekly1.txt in turn, producing a temporary file.
Reading the temporary file line-by-line will then generate humungous.sql containing all of the contents of each of the sql files mentioned. All you have to do then is run sqlplus using humungous.sql
I've included a couple of smarts - If you repeat one of the SQL filenames as humungous.sql is built, then that procedure is only included the once, so in the daily2.txt list, the second D2S1.sql would be skipped. However D0S9.sql would be run a second time because of the again flag.
This could be extended if needed - for instance, if you had the current dayname in a variable then you could add the line
IF /i NOT "%%j"=="%dayname%" (SET "excludeme=Y")
and the line
dWdX.sql Wed
to ensure that WdX.sql eould only be run on Wednesdays

Related

Why does batch file execution stop before processing the second "call" statement?

I've a fairly rudimentary knowledge of batch files and usually manage to get by but have hit a problem which I can't solve.
The batch files are run on Windows 7 Ultimate and Windows 10 Professional systems and are usually invoked by a Scheduler program, although sometimes I just click the relevant desktop icon. Essentially, the role of the batch files is to download specific files (TV programmes) which are listed in an external text file, in my case, located in my Dropbox account. For each item in the text file (TV.txt) there are two lines, one naming the file, the other listing its ID:
name1
ID1
name2
ID2
name3
ID3
The batch files successively work through the items listed in the text file, one file works on the "IDs", the second on the "names".
The "IDs" file (tv-pid.cmd) consists of the following:
set $textFile="D:\Dropbox\Get_iPlayer\0-TVdl\tv.txt"
for /f "delims=" %%a in ('type %$textFile%') do get_iplayer --pid %%a
The "names" file (tv-nopid.cmd) consists of the following:
set $textFile="D:\Dropbox\Get_iPlayer\0-TVdl\tv.txt"
for /f "delims=" %%a in ('type %$textFile%') do get_iplayer --get %%a
Each batch file works well on its own, the problem is when I try to combine the two into a single batch file.
If I create a "combined" batch file (tv.cmd):
call tv-pid.cmd
call tv-nopid.cmd
the first "call" is executed but the batch operation terminates before calling the second file.
Equally if I create a "combined" batch file (not using "call" commands)
set $textFile="D:\Dropbox\Get_iPlayer\0-TVdl\tv.txt"
for /f "delims=" %%a in ('type %$textFile%') do get_iplayer --pid %%a
set $textFile="D:\Dropbox\Get_iPlayer\0-TVdl\tv.txt"
for /f "delims=" %%a in ('type %$textFile%') do get_iplayer --get %%a
the same happens, the download activity on line 2 is executed after which the batch operation terminates.
Personally I would prefer a solution based on the "call" commands, but I don't mind.
set $textFile="D:\Dropbox\Get_iPlayer\0-TVdl\tv.txt"
set "idnames="
for /f "delims=" %%a in ('type %$textFile%') do (
if defined id_names (
set "id_names="
call get_iplayer --pid %%a
) else (
set "id_names=Y"
call get_iplayer --get %%a
)
This may work. I've no idea what get_iplayer is or does.
The idea here is that the line-content alternates, so toggling the variable id_names between set-to-a-value and set-to-no-value (=cleared) allows us to execute get_iplayer with the correct options.
Note that your code would execute get_iplayer with the option pid or get for each line of the input file - which may be causing the problem.

execute batch files in parallel and get exit code from each

How can I execute set of batch files from single batch file in parallel and get the exit code from each. When I use start it executes the batch file in parallel (new cmd window) but don't return the exit code from each. And while using call, I can get the exit code but the batch file execution happens sequentially. I have following code:
ECHO ON
setlocal EnableDelayedExpansion
sqlcmd -S server_name -E -i select_code.sql >\path\output.txt
for /f "skip=2 tokens=1-3 delims= " %%a in ('findstr /v /c:"-" \path\output.txt') do (
echo $$src=%%a>\path1\%%c.prm
echo $$trg=%%b>>\path1\%%c.prm
set param_name=%%c
start cmd \k \path\exec_pmcmd_ctrm.bat workflow_name %%param_name%%
ping 1.1.1.1 -n 1 -w 5000 > nul
set exitcode="%V_EXITCODE%"
echo %exitcode%>>\path\exitcode.txt
)
This executes the exec_pmcmd_ctrm.bat 3 times with different variable in parallel , but I am unable to get the exit code from each execution. I tried using call but then I miss the parallel execution of bat file. Any help in this regard?
First of all, the "exit code" from another Batch file (that ends with exit /B value command) is taken via %ERRORLEVEL% variable (not %V_EXITCODE%). Also, if such a value changes inside a FOR loop, it must be taken via Delayed Expansion instead: !ERRORLEVEL! (and EnableDelayedExpansion at beginning of your program). However, these points don't solve your problem because there is a misconception here...
When START command is used (without the /WAIT switch), a parallel cmd.exe process start execution. This means that there is not a direct way that the first Batch file could know in which moment the parallel Batch ends in order to get its ERRORLEVEL at that point! There is not a "wait for a started Batch file" command, but even if it would exist, it don't solve the problem of have several concurrent Batch files. In other words, your problem can not be solved via direct commands, so a work around is necessary.
The simplest solution is that the parallel Batch files store their ERRORLEVEL values in a file that could be later read by the original Batch file. Doing that imply a synchronization problem in order to avoid simultaneous write access to the same file, but that is another story...
Here is a pure batch-file solution. Basically, this script executes all batch files located in the same directory simultaneously, where the exit code (ErrorLevel) of each one is written to an individual log file (with the same name as the batch file and extension .log); these files are checked for existence; as soon as such a log file is found, the stored exit code is read and copied into a summary log file, together with the respective batch file name; as soon as all log files have been processed, this script is terminated; the exit code of this script is zero only if all the exit codes of the executed batch files are zero too. So here is the code -- see all the explanatory rem remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Collect available scripts in an array:
set /A "INDEX=0"
for %%J in ("%~dp0*.bat" "%~dp0*.cmd") do (
if /I not "%%~nxJ"=="%~nx0" (
set "ITEM=%%~fJ"
call set "$BATCH[%%INDEX%%]=%%ITEM%%"
set /A "INDEX+=1"
)
)
rem // Execute scripts simultaneously, write exit codes to individual log files:
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
start "" /MIN cmd /C rem/ ^& "%%~fJ" ^& ^> "%%~dpnJ.log" call echo %%^^ErrorLevel%%
)
rem // Deplete summary log file:
> "%~dpn0.log" rem/
rem // Polling loop to check whether individual log files are available:
:POLLING
rem // Give processor some idle time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished script,
rem so its log file is availabe, the related array element becomes deleted,
rem so finally, there should not be any more array elements defined: */
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
rem // Suppress error message in case log file is not yet available:
2> nul (
rem // Read exid code from log file:
set "ERRLEV=" & set "FILE="
< "%%~dpnJ.log" set /P ERRLEV=""
if defined ERRLEV (
rem // Copy the read exit code to the summary log file:
set "NAME=%%~nxJ"
>> "%~dpn0.log" call echo(%%ERRLEV%% "%%NAME%%"
rem // Undefine related array element:
set "%%I="
rem // Store log file path for later deletion:
set "FILE=%%~dpnJ.log"
)
rem // Delete individual log file finally:
if defined FILE call del "%%FILE%%"
)
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set $BATCH[ && goto :POLLING
rem // Check individual exit codes and return first non-zero value, if any:
set "ERRALL="
for /F "usebackq" %%I in ("%~dpn0.log") do (
if not defined ERRALL if %%I neq 0 set "ERRALL=%%I"
)
if not defined ERRALL set "ERRALL=0"
endlocal & exit /B %ERRALL%
This approach is quite similar to the one I used in the following Improving Batch File for loop with start subcommand, but there the outputs of simultaneously executed commands are collected.

Ensure a batch file is called with given arguments at most N times

I have a Windows batch file that receives several arguments, and performs some tasks on files.
My main purpose is to execute a certain task (the batch file with a given set of arguments) at most 5 times. In order to do this, I redirected its outputs (and echoed the calling arguments) to a log.file.
I want to check if the same command has been issued before, so I do a find:
.....
set retryC=0
#set cc=%*
:: replace quote with double quote so it works with find
#set cc=%cc:"=""%
setlocal EnableDelayedExpansion
for /f "tokens=3" %%f in ('find /C "%cc%" "d:\temp\log.file"') do set retryC=%%f
#echo retry count = %retryC%
#echo local retry count = !retryC!
endlocal
This will only echo a zero result in the log file no matter how many times it's executed.
retry count = 0
local retry count = 0
However, if I copy the line that's being output in the log by the for command, and execute it directly in the console, it works
>for /F "tokens=3" %f in ('find /C "-p1 ""p1val"" -p2 ""p2val""" "d:\temp\log.file"') do set retryC=%f
>set retryC=14
Any ideas why this happens and how I can get the same result inside the batch file?
I already tried to perform a copy /v /y "d:\temp\log.file" "d:\temp\tmp.file" before the find, and perform the for on it, with the same (zero) result

BATCH FILE - IF Exists & Output Error

INFO: assets.txt contains a list of cpu names which I can connect to over the network.
I need to copy this new .exe to well over 200+ computers and figured i could use the c$ admin share. This is really the only way I can do this without going to workstations individually or remoting in one by one.
This script works without the 'if exists' however I need to check if the directory exists before attempting the copy. I don't understand why it isn't working. I am also running this script using my domain administrative account.
#echo off
REM Pull Computer Asset Tags from file
for /F "tokens=*" %%A in (assets.txt) do (
echo Start Processing %%A
REM Temporarily set file path for existence check
set file=\\%%A\C$\Program Files\Intouch2\Intouch2ca.exe
if EXIST "%file%" (
REM Rename old .exe
ren "\\%%A\C$\Program Files\Intouch2\Intouch2ca.exe" "Intouch2ca.bak"
REM copy new .exe from server to cpu asset
xcopy "\\server\my dir\management\it\software\Intouch Upgrade\Intouch2ca.exe" "\\%%A\C$\Program Files\Intouch2\" /Y
echo END Processing %%A
echo.
echo ------------------------------------------------------------
echo.
)
)
I also haven't been able to get the error output to a log file.
I have tried this but it isnt exactly what I would like.
xcopy "\\server\my dir\management\it\software\Intouch Upgrade\Intouch2ca.exe" "\\%%A\C$\Program Files\Intouch2\" /Y 1>>errors.log 2>&1
How can I pretty that up so it only shows errors and lists the %%A where the error occured?
Thank you all in advance for your time.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, in your case, file is being changed within the block, so the value cmd uses is its initial value when the entire for is parsed.
Solution 1: use \\%%A\C$\Program Files\Intouch2\Intouch2ca.exe in place of %file%
Solution 2: start your batch with setlocal enabledelayedexpansion on a separate line after the #echo off, then use !file! in place of %var%
Solution 3: call an internal routine to use the mofified value as %file%
Solution 4: Create the directory regardless. MD newname 2>nul will silently create a new directory if it doesn't already exist
An error on a copy will set an errorlevel and you can write a custom error message.
copy "\\server\my dir\management\it\software\Intouch Upgrade\Intouch2ca.exe" "\\%%A\C$\Program Files\Intouch2\" >nul 2>&1
if errorlevel 1 >> errors.txt echo "Error in %%A"

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