I have a program in HTA and all the auxiliary files are in the same folder, a subfolder of the AppData folder. I created an uninstaller HTA which uninstalls the program by simply removing the folder with FSO DeleteFolder() method. I converted it to an executable with HtaEdit. (If you don't know this program, it doesn't matter). What the executable does is that it creates an HTA in a temporary folder (along with the auxiliary files) and runs it. The problem is that when it does the DeleteFolder() method, an error message comes up saying "denied access". I don't think that it's an administrator problem since it's in the current user's AppData folder. When I try deleting another folder that way, it works just fine. I think that there are usually problems deleting the folder containing an HTA file which is being run, but the HTA file isn't in the folder I'm trying to delete but in a temporary folder. However, it's been called by an executable in the folder I'm trying to delete.
I'm using VBScript, but it does the same thing if I use JavaScript.
You can't delete a folder from within the same folder as long as there's still something holding an open handle to the folder or something inside it. For example, the following code will usually delete the parent folder of a VBScript:
Set fso = CreateObject("Scripting.FileSystemObject")
dir = fso.GetParentFolderName(WScript.ScriptFullName)
fso.DeleteFolder(dir)
or, in case of an HTA (which doesn't have a WScript object):
Set fso = CreateObject("Scripting.FileSystemObject")
htaPath = Replace(oHTA.CommandLine, """", "") '<HTA:APPLICATION ID="oHTA" ...>
dir = fso.GetParentFolderName(htaPath)
fso.DeleteFolder(dir)
These work, because the script interpreter reads the entire script into memory when the script is launched, so no open handle to the file remains.
However, the deletion of the folder will fail with a "permission denied" error if the folder is the current working directory of the script process, because in that situation there still is an open handle to the folder. The same applies if, for instance, the folder is open in Explorer or a command prompt.
You can check for open handles with handle.exe or Process Explorer.
Related
I have two files in one folder, a vbs file called notify.vbs and an executable called foo.exe.
the foo.exe is a node.js pkg-bundled executable that triggers a native desktop notification with a random message from a local text file after 5 seconds from execution. Although pkg had one inconvencience which is that executing foo.exe launches a cmd interface, which doesn't suit my purpose. This is where notify.vbs come in handy as it can execute foo.exe while keeping the cmd interface hidden just like I wanted. notify.vbs looks like this:
Set oShell = CreateObject("Shell.Application")
oShell.ShellExecute "foo.exe", , , "runas", 1
It works fine. But then, when I change the location of foo.exe for example in a folder called bar directly in the C drive and change the path in the script to:
Set oShell = CreateObject("Shell.Application")
oShell.ShellExecute "C:\bar\foo.exe", , , "runas", 1
the script starts foo.exe and closes it immediately.
Basically notify.vbs only executes foo.exe when they're in the same folder. When I change the location of either of them it does that weird behaviour I mentioned. I verified the functionality of foo.exe in different locations. It always works. I even verified the path in script each time I tried a different location, it's always correct as it actually does execute foo.exe but it closes it immediately after.
I have no experience with VBScript as I copy pasted that script. Thanks in advance.
I am trying to create a VBS file that can extract the content of a ZIP file from a relative path to an absolute file path destination. The reason I am trying to create this VBS file is because my overall goal is to create an EXE file through iExpress that will copy a bunch of code to a user's computer and install nodeJS at the same time.
My thoughts on taking this approach have been to create a batch script that would copy the directory of code to a set position on the user's computer, and then execute an MSI file to install nodeJS. I have been able to get a batch script that can do that, but now I want to package it all nicely into an EXE. For this I believe I need to put the code directory into a ZIP file so I can add it to the EXE file, and then my EXE needs some functionality to extract this.
To add this functionality, my research seems to indicate that VBS is the best option for this (especially considering I am hoping to also compensate for users who have restricted access to PowerShell cmdlets). Searching around seems to indicate that a file of this sort of variety is meant for what I want to do.
ZipFile="test.zip"
ExtractTo="C:\test-install\test"
Set fso = CreateObject("Scripting.FileSystemObject")
sourceFile = fso.GetAbsolutePathName(ZipFile)
destFolder = fso.GetFolder(ExtractTo)
Set objShell = CreateObject("Shell.Application")
Set FilesInZip=objShell.NameSpace(sourceFile).Items()
objShell.NameSpace(destFolder).copyHere FilesInZip, 16
Set fso = Nothing
Set objShell = Nothing
Set FilesInZip = Nothing
This does not seem to work though as on this line...
objShell.NameSpace(destFolder).copyHere FilesInZip, 16
... it complains to me about destFolder needing to be an object. I am not too sure what I am meant to change to fix this error (I am not too familiar with VBS) and most examples on the internet set the ExtractTo variable as a relative file path initially and run a command to turn this into an absolute file path. I don't want to do this though as when iExpress turns it all into an EXE my understanding is that the relative file path starts in some random TEMP folder somewhere.
Any guidance on the error in my VBS file, or indication of a better path to take for extracting ZIP files would be greatly appreciated, thank you.
It seems my inexperience with VBS was the root of my troubles here. The trouble ended up being that on this line:
objShell.NameSpace(destFolder).copyHere FilesInZip, 16
It needed to be this:
objShell.NameSpace(destFolder).copyHere(FilesInZip), 16
I am not sure if this is specific to a certain version of the language or not, because my error was copied from numerous examples of a similar script across the internet, but I haven't really been able to find further details on this (I will edit this answer if more is found).
When I was trying to get elevated rights for my batch script, when I found two related SO questions
How to request Administrator access inside a batch file
How can I auto-elevate my batch file, so that it requests from UAC administrator rights if required?
...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)
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.
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)
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...
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.
I've made a logon vbs (first ever) so when someone logs on to the domain it copies a file from a network drive to each local machine. Would this work (as long as the directories are correct)?
<%
dim filesys
set filesys=CreateObject("Scripting.FileSystemObject")
If filesys.FileExists("W:\Student Shared Area\SBN\registration.xml") Then
filesys.CopyFile "W:\Student Shared Area\SBN\registration.xml" _
, "C:\Program Files\New-Media-Learning\Logicator\"
End If
%>
I see two potential issues with your code:
Drive W: is not (yet) mapped when the logon script runs. This won't be an issue when you map the drive before copying the file, but it might still be better to use a UNC path for the source:
filesys.CopyFile "\\server\share\Student Shared Area\SBN\registration.xml", "C:\Program Files\New-Media-Learning\Logicator\"
Personally I'd prefer Group Policy Preferences, though, if that's an option for you.
Users don't have write permissions on C:\Program Files\New-Media-Learning\Logicator\ (the logon script runs in the user's context). This can be mitigated by changing permissions on that folder with a GPO.
When I go to my batch file's location and open it, the batch file works. My batch file is simply:
cd .\data
dir/b/o:n > names.txt
As you can see, I'm in my current directory and moving down to the sub directory "data" and coping all the names and creating a file called names.txt.
When I say
shell "location of file"
it opens the batch file, but the directory that is defaulted to is C:\my documents, so my commands won't work because it cannot find the sub directory. I want this to be a dynamic batch file, and therefore i need to write something in VBA that will open the batch file under its current directory or something to this effect.
How do I do this?
The following should give you the effect you seek.
My test code is:
Option Explicit
Sub TryShell()
Dim PathCrnt As String
PathCrnt = ActiveWorkbook.Path
Call Shell(PathCrnt & "\TryShell.bat " & PathCrnt)
End Sub
My test batch file is named TryShell.bat and contains:
cd %1
dir *.* >TryShell.txt
I have placed my batch file in the same folder as the workbook containing my macro.
The statement PathCrnt = ActiveWorkbook.Path sets PathCrnt to the name of the directory containing the active workbook. You can set PathCrnt to whatever directory you require.
When I call Shell, I have added PathCrnt as a parameter.
In my batch file, I set the current directory to %1 which is the first parameter.
The dir command works as I wish because the current directory is my directory and not the system default directory.
Hope this is clear.
C:\My Documents is probably the directory where your speadsheet is located. If you add
ChDir "C:\TheFolderWhereYourBatchIs"
before launching your Shell command and that should work...
Alternatively, you could change your batch file to use an absolute directory instead of a relative one.