Home » excel » excel – How can my code find out if it's running as VBScript, .HTA, or VBA?

excel – How can my code find out if it's running as VBScript, .HTA, or VBA?

Posted by: admin March 9, 2020 Leave a comment

Questions:

(Final edit: The code I ended up putting together that works is way down below, it’s probably the final reply in this thread. 🙂 )

I’m trying to write generic copy-and-paste code that will work in a standalone VBScript (a .vbs file), in a .hta file, and as VBA (for instance, in an Excel file). To do that, I need some way for the code itself to tell what engine it’s running in.

The best idea I’ve heard so far involved testing if certain objects exist or not, but in VBA, that fails at compile time (so I can’t bypass it with On Error), so that didn’t work out. Trying to find out the name of the file it’s running didn’t end up being viable; that’s one of the things that’s done differently depending on which of the three script engines the code’s running in.
I would love to have something simple like this, but am not sure what to fill it in with:

Edit: Most responses so far involve checking for possibly non-existent objects, which outright does not work in VBA with Option Explicit on (it throws a compile-time error, so On Error doesn’t work, and turning off Option Explicit is not an option). Is there some other roundabout / out-of-the-box way to find out what’s needed here?

Option Explicit

'--- Returns a string containing which script engine this is running in,
'--- either "VBScript", "VBA", or "HTA".

Function ScriptEngine()

    If {what goes here?} Then ScriptEngine="VBS"
    If {what goes here?} Then ScriptEngine="VBA"
    If {what goes here?} Then ScriptEngine="HTA"
    End Function

If this is filled in right, you should be able to copy and paste that function into any VBA, VBS, or HTA file without modification, call it, and get a result instead of an error, even when Option Explicit is on.
What’s the best way to go about this?

How to&Answers:

The restriction on requiring Option Explicit in the VBA implementation makes this a little more difficult than it otherwise would be (it’s a one-liner without it)… Ironically it also turns out to be the key to the solution.
If you don’t limit yourself to a single function, you can get away with it by doing something like this:

Dim hta

Sub window_onload()
     hta = True
End Sub

Function HostType()
    On Error Resume Next
    If hta Then
        HostType = "HTA"
    Else
        Dim foo
        Set foo = foo
        If Err.Number = 13 Then
            HostType = "VBA"
        Else
            HostType = "VBS"
        End If
    End If
End Function

It works like this – If it’s loaded via an HTA file, the window_onload event handler runs, setting the hta variable to True. That’s the first test. The second “test” is on the error thrown by the line Set foo = foo. This is a type mismatch in VBA, where it is interpreted as trying to Set a Variant to Empty, which isn’t a compatible type. The same line of code throws an error 424 (Object required) in VBScript because it is not a strongly typed language. That means VBA’s type check is skipped and it attempts to actually perform the assignment (which fails). The rest is just figuring out how it threw and returning the result.

Test code

VBA

Option Explicit

Dim hta

Sub Test()
    Debug.Print HostType    'VBA
End Sub

Sub window_onload()
     hta = True
End Sub

Function HostType()
    On Error Resume Next
    If hta Then
        HostType = "HTA"
    Else
        Dim foo
        Set foo = foo
        If Err.Number = 13 Then
            HostType = "VBA"
        Else
            HostType = "VBS"
        End If
    End If
End Function

VBScript

WSCript.Echo HostType

Dim hta

Sub window_onload()
     hta = True
End Sub

Function HostType()
    On Error Resume Next
    If hta Then
        HostType = "HTA"
    Else
        Dim foo
        Set foo = foo
        If Err.Number = 13 Then
            HostType = "VBA"
        Else
            HostType = "VBS"
        End If
    End If
End Function

HTA

<HTML>
    <BODY>
        <script type="text/vbscript">
            Dim hta

            Sub Test()
                MsgBox HostType 
            End Sub

            Sub window_onload()
                 hta = True
            End Sub

            Function HostType()
                On Error Resume Next
                If hta Then
                    HostType = "HTA"
                Else
                    Dim foo
                    Set foo = foo
                    If Err.Number = 13 Then
                        HostType = "VBA"
                    Else
                        HostType = "VBS"
                    End If
                End If
            End Function
        </script>
        <button onclick="vbscript:Test()">Click me</button> 
    </BODY>
</HTML>

EDIT:

FWIW, the one-liner referenced above if Option Explicit isn’t needed is simply this:

Function HostString()
    HostString = Application & document & WScript
End Function

All three objects have a default property that returns a String. In VBScript, this will return “Windows Script Host”. In VBA, it will return the name of the host (i.e. “Microsoft Excel” in Excel). In HTA it will return “[object]”.

Answer:

Although I agree with @this here’s my Option Explicit-safe method with no On Error statements that uses Window_OnLoad listener for HTA (like Comintern did) and a line label trick to distinguish VBScript from VBA.

Dim IsInHTA, IsInVBScript

Sub Window_Onload()
    IsInHTA = True
End Sub

Sub LineLabelTest()
'VBA and VB6 (maybe VB5 too, IDK) treats "DummyLabel:" as a line label
'VBScript treats "DummyLabel" as an expression to call and treats ":" as a statement separator.
DummyLabel:
End Sub

Sub DummyLabel()
    'this is called by the LineLabelTest subroutine only in VBScript
    IsInVBScript = True
End Sub

Function HostType()
    LineLabelTest

    If IsInVBScript Then
        If IsInHTA Then 
            HostType = "HTA"
        Else
            HostType = "VBS" 'Other hosts incuding WSH, ASP, Custom
        End If
    Else
        HostType = "VBA" 'VBA or VB6 (maybe VB5 too, don't know)
    End If
End Function

Answer:

Try to check existence of context objects, like

Function ScriptEngine()
    dim tst
    on error resume next
    Err.Clear
    tst = WScript is Nothing
    if Err=0 then ScriptEngine="WScript" : exit function
    Err.Clear
' similar way check objects in other environments

End Function

Answer:

Any undeclared variable will be Variant/Empty, so VarType(something) will be vbEmpty (or 0) if something is undefined.

Unless VarType doesn’t exist outside the VBA standard library (I’ve no idea TBH), then there’s no need to trap/skip/handle any errors for this to work – tested in VBA:

Function GetHostType()
    If VarType(wscript) <> vbEmpty Then
        GetHostType = "VBS"
        Exit Function
    End If        
    If VarType(Application) <> vbEmpty Then
        GetHostType = "VBA"
        Exit Function
    End If        
    GetHostType = "HTA"
End Function

Note that this will yield unexpected results if e.g. Application or WScript is defined somewhere and the host isn’t VBA or VBScript, respectively.

Alternatively, this would work whether VarType is defined or not:

Function GetHostType()
    On Error Resume Next

    If Not WScript Is Nothing Then
        If Err.Number = 0 Then
            GetHostType = "VBS"
            Exit Function
        End If
    End If
    Err.Clear ' clear error 424 if not VBS

    If Not Application Is Nothing Then
        If Err.Number = 0 Then
            GetHostType = "VBA"
            Exit Function
        End If
    End If
    Err.Clear ' clear error 424 if not VBA

    GetHostType = "HTA"
End Function

Again it assumes that no object by these names are defined; the mechanism relies on WScript/Application being a Variant/Empty and thus throwing run-time error 424 “Object Required” when tested with Is Nothing.

Note that you can’t have Option Explicit specified for this. The reason is because if you do Dim WScript and Dim Application to satisfy the compiler, then at runtime these variables will be shadowing the global identifiers you’re checking, and the function will consistently return whatever host you checked first.

Answer:

I’d suggest that you are approaching the problem backward. Instead of asking “who’s the host here?”, you should instead have a common interface for tasks. As an example, You would have a module – let’s call it MyAPI:

Public Function MySleep(Milliseconds As Long)
End Function

You can then implement one for VBA, one for VBS, other for HTA (though I question whether there’s a real difference. Your host-agnostic code then would require inclusion of that module of which all should allow for. For example, see here for including a file to VBS. It also looks like HTA has something similar. In VBA, that’s just another module.

Then in your agnostic-host code, you would call MySleep instead of API-declared Sleep or WScript.Sleep, and let the included module provide the host-specific implementation without any branching and therefore any need to disable Option Explicit nor testing for non-existent objects.

Answer:

For anyone coming across this in the future, this is the final code I ended up making, and it works! Special thanks to everyone in this thread that contributed the ideas that came together to make this work! 🙂

'------------------------------------------------------------------
'--- Function ScriptEngine
'---
'--- Returns a string containing which script engine this is
'--- running in.
'--- Will return either "VBScript","VBA", or "HTA".

Function ScriptEngine

    On Error Resume Next

    ScriptEngine="VBA"

    ReDim WScript(0)
    If Err.Number=501 Then ScriptEngine="VBScript"

    Err.Clear

    ReDim Window(0)
    If Err.Number=501 Then ScriptEngine="HTA"

    On Error Goto 0

    End Function

'-------------------------------------------------------------------

Answer:

All programs have a host that provides a global Application object at a minimum. WScript provides the global WScript, Word/Excel an Application object, an HTA an IE Window object (which through the parent property gives access to InternetExplorer.Application object.

In COM we call QueryRef on the IUnknown interface to find what objects it has. This happens under the hood. The principal is you try to use it and look for the error that says for a property E_Not_Implemented.

This is the standard on what should be in an Application object https://docs.microsoft.com/en-au/previous-versions/windows/desktop/automat/using-the-application-object-in-a-type-library

So wscript.name, InternetExplorer.Application.Name, and Word.Application.Name.

In Wscript this code prints Windows Scripting Host. In Word it prints Normal 424 Object Required. In an HTA Microsoft VBScript runtime error 424 Object required.

On Error Resume Next
x = wscript.name
If err.Number = 0 then
    Msgbox x
Else
    Msgbox err.source & " " & err.number & " " & err.description
End If

Likewise in Word Microsoft Word, VBS Microsoft VBScript runtime error 424 Object required, and HTA Microsoft VBScript runtime error 424 Object required.

On Error Resume Next
x = Application.Name
If Err.Number = 0 Then
    MsgBox x
Else
    MsgBox Err.Source & " " & Err.Number & " " & Err.Description
End If

Also note testing for Application is defined or not is not robust. Excel also has an Application object. You have to test for the name.