Why doesn't FileExists support wildcards? - vbscript

Consider this example VBScript fragment:
Dim fs
Set fs = CreateObject("Scripting.FileSystemObject")
If fs.FileExists("D:\Folder\File*.ext") Then ' Finds nothing!
fs.CopyFile "D:\Folder\File*.ext", "D:\OtherFolder\"
fs.Deletefile "D:\Folder\File*.ext"
End If
The FileExists method turns out not to support wildcards (* and ?). Not does FolderExists. I expected wildards to just work because they work fine for all similar methods in the FileSystemObject: CopyFile, CopyFolder, MoveFile, MoveFolder, DeleteFile, DeleteFolder and the Get* filename handling methods like GetAbsolutePathName.
Of course there are ways to work around this, like GetFolder and iterating over its files. But FileExists would have been much more readable, convenient, natural and consistent.
The fs.FileExists inconsistency feels like an API design problem. What could be the reason? Is there some idea behind it?

Only someone from the team that designed the Microsoft Scripting Runtime API (scrrun.dll), which these functions are a part of, can answer this question for sure.
But my guess is that FileExists is nothing but a wrapper for the CreateFile Windows API function with the dwCreationDisposition parameter set to OPEN_EXISTING ("Opens a file or device only if it exists."). This Windows API function does not support wildcards, so FileExists can't, either.
When the file does not exist, the system will respond with error 2 ("The system cannot find the file specified.") and FileExists will return False.
The above is based on using Process Monitor to inspect the behavior of a FileExists call.
It would be moot to discuss whether this is an API design oversight and whether it should be any different.
That being said, there is no reason for an "exists" check in the code you show.
If you want to move files from location A to location B, just do that.
If there is something to move, it will be moved. If there is nothing to move, there will be an error you can inspect. The "exists" check provides no extra information whatsoever.
Dim fs, source
Set fs = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
fs.MoveFile "File*.ext", "D:\OtherFolder\"
If Err.Number = 0 Then
MsgBox "Done"
ElseIf Err.Number = 53 Then ' File not found
MsgBox "Nothing to do"
ElseIf Err.Number = 76 Then ' Path not found
MsgBox "Target path not found"
Else
MsgBox "Unexpected Error " & Err.Number & " - " & Err.Description
End If
On Error Goto 0
For convenience I would wrap that in a Sub so that I can re-use it and the On Error Resume Next won't leak into the rest of my code.
It's also worth noting that within the same volume, MoveFile will be way faster than copy-and-delete.

Why don't run DIR thru WSShell.Exec and capture its output?
set ows=createobject("Wscript.shell")
path="C:\windows\system32\"
wild="*.exe"
recurse="/S" ' or ""
Set oExec=ows.Exec("%comspec% /c dir /b " & recurse &" "& chr(34) & path & wild & chr(34) )
s= oExec.StdOut.ReadAll()
'using the result
if s =vbnullstring then
Wscript.echo "No files found"
else
s=split(s,vbcrlf)
wscript.echo "Files found " & ubound(s)
for each i in s
wscript.echo i
next
wscript.echo "End of list"
end if

Related

Delete method vbscript producing path not found error when the path is valid

strFolderPathToDelete = strBackupFoldToDelete & strInternationalDate
set objFSOFolderToDelete = CreateObject("Scripting.FileSystemObject")
set objDeleteFolder = CreateObject("Scripting.FileSystemObject")
'Wscript.Echo strFolderPathToDelete the folder path is valid
set objDeleteFolder = objFSOFolderToDelete.GetFolder(strFolderPathToDelete)
'Wscript.Echo objDeleteFolder the folder path is valid
objDeleteFolder.Delete true ' why this line produces error path not found?
Try calling the DeleteFolder method of the FileSystemObject to see if that helps, and surround it with some error-handling with the hopes of isolating the problem, like so:
strFolderPathToDelete = strBackupFoldToDelete & strInternationalDate
With CreateObject("Scripting.FileSystemObject")
If .FolderExists(strFolderPathToDelete) Then
On Error Resume Next : Err.Clear
.DeleteFolder strFolderPathToDelete, True
If 0 = Err.Number Then
WScript.Echo "Successfully deleted folder: " & strFolderPathToDelete
Else
WScript.Echo "Error (" & CStr(Err.Number) & ") deleting folder: " & Err.Description
End If
On Error Goto 0
Else
WScript.Echo "Sorry, folder does not exist: " & strFolderPathToDelete
End If
End With
It's quite possible the folder might be a symbolic link or junction to another physical path, or perhaps a DFS link or share that you don't have sufficient DELETE permissions to.
Also, ensure the folder is empty (not containing any hidden or system files), and ensure it not being accessed (with any open handles) prior to deleting it. Enterprise security, anti-virus, and backup solutions can occassionally cause unexpected problems -- as well as malware on infected systems.
Hope this helps.

wshShell.Exec Error 80070002 "The system cannot find the file specified"

I'm trying to use either wshShell.Exec or wshShell.Run to run a script through another script. I have tried both methods, and both methods give me the same error. I've Googled the issue and can't find anything that seems to fix the issue. The only suggestion that really was very relevant was to try using wshShell.Run instead of Exec.
Here's the relevant part of my script:
strScriptPath = "T:\IT resources\Scripts\Shutdown Scripts"
strForceShutdown = "ForceShutdown.vbs"
For j = 0 to 99
Set objActive = wshShell.Run(strForceShutdown)
' In case I ever need to get this working to run it from another folder.
' Set objActive = wshShell.Exec("cd " & strScriptPath & "")
' Set objActive = wshShell.Exec("wscript " & strForceShutdown & "")
constConf = MsgBox("Automatic shutdown initializing. Continue?" & chr(10) & "Y=Shutdown N=Postpone 30 minutes",4,"Automatic Shutdown Notification")
If constConf = 7 Then
objActive.Terminate
Wscript.Sleep(1800000)
Else
objActive.Terminate
Exit For
End If
Next
Thanks for any help!
Shell.Run returns an integer, so you can't call a method (Terminate) on its return value. You also can't Set it since it's not an object.
You can call your shutdown script by just running it. Give it the full path, however, not a relative path. Scripts launched from Task Scheduler often have different "starting folders" than those launched manually so don't rely on your script finding the other one relatively.
Also, you'll have to add Chr(34) before and after your path to account for any spaces.
strForceShutdown = "c:\path\to\ForceShutdown.vbs"
wshShell.Run Chr(34) & strForceShutdown & Chr(34)
Finally, why launch the script and then ask whether to shutdown? Why not just launch your script after the user has responded and then you don't have to worry about terminating a running process.

Can I list apps that don't have registry entries?

I've been working on a way to quickly and easily list all of the software installed on my machine. Once complete, I'd like to send it out to my group so that I can have everyone run it. Since the purpose of this exercise is generate a list of all of the applications that we absolutely require access to to our IT administrators, I don't want to miss anything important.
Up to this point, I've used code very similar to this - it looks in the registry at SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ and Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\ and gives me all of the software that has been installed. However, a bunch of important programs are conspicuously absent (e.g. R, RStudio, SQL Developer), and I assume it's because they do not use Windows Installers.
This brings me to my question - is there a way I can list all of the programs that can be run on my machine (that have not impacted the registry)? Essentially, I think I want all of the non-system *.exe files, but that is probably oversimplifying things.
Anyone have any ideas? My code is VBS now, but I can muddle my way through most things.
If you want to find them all then you need to search every single file on your machine and check whether or not it has an executable extension. I'm reasonably confident that you are not going to want to do this.
I read your answer and laughed, since I was also "reasonably confident" that I did not want to go through all of the files on my (or anyone else's) machine. Once the laughing stopped, I realized that that's essentially what I had to do...
I've come up with something that works, and it now takes minutes to run (it took seconds to only check the registry), but it does work. I'm putting it here in case it can assist someone else, or maybe someone can find a way to make it more efficient. You need to supply some paths to folders where you want to look for exe files, and a file that you want to output to.
Thanks for reading.
On Error Resume Next
Folders = Array("C:\users\me","C:\SoftwareFolder1","C:\SoftwareFolder2","C:\SoftwareFolder3")
sFile="C:\myExeFiles.txt"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Const ForWriting = 2
Const OverwriteIfExist = -1
Set fFile = objFSO.CreateTextFile(sFile, OverwriteIfExist, OpenAsASCII)
For Each x In Folders
Set objFolder = objFSO.GetFolder(x)
suckTheData objFSO, fFile, objFolder
Set objFolder = Nothing
Next
MsgBox("Done")
Set objFSO = Nothing
Sub suckTheData(objFSO, fFile, objFolder)
' *** STEP 1 *** 'Find files with a partiular extension in this folder
For Each objFile In objFolder.Files
If UCase(objFSO.GetExtensionName(objFile.Name))="EXE" Then
fFile.Write objFile & vbCrLf
If Err.Number <> 0 Then
fFile.Write "Error: " & objFile & " " & Err.Number & Err.Source & " " & Err.Description & vbCrLf
End If
End If
Next
Set objFile = Nothing
' *** STEP 2 *** 'Now that we've processed files, repeat for subdirectories
For Each subf In objFolder.SubFolders
'some folders can't/shouldn't be checked -
'16 is a normal folder, 32 is an archive, 1046 is symbolic, etc
If subf.Attributes ="16" Then
suckTheData objFSO, fFile, subf
End If
Next
Set subf = Nothing
End Sub

Is file path needed in (Shell Function) VBA?

I have a Matlab-generated executable file, Myfile.exe to call from excel-vba. I learned (Shell Function) is what I need to use.
I don't want to include the whole file path as I do not want to restrict the user to a certain folder in a certain location on each computer.
I have the following code to call the executable, which works fine:
Sub MyExe()
On Error Resume Next
Shell ("C:\Users\elwany\Desktop\Myfolder\Myfile.exe")
If Err <> 0 Then
MsgBox "Can't start the application.", vbCritical, "Error"
End If
End Sub
My problem/question is
I put the executable + the Excel file with the VBA project in the same folder (Myfolder), and then I modify the code to:
Sub MyExe()
On Error Resume Next
Shell ("Myfile.exe")
If Err <> 0 Then
MsgBox "Can't start the application.", vbCritical, "Error"
End If
End Sub
Sometimes it works, sometimes it doesn't!
For example, yesterday I ran the VBA code, it worked. Today I opened the same Excel file, same folder, same everything, it gives "Can't Start Application" error msg!!
Is it not okay to remove the file path even if I have everything in one folder?
Why does it sometimes work, sometimes not?
Is adding the file path absolutely mandatory?
As you have enquire further about different directories note that you can either
Use ChDir as per my earlier comment to your question
Use Dir instead to validate that myfile.exe is where it needs to be. This method doesn't need error handling to handle the file being missing.
Sub TestB()
Dim strPath As String
strPath = Dir("c:\temp\myfile.exe")
If Len(strPath) > 0 Then
Shell strPath
Else
MsgBox "Path doesn't exist"
End If
End Sub
Sub TestA()
On Error Resume Next
'use the host workbook path
' ChDir ThisWorkbook.Path
'set path here
ChDir "C:\temp"
Shell ("Myfile.exe")
If Err <> 0 Then
MsgBox "Can't start the application.", vbCritical, "Error"
Else
MsgBox "sucess!", vbOKOnly
End If
End Sub
When you run a shell like this without a path specified it runs from the Active Directory. What the Active Directory is depends on the OS, not Excel/VBA (unless you explicitly set it)
Try this instead
Sub MyExe()
On Error Resume Next
Shell (ThisWorkbook.Path & "\Myfile.exe")
If Err <> 0 Then
MsgBox "Can't start the application.", vbCritical, "Error"
End If
End Sub

how to check vbs script in windows is running or not?

I have created a VBS script in Windows. I will run this script to get size of a file.
I made this to run for ever. (even this is my requirement).
How should I know if it is running or stopped?
------ Exact Script starts here -----
Set FSO = CreateObject("Scripting.FileSystemObject")
Set FSO_check=CreateObject("Scripting.FileSystemObject")
do while infiniteLoop=0
----- This code is lasting for ever ----
Loop
Am i clear in my ques?
How about using the commandline property? I think this need Windows XP and up.
For example:
Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery _
("Select * From Win32_Process where name = 'wscript.exe'")
For Each objProcess In colProcess
WScript.Echo objProcess.Name, _
objProcess.ProcessId, _
objProcess.CommandLine
Next
I made a script and at the beginning i wanted to avoid having multiple / duplicate instances of the same process running. This is my code to quit the newer instance in case it gets launched when already running.
Note: This does note prevent multiple wscripts files from running - just prevents the same particular file from having simultaneous instances.
To adapt to suit the original question... just use the block which checks current running processes, if the any of the wscript file names equal the script file you're looking for, then it's running.
function duplicate_check
'if two scripts get here at the same time - stagger when the generate _
'the list of running processes _
'ie - give 1 of them time to close
'else they can both quit leaving no more scripts running
call random_script_sleep(2500,500)
dim myfilename
myfilename = Wscript.ScriptName
strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("select * from win32_process")
'fill arrary w/ all running script names
i = 0
For Each objProcess in colProcesses
If objProcess.Name = "wscript.exe" Then
strScriptName = Trim(Right(objProcess.CommandLine,Len(objProcess.CommandLine) - InstrRev(objProcess.CommandLine,"\")))
strScriptName = Left(strScriptName, Len(strScriptName) - 1)
a(i)= strScriptName '
i=i+1
end if
Next
'kill script if already running w/ same name
if i > 1 then 'if > 1 wscript instance
for s = 0 to i
if a(s) = myfilename then
wscript.quit
end if
next
end if
'sometimes duplicate check fails, if so, write to error log
If Err.Number = 1 Then
error_notes = " #duplicate check, firstdupcheck(0/1):" & firstdupcheck
call error_log
error_notes = "error undefined"
end if
' if debugmsg = 1 then CreateObject("WScript.Shell").Popup _
' "found this many scripts: " & i & vbCrlf & _
' "<>" & i & vbCrlf & _
' ", 1, "debug popup"
end function
#nimizen answer is not correct. If you have another wscript running, it will return that your script is already running.
#h pic answer is correct, but I get an error with the "a" array in my windows. So I changed it a little and cleaned to work.
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("select * from win32_process where name = 'wscript.exe'")
i = 0
For Each objProcess in colProcesses
if not (IsNull(objProcess.CommandLine )) then
strScriptName = Trim(Right(objProcess.CommandLine,Len(objProcess.CommandLine) - InstrRev(objProcess.CommandLine,"\")))
strScriptName = Left(strScriptName, Len(strScriptName) - 1)
if strScriptName = Wscript.ScriptName then
i=i+1
end if
if i > 1 then 'if > 1 wscript instance
'Wscript.Echo "Duplicate :"&strScriptName&" ."
Wscript.Quit
end if
end if
Next
'Wscript.Echo "Pause 2 have 2 scripts running ."
Hmm, well first if its an infinite loop script, then you're probably using it to periodically check a folder to do some work. This sounds bad but is usually less resource intense than hooking into WMI for notifications. If its up and running, its running. The real problem is discriminating it from all the other WScript and CScripts scripts you may have running.
MS Sysinternals Process Explorer http://technet.microsoft.com/en-us/sysinternals/bb896653 is good at telling you information about your running processes. Mostly I would use it to tell by unique command line arguments which script process is hosting which script.
There is no easy way to exactly find your script's process Id from within a script. It is one of the few pieces of runtime information not exposed in the script environment's object model. Since you are already using the File System Object perhaps you could have it look for a dummy file name to use as the indicator that it is running. If the script couldn't create or open the file then you could assume that another instance of the script is already running.
Or have another unique named dummy file that you can easily create and your script automatically deletes during its processing run. That way you simply create an empty file of that name as a test and if it doesn't disappear in a few seconds you know no instances of your script are running.
Also I was thinking that you could launch your script from another script using Exec() which returns the ProcessID of the launched script and then release your reference to it, storing the ProcessID wherever you need it for later use.
Set oExec = WshShell.Exec( "infinite.vbs" )
MyProcessID = oExec.ProcessID ' procID of shell'd program.
set oExec = Nothing
and do something with MyProcessID
Then I noticed this posting
Find my own process ID in VBScript
Which uses Exec() to run an HTA script, get its ProcessID and look that up in WMI, then use the result set from WMI to locate the Parent process' ProcessID which should be the script making the Exec() and WMI calls. The problem with it is the HTA may terminate before WMI gets a chance to find it.
Dim iMyPID : iMyPID = GetObject("winmgmts:root\cimv2").Get("Win32_Process.Handle='" & CreateObject("WScript.Shell").Exec("mshta.exe").ProcessID & "'").ParentProcessId
Mostly I get the feeling that this is all overkill for whatever reason you think you need to know if the script is running. Instead focus on what action "knowing if the process is running or not" causes you to take. Since that hasn't been explained we really can't offer you an alternative strategy to get you there, but I'm sure a more simple solution is available. TheFolderSpy http://venussoftcorporation.blogspot.com/2010/05/thefolderspy.html for example would be one alternative way to run your program without an infinite loop.
Don't forget to use a sleep command in your loop to let other programs get work done. It makes a great difference in resource use. Also you only need one FSO instance, make it global to all your code by creating it early on in your script before any subroutines.
Since you are looking at the size of a file, you are probably checking it for changes. I've found that a loop with a small WScript.Sleep 200 delay in it is good to detect if a file is done being altered. That way you can process the file instead of having to skip it until the next main loop pass which should be set to 10 seconds or more.

Resources