I noticed that I can Dim a variable after using it or (in other words) use it before Diming, even when using Option Explicit. Try this:
Option Explicit
x = "before Dim"
WScript.Echo x
Dim x
x = "after Dim"
WScript.Echo x
The same works fine in a Sub or Function.
Apparently, it does not matter on which line the Dim is, as long as it is in the same scope (current Function/Sub or global). I wonder why this works. Microsoft's documentation explicitly says (emphasis mine):
The lifetime of a script-level variable extends from the time it is
declared until the time the script is finished running.
I read this like: first Dim then use. But I was wrong...? Why is this?!
VB6 does not allow this:
---------------------------
Microsoft Visual Basic
---------------------------
Compile error:
Variable not defined
---------------------------
OK Help
---------------------------
The extract is the contract and what you must follow because we don't take dependencies on implementation. For your general info declares are hoisted to the top. Eric Lippert wrote:
Why can we use a variable before declaring it in VBScript?
...
What's up with that? Well, let me ask you this -- if you think that
looks weird, why do you think this looks normal?
Dim s
s = Foo(123)
Function Foo(x)
Foo = x + 345
End Function
There the function is being used before it is declared, but that
doesn't bug you, right?
Similarly, variables can be used before they are declared. The
behaviour is by design. Variable declarations and functions are
logically "hoisted" to the top of their scope in both VBScript and
JScript.
EDIT
VBScript follows VBA rules as far as possible. This is the implementation doc for implementing VBA. For VBS/VBA programmers it is for info only.
https://msdn.microsoft.com/en-us/library/dd361851.aspx
Related
I have a function that does the same operation for all of my scripts, but only the variable in which the Pass-Fail value is stored, would change.
For example, in one script -> the status is stored in Envrionment.Value("Current_Status")
in another script -> the status is stored in DataTable.Value("Status",1)
in another script -> the status is stored in objRS("AddCriteria_Status").Value
So i am trying to make a function in which i pass on these parameters as strings and then later use them as variable names. Here is the sample code:
Envrionment.Value("Current_Status") = "none"
Environment.Value("Fail_text") = "none"
Call AddCriteria("Environment.Value(""Current_Status"")","Environment.Value(""Fail_text"")")
Pubic Function AddCriteria(varStatus,varActual)
varTemp = ""
Execute(varStatus+ "=InProgress") 'change status to InProgress by the time execution is done
Execute(varActual + "=not_defined") 'this will have the reason the case failed
....code
If varTemp = "FAIL" Then
Execute(varStatus+ "=PASS")
Execute(varActual + "=PASS")
Else
Execute(varStatus+ "=FAIL")
Execute(varActual + "=Criteria did not get added")
End If
End Function
On calling the sub-routine i want the value of Environment.Value("Current_Status") to change from "none" to "InProgress" and then to "PASS"
But after the "Execute" command is executed, the Environment variable become empty.
Since CVar is not supported in VBScript, i cannot use it.
I tried Eval, but it doesn't work in the other direction i.e.:
If you change the value of Environment.Value("Current_Status"), then the value Eval(varStatus) changes, but I could not find a way to change the value of Eval(varStatus) so that the value of Environment.Value("Current_Status") changes.
Please help out. I am stuck at this for a week.
!!!What I'm trying to accomplish!!!
In a .vbs file, pass on any string to a function as a parameter; and convert it into a variable name in that function. Simple example: pass a string "abc" as a parameter to a function -> and within that function, convert the string to a variable name to store value [say, abc = "PASS"]
!!!How I attempt to do it!!!
I tried using Execute command as that is a solution that I got from a previous post
[vbscript Eval a string to a Variable in a loop?
Using "CVar" is a way but that is not supported in VBScript. So I ran out of ideas
!!!Problems that I faced!!!
Honestly, I didn't understand the logic of using "Execute", but i tried it nevertheless. Sadly, it didn't work out. When using execute command (as mentioned in the code), the environment variables become empty.
Ideas:
Use ExecuteGlobal to execute the assignment you want to execute --
if that´s what you want. Eval and especially Execute have subtle limitations regarding the scope they live in.
The target variable (i.e. the variable that receives a value in the
assignment that is evaluated by ExecuteGlobal) must be a global
variable.
If the ExecuteGlobal call happens on an Action's global scope, the
target variable must be declared there, too. (I think.)
If the ExecuteGlobal call happens in a routine in a function
library, the target variable must be declared there, too. (I know that for sure. But read on.)
To further help you, I'd need an update on your question because it is not clear what you want to accomplish, and what problems you see. Because -- Eval does not change values, it just evaluates an expression supplied as a string, and returns its value. If the expression has side-effects, like setting a global variable, then you might be out of luck because...well...it depends on where that global variable is declared, and initialized (if at all), and where the ExecuteGlobal call happens. Actions and libraries do NOT share one global scope, even if it looks like they do, and that can create a lot of strange behavior.
But as I said, if you clarify what you are trying to accomplish (got 90% of that), how you attempt to do it (got 40% of it), and what problems you face (got 10% of it), I´m sure I can update this answer so it approaches a solution.
** Update **
I use this library code for all runtime expression evaluation, be it from within a library or Action:
' Interpret (execute) a piece of VSH source code consisting of statements -- success?
' Code: String containing VBS source code. Passed by reference for performance reasons only
Public Function ExecCode (ByRef Code)
Dim ErrNumber
Dim ErrDescription
On error resume next ' Avoid getting kicked out by errors in the code contained in Code
ExecuteGlobal Code
ErrNumber=Err.Number
ErrDescription=Err.Description
On error goto 0 ' Re-enable RTE handling
If ErrNumber <> 0 Then
ExecCode=false
Print "Code execution failed ('" & ErrDescription & "'), code:" & vbNewline & Code & "<eof>"
else
ExecCode=true
End If
End Function
Dim GlobalVar
' Interpret (execute) a piece of VSH source code consisting of a single expression -- success?
' Expr; String containing a VBS expression. Passed by reference for performance reasons only.
' Target: Variable receiving the value to which the expression evaluates
Public Function EvalCodeAndAssign (ByRef Expr, ByRef Target)
' In order to force "Option explicit", we don´t use Eval, but ExecCode (and thus ExecuteGlobal):
Dim Code: Code="Option Explicit: GlobalVar=(" & Expr & ")"
Dim Result: Result=ExecCode (Code)
If Result Then
Target=GlobalVar
End If
EvalCodeAndAssign=Result
End Function
Update 2: if the statement that you pass to ExecuteGlobal contains quotes (which I think are missing in your code), the must be quoted, I.e. you must use double-quotes, like in
ExecuteGlobal "x=""This is a string"""
Because what ExecuteGlobal/Execute/Eval do is: take a string and interpret it as VBScript code. The code you are trying to use is not valid due to missing quotes.
I have one project which provides a service to the others, and the return value of the method that provides this service is String. Within that project, I use some named constants to represent special out of band values that are returned in lieu of expected or recoverable errors, otherwise the service returns an XML string.
Something like the following:
' modService.bas
const SERVICE_BADARG as String = "Unsupported argument."
const SERVICE_TOOMANY as String = "Too many Foos."
' cServiceProvider.cls
Private Function GetXMLString() as String
' generate and return XML string holding all sorts of generic stuff
End Function
Public Function PerformService(argument as String) as String
' do some stuff
If (some_condition = true) Then
PerformService = SERVICE_BADARG
Else If (some_other_condition = true) Then
PerformService = SERVICE_TOOMANY
Else
PerformService = GetXMLString()
I'd like to be able, from other projects, be able to get at these constants without redundantly defining them. If possible, I'd also like to avoid putting them in the class (where they will be duplicated unnecessarily) and to avoid making a property for each one.
They are all constants, none of them ever change.
Why not just define the constants in a CONSTANTS.BAS module, and then include that in each project? That way, to VB it would look like the definitions were duplicated, but from your perspective as a developer and a maintenance programmer, the definitions would all be collected in a single place?
Another option would be to create a DLL that defined the constants, but that would make using the values of those constants more costly in all of your code because rather than being compiled directly into the object code, they would have to be retrieved from a call to an external DLL. That seems like overkill for something that is truly constant.
Consider that a "constant" value is not necessarily the same thing as a "read-only" value. A constant value, like pi, will never change, so there is really not much to be lost by duplicating those values. You won't ever need to go back in and change them. Read-only values (like your error message strings) might change, so they're not really constants. It might make sense to place those into a DLL. Especially since performance isn't all that critical when all you're trying to do is shown an error message.
Unfortunately, VB gives you no mechanism of embedding constants into DLLs for compile-time use. You would have to return properties, as you said you didn't want to do.
I would definitely use the BAS route, unless there is a reason against it. An alternative to this would be to create a type library, and define the string constants in there. To do this, you will have to learn ODL, and use the MkTypeLib.exe program which comes with VB6. Or, if you somehow have access to "Advanced Visual Basic 6.0" by Matt Curland, there is a tool which allows you to create type libraries.
I know this is old, but in case anyone is still wondering...
try this pattern:
Public Function SERVICE_BADARG() As String
SERVICE_BADARG = "Unsupported argument."
End Function
Public Function SERVICE_TOOMANY() As String
SERVICE_TOOMANY = "Too many Foos."
End Function
Or more compactly with colons to put stuff on the same line:
Public Function SERVICE_BADARG() As String: SERVICE_BADARG = "Unsupported argument.": End Function
Public Function SERVICE_TOOMANY() As String: SERVICE_TOOMANY = "Too many Foos.": End Function
You could see a constant as a function without arguments. The advantage is that a function can be public, so you don't have to create a DLL. It also gets around only being able to declare them before other functions. VB6 allows you to hide the brackets:
x = "error: " & SERVICE_TOOMANY
Select Case y
Case SERVICE_BADARG
z = "error: y is a bad arg"
Case SERVICE_TOOMANY
z = "error: y is too many"
End Select
The disadvantage is a little overhead, but this is typically negligible
How can I do something similar to the following C code in VB 6?
#ifdef _DEBUG_
// do things
#else
// do other things
#end if
It's pretty much the same as the other languages that you're used to. The syntax looks like this:
#If DEBUG = 1 Then
' Do something
#Else
' Do something else
#End If
It's easy to remember if you just remember that the syntax is exactly the same as the other flow-control statements in VB 6, except that the compile-time conditionals start with a pound sign (#).
The trick is actually defining the DEBUG (or whatever) constant because I'm pretty sure that there isn't one defined by default. There are two standard ways of doing it:
Use the #Const keyword to define the constant at the top of each source file. The definition that you establish in this way is valid throughout the entire source module. It would look something like:
#Const DEBUG = 1
Set the constant in the project's properties. This would define a constant that is valid throughout the entire project (and is probably what you want for a "Debug" mode indicator).
To do this, enter something like the following in the "Conditional Compilation Constants" textbox on the "Make" tab of the "Project Properties" dialog box:
DEBUG = 1
You can define multiple constants in this dialog by separating each of them with a colon (:):
DEBUG = 1 : VERSION2 = 1
Remember that any constant which is not defined is assumed to be 0.
Cody has told you about conditional compilation. I'd like to add that if you want different behaviour when debugging on the IDE (e.g. turn off your own error handling so that the IDE traps errors) you don't need conditional compilation. You can detect the IDE at runtime like this.
On Error Resume Next
Debug.Print 1/0
If Err=0 then
'Compiled Binary
Else
'in the IDE
End if
This works because Debug.Print is omitted in the compiled EXE.
EDIT Remember to turn off On Error Resume Next !
EDIT You can wrap the check in a function like this (thanks CraigJ)
To achieve the same effect as MarkJ, but with error handling, you can use the following code.
Public Function GetRunningInIDE() As Boolean
Dim x As Long
Debug.Assert Not TestIDE(x)
GetRunningInIDE = x = 1
End Function
Private Function TestIDE(x As Long) As Boolean
x = 1
End Function
When you are running from within the IDE, there will be an extra overhead of calling a function (which is ridiculously small). When compiled, this evaluates to a simple number comparison.
This is my short and stable code. I think it is better than conditional constants, because you don't need to change it every complication time.
Public Function InIDE() As Boolean
On Error Resume Next
Debug.Print 0 / 0
InIDE = Err.Number <> 0
End Function
I have run into an annoying problem in QTP (Quick test pro) using VBScript.
I have defined this sub in VBScript (I have also tried defining it as a function with the same result):
Sub KillProcess()
KillprocessIE = "The process i want to kill"
Set ProcessList2 = GetObject("winmgmts://.").InstancesOf("win32_process")
For Each Process In ProcessList2
If Process.Name = KillProcessIE Then
Process.Terminate
Exit for
End If
Next
End Sub
But when I try to run it either by using
call KillProcess()
KillProcess()
KillProcess
I get an error saying "Typer stemmer ikke overens: 'KillProcess'" with translated from Danish means something like "Types does not match: 'KillProcess'. I am guessing it is a type mismatch error but due to translation I cant be sure.
What types is it talking about? I have no arguments in my function call and I am not assigning any values to anything?
It should also be said that if I run the exact same code directly without defining it as a function, it works without issue.
Whenever you call a sub or function that is not defined, you get a type mismatch error.
Even though this is a miracle per se (for which I could find a reasonable reasoning for only for functions, not for subs) it indicates that in your specific scenario, KillProcess was not known in the script in which you called it.
Possible causes:
The KillProcess declaration was in a function library which was not listed in the
associated function library settings dialog.
The KillProcess declaration was active, but your call(s) contained one or more typos,
like "KillProccess", or similar stuff.
As Motti indicated, the code snippet you posted looks fine, so there must be some other glitch.
Another hint regarding subs versus functions: Be aware that you usually never call a Sub with brackets for the arguments. If you do specify them, they are considered to be part of a term to be evaluated, and since
X term
is the same as
X (term)
this consequently does not yield an error message in this case.
However, for Subs with two or more arguments, specifying all actual arguments in one bracket pair, like in
Y (term1, term2)
yields an error, something like "cannot call a Sub with arguments in brackets", or so. This is hard to understand if you consider that one argument indeed can be specified in brackets.
(For a more complete overview about the paranthesis topic, see ByRef and ByVal in VBScript and linked questions).
General suggestion: Use
option explicit
at the top of all scripts (especially function libraries) all the time. RTFM this if needed. It might look like more work (because it forces you to declare all variables before you can use them), but it is useful to avoid subtle bugs.
I think you have run into the strange behavior QTP but though logic when you understand why.
The reason for why this occurs is probably because you "run from step" call KillProcess() and having the function defined above like:
Sub KillProcess()
.
.
.
End Sub
call KillProcess()
If you run the code from "Call KillProcess()" QTP will return the "Run Error" "Type Mismatch"
If instead let the function be defined below the executing statement like this
call KillProcess()
Sub KillProcess()
.
.
.
End Sub
Then QTP "knows" the function that you are calling and will execute the script like it should.
The reason for this is, that when using "Run from step" only reads the line of codes from the step and below and not what you have written above.
What is a good way to use asserts in VBScript scripts?
Is there built-in functionality for it or will it have to be emulated? What is best practice?
One application is to test for objects being Nothing during development.
An operational answer (for others that may need it) is to define this function, from Rosetta Code:
sub Assert( boolExpr, strOnFail )
if not boolExpr then
Err.Raise vbObjectError + 99999, , strOnFail
end if
end sub
Invocation:
Set obj2 = Nothing
Assert Not obj2 Is Nothing, "obj2 is Nothing!"
Output:
someScript.vbs(17, 3) (null): obj2 is Nothing!
Sadly, I don't believe VBScript has anything built-in. You'll need to define your own Assert method and probably use a pre-processor build script or something to remove them when building a release copy of your script. (Actually removing -- at least commenting out -- the Assert call is best, rather than just making the body of the Assert not do anything in the released code.)