Search code examples
vbscriptscopesecure-crt

Issue with variable set to variable in VBS


In a VBS I have which I use in conjunction with SecureCRT to automate some processes on Cisco devices, I have (very much pared down) the following code:

Sub prConnectToHost(strConnectHost)

  'If no host is passed into subroutine then we need to prompt for one.
  If strConnectHost = "" Then strConnectHost = LCase(crt.Dialog.Prompt("Enter hostname or IP address:", "Connect to a host", strHost, False))

  strHost = strConnectHost

  'If user hits Cancel or hits Ok with no hostname entered then exit.
  If strHost = "" Then
    booReconnect = False
    Exit Sub
  End If

  'Write to connection log
  Call prWriteToConnectionLog

  'Run command capture subroutine.
  Call prCommandLoop

  Set intWaitString = Nothing:  Set strScreenGet = Nothing
  Set strLatestScriptVersion = Nothing:  Set strConnectHost = Nothing
End Sub

Sub Main has a section like this:

Do While booReconnect = True
  Call prConnectToHost("")
Loop

crt.Dialog.Prompt is the same as MsgBox, only it centres on the window and not the screen, so it's a little neater. The variable strHost is the actual hostname string which is global in the script and contains the hostname we want to connect to. It is used in the Prompt line as a default text, the idea being that if you disconnect and the booReconnect flag is set, this Sub is called again, and next time you're prompted for a hostname the old one is there - useful if you spelled it wrong first time, or you're connecting to a bunch of devices with a similar name.

You can see where we call prCommandLoop at the end of this Sub, which is a loop which uses a crt Function called WaitForStrings which puts the script on hold until it finds a particular string sequence. When it does, it fires off some stuff, then loops back around until it sits waiting again.

One of the automation commands detects for the presence of the connection menu (so therefore we have quit the router session) and prompts the user for another hostname to connect to.

The important bit is in the variable clearup at the end - Set strConnectHost = Nothing. If I leave this in and immediately exit prCommandLoop with booReconnect set, as soon as Set strConnectHost = Nothing is applied, strHost dies - if I try to reference it I get an error Object Variable not set. I experimented with putting a MsgBox strHost line right at the end of the Sub, which proved this.

The bizarre thing is that if I choose a different automation command in prCommandLoop first and then quit the session, the Set strConnectHost = Nothing doesn't seem to bother anyone.

Can anyone help me explain why this is a problem, as it is baffling me. I can easily work around it (by not issuing Set strConnectHost = Nothing at the end of the prConnectToHost Sub), but I just want to understand what the problem is.


Solution

  • Set is used to assign objects to variables. Think of Nothing as a very special object

    >> WScript.Echo IsObject(Nothing)
    >>
    -1
    

    which is useful only to indicate the emptiness of the variable. Your

    Set strConnectHost = Nothing
    

    assigns this Nothing to strConnectHost. After that, the variable is good for nothing - it holds the empty object that can't be printed or used in computations or asked to do methods.

    The type prefix fraud (*str*ConnectHost) should alert you that this is fishy. You work with strings (and numbers?); to clear/reset them use (simple) assignment with Empty:

    >> strConnectHost = Empty
    >>
    >> WScript.Echo IsEmpty(strConnection)
    >>
    -1
    

    or with a suitable value:

    intWaitString = -1 ' or 0 ...
    

    (assuming intWaitString isn't another type prefix fraud).

    SECOND ATTEMPT:

    I assume you call your sub like this:

    strHost = "SomeHost"
    prConnectToHost strHost
    

    The relevant digest of your sub is:

    Sub prConnectToHost( [ByRef] strConnectHost)
      ...
      Set strConnectHost = Nothing
    End Sub
    

    As VBScript uses by reference passing of parameters as default, your modification changes the caller variable strHost. This happens to non-object variables too:

      Dim sVar : sVar = "String 0"
      WScript.Echo 0, sVar
      changeString sVar
      WScript.Echo 1, sVar
    
      Sub changeString( sByRefVar )
        sByRefVar = "String 1: changed by changeString( ByRef sByRefVar )"
      End Sub
    

    output:

    0 String 0
    1 String 1: changed by changeString( ByRef sVar )
    

    In your case the modification assigns Nothing to the variable that is called strConnectHost in the Sub and strHost on the caller level. As I said before, that makes the variable useless (except of testing for Is Nothing).

    I hope that explains the clobbering of strHost.

    WRT 'memory management': Except for very special cases, you don't need to clear/reset/SetToNothing variables in VBScript. Using local variable in your Subs/Functions is all that is necessary. If you decide to use global variables and manage their state yourself, you must pay attention to the variable types: Changing the type from object (including Nothing) <=> non-object and lying about types by misleading type prefixes is dangerous/a sure way to desaster. If you think you must clear strHost, assign Empty or "" to strConnectHost.

    NEXT ADDITION

    All VBScript variables are Variants, but not all Variants are created equal:

    >> s0 = "string"
    >> s1 = CStr( 12.35 )
    >> WScript.Echo TypeName( s0 ), TypeName( s1 )
    >>
    String String
    >> n0 = 1
    >> n1 = CByte( n0 )
    >> WScript.Echo TypeName( n0 ), TypeName( n1 )
    >>
    Integer Byte
    

    TypeName() and VarType() show the sub-types and a progammer can use a set of C[hange/onvertTo]<Type>() functions to enforce them - to a degree, as assignments may change types 'under the hood'.

    >> WScript.Echo TypeName( n0 ), TypeName( n1 )
    >>
    Integer Byte
    >> n0 = 1.1
    >> n1 = 2 ^ 20
    >> WScript.Echo TypeName( n0 ), TypeName( n1 )
    >>
    Double Double
    

    There are even Type Mismatch Errors:

    >> WScript.Echo Nothing
    >>
    Error Number:       13
    Error Description:  Type mismatch
    >>
    
    >> WScript.Echo s0 Is Nothing
    >>
    Error Number:       424
    Error Description:  Object required
    

    So sub-types matter. Some people think type prefixes are uncool, but others see them as valuable help in weakly typed languages. If you decide to use them, you should use them correctly -

       Set strWhatEver = objWhatever
       objWhatever = intWhatever
       intWhatever = objWhatever
       If strWhatEver = intWhatever Then
    

    all smell (of not paying attention to types and hard to pin down errors in later code).