The VB Family: VB6, VBA and VBScript

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Visual Basic 17.13 on .NET 10 (SDK 10.0.402) and was checked on 2026-10-19. Visual Basic 6, VBA and VBScript are different products: the name and a large part of the syntax are shared, but each has its own runtime, its own type system and its own support status. This page explains how they differ from Visual Basic .NET so that you can recognise old code, understand what you may still be asked to maintain, and know where to go for each dialect. It is a recognition guide, not a recommendation: nothing here is a pattern for new code.

Two kinds of code blocks appear on this page, and the difference matters:

  • Legacy snippets are old-dialect code (VB6, VBA, VBScript, Classic ASP). Each one has a block title that starts with "Legacy" and says that it was not built and not run: no tool in the .NET SDK can compile it, and most of it needs Windows, COM and a host application. Treat them as the "old code you may meet" that this page exists to decode. They are original illustrations, not copies of any book or manual.

  • Visual Basic .NET examples are complete console programs that start with Option Strict On, were built with warnings treated as errors and were run in the .NET 10 SDK container (Linux, arm64) with the root namespace ShelfApp. The output shown is the real output. The compiler messages quoted in the tables are the ones the SDK printed when each construct was put into a program of its own.

The page that explains what to do with old code is Migrating Legacy Visual Basic. The history of the language versions is on History, Versions and Language Strategy.

The family at a glance

The Visual Basic family tree: BASIC leads to Visual Basic 1 to 6, which has relatives VBA and VBScript, and to Visual Basic .NET, which continues to version 17.13

The tree has one trunk. BASIC (1964) led through many dialects to Visual Basic 1.0 in 1991 and to Visual Basic 6.0 in 1998. Three things grew out of that trunk, and one of them is a break:

  • VBA (Visual Basic for Applications) is the VB6-style language embedded in Microsoft Office and in other host programs. It is alive and supported, but only as a part of its host.

  • VBScript is a small, untyped scripting language in the same family. Windows Script Host runs it from the command line, Internet Explorer once hosted it and Classic ASP uses it on the server. It is deprecated and is being removed from Windows in phases.

  • VB6 itself is the compiled language and IDE. Its tools are out of support; its runtime is still supported with Windows.

  • Visual Basic .NET (2002 onwards, the language of this whole section) is a rewrite on the .NET common language runtime. It kept the look of the language and changed almost everything underneath, so it never was source compatible with VB6.

Aspect Visual Basic .NET VB6 VBA VBScript Classic ASP

Runs on

the .NET runtime (CLR), with the SDK compiler (Roslyn)

the VB6 runtime library (a COM component) plus Windows; native code or interpreted p-code

the host application (Excel, Word, Access and others)

a scripting engine, hosted by Windows Script Host, Internet Explorer or IIS

IIS, which calls the VBScript engine

Compiled or interpreted

compiled to IL, then JIT-compiled

compiled (native or p-code)

compiled to p-code inside the document

interpreted from source text

.asp pages whose script is run by the VBScript engine when IIS serves the page

Typing

static types, Option Strict On recommended; Object for the rare untyped value

typed with As, Variant as default; Option Explicit optional

same as VB6

only Variant: there is no As clause

same as VBScript

Object model

classes, inheritance, interfaces, generics

classes with Implements (interface inheritance only), no implementation inheritance

class modules like VB6, document objects

Class blocks (since VBScript 5), no inheritance

the same, plus built-in Request, Response, Session, Application, Server

Component model

.NET assemblies; COM only through interop

COM (ActiveX DLLs and controls)

COM, plus the host’s object model

COM through CreateObject

COM through Server.CreateObject

Memory

garbage collector, IDisposable for resources

reference counting, deterministic Class_Terminate

reference counting

reference counting

reference counting

Where code lives

.vb files in an SDK project (.vbproj)

.vbp, .frm, .bas, .cls, .ctl files

inside the document or workbook (.xlsm, .docm, .accdb)

.vbs files

.asp files, global.asa

Platforms

Windows, Linux, macOS for console and libraries; Windows for WinForms and WPF

Windows only

Windows and macOS Office, with differences

Windows only

Windows with IIS

Status on 2026-10-19

supported; Visual Basic 17.13 is current

IDE unsupported since 2008-04-08; runtime supported with Windows

supported with Office

deprecated, being removed

legacy; an IIS feature

Support status in detail

Support statements change, so this section says what the official pages state on the check date and then describes the direction in general terms.

Visual Basic 6. The IDE (the Visual Basic 6.0 development environment) has been out of support since 2008-04-08. The runtime is covered by an "It Just Works" commitment: Microsoft supports the VB6 runtime for the lifetime of the Windows version that it ships with, and the support is limited to serious regressions and critical security issues. In practice this means that a VB6 program may keep running on a supported Windows, but nobody supplies a supported compiler, new controls or a language that changes. Anything you build with the VB6 IDE today depends on tools that have been out of support for many years. Installing the IDE from an unofficial archive is not something this section describes or recommends.

VBA. VBA belongs to Office. Its language reference is part of the Office documentation, and its life follows Office’s. VBA code in a document runs inside the host, uses the host’s object model and, since Office applications moved to 64-bit, needs PtrSafe and LongPtr for Declare statements. Microsoft blocks macros in files that came from the internet by default, which matters to anyone who distributes macro-enabled documents. For new work, Office offers other extension models; this section does not cover them.

VBScript. Microsoft announced the deprecation of VBScript in October 2023. The "Deprecated features" page says that VBScript will become a feature on demand before it is removed from Windows, and the "Resources for deprecated features" page adds that the feature on demand is initially preinstalled so that existing scripts keep running while you prepare. The Windows IT Pro blog post on the timeline describes the later phases in general terms: the feature on demand stops being preinstalled, then it is disabled by default, then it is removed. The dates are in that blog post (the publisher blocks scripted access, so it is listed without a link check); this page does not repeat them because they are plans and can move. Scripts that must survive should be rewritten in PowerShell or in a .NET program, and Internet Explorer hosting is irrelevant, because Internet Explorer is retired.

Classic ASP. Classic ASP is the original server-side page model on IIS, using VBScript or JScript in .asp files. It is an optional IIS feature, it is not a .NET technology and it is a legacy platform. The successors on .NET are ASP.NET Web Forms (which can use Visual Basic code-behind on .NET Framework) and, for new work, ASP.NET Core, which has no Visual Basic templates or Razor syntax: see Web, HTTP and Services and Migrating to Modern ASP.NET.

Visual Basic .NET. The language is supported, stable and "consumption-only": 17.13 is the current version and the compiler accepts nothing newer. See History, Versions and Language Strategy.

What a VB6 project looks like

A VB6 project is a set of text files plus binary resources. The project file (.vbp) lists the pieces. A form (.frm) holds the visual design as text at the top and the code below it; the binary parts of a form (images, icons) go to a .frx file. Standard modules are .bas files (plain procedures and global variables), class modules are .cls files and user controls are .ctl files. The IDE builds an .exe (a standalone program), an ActiveX .dll or .ocx (a COM component) and registers the component in the Windows registry.

Legacy VB6 form file (.frm), not built and not run
VERSION 5.00
Begin VB.Form frmShelf
   Caption         =   "Shelf"
   ClientHeight    =   2400
   ClientWidth     =   4200
   Begin VB.CommandButton cmdAdd
      Caption         =   "Add"
      Height          =   375
      Left            =   120
      Top             =   1800
      Width           =   1215
   End
   Begin VB.ListBox lstBooks
      Height          =   1230
      Left            =   120
      Top             =   600
      Width           =   3855
   End
   Begin VB.TextBox txtTitle
      Height          =   375
      Left            =   120
      Top             =   120
      Width           =   3855
   End
End
Attribute VB_Name = "frmShelf"
Option Explicit

Private Sub cmdAdd_Click()
    Dim title As String
    title = txtTitle.Text
    If Len(title) = 0 Then
        MsgBox "Type a title first"
        Exit Sub
    End If
    lstBooks.AddItem title
    txtTitle = ""
End Sub

Three details of that file are worth decoding. The Begin ... End block is the designer’s output (Visual Basic .NET puts the same information in a .Designer.vb file, or in XAML for WPF). The handler cmdAdd_Click is connected to the button by its name; Visual Basic .NET uses Handles or AddHandler instead, as explained in Events and Handlers. And txtTitle = "" assigns to the control’s default property (Text), which Visual Basic .NET no longer allows without an index; see Properties and Default Properties.

The Windows Forms equivalent, with the designer file and the Application.myapp startup, is on Windows Forms Applications.

Types: what changed

The biggest source of old-code bugs is the type system. The table lists the VB6 and VBA types and what happens to them in Visual Basic .NET.

VB6 / VBA Visual Basic .NET What to know

Integer (16 bits)

Short (System.Int16)

The name Integer moved to the 32-bit type. Old code that meant "a small number" now has a different range; old code that relied on overflow at 32,767 behaves differently.

Long (32 bits)

Integer (System.Int32)

Long is now 64 bits (System.Int64). VBA on 64-bit Office has a LongLong type, and LongPtr is a pointer-sized integer.

Currency (scaled 64-bit integer, four decimals)

Decimal

There is no Currency type: BC30002, "Type 'Currency' is not defined". Decimal has 28 to 29 significant digits and is not a binary floating-point type.

Variant

Object (or, better, a real type or a generic)

Variant is rejected: BC30804. Object can hold any value, but with Option Strict On you must convert before you use it.

Variant "Empty", "Null"

Nothing, DBNull.Value

Empty is an uninitialised variant, Null a database-style missing value. They are different things in .NET.

String * n (fixed length)

String, Char arrays, or Span

Fixed-length strings do not exist. The declaration is a syntax error (BC30205).

Boolean

Boolean

True converts to -1 with CInt, but Convert.ToInt32(True) gives 1. Code that stores booleans as numbers is a classic migration bug.

Date (a Double: days since 1899-12-30)

Date (System.DateTime, ticks since 0001-01-01)

The two are not the same value. Date.FromOADate converts an OLE automation date.

Byte, Single, Double, Object

Byte, Single, Double, Object

Unchanged in meaning. Byte is unsigned; SByte is new.

Type ... End Type (record)

Structure ... End Structure

Type is rejected: BC30802. A Structure can have methods and properties, and it is a value type.

Dim a, b As Integer

the same text

In VB6 only b was an Integer and a a Variant. In Visual Basic .NET both are Integer.

DefInt A-Z and similar

not available

Typing by first letter is gone. Declare each variable.

arrays with Option Base and Dim a(1 To 5)

arrays whose lower bound is always 0

Option Base is rejected (BC30206) and 1 To 5 is BC32059, "Array lower bounds can be only '0'".

The program below prints the facts behind that table. It also shows what Variant turned into. The TypeName and VarType functions still exist in Visual Basic .NET, which helps when you port old code that inspects a variant.

Example 1: the type facts (built and run)
Option Strict On

Module F1Types
    Sub Main()
        Console.WriteLine("Whole numbers")
        Console.WriteLine($"  Short   {Short.MinValue} to {Short.MaxValue}  (16 bits, the VB6 Integer)")
        Console.WriteLine($"  Integer {Integer.MinValue} to {Integer.MaxValue}  (32 bits, the VB6 Long)")
        Console.WriteLine($"  Long    {Long.MinValue} to {Long.MaxValue}  (64 bits, new in VB.NET)")

        Console.WriteLine("Currency was a scaled 64-bit integer with four decimals")
        Dim currencyMax = Long.MaxValue / 10000D
        Dim currencyMin = Long.MinValue / 10000D
        Console.WriteLine($"  VB6 Currency range: {currencyMin:N4} to {currencyMax:N4}")
        Console.WriteLine($"  Decimal holds 28-29 digits: {Decimal.MaxValue:N0}")

        Console.WriteLine("Booleans and dates")
        Console.WriteLine($"  CInt(True) = {CInt(True)}, Convert.ToInt32(True) = {Convert.ToInt32(True)}, True.ToString() = {True}")
        Dim day0 = Date.FromOADate(0)
        Console.WriteLine($"  VB6 Date zero (OADate 0) = {day0:yyyy-MM-dd}; DateTime.MinValue = {DateTime.MinValue:yyyy-MM-dd}")

        Console.WriteLine("Variant became Object")
        Dim items As Object() = {1S, 2, 3L, 4.5R, "five", #1/2/2000#, True, Nothing}
        For Each item In items
            Console.WriteLine($"  {If(item Is Nothing, "Nothing", TypeName(item)),-8} VarType={VarType(item)}")
        Next

        Console.WriteLine("Declaring two names in one Dim")
        Dim a, b As Integer
        Console.WriteLine($"  a is {a.GetType().Name}, b is {b.GetType().Name}  (VB6 made a a Variant)")
    End Sub
End Module

Output:

Whole numbers
  Short   -32768 to 32767  (16 bits, the VB6 Integer)
  Integer -2147483648 to 2147483647  (32 bits, the VB6 Long)
  Long    -9223372036854775808 to 9223372036854775807  (64 bits, new in VB.NET)
Currency was a scaled 64-bit integer with four decimals
  VB6 Currency range: -922,337,203,685,477.5808 to 922,337,203,685,477.5807
  Decimal holds 28-29 digits: 79,228,162,514,264,337,593,543,950,335
Booleans and dates
  CInt(True) = -1, Convert.ToInt32(True) = 1, True.ToString() = True
  VB6 Date zero (OADate 0) = 1899-12-30; DateTime.MinValue = 0001-01-01
Variant became Object
  Short    VarType=Short
  Integer  VarType=Integer
  Long     VarType=Long
  Double   VarType=Double
  String   VarType=String
  Date     VarType=Date
  Boolean  VarType=Boolean
  Nothing  VarType=Object
Declaring two names in one Dim
  a is Int32, b is Int32  (VB6 made a a Variant)

The Currency line deserves a note. VB6’s Currency stored a 64-bit integer divided by 10,000, so the range printed is exactly the one that the arithmetic gives. Moving it to Decimal keeps every value, and Decimal goes far beyond that range, but code that relied on Currency rounding to four places must now round explicitly with Math.Round(value, 4). See Numbers and Math.

Syntax and statements that were removed or changed

Some VB6 syntax is simply not Visual Basic .NET. The compiler says so clearly, which is a gift when you migrate: each row below was put in a separate program with Option Strict On and the SDK printed the message shown.

Legacy construct Visual Basic .NET What the compiler printed

Set b = a and Let x = 5

plain assignment: b = a

BC30807: 'Let' and 'Set' assignment statements are no longer supported.

Dim v As Variant

Dim v As Object

BC30804: 'Variant' is no longer a supported type; use the 'Object' type instead.

GoSub Report …​ Return

a Sub or Function with parameters

BC30814: 'GoSub' statements are no longer supported.

While ... Wend

While ... End While, Do While ... Loop

BC30809: 'Wend' statements are no longer supported; use 'End While' statements instead.

Type BookRec ... End Type

Structure BookRec ... End Structure

BC30802: 'Type' statements are no longer supported; use 'Structure' statements instead.

Public Property Get X() As Integer

Public Property X As Integer with Get and Set blocks

BC30808: Property Get/Let/Set are no longer supported; use the new Property declaration syntax.

Option Base 1

not available: the lower bound is 0

BC30206: 'Option' must be followed by 'Compare', 'Explicit', 'Infer', or 'Strict'.

Dim a(1 To 5) As Integer

Dim a(4) As Integer (upper bound 4)

BC32059: Array lower bounds can be only '0'.

Show 3 (a call without parentheses)

Show(3)

BC30800: Method arguments must be enclosed in parentheses.

Dim c As Currency

Dim c As Decimal

BC30002: Type 'Currency' is not defined.

Dim s As String * 10

Dim s As String

BC30205: End of statement expected.

Open "f.txt" For Output As #1 and Print #1, x

StreamWriter, or the FileOpen and PrintLine functions of the runtime

BC30800 and BC32017: the statement form is not parsed

Console.WriteLine(anything + 1) where anything is a Variant-like Object

convert first: CInt(anything) + 1

BC30038: Option Strict On prohibits operands of type Object for operator ''.+

Dim s As String = "a" + 1 (the VB6 habit of joining with +)

& joins strings: "a" & 1

BC30512, twice: Option Strict On disallows implicit conversions from 'Double' to 'String'. and Option Strict On disallows implicit conversions from 'String' to 'Double'.

Source: BC30807, BC30804, BC30814 and the other messages in the table

Some old-looking constructs do still compile, and that is itself a trap. The following were checked and work in Visual Basic .NET on .NET 10: assigning the result to the function’s own name (Total = x * 2, an old way to return a value, still compiles next to Return), Debug.Print, Left$ and the other $ string function names, Mid, UBound, LBound, ReDim Preserve, TypeName, VarType, IIf, Call, Err.Number, On Error (which is documented on its own page: see Unstructured Error Handling: On Error), the FileOpen family and MsgBox (the last needs Windows Forms on .NET: see The My Namespace). A program in which every old construct still compiles is not necessarily correct: Mid is still 1-based while Substring is 0-based, IIf still evaluates both branches while If() does not, and the function-name assignment does not leave the procedure the way Return does.

Procedures: ByRef by default, and Set, Let and Get

In VB6 and VBA a parameter without a modifier is passed ByRef: the callee can change the caller’s variable. In Visual Basic .NET the default is ByVal, and you must write ByRef when you mean it. Code that you migrate must make that decision for each parameter.

Legacy VB6 procedure that changes the caller’s variable, not built and not run
Private Sub Rename(title As String)      ' ByRef, because nothing was written
    title = "Changed"
End Sub

Private Sub Demo()
    Dim t As String
    t = "Original"
    Rename t                              ' call without parentheses
    Debug.Print t                         ' prints Changed
End Sub

In Visual Basic .NET the same shape is written with parentheses, changes nothing unless you ask, and Set is gone:

Example 2: ByVal by default, ByRef on request (built and run)
Option Strict On

Module F3Procedures
    Sub Rename(title As String)
        title = "Changed"
    End Sub

    Sub RenameByRef(ByRef title As String)
        title = "Changed"
    End Sub

    Sub FillFirst(values As Integer())
        values(0) = 99
    End Sub

    Function Twice(x As Integer) As Integer
        Twice = x * 2
    End Function

    Sub Main()
        Dim title = "Original"
        Rename(title)
        Console.WriteLine($"ByVal (the default): {title}")
        RenameByRef(title)
        Console.WriteLine($"ByRef (must be written): {title}")

        Dim data = {1, 2, 3}
        FillFirst(data)
        Console.WriteLine($"An array passed ByVal still shares its elements: {data(0)}")

        Console.WriteLine($"Assigning to the function name still works: {Twice(21)}")

        If OperatingSystem.IsWindows() Then
            Dim dictionary As Object = Microsoft.VisualBasic.Interaction.CreateObject("Scripting.Dictionary")
            Console.WriteLine($"Scripting.Dictionary created through COM: {dictionary IsNot Nothing}")
        Else
            Console.WriteLine("Scripting.Dictionary is a COM class: it needs Windows, so this Linux run skips it")
        End If
    End Sub
End Module

Output:

ByVal (the default): Original
ByRef (must be written): Changed
An array passed ByVal still shares its elements: 99
Assigning to the function name still works: 42
Scripting.Dictionary is a COM class: it needs Windows, so this Linux run skips it

Notice what the last line of the output says: CreateObject("Scripting.Dictionary") creates a COM object, and COM exists only on Windows. The COM Interop and Office Automation page covers it. Passing an array ByVal still lets the callee change the elements, because the array is a reference; only the reference is copied. The same is true of Collection objects and every class instance.

Set existed because VB6 had default properties and so x = y was ambiguous between "copy the value" and "make x refer to the object". In Visual Basic .NET a default property needs an argument (an indexer), so the ambiguity is gone and so is Set. Properties were written as three procedures in VB6, Property Get, Property Let (value) and Property Set (object); Visual Basic .NET has one Property block with Get and Set. See Properties and Default Properties.

Objects: classes, Implements and lifetime

VB6 has class modules, but only interface inheritance through Implements: a VB6 class can implement an interface and can use Public, Private and Friend access, but it cannot derive from another class. Visual Basic .NET adds Inherits, MustInherit, Overridable, Overrides, generics and Interface blocks; see Inheritance and Polymorphism and Interfaces. VBScript 5.0 and later has Class ... End Class with properties and methods, but no inheritance and no Implements.

The other big difference is object lifetime. VB6 objects are reference counted: when the last reference goes away, Class_Terminate runs at once, and programs depend on it to close files and connections. Visual Basic .NET objects are garbage collected, so there is no moment at which a finalizer is guaranteed to run. Resources must be released with Using and IDisposable; see Object Lifetime and Disposal. Setting an object variable to Nothing still lets the garbage collector reclaim it, but it does not run any code.

Legacy VB6 class with deterministic cleanup, not built and not run
' Class module: LogFile.cls
Option Explicit
Private m_file As Integer

Private Sub Class_Initialize()
    m_file = FreeFile
    Open "C:\temp\shelf.log" For Append As #m_file
End Sub

Public Sub WriteLine(text As String)
    Print #m_file, text
End Sub

Private Sub Class_Terminate()
    Close #m_file             ' runs the moment the last reference is released
End Sub

The Visual Basic .NET shape is a class that implements IDisposable and a Using block, as in the migration table on Migrating Legacy Visual Basic.

Collections, dictionaries and bounds

VB6 programs used the built-in Collection object (1-based, keys are case-insensitive, items are Variant) and, for dictionaries, the COM class Scripting.Dictionary. Both have .NET relatives. The Microsoft.VisualBasic.Collection class still exists in the runtime library and keeps the VB6 rules; the generic List(Of T) and Dictionary(Of TKey, TValue) are the right choice for new code. The program below shows both, and the array rule that replaces Option Base.

Example 3: Collection, List, array bounds and Dictionary (built and run)
Option Strict On

Module F2Collections
    Sub Main()
        Dim shelf As New Microsoft.VisualBasic.Collection()
        shelf.Add("Dune", "k1")
        shelf.Add("Emma", "k2")
        shelf.Add("Ulysses")
        Console.WriteLine($"Collection is 1-based: shelf(1) = {shelf(1)}, shelf(3) = {shelf(3)}, Count = {shelf.Count}")
        Console.WriteLine($"Keys ignore case: shelf(""K2"") = {shelf("K2")}")
        shelf.Remove(1)
        Console.WriteLine($"After Remove(1): shelf(1) = {shelf(1)}")
        Try
            Console.WriteLine(shelf(0))
        Catch ex As IndexOutOfRangeException
            Console.WriteLine("shelf(0) throws IndexOutOfRangeException")
        End Try

        Dim titles As New List(Of String) From {"Dune", "Emma", "Ulysses"}
        Console.WriteLine($"List(Of String) is 0-based: titles(0) = {titles(0)}")

        Dim ratings(4) As Integer
        Console.WriteLine($"Dim ratings(4) has {ratings.Length} elements, LBound = {LBound(ratings)}, UBound = {UBound(ratings)}")
        ReDim Preserve ratings(6)
        Console.WriteLine($"ReDim Preserve ratings(6) -> {ratings.Length} elements")

        Dim text = "Hello World"
        Console.WriteLine($"Mid(text, 7, 5) = {Mid(text, 7, 5)}   text.Substring(6, 5) = {text.Substring(6, 5)}")

        Dim stock As New Dictionary(Of String, Integer)()
        stock("Dune") = 3
        Console.WriteLine($"Dictionary.Count = {stock.Count}; reading a missing key does not add it, it throws:")
        Try
            Console.WriteLine(stock("Emma"))
        Catch ex As KeyNotFoundException
            Console.WriteLine($"  KeyNotFoundException, Count is still {stock.Count}")
        End Try
    End Sub
End Module

Output:

Collection is 1-based: shelf(1) = Dune, shelf(3) = Ulysses, Count = 3
Keys ignore case: shelf("K2") = Emma
After Remove(1): shelf(1) = Emma
shelf(0) throws IndexOutOfRangeException
List(Of String) is 0-based: titles(0) = Dune
Dim ratings(4) has 5 elements, LBound = 0, UBound = 4
ReDim Preserve ratings(6) -> 7 elements
Mid(text, 7, 5) = World   text.Substring(6, 5) = World
Dictionary.Count = 1; reading a missing key does not add it, it throws:
  KeyNotFoundException, Count is still 1

The rules to carry over from the output are these:

  • Dim ratings(4) declares the upper bound, so it has five elements, and UBound returns 4. The same declaration in VB6 with Option Base 1 would have four. This is a frequent source of off-by-one errors in migrations and in beginner texts.

  • A Collection index of 0 is out of range, while an array and a List(Of T) start at 0. Code that loops from 1 to Count must be rewritten when it moves to a list.

  • Scripting.Dictionary has a surprising behaviour: reading a missing key adds it with an empty value. Dictionary(Of TKey, TValue) throws KeyNotFoundException instead, and offers TryGetValue. Its CompareMode property (binary or text) maps to a StringComparer passed to the constructor.

  • Mid and the other VB runtime string functions are 1-based, and String.Substring is 0-based. Both are correct; mixing them without care is not.

Error handling

All three dialects use On Error: VB6 and VBA support On Error GoTo label and On Error Resume Next and have the Err object, while VBScript has only On Error Resume Next (and On Error GoTo 0), so a script must test Err.Number after each risky call. Visual Basic .NET has structured Try/Catch/Finally and still has On Error for compatibility. The full story, with the Err numbers, is on Unstructured Error Handling: On Error and Structured Exception Handling.

Legacy VBA procedure with a handler label, not built and not run
Public Function SafeDivide(a As Double, b As Double) As Double
    On Error GoTo Fail
    SafeDivide = a / b
    Exit Function
Fail:
    If Err.Number = 11 Then            ' division by zero
        SafeDivide = 0
    Else
        Err.Raise Err.Number, , Err.Description
    End If
End Function

File access and the FileSystemObject

VB6 and VBA read and write files with the statements Open, Print #, Input #, Line Input #, Get, Put and Close, using a file number (FreeFile returns an unused one). VBScript has no such statements: it uses the COM class Scripting.FileSystemObject (FSO) and its TextStream objects. Visual Basic .NET uses System.IO: File, Directory, Path, StreamReader and StreamWriter. The FileOpen, PrintLine and related functions remain in the runtime library for old code. See Files, Directories and Streams.

Legacy VBScript that reads a file through the FileSystemObject, not built and not run
Option Explicit
Dim fso, stream, line
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists("C:\temp\catalog.txt") Then
    Set stream = fso.OpenTextFile("C:\temp\catalog.txt", 1)    ' 1 = ForReading
    Do While Not stream.AtEndOfStream
        line = stream.ReadLine
        WScript.Echo line
    Loop
    stream.Close
End If

The tested System.IO equivalents are in the migration table on Migrating Legacy Visual Basic.

VBA in detail

VBA is the dialect that people still write every day. It is the language behind Excel, Word, Access, Outlook and PowerPoint macros, and other products embed it too. What makes it different from Visual Basic .NET is not only the syntax but the host:

  • The code is stored inside the document (a macro-enabled workbook such as .xlsm, a document such as .docm) and runs in the host’s process.

  • The program’s world is the host’s object model: Application, Workbook, Worksheet, Range in Excel, Document and Selection in Word. The language reference documents the language; the host’s reference documents the objects.

  • The editor is the Visual Basic Editor inside the host. There is no project file, no compiler output and no package manager.

  • Entry points are macros (Public Sub with no parameters) and event handlers such as Workbook_Open.

  • Option Explicit is a per-module setting, and without it undeclared variables are Variant.

  • 64-bit hosts need Declare PtrSafe and LongPtr for calls into Windows DLLs.

Legacy VBA macro that fills an Excel range, not built and not run
Option Explicit

Public Sub ListTitles()
    Dim titles As Variant
    Dim i As Long
    titles = Array("Dune", "Emma", "Ulysses")
    For i = LBound(titles) To UBound(titles)
        ThisWorkbook.Worksheets("Shelf").Cells(i + 2, 1).Value = titles(i)
    Next i
End Sub

The corresponding "Visual Basic .NET drives Excel from outside" pattern, with its COM caveats, is on COM Interop and Office Automation; the Open XML approach that needs no Excel at all is on file formats.

VBScript and Windows Script Host

VBScript is an interpreted language in .vbs files. Windows Script Host runs it with cscript.exe (console output) or wscript.exe (message boxes). Its distinguishing rules:

  • There are no types: every variable is a Variant, and Dim x As Integer is a syntax error. Subtypes (Integer, String, Date) are inferred from the assigned value.

  • There is no Enum, no Declare, no file I/O statements and no GoTo in error handling.

  • Objects come from COM through CreateObject and GetObject, and are assigned with Set.

  • WScript is the host object: WScript.Echo, WScript.Arguments, WScript.StdIn, WScript.StdOut, WScript.Quit.

  • Option Explicit is available, but optional; without it a typo creates a new variable.

  • Classes (Class ... End Class), Select Case, With, regular expressions (the RegExp object) and Eval/Execute exist.

Legacy VBScript, run with cscript //nologo shelf.vbs; not built and not run
Option Explicit

Class Book
    Public Title
    Public Copies
    Public Function Describe()
        Describe = Title & " (" & Copies & ")"
    End Function
End Class

Dim dict, key, b
Set dict = CreateObject("Scripting.Dictionary")
Set b = New Book
b.Title = "Dune"
b.Copies = 3
dict.Add "dune", b
For Each key In dict.Keys
    WScript.StdOut.WriteLine dict(key).Describe()
Next

A PowerShell script is the usual replacement for a VBScript administration script; a small .NET console program is the replacement when the script is really an application. Both are shown in the migration table on Migrating Legacy Visual Basic. The Scripting.Dictionary of the listing is the COM class discussed in the collections section above.

Classic ASP

Classic ASP mixes HTML with VBScript between <% and %> delimiters. The server runs the script on each request and sends the HTML that it writes. The built-in objects are the HTTP model in miniature: Request (query string, form, cookies, server variables), Response (output, redirects, cookies), Session (per-user state), Application (state shared by all users), Server (helpers such as HTMLEncode, MapPath and CreateObject). A file named global.asa holds the Application_OnStart and Session_OnStart handlers.

Legacy Classic ASP page, not built and not run
<%@ Language="VBScript" %>
<% Option Explicit %>
<html>
<body>
<%
Dim title
title = Request.QueryString("title")
If Len(title) > 0 Then
    Session("lastTitle") = title
End If
%>
<p>Last search: <%= Server.HTMLEncode(Session("lastTitle")) %></p>
</body>
</html>

The Server.HTMLEncode call is the point to keep: output written with Response.Write or <%= %> is not encoded for you, so anything that comes from a user must be encoded by hand, or the page is open to cross-site scripting. The equivalent in ASP.NET Web Forms is <%: %>, which encodes; see ASP.NET Web Forms security. The state model (session and application state) carries over to Web Forms in State Management and Caching.

Source: ASP overview

Alternatives you may hear about

Some third-party projects aim at VB6 compatibility or at running Visual Basic on other runtimes. They are not part of Microsoft’s product line, this section does not evaluate or endorse them, and none of them changes the status of the official products. The one worth naming for historical reasons is Mono’s vbnc compiler: it is abandoned. The supported compiler is the Roslyn Visual Basic compiler in the .NET SDK (dotnet build), which runs on Windows, Linux and macOS. For the part of a VB6 program that has to stay VB6, the supported path is the runtime that ships with Windows, not an unofficial installer.

Where the books are outdated

The two books that this section was checked against describe the family as it was in the mid-2000s and the 2010s. The current position, with the evidence on this page:

Book habit Today

VBScript is available in "all modern versions of Windows" and runs in browsers

VBScript is deprecated, is a feature on demand that is being phased out, and Internet Explorer is retired

Installing Visual Basic 6 from an archive in a virtual machine as a way to learn the family

The IDE is unsupported; this section never recommends installing it

Mono as a Visual Basic compiler

vbnc is abandoned; use dotnet build

Visual Basic .NET is "backward compatible" and makes the move from VB6 easy

It was never source compatible; see the removed-syntax table above and Migrating Legacy Visual Basic

Several widespread misconceptions about the family, including some in beginner texts, are corrected here by stating the right form:

  • VB6 has no implementation inheritance (Implements only), but it does have Public, Private and Friend access modifiers.

  • VBScript does have classes (since version 5.0), but only one data type, Variant; VBA does not force a typed Dim; and VB6 already had Boolean, Date and Byte. The three dialects are therefore not distinguished by the type lists that are often printed.

  • Type is the VB6 and VBA record; Visual Basic .NET uses Structure.

  • What a beginner text sometimes calls "implicit declaration" in Visual Basic .NET is type inference (Option Infer On) for a declared local with an initializer. It is not the same as using an undeclared variable, which Option Explicit On forbids.

  • A variable cannot be "global, declared outside of any module": in VB6 a global belongs to a standard module, and in Visual Basic .NET it is a Shared member or a module member.

  • Initializers in a Dim statement (Dim n As Integer = 5) are Visual Basic .NET syntax; VB6 and VBA cannot do it.

  • Strings are joined with &, not `; ` works in VB6 only by accident of Variant conversion rules and is rejected or surprising under Option Strict On.

  • Mid is 1-based, so Mid("Hello World", 7, 5) gives "World"; Dim a(10) As Integer has eleven elements in every dialect that counts by upper bound; the column dimension of a two-dimensional array in UBound(a, 2) is dimension 2, and UBound returns a bound, not a length.

  • Class MyClass does not compile in Visual Basic .NET, because MyClass is a keyword.

Common mistakes

Mistake Correct form

Assuming Integer is 16 bits when porting

It is 32 bits; the 16-bit type is Short. Check every Integer and Long in old code, and every file format or Declare that depends on the width.

Keeping Variant as Object everywhere

Choose a real type, or Object only for genuinely mixed data; use Option Strict On so the compiler checks each conversion.

Porting Dim a, b As Integer as if a were a Variant

Both are Integer in Visual Basic .NET; declare each variable on its own line.

Leaving parameters without ByVal or ByRef in migrated code

The default changed from ByRef to ByVal; decide for each parameter, and write ByRef where the callee must change the argument.

Relying on Class_Terminate to close files

Use Using with IDisposable; finalizers do not run at a predictable moment.

Assuming Scripting.Dictionary exists everywhere

It is a COM class and needs Windows; use Dictionary(Of TKey, TValue) in new code.

Starting new VBScript or Classic ASP work

Both are legacy; write PowerShell or .NET for scripts and ASP.NET (Core, with a C# host, or Web Forms on .NET Framework) for pages.

Treating Currency as Double

Use Decimal, and round explicitly.