3

When I was trying to get elevated rights for my batch script, when I found two related SO questions

...that led to answers that worked partially. For some reason, I had issues with command line passing for file path arguments containing spaces within the VBS script, so I tried to break the solution into 3 parts and concentrated on the inner (VBS) step, then adding the last step by calling a batch from that VBS which could not be found, despite in the same folder as the VBS script. I found that drag & drop isn't "that easy" and that it's different when using .vbs instead of .bat or .exe as drop targets.

Here is my actual question:

If I drag a file and drop it onto an executable (exe) or batch file (bat, cmd), The current working directory is determined by the source of the dragged item. Its directory is set as the working directory for the program or script that processes it.

If I drop a file onto a VBS script, it's different. On Windows 8.1 x64 I observe it to be C:\Windows\System32 even if the argument resides in the same folder as the VBS.

I can simply use a batch file (as drag'n'drop relay) like this

my.vbs %*

to get "the normal" .bat behaviour (drop source dictates CWD), but I also want to understand it.

Is it a bug or a feature? Is it consistent over all Windows versions?


edit: added the background (on top) for the question showing how I got there (+minor corrections)

Community
  • 1
  • 1
Wolf
  • 9,679
  • 7
  • 62
  • 108

4 Answers4

5

After some API monitoring, this is what I see

When you drop a file over a .exe file the explorer.exe uses CreateProcess API function to start the process, passing the executable as lpApplicationName, and the executable and dropped file as lpCommandLine. The lpCurrentDirectory is set in the function call by the caller process to the folder containing the dropped file[1].

When you drop a file over a .cmd file the explorer.exe also uses CreateProcess API, but in this case the lpApplicationName is null and the lplCommandLine contains the batch file and the dropped file. lpCurrentDirectory is also set to the parent folder of the dropped file[1].

When you drop a file over a .vbs file, ShellExecuteEx is used and the lpDirectory field of the SHELLEXECUTEINFO structure is null, so, the process created inherits the current active directory of the parent process. By default the current active directory of the explorer.exe process is %systemroot%\system32, but it is possible to start a explorer instance with a different current active directory that will be inherited in this kind of drop operations.

[1] If we drop more than one file, the path of the file passed as first argument is used

note just for information: to test the active directory inherit the process followed was:

  • Open a cmd instance and change the current active directory to c:\temp
  • Kill all explorer.exe instances
  • From the cmd instance call explorer.exe. This explorer instance has the active directory in the cmd window as its current active directory.
MC ND
  • 69,615
  • 8
  • 84
  • 126
  • 1
    Impressive research! I just dropped a selection of two files from different folders (from search results) onto a bat file. I observed that the CWD of the script is set to the parent folder of the first argument, maybe worth to add to the *`set to the parent folder of the dropped file`* part of your answer. – Wolf Feb 06 '17 at 15:02
  • @Wolf, I forget to include it, thank you. Answer updated. – MC ND Feb 06 '17 at 15:13
  • I expect to see `CreateProcess` being used for `.bat` as well, and all other registered file types are just "opened" using `ShellExecuteEx`? Would you think it makes sense to search for a reference for the intended behaviour? Could this be a useful entry point: [Application Registration (Windows)](https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx#related_topics)? – Wolf Feb 06 '17 at 15:22
  • 1
    @Wolf, I don't know if there is even an "intended behaviour". It is documented (your link) the process to find an application, but not how it is started or even who starts it. It is entertaining to dig and see how it works, but I will not rely on it. It can change. For the case in this question, if the started process *depends* on what its active directory is, it is the script's responsability to ensure its environment is correct. – MC ND Feb 06 '17 at 16:05
  • Well, maybe I better add this to the question: I was trying to figure out an error in a complicated batch+vbs combination step by step and this involved breaking a bigger script into parts, then it happened that I got an unexpected "file not found" error that I had to figure out next... – Wolf Feb 06 '17 at 16:11
  • I think that's showing the (technical) reason best whereas [Ansgar's answer](http://stackoverflow.com/a/42057410/2932052) shows the way out. – Wolf Feb 06 '17 at 22:33
  • In addition to an upvote, I want to say *Hell thanks*! You solved a mysterious problem with my vbs project (that processes files from command line). – iBug Dec 22 '17 at 15:31
2

The Arguments property holds the full paths to all items dropped on the script, so you can determine the directory of each dropped item like this:

Set fso = CreateObject("Scripting.FileSystemObject")
For Each item In WScript.Arguments
  WScript.Echo fso.GetParentFolderName(item)
Next

Assuming that the working directory would be defined by what is dropped onto a script is a misguided approach. If you require that logic you can implement it in the script yourself, though, e.g. like this:

Set fso = CreateObject("Scripting.FileSystemObject")
Set sh  = CreateObject("WScript.Shell")
For Each item In WScript.Arguments
  sh.CurrentDirectory = fso.GetParentFolderName(item)
  'working directory is now the parent folder of the current item

  '...
Next

If you need the working directory to be the parent directory of the VBScript file you can derive that from the ScriptFullName property:

Set fso = CreateObject("Scripting.FileSystemObject")
WScript.Echo fso.GetParentFolderName(WScript.ScriptFullName)
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • Thanks a lot for the guidance, and it makes (on the other hand) not so much sense, if I drop multiple targets, say from a search, onto a script to speak of the source directory... – Wolf Feb 05 '17 at 21:34
2

If I lookup the file types in the Windows registry, I see the following in their Shell\Open\Command values:

  • batfile: "%1" %*
  • cmdfile: "%1" %*
  • exefile: "%1" %*
  • VBSFile: "%SystemRoot%\System32\WScript.exe" "%1" %*

This seems to suggest that bat, cmd, exe are treated as executable on their own, maybe for historical reasons, whereas VBS is considered an ordinary script, that is only executable because its extension is registered with some executable to be called to interpret it. Pretty much the same as Python or Perl.

[Update] Indeed I proved that a Python script shows exactly the same behaviour as my VBS script: calling it from the command line providing arguments keeps the CWD, dropping a file on it causes the CWD to be C:\Windows\System32. So my question seems to be sort of wrong, but finally it helped people to point me into the right direction for further research...

Wolf
  • 9,679
  • 7
  • 62
  • 108
0

c:\windows\system32\CScript.exe or c:\windows\system32\Wscript.exe are the programs that run vbscript. As you can see they are in system32.

Windows uses ShellExecuteEx to start programs - see it's rules at MSDN:
ShellExecuteEx function (Windows)

ShellExecuteEx uses CreateProcess (CreateProcessEx) to actually start a program. CreateProcess function (Windows)

Edit

CMD doesn't use the registry for it's own stuff. Those registry entries are for programs other than CMD.

CMD main goal is to be MS Dos 5 compatible while enhancing it, it will correctly run most DOS batch files. It was written by IBM engineers working on OS/2 NOT Windows.

Edit 2

The core of your problem is that you are attempting to write programs as if you are a user typing to operate a computer.

As a programmer you don't make assumptions. The easiest way to not take assumptions is to specify full paths to what you want. Your batch file shouldn't care what the current directory is. EG In CMD there is a current directory per drive for compatibility with MSDos 5 (and programs in a console tend to share them but don't have to). In Windows there is one current directory per program. The default current directory has changed over the years.

The only time you should work with the current directory is if you are writing a batchfile for a user to use. EG If you type dir it does the current directory, a batchfile meant to be a general command should work the same way.

Freddie
  • 269
  • 1
  • 4
  • Well, I expected something like this, but also `.bat` and `.cmd` scripts are executed by a command interpreted and this also resides in `system32` -- at least one of them ;-) -- Why the difference? – Wolf Feb 05 '17 at 21:14
  • CMD does its own thing. CMD isn't even from Windows but OS/2. – Freddie Feb 05 '17 at 21:31
  • Seems that would be the same for Python or Perl scripts..., isn't it that bat and cmd are built-in scripting capabilities in Windows since NT? – Wolf Feb 05 '17 at 21:37
  • CMD pre-processes things. – Freddie Feb 05 '17 at 21:39
  • you mean `cmd.exe`? – Wolf Feb 05 '17 at 21:42
  • Yes. See CMD's behaviour here https://onedrive.live.com/?authkey=%21AAFjUcKneSZhDjw&id=E2F0CE17A268A4FA%21121&cid=E2F0CE17A268A4FA for **The Windows NT Command Shell Ch 1** which used to be available from MS web site. – Freddie Feb 05 '17 at 21:47
  • It has nothing to do with where the interpreting script is stored, see [my results](http://stackoverflow.com/a/42057809/2932052), but thanks anyway, you helped me to narrow down the actual question. – Wolf Feb 05 '17 at 22:17