Variable scope in VBScript functions - vbscript

I have a question about variable scope in VBScript. I know there's the following keywords (from autoitscript.com):
Dim = Local scope if the variable name doesn't already exist globally (in which case it reuses the global variable!)
Global = Forces creation of the variable in the Global scope
Local = Forces creation of the variable in the Local/Function scope
Imagine that I have the following .vbs file:
Dim strPath
strPath = "C:\folder"
DisplayPath strPath
Sub DisplayPath(strPath) 'Does this strPath get it's own local scope?
MsgBox strPath
End Sub
In the function: DisplayPath(strPath), is strPath a local variable? Or do functions/subs have access to the strPath defined at the top of the main section of the script as a global variable?
Also, what's the point of explicitly using Dim versus just defining variables as I use them, which is possible in scripting languages?

The strPath in the DisplayPath procedure will be a new variable but not for the reasons you expect, there is subtle problem with your code that will cloud the issue.
When calling Sub procedure the VBScript syntax does not include parentheses. For example:-
Sub MyProc(Param1, Param2)
'' # Do stuff
End Sub
MyProc("Hello", "World")
the above would result in a syntax error. It should be called:-
MyProc "Hello", "World"
Now when there is only one parameter a syntax error does not occur. This is because another use of parentheses is as part of an expression e.g. '(a + b) * c'. In the case of:-
DisplayPath(strPath)
VBScript resolves the "expression" (strPath) and pass the result to DisplayPath. Its this result that gives rise to new storage hold the result the expression.
Had you called with
DisplayPath strPath
no new created.
However what about this:-
Sub DisplayPath(something)
MsgBox something
End Sub
There is still no new storage allocated. something will point at the same memory that strPath does.
Edit
The code below works:-
Dim strPath
strPath = "c:\folder"
Display
Sub Display()
MsgBox strPath
End Sub
The declaration of strPath outside of a procedure causes it to have global scope.
As to the point of using explicit Dim what would happen if the assignment line above looked like this?
strPath = "c:\folder"
A new variable called strPath would come into existence and strPath would remain empty. You should always begin your VBScript files with the line:-
Option Explicit
This will force you to explicitly Dim all variables to be used and will save you hours of debugging time.

Related

Retrieving an argument of a VBScript

How do I pass and return arguments from a VBScript WITHOUT using cscript.exe?
For example, I want to call script2 from script1 that returns a value to script1 without any involvement of cscript.exe.
I have searched various answers but they somehow involve the usage of cscript.exe.
This script gets installed voices and sets the one provided in the file voice.txt.
Set WshShell = CreateObject("WScript.Shell")
WShShell.CurrentDirectory = "..\Confirmatory Texts"
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists("voice.txt") Then
Set temp = FSO.OpenTextFile("voice.txt")
confirm_voice = temp.ReadLine()
temp.Close
Set Sapi = CreateObject("SAPI.SpVoice")
For Each Voice In Sapi.GetVoices
i = i + 1
Next
For loopvar = 0 To i-1
If loopvar = CInt(confirm_voice) Then
Set Sapi.Voice = Sapi.GetVoices.Item(loopvar)
End If
Next
Else
WScript.Echo "An Error Occured"
End If
If I call this script from another script, how can I make this script to return some value to the script that invoked it?
VBScript doesn't really provide call or import mechanisms for other VBScript files. The closest thing is to read the contents of the other file and run them via ExecuteGlobal.
Demonstration:
Put the following two files in the same directory and run script1.vbs. It will read the contents of script2.vbs and make the function Square available in the global scope by running the code via ExecuteGlobal. Once the function is available in the global scope you it can be used in the rest of the script.
script1.vbs:
Set fso = CreateObject("Scripting.FileSystemObject")
dir = fso.GetParentFolderName(WScript.ScriptFullName)
script = fso.BuildPath(dir, "script2.vbs")
ExecuteGlobal fso.OpenTextFile(script).ReadAll '"import" code into global scope
WScript.Echo Square(3)
script2.vbs:
Function Square(i)
Square = i*i
End Function

If FileExists delete another file

I am trying to add a sub-routine to a VBScript. In short, I am trying to see if one type of file exists, it will delete another file.
There will be files like:
SOCAL_CU59_res.dxf
SOCAL_CU59_main.dxf
SOCAL_CU59_mot.dxf
SOCAL_CU59_motl.dxf
but on occassion there may be a file with an "x" at the end of the filename:
SOCAL_CU59_resx.dxf
SOCAL_CU59_mainx.dxf
SOCAL_CU59_motx.dxf
SOCAL_CU59_motlx.dxf
They would all be in the same folder. The "x" file has priority. So if it exist I want to delete the matching file file without the "x".
Here is what I have so far but errors. The check filesize routine I added works great but it's after that I am having no luck:
Dim oFSO, sDirectoryPath, oFOLDER, oFile
Set oFSO = CreateObject("Scripting.FileSystemObject")
sDirectoryPath = "S:\SOCAL\Section_11\Road DXFs\"
RecurseFolders sDirectoryPath
Sub RecurseFolders(sFolder)
'Here we set the oFolder object, note that its variable scope is within
'this sub, so you can set it many times and it's value will only be
'that of the sub that's currently running.
Set oFolder = oFSO.GetFolder(sFolder)
'Here we are looping through every file in the directory path.
For Each oFile In oFolder.Files
'This just checks for a file size less than 100Kb
If oFile.Size <= 1085 And Right(LCase(oFile.Name),3) = "dxf" Then
oFile.Delete True
End If
Next
For Each oFile In oFolder.Files
'This checks if there is a file with an 'x' at the end of filename
If FileExists (Right(oFile.Name),1) = "x" Then
oFile.Delete True
End If
Next
'Here we do the recursive bit. We need to loop through each folder in
'the directory too and call the same sub to ensure we check every folder
'in the path.
For Each oFolder In oFolder.SubFolders
RecurseFolders oFolder.Path
Next
End Sub
The script creates both files, but does not delete the file that does NOT have the "x". The error says for line 204, Char 5:
Wrong number of arguments or invalid property assignment: 'Right'
The line the error refers to is: If FileExists (Right(oFile.Name),1) = "x" Then.
You have a few inherent problems that you need to correct in order to do this properly. First, you need to make the parenthesis correction mentioned by Ansgar Wiechers. Second, you should remove the duplicate loop. There's no need to loop over all of the files multiple times. Finally, you should store the files to be deleted until after the loop has finished. Deleting a file while it is in the file set that is currently being looped over could produce unexpected results or unexplained errors.
With that said, here's how I would approach this. You'll note all of the corrections I've mentioned.
Dim oFSO, sDirectoryPath, oFOLDER, oFile
Set oFSO = CreateObject("Scripting.FileSystemObject")
sDirectoryPath = "S:\SOCAL\Section_11\Road DXFs\"
Dim arrFilesToDelete() 'an empty dynamic array to hold files to be deleted later
Dim i = 0 'an iterator used to track the array pointer
RecurseFolders sDirectoryPath
DeleteExtraFiles arrFilesToDelete
Sub RecurseFolders(sFolder)
'Here we set the oFolder object, note that its variable scope is within
'this sub, so you can set it many times and it's value will only be
'that of the sub that's currently running.
Set oFolder = oFSO.GetFolder(sFolder)
'Here we are looping through every file in the directory path.
For Each oFile In oFolder.Files
'Is the file a "dxf" file
If LCase(Right(oFile.Name)) = "dxf" Then
'This just checks for a file size less than 100Kb
If oFile.Size <= 1085 And Right(LCase(oFile.Name),3) = "dxf" Then
End If
'This checks if there is an 'x' at the end of filename
If LCase(Right(oFile.Name) 5) = "x.dxf" Then
'if so, store its counterpart for deletion later
sBadFile = Replace(oFile.Name, "x.dxf", ".dxf")
ReDim Preserve arrFilesToDelete(i)
arrFilesToDelete(i) = oFile.Path & "\" & sBadFile
i = i + 1
End If
End If
Next
'Here we do the recursive bit. We need to loop through each folder in
'the directory too and call the same sub to ensure we check every folder
'in the path.
For Each oFolder In oFolder.SubFolders
RecurseFolders oFolder.Path
Next
End Sub
Sub DeleteExtraFiles(arrFiles)
For Each sFile in arrFiles
If oFSO.FileExists(sFile) Then
oFSO.DeleteFile sFile
End If
Next
End Sub
You put the inner closing parenthesis in the wrong place. The parameter 1 belongs to the function Right. Change this:
If FileExists (Right(oFile.Name),1) = "x" Then
into this:
If FileExists (Right(oFile.Name,1)) = "x" Then
With that said, there might be other issues with that line. VBScript doesn't have a built-in function FileExists and your code snippet doesn't reveal if that function is implemented elsewhere in your code, so whether passing it a character and comparing its return value to the character x actually makes sense is hard to say.
If you meant to use the FileSystemObject method FileExists you'd need to call it from the actual FileSystemObject instance:
If oFSO.FileExists(...) Then
and pass it a filename or path, not a single character or a boolean value.
If you want to test if for any given file foo.ext another file foox.ext exists, and in that case delete foo.ext you'd do something like this:
For Each oFile In oFolder.Files
xFilename = oFSO.GetBaseName(oFile) & "x." & oFSO.GetExtensionName(oFile)
If oFSO.FileExists(oFSO.BuildPath(oFile.Parent, xFilename)) Then
oFile.Delete True
End If
Next

Error with Loop and recursive function

I am trying to create a VbScript file that will read a text file that has a list of folder names in it.
From these folder names I need to create a second text file that prints out all the files with a specific extension.
I have used this code to do the second part of the task
Option Explicit 'force all variables to be declared
Const ForWriting = 2
Dim objFSO 'File System Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim objTS 'Text Stream Object
Set objTS = objFSO.OpenTextFile("C:\Output.txt", ForWriting, True)
Call Recurse("C:\")
objTS.Close()
Sub Recurse(strFolderPath)
Dim objFolder
Set objFolder = objFSO.GetFolder(strFolderPath)
Dim objFile
Dim objSubFolder
For Each objFile In objFolder.Files
'only proceed if there is an extension on the file.
If (InStr(objFile.Name, ".") > 0) Then
'If the file's extension is "pfx", write the path to the output file.
If (LCase(Mid(objFile.Name, InStrRev(objFile.Name, "."))) = ".exe") Then _
objTS.WriteLine(objfile.Path)
End If
Next
For Each objSubFolder In objFolder.SubFolders
Call Recurse(objSubFolder.Path)
Next
End Sub
I have tried to put this in a loop but when I do I get a syntax error for this line Sub Recurse(strFolderPath)
Any help you can give me would be appreciated
One interpretation of
I have tried to put this in a loop but when I do I get a syntax error
for this line Sub Recurse(strFolderPath)
is that the structure of your resulting script looks like:
Do Until tsIn.AtEndOfStream
p = tsIn.ReadLine
Sub Recurse(p)
End Sub
Call Recurse(p)
Loop
output:
cscript 27537600-B.vbs
..\27537600-B.vbs(3, 4) Microsoft VBScript compilation error: Syntax error
VBScript does not allow nested Sub/Function definitions, especially in loops (you may get away with mixing simple statements and Sub/Function definitions in top-level code, but that's more a bug than a feature). If you re-structure the script like
Do Until tsIn.AtEndOfStream
p = tsIn.ReadLine
Call Recurse(p)
Loop
Sub Recurse(p)
End Sub
you won't get a syntax error on the Sub line.

Does anyone remember what the statement/command "WaitOn" meant in VB3?

In the Form_Load event of this ultralegacy app I need to transliterate over to a web app is this command/statement "WaitOn" that occurs right after the On Error GoTo...
Does anyone remember what WaitOn means?
Here's the code snippet:
Dim sCmd As String
Dim iFileHandle As Integer
Dim sFileName As String
Dim i As Integer
Dim sKeyWord As String
Dim sWindowPosition As String
Dim iWindowState As Integer
Dim sSystemId As String
Dim sMetrics() As String
On Error GoTo MainFormLoadErr
WaitOn
ReDim gsFundsUsed(0 To 0)
ReDim gsObjectsUsed(0 To 0)
Set gsActiveSpread = Nothing
.
.
.
MainFormLoadExit:
WaitOff
Close
Exit Sub
MainFormLoadErr:
MsgBox Error$(Err) & " in MainForm Load"
Resume MainFormLoadExit
There is a corresponding WaitOff down there I just found. I don't think WaitOn is part of a line label.
As #C-Pound Guru suggested, WaitOn and WaitOff were methods in one of the (many) modules of the program. Not clear from the the names of the subroutines was the fact that their task was to set the mouse pointer to the Wait Cursor, and then return to the default, later.
Sub WaitOn ()
On Error Resume Next
Screen.MousePointer = 11
End Sub
Sub WaitOff ()
On Error Resume Next
Screen.MousePointer = 0
End Sub
I've never come across a 'WaitOn' or 'WaitOff' command in VB. You might want to double-check the code to see if there's a WaitOn method written (and a WaitOff method as well). It's not a label as VB labels end with a colon (:).
What happens if you right-click and Go To Definition? And does the code currently run?
Check the references - maybe it's something from a non-standard dll.

Overriding CreateObject Function in VBScript

I want to override the default CreateObject() function in VBScript with my own.
Basically this example in VB6:
http://www.darinhiggins.com/the-vb6-createobject-function/
I cannot figure out is this line:
Set CreateObject = VBA.CreateObject(Class$, ServerName$)
How do I refer to "VBA" in VBSript?
This quick test seems to work...
Function CreateObject(className, serverName)
'---- override the CreateObject
' function in order to register what
' object is being created in any error message
' that's generated
Dim source, descr, errNum
WScript.echo "In custom CreateObject"
If Len(serverName) > 0 Then
Set CreateObject = WScript.CreateObject(className, serverName)
Else
Set CreateObject = WScript.CreateObject(className)
End If
End Function
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject", "")
path = fso.GetAbsolutePathName(".")
WScript.echo path
No guarantees! ;-)
I don't think you can override it so that all code will use it, only YOUR code.
In which case, it doesn't matter what it's called (unless you have tons of existing code you can't change). Can you call it CreateObjectEx() or ExCreateObject() or something like that? Have this function add all your error handling and such and then turn around and call the main/core CreateObject() method

Resources