My own version to solve all drag&drop problems:
Can handle any filenames, like
d1_;,=
d2_four spaces
d3_One&Amp
d4_Amp&with space
But there is still a strong problem, filenames like a&()
can't be handled by the drag.bat, because the filename creates a syntax error even before the batch file is called.
But with a nasty hack it can be solved, then even the hard ones works!
d5_syntax&()error
cmd.exe can start an AutoRun (batch) command, if registered.
This AutoRun batch file gets no arguments, but the complete command in cmdcmdline
.
I simply parse that data and if necessary I rebuild new quoted arguments (only 300 lines).
For modifying the registry entry use
AutoRun --show
AutoRun --install
AutoRun --remove
drag.bat
@echo off
setlocal ENABLEDELAYEDEXPANSION
rem *** Take the cmd-line, remove all until the first parameter
rem *** Copy cmdcmdline without any modifications, as cmdcmdline has some strange behaviour
set "params=!cmdcmdline!"
set "params=!params:~0,-1!"
set "params=!params:*" =!"
call :fn_$arg_parser params
REM ** FOR /L %%# in (0 1 !arg#-1!) DO (
REM ** echo arg%%# = '!arg%%#!'
REM ** )
set count=0
rem Split the parameters on spaces but respect the quotes
set "params=!params:^;^=""S!"
for %%G IN (!params:^;^=""S!) do (
set /a count+=1
set "_item=%%~G"
set "item_!count!=!_item:""=;!"
rem echo !count! %%~G
)
rem list the parameters
for /L %%n in (1,1,!count!) DO (
echo arg%%n #!item_%%n!#
)
pause > NUL
REM ** The exit is important, so the cmd.exe doesn't try to execute commands after ampersands
exit
:fn_$arg_parser arg_line
REM *** @TODO: ENDLOCAL all arg-... variables
::: Output arg0, ..., arg and arg#
::: arg0 ... arg contains unquoted arguments
::: an argument that contains only one quote (last character in line) is treated as quoted arg=" -> arg=, arg.quoted=1
::: arg0.quoted contains 1 if the argument was quoted
::: \ escapes the next character (also inside quotes), can escape delimiters outside quotes, can escape quotes
::: Only \" is replaced to a single quote ", all other \ are unchanged
(set^ arg_line=!%1!)
call :strlen arg_len arg_line
set /a arg_len-=1
set "quoted="
rem set "escapeChar=" --- "Say: \"Hello\" to frank\n"
set /a arg#=0
set "arg0="
for /L %%I in (0 1 !arg_len!) DO (
for %%# in (!arg#!) DO (
set "char=!arg_line:~%%I,1!"
set "isDelim="
if "!escapeChar!!char!" == ^""" set "quoted=!quoted:~,0!"
REM echo %%I: "!char!" quote: !quote!
%= only outside quotes =%
%= check if char is a delim =%
FOR /F "tokens=1,2 delims= " %%D in ("isDelim!char!NO") DO (
if "%%D" == "isDelim" (
set isDelim=1
)
)
REM *** Split arguments by not escaped delimiter
if "!isDelim!,!quoted!,!escapeChar!" == "1,," (
REM *** Finalize arg, test if arg is quoted
if defined arg%%# (
set arg%%#.quoted=
REM echo #in %%# "!arg%%#:~,1!!arg%%#:~-1!"
if "!arg%%#:~,1!!arg%%#:~-1!" == """" (
if "!arg%%#:~-2,-1!" NEQ "\" (
set arg%%#.quoted=1
set "arg%%#=!arg%%#:~1,-1!"
)
)
if defined arg%%# (
set ^"arg%%#=!arg%%#:"\"="!"
)
set /a arg#+=1
)
)
if defined escapeChar (
set "escapeChar="
if "!char!" EQU ^""" (
REM *** Replace escaped quotes with "\" that can be safely detected and replaced later
set "char="\""
) ELSE IF defined isDelim (
set "isDelim="
) ELSE (
set "char=\!char!"
)
) ELSE if "!char!" == "\" set "escapeChar=\"
if defined quoted set "isDelim="
REM *** Append char to arg
if "!isDelim!!escapeChar!" == "" (
set "arg%%#=!arg%%#!!char!"
)
)
)
for %%# in (!arg#!) DO (
REM *** Last character is \
if defined escapeChar (
set "arg%%#=!arg%%#!\"
)
REM *** Duplicated code
REM *** Finalize arg, test if arg is quoted
if defined arg%%# (
set arg%%#.quoted=
REM echo out %%# "!arg%%#:~,1!!arg%%#:~-1!"
if "!arg%%#:~,1!!arg%%#:~-1!" == """" (
if "!arg%%#:~-2,-1!" NEQ "\" (
set arg%%#.quoted=1
set "arg%%#=!arg%%#:~1,-1!"
)
)
if defined arg%%# (
set ^"arg%%#=!arg%%#:"\"="!"
)
set /a arg#+=1
)
)
REM *** Create a helper variable with name "arg#-1"
set /a tmp=!arg#!-1
set "arg#-1=!tmp!"
exit /b
::: detector line %~* *** FATAL ERROR: missing parenthesis or exit /b
:strlen
(
setlocal EnableDelayedExpansion
(set^ tmp=!%~2!)
if defined tmp (
set "len=1"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
AutoRun.bat
@echo off
REM *** To enable this script, call it by --install
REM *** To see the current values
REM *** reg query "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun
REM *** reg query "HKEY_LOCAL_MACHINE\\Software\Microsoft\Command Processor" /v AutoRun
REM *** To delete the keys
REM *** reg DELETE "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun /f
REM *** reg DELETE "HKEY_LOCAL_MACHINE\\Software\Microsoft\Command Processor" /v AutoRun /f
setlocal EnableDelayedExpansion
REM *** ALWAYS make a copy of the complete CMDCMDLINE, else you destroy the originial!!!
set "_ccl_=!cmdcmdline!"
echo(>CON
REM *** %1 contains only data, when the script itself was called from the command line
if "%~1" NEQ "" (
goto :direct_call
)
REM echo #0 '!_ccl_!' > CON
REM *** The check is necessary to distinguish between a new cmd.exe instance for a user or for a "FOR /F" sub-command
if "!_ccl_:~1,-2!" == "!comspec!" (
REM ***** INTERACTIVE ****
echo %DATE% %TIME% comspec >> "%~dp0\started.log"
FOR %%M in ("%~dp0\cmdMacros.mac") DO (
endlocal
echo ********************************************************************
echo * AutoRun executed from "%~f0"
if exist "%%~M" (
doskey /macrofile="%%~M"
echo * Macros loaded from "%%~M"
) ELSE (
echo * Macrofile missing at "%%~M"
)
echo ********************************************************************
)
REM set "path=%path%;%~dp0"
SET "BATLIB=C:\Project\BatchLibrary"
call cd /D "%%BATLIB%%"
) ELSE (
REM *** This is a FOR command, a call by an explorer click or a drag & drop operation
REM *** Try to detect a PROBLEMATIC Drag&Drop operation, if it has the format `%comspec% /c ""`
REM *** It's only problematic when there is one & in the data
REM *** But it's not bullet proof, FOR /F in ('""C:\temp\myfile.bat" C:\temp\file&second.bat"') can't be distinguished
REM *** First check, that the cmdline starts with `%comspec% /c ""....`
if "!_ccl_!" NEQ "!_ccl_:*/c ""=#!" if "!comspec! /c ""!_ccl_:*/c ""=!" == "!_ccl_!" (
REM *** Only handle it, if there is at least one & and no pipe |
if "!_ccl_!" NEQ "!_ccl_:&=!" if "!_ccl_!" EQU "!_ccl_:|=!" (
call :DRAG_and_drop
%DEBUG_DRAG% echo # BACK from DRAG_and_drop
)
)
REM *** Leaving now
REM *** This is a "FOR /F" sub-command
REM *** or a "harmless" drag&drop
REM *** or the second run of a problematic drag&drop
REM echo %DATE% %TIME% internal "!_ccl_!" >> "%~dp0\started.log"
endlocal
)
exit /b
:DRAG_and_drop
REM *** This results into recursive execution of this script
REM *** Be sure, that it's not an endless recursion
REM *** Remove the front
REM set "_ccl_=!_ccl_:&=+!"
set "_ccl_=!_ccl_:~0,-1!"
set "_ccl_=!_ccl_:*/c "=!"
set "DEBUG_DRAG=REM "
%DEBUG_DRAG% echo #A '!_ccl_!' > CON
call :arg_parser argv _ccl_
REM *** Check if there is at least one unquoted, problematic &
REM *** This arg must not have spaces
set "isDragAndDrop="
if defined argv.onlySingleSpace (
set "isDragAndDrop=1"
set "problematic="
set "args="
FOR /L %%# in (0 1 !argv#-1!) DO (
set "arg=!argv[%%#]!"
REM *** Check if it's an absolute path
FOR /F "tokens=1,*" %%1 in (": !arg!") DO (
if "%%~f2" NEQ "%%~2" (
set "isDragAndDrop="
%DEBUG_DRAG% echo *** 'NO PATH' !arg! NEQ %%~f2 > CON
exit /b
)
)
if defined argv[%%#].quoted (
set "arg="!arg!""
) ELSE (
REM *** If not quoted but contains a &
if "!arg!" NEQ "!arg:&=!" (
if "!arg:~1,2!" EQU ":\" (
set "arg="!arg!""
set "problematic=1"
) ELSE (
REM *** This can't be drap&drop, because arg doesn't begin with ":\"
set "isDragAndDrop="
%DEBUG_DRAG% echo *** NO DRIVE '!arg!' > CON
exit /b
)
)
)
set "args=!args!!arg! "
)
set "args=!args:~,-1!"
)
if not defined problematic (
set "isDragAndDrop="
REM echo *** 'problematic = 0' > CON
)
if defined isDragAndDrop (
REM ECHO *** HANDLE isDragAndDrop *** > CON
REM echo *** '!args!' > CON
REM *** The TWO SPACES between comspec and /c are essential!
REM *** The AutoRun script detects the recursion call by this difference
(
endlocal
%comspec% /c ^"%args%"
)
REM *** Back from the dragDrop script
REM *** EXIT to avoid a second call
exit
)
REM *** This is the path for unproblematic content
REM *** Or it was detected, that this isn't a drap&drop operation
exit /b
:direct_call
if "%~1" == "--install" (
reg add "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v "AutoRun" /t REG_SZ /d "%~f0"
exit /b
)
if "%~1" == "--show" (
reg query "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun
exit /b
)
if "%~1" == "--remove" (
reg DELETE "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun /f
)
exit /b
:arg_parser arg_line
REM *** @TODO: ENDLOCAL all arg-... variables
::: Output arg0, ..., arg and arg#
::: arg0 ... arg contains unquoted arguments
::: an argument that contains only one quote (last character in line) is treated as quoted arg=" -> arg=, arg.quoted=1
::: arg0.quoted contains 1 if the argument was quoted
::: \ escapes the next character (also inside quotes), can escape delimiters outside quotes, can escape quotes
::: Only \" is replaced to a single quote ", all other \ are unchanged
set "var=%1"
(set^ arg_line=!%2!)
call :strlen arg_len arg_line
set /a %var%_len-=1
set "quoted="
rem set "escapeChar=" --- "Say: \"Hello\" to frank\n"
set /a %var%#=0
set "%var%[0]="
set "%var%.onlySingleSpace=1"
for /L %%I in (0 1 !arg_len!) DO (
for %%# in (!%var%#!) DO (
set "char=!arg_line:~%%I,1!"
set "isDelim="
if "!escapeChar!!char!" == ^""" set "quoted=!quoted:~,0!"
REM echo %%I: "!char!" quote: !quote!
%= only outside quotes =%
%= check if char is a delim =%
if not defined quoted (
FOR /F "tokens=1,2 delims= " %%D in ("isDelim!char!NO") DO (
if "%%D" == "isDelim" (
set isDelim=1
if "!arg_line:~%%I,2!" == " " (
%DEBUG_DRAG% echo ##### NOT onlySingleSpace > CON
set "%var%.onlySingleSpace="
)
)
)
)
REM *** Split arguments by not escaped delimiter
if "!isDelim!,!quoted!,!escapeChar!" == "1,," (
REM *** Finalize arg, test if arg is quoted
if defined %var%[%%#] (
set %var%[%%#].quoted=
REM echo #in %%# "!%var%[%%#]:~,1!!%var%[%%#]:~-1!"
if "!%var%[%%#]:~,1!!%var%[%%#]:~-1!" == """" (
if "!%var%[%%#]:~-2,-1!" NEQ "\" (
set %var%[%%#].quoted=1
set "%var%[%%#]=!%var%[%%#]:~1,-1!"
)
)
REM * NOT USED HERE * if defined %var%[%%#] (
REM * NOT USED HERE * set ^"%var%[%%#]=!%var%[%%#]:"\"="!"
REM * NOT USED HERE * )
set /a %var%#+=1
)
)
REM * NOT USED HERE * if defined escapeChar (
REM * NOT USED HERE * set "escapeChar="
REM * NOT USED HERE * if "!char!" EQU ^""" (
REM * NOT USED HERE * REM *** Replace escaped quotes with "\" that can be safely detected and replaced later
REM * NOT USED HERE * set "char="\""
REM * NOT USED HERE * ) ELSE IF defined isDelim (
REM * NOT USED HERE * set "isDelim="
REM * NOT USED HERE * ) ELSE (
REM * NOT USED HERE * set "char=\!char!"
REM * NOT USED HERE * )
REM * NOT USED HERE * ) ELSE if "!char!" == "\" set "escapeChar=\"
if defined quoted set "isDelim="
REM *** Append char to arg
if "!isDelim!!escapeChar!" == "" (
set "%var%[%%#]=!%var%[%%#]!!char!"
)
)
)
for %%# in (!%var%#!) DO (
REM *** Last character is \
if defined escapeChar (
set "%var%[%%#]=!%var%[%%#]!\"
)
REM *** Duplicated code
REM *** Finalize arg, test if arg is quoted
if defined %var%[%%#] (
set %var%[%%#].quoted=
if "!%var%[%%#]:~,1!!%var%[%%#]:~-1!" == """" (
if "!%var%[%%#]:~-2,-1!" NEQ "\" (
set %var%[%%#].quoted=1
set "%var%[%%#]=!%var%[%%#]:~1,-1!"
)
)
REM * NOT USED HERE * if defined %var%[%%#] (
REM * NOT USED HERE * set ^"%var%[%%#]=!%var%[%%#]:"\"="!"
REM * NOT USED HERE * )
set /a %var%#+=1
)
)
REM *** Create a helper variable with name "arg#-1"
set /a tmp=!%var%#!-1
set "%var%#-1=!tmp!"
exit /b
::: detector line %~* *** FATAL ERROR: missing parenthesis or exit /b
:strlen
(
setlocal EnableDelayedExpansion
(set^ tmp=!%~2!)
if defined tmp (
set "len=1"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!tmp:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "tmp=!tmp:~%%P!"
)
)
) ELSE (
set len=0
)
)
(
endlocal
set "%~1=%len%"
exit /b
)