0

I was making a batch file to take dragged-and-dropped folders for program input. Everything was working fine until I passed a folder, which for the sake of this post, called foo&bar.

Checking what %1 contained inside the batch file looked like C:\path\to\foo or C:\path\to\foo\foo. If the file path were in quotes it would work, so the only working code that slightly takes this into effect is :

set arg1=%1
cd %arg1%*
set arg1="%CD%"

Which changes directory to the passed argument using wildcards. However this only works once for if there is another folder with un-escaped characters inside the parent folder, passing the child folder would result in the parent folders' value.

I tried the answer of this post, which suggests to output the argument using a remark and redirection statement during an @echo on sequence. However no progress occurred in rectifying the problem. Any suggestions?

To recap, I am looking for ways to pass folders with un-escaped characters as arguments to a batch file. The implementation should preferably be in a batch file, but answers using VBScript are welcome. However the starting program must be in batch as this is the only program of the 3 that accepts files as arguments.

To test this, create a batch file with following code:

@echo off
set "arg1=%~1"
echo "the passed path was %arg1%"
pause

Then create folders called foobar and foo&bar. Drag them onto the batch file to see their output. foo&bar will only return C:\path\to\foo.

Jouster500
  • 762
  • 12
  • 25
  • It would probably be much easier to use a real scripting language, e.g., Powershell or vbscript. Batch language simply isn't designed for this sort of scenario. – Harry Johnston Oct 08 '17 at 22:15
  • I created my own test. Yes, the `&bar` is not pass in %1. Other posts on SO have concluded that Explorer is not very smart. It will use quoting if there is a SPACE in the file/dir name, but does nothing for other special characters. – lit Oct 08 '17 at 22:22
  • @HarryJohnston Thats why I run VBScript in hybrid to batch as batch can take files as input and VBScript can do advanced programs with said input. In truth, it probably would be easier, but this project has gone on for well over 3 years now. – Jouster500 Oct 08 '17 at 22:22
  • @lit That is why I was looking for ways to get around this. Maybe something with a VBScript or a few system configurations. What if there was a way to repass the argument back into the batch file with a space so that it uses quotes? – Jouster500 Oct 08 '17 at 22:27
  • @Jouster500 - I do not know of any control that the receiving process would have over what the calling process sends. Perhaps there is something somewhere deep in the bowels of drag and drop. – lit Oct 08 '17 at 22:50
  • 1
    I'm not sure what you mean by "batch can take files as input"? Dragging and dropping onto a .vbs file works for me. – Harry Johnston Oct 09 '17 at 02:14
  • @HarryJohnston Im going to try testing it with VBS methods to see if it can work that way. Kind of busy at the moment though so I can not test immediately. When I said "batch can take files as input" the file explorer started acting up when i tried hovering files over batch files, it says something along the lines of "open file with" and hovering over vbscripts says "copy". Explorer mislead me into thinking that vbscripts didnt take dragged files for input – Jouster500 Oct 09 '17 at 02:17
  • 1
    The way you tried with the REM technic can't work here, as even `%*` doesn't contain the full filename, the only way is to access the `cmdcmdline` variable, see also [Drag and drop batch file for multiple files?](https://stackoverflow.com/a/5192427/463115) – jeb Oct 09 '17 at 15:12

1 Answers1

4

OK, so the problem is that Explorer is passing this as the command line to cmd.exe:

C:\Windows\system32\cmd.exe /c ""C:\path\test.bat" C:\path\foo&bar"

The outermost quotes get stripped, and the command becomes

"C:\working\so46635563\test.bat" C:\path\foo&bar

which cmd.exe interprets similarly to

("C:\working\so46635563\test.bat" C:\path\foo) & bar

i.e., bar is considered to be a separate command, to be run after the batch file.


The best solution would be to drag-and-drop not directly onto the batch file but onto, say, a vbscript or a Powershell script or a plain old executable. That script could then run the batch file, either quoting the argument appropriately or putting the directory path into an environment variable rather than on the command line.


Alternatively, you can retrieve the original command string from %CMDCMDLINE% like this:

setlocal EnableDelayedExpansion
set "dirname=!CMDCMDLINE!"
set "dirname=%dirname:&=?%"
set "dirname=%dirname:" =*%"
set "dirname=%dirname:"=*%"
set "dirname=%dirname: =/%"
for /F "tokens=3 delims=*" %%i in ("%dirname%") do set dirname=%%i
set "dirname=%dirname:/= %"
set "dirname=%dirname:?=&%"
set dirname
pause
exit

Note the exit at the end; that is necessary so that cmd.exe doesn't try to run bar when it reaches the end of the script. Otherwise, if the part of the directory name after the & happens to be a valid command, it could cause trouble.

NB: I'm not sure how robust this script is.

I've tested it with the most obvious combinations, but YMMV. [It might be more sensible to use delayed expansion exclusively, I'm not sure. It doesn't seem to be necessary except in the first set command. Jeb's answer here might be a better choice if you're going this route.]


For the curious, the script works like this:

  • Load the original command line into dirname [necessary for the reason pointed out by jeb]
  • Replace all the & characters with ?
  • Replace all the quote marks with *
  • If a quote mark is followed by a space, suppress the space.

NB: it is necessary to suppress the space to deal with both the case where the path contains a space (in which case Explorer adds quote marks around it) and the case where it doesn't.

  • Replace all remaining spaces with /

NB: ? * and / are illegal in file names, so these replacements are safe.

At this point the string looks like this:

C:\Windows\system32\cmd.exe//c/**C:\path\test.bat**C:\path\foo?bar**

So we just need to pull out the third asterisk-delimited element, turn any forward slashes back into spaces and any question marks back into ampersands, and we're done. Phew!

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • Do not use `set "dirname=%CMDCMDLINE:&=?%"` as this changes also the cmdcmdline variable itself. You should use delayed expansion and copy it first to another variable – jeb Oct 09 '17 at 15:15
  • @jeb, thanks, corrected. That's utterly bizarre though, why does that happen? – Harry Johnston Oct 09 '17 at 20:32
  • I can't say why, but only cmdcmdline is known for this effect, see also [Destructive env var substring](http://www.dostips.com/forum/viewtopic.php?f=3&t=4312) – jeb Oct 10 '17 at 08:23
  • I'm going to go ahead and mark this off as good, tested it and working.However I do want to ask if I did this through VBScript, would i have to worry about any similar issue like this one? – Jouster500 Oct 11 '17 at 20:58
  • Only if you're explicitly using eval() or a similar function, which you would have no reason to do in this situation. The underlying problem is that batch treats the content of a variable in the same way that it treats code. I won't go so far as to say that it is the *only* language in which this happens, but it's the only one I'm familiar with. :-) – Harry Johnston Oct 11 '17 at 22:53