Compiling a VB program for command line use only

I saw someone compile a VB program so that it is used at a windows command prompt only, and the output such as parameter lists and messages are output to the command prompt.  For example utilities such as ping and tracert.  These are executed from a command line only.  I know this can be done, but I'm not sure how to do it.  Any help would be appreciated.  Thanks.
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

If you run a VB program with parameters the parameters will be stored in the Command$ variable inside the program. Just check the value of Command$ to get the parameters used when your program was run.
Vbmaster is correct, any parameters passed to the vb program on the command line are stored in the Command$ variable inside the program... However, you can not print information back to the console window that the VB program was launched from...

This is a direct quote from microsoft:

   "If a Visual Basic application is started from a console application, the operating system automatically detaches it from the console, preventing the Visual Basic application from interacting with it."

This means that once the vb program is launched it can not write back to the console window, however, you can attach a new console window to your application.

See the following microsoft KB article:

HOWTO: Attach a Console Window to Your Visual Basic Program 

"HOWTO: Attach a Console Window to Your Visual Basic Program"
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

Your best bet is to develop the application in a non "Visual" environment such as C or QBasic.
You can easily make a "formless" VB application. Create a new project. Add a module (.BAS). Change the project propertys so that the startup object is SUB MAIN(). Delete the default form from the project. Place all your code in the SUB MAIN routine. Compile and run the .EXE from the DOS prompt.


You can create a formless application, but you still can't write information back to the original console window that launched the application...

I have done this using a piece of code from the site. You need a program that modifies the EXE to tell it that is no longer a Windows Program. I can send you the program if you like along with an example and the artical from the VBPJ. It allows both input and output to the console.

Please post your code, or a url to it.  If someone else buys this question they get nothing for their points...

Here is the module to create the Console App. Create a Vb project add this module and start from Sub Main. Run This from the command line with your EXE as the parameter. See below for a CGI4VB module check out the Main Section for Sending and Recieving Data to and from stdout and stdin. ( CGI4VB main will not run as it is as I have not included all the modules and forms but it should give you enough information to make it work ). Sorry for the long post.

' ------------------------------------------------------
' MakeConsole.BAS -- Copyright (c) Slightly Tilted Software
' By: L.J. Johnson       Date: 11-30-1997
' Comments:    Contains MAIN(), plus the function
'              which take a standard VB 5.0 EXE (  or VB 6.0 ! )
'              and change it to a 32-bit console app
' ------------------------------------------------------
Option Explicit
Option Base 1
DefLng A-Z

Private Const GENERIC_READ                As Long = &H80000000
Private Const OPEN_EXISTING               As Long = 3&
Private Const FILE_ATTRIBUTE_NORMAL       As Long = &H80&

Private Const SCS_32BIT_BINARY = 0&
Private Const SCS_DOS_BINARY = 1&
Private Const SCS_WOW_BINARY = 2&
Private Const SCS_PIF_BINARY = 3&
Private Const SCS_POSIX_BINARY = 4&
Private Const SCS_OS216_BINARY = 5&

Private Const constMsgTitle = "Make Console App"

' ---------------------------------------------
' Windows API calls
' ---------------------------------------------
Public Declare Sub CopyMem _
   Lib "kernel32" Alias "RtlMoveMemory" _
   (dst As Any, src As Any, ByVal Size As Long)
Private Declare Function CloseHandle _
   Lib "kernel32" _
   (ByVal hObject As Long) As Long
Private Declare Function CreateFile _
   Lib "kernel32" Alias "CreateFileA" _
   (ByVal lpFileName As String, _
    ByVal dwDesiredAccess As Long, _
    ByVal dwShareMode As Long, _
    ByVal lpSecurityAttributes As Long, _
    ByVal dwCreationDisposition As Long, _
    ByVal dwFlagsAndAttributes As Long, _
    ByVal hTemplateFile As Long) As Long

Public Sub Main()
   Dim strCmd              As String
   Dim strMsg              As String
   Dim strRtn              As String
   strCmd = Command$
   If Trim$(strCmd) = "" Then
      strMsg = "You must enter the name of a VB 5.0 standard executable file."
      MsgBox strMsg, vbExclamation, constMsgTitle
      If InStr(1, strCmd, ".", vbTextCompare) = 0 Then
         strCmd = strCmd & ".EXE"
      End If
      If Exists(strCmd) = True Then
         strRtn = SetConsoleApp(strCmd)
         MsgBox strRtn, vbInformation, constMsgTitle
         strMsg = "The file, " & Trim$(strCmd) & ", does not exist."
         MsgBox strMsg, vbCritical, constMsgTitle
      End If
   End If
End Sub

Private Function SetConsoleApp(xstrFileName As String) As String
   Dim lngFileNum          As Long
   Dim ststrMZ_Header      As String * 512
   Dim strMagic            As String * 2
   Dim strMagicPE          As String * 2
   Dim lngNewPE_Offset     As Long
   Dim lngData             As Long
   Dim strTmp              As String
   Const PE_FLAG_OFFSET    As Long = 93&
   Const DOS_FILE_OFFSET   As Long = 25&
   ' ---------------------------------------------
   ' See if file actually exists
   ' ---------------------------------------------
   strTmp = Trim$(Dir$(xstrFileName))
   If Len(strTmp) = 0 Then
      SetConsoleApp = "Failed -- The file, " & xstrFileName & ", does not exist!"
      GoTo ExitCheck
   End If
   ' ---------------------------------------------
   ' Get a free file handle
   ' ---------------------------------------------
   On Error Resume Next
   lngFileNum = FreeFile
   Open xstrFileName For Binary Access Read Write Shared As lngFileNum
   ' ---------------------------------------------
   ' Get the first 512 characters from from file
   ' ---------------------------------------------
   Seek #lngFileNum, 1
   Get lngFileNum, , ststrMZ_Header
   ' ---------------------------------------------
   ' Look for the "magic header" values "MZ"
   ' If it doesn't exist, then it's not an EXE file
   ' ---------------------------------------------
   If Mid$(ststrMZ_Header, 1, 2) <> "MZ" Then
      SetConsoleApp = "Failed -- File is not an executable file."
      GoTo ExitCheck
   End If
   ' ---------------------------------------------
   ' Check to see if it's a MS-DOS executable
   ' ---------------------------------------------
   CopyMem lngData, ByVal Mid$(ststrMZ_Header, DOS_FILE_OFFSET, 2), 2
   If lngData < 64 Then
      SetConsoleApp = "Failed -- File is 16-bit MSDOS EXE file."
      GoTo ExitCheck
   End If
   ' ---------------------------------------------
   ' Get the offset for the new .EXE header
   ' ---------------------------------------------
   CopyMem lngNewPE_Offset, ByVal Mid$(ststrMZ_Header, 61, 4), 4
   ' ---------------------------------------------
   ' Get the "magic" header (NE, LE, PE)
   ' ---------------------------------------------
   strMagic = Mid$(ststrMZ_Header, lngNewPE_Offset + 1, 2)
   strMagicPE = Mid$(ststrMZ_Header, lngNewPE_Offset + 3, 2)
   Select Case strMagic
      ' ---------------------------------------------
      ' Check for NT format
      ' ---------------------------------------------
      Case "PE"
         If strMagicPE <> vbNullChar & vbNullChar Then
            SetConsoleApp = "Failed -- File is unknown 32-bit NT executable file."
            GoTo ExitCheck
         End If
         ' ---------------------------------------------
         ' Get the subsystem flags to identify NT
         '     character-mode
         ' ---------------------------------------------
         lngData = Asc(Mid$(ststrMZ_Header, lngNewPE_Offset + PE_FLAG_OFFSET, 1))
         If lngData <> 3 Then
            On Error Resume Next
            Err.Number = 0
            Seek #lngFileNum, lngNewPE_Offset + PE_FLAG_OFFSET
            Put lngFileNum, , 3
            If Err.Number = 0 Then
               SetConsoleApp = "Success -- Converted file to console app."
               SetConsoleApp = "Failed -- Error converting to console app: " & Err.Description
            End If
            SetConsoleApp = "Failed -- Already a console app"
         End If
      Case Else
         SetConsoleApp = "Failed -- Not correct file type."
   End Select

   ' ---------------------------------------------
   ' Close the file
   ' ---------------------------------------------
   Close lngFileNum
   On Error GoTo 0
End Function

Public Function Exists(ByVal xstrFullName As String) As Boolean
On Error Resume Next       ' Don't accept errors here
   Const constProcName     As String = "Exists"
   Dim lngFileHwnd         As Long
   Dim lngRtn              As Long

   ' ------------------------------------------
   ' Open the file only if it already exists
   ' ------------------------------------------
   lngFileHwnd = CreateFile(xstrFullName, _
                            GENERIC_READ, 0&, _
                            0&, OPEN_EXISTING, _
                            FILE_ATTRIBUTE_NORMAL, 0&)
   ' ------------------------------------------
   ' If get these specific errors, then
   '     file doesn't exist
   ' ------------------------------------------
   If lngFileHwnd = 0 Or lngFileHwnd = -1 Then
      Exists = False
      ' Success -- Must close the handle
      lngRtn = CloseHandle(lngFileHwnd)
      Exists = True
   End If

On Error GoTo 0
End Function


Option Explicit
' CGI routines used with VB 4.0 (32bit) using STDIN / STDOUT.
' Version: 1.4 (December 1996)
' Author:  Kevin O'Brien <>
'                        <>
' Revisions:
' Version 1.4 December 1996
'   call WriteFile as a Function rather than as a Sub
'   generate error when url-decoding bad data
'   remove unused variables
' Version 1.3 October 1996
'   add all standard environment variables per CGI/1.1 specs
'   pass sEncoded to urlDecode ByVal to preserve encoded data
'   handle a query string entered with a form, instead of either/or
'   create separate function for storing name/value pairs
' Version 1.2 October 1996
'   replace HTTP/1.0 headers with Status headers
'   add sendHeader and sendFooter procedures
'   decode form name as well as form value
'   create separate function for url-decoding
' Version 1.1 September 1996
'   add HTTP headers
'   add SendB procedure to send data without a vbCrLf
Declare Function GetStdHandle Lib "kernel32" _
    (ByVal nStdHandle As Long) As Long
Declare Function ReadFile Lib "kernel32" _
    (ByVal hFile As Long, _
    lpBuffer As Any, _
    ByVal nNumberOfBytesToRead As Long, _
    lpNumberOfBytesRead As Long, _
    lpOverlapped As Any) As Long
Declare Function WriteFile Lib "kernel32" _
    (ByVal hFile As Long, _
    ByVal lpBuffer As String, _
    ByVal nNumberOfBytesToWrite As Long, _
    lpNumberOfBytesWritten As Long, _
    lpOverlapped As Any) As Long
Declare Function SetFilePointer Lib "kernel32" _
   (ByVal hFile As Long, _
   ByVal lDistanceToMove As Long, _
   lpDistanceToMoveHigh As Long, _
   ByVal dwMoveMethod As Long) As Long
Declare Function SetEndOfFile Lib "kernel32" _
   (ByVal hFile As Long) As Long

Public Const STD_INPUT_HANDLE = -10&
Public Const STD_OUTPUT_HANDLE = -11&
Public Const FILE_BEGIN = 0&

' environment variables
Public CGI_Accept            As String
Public CGI_AuthType          As String
Public CGI_ContentLength     As String
Public CGI_ContentType       As String
Public CGI_GatewayInterface  As String
Public CGI_PathInfo          As String
Public CGI_PathTranslated    As String
Public CGI_QueryString       As String
Public CGI_Referer           As String
Public CGI_RemoteAddr        As String
Public CGI_RemoteHost        As String
Public CGI_RemoteIdent       As String
Public CGI_RemoteUser        As String
Public CGI_RequestMethod     As String
Public CGI_ScriptName        As String
Public CGI_ServerSoftware    As String
Public CGI_ServerName        As String
Public CGI_ServerPort        As String
Public CGI_ServerProtocol    As String
Public CGI_UserAgent         As String

Public lContentLength As Long   ' CGI_ContentLength converted to Long
Public hStdIn         As Long   ' handle of Standard Input
Public hStdOut        As Long   ' handle of Standard Output
Public sErrorDesc     As String ' constructed error message
Public sEmail         As String ' webmaster's/your email address
Public sFormData      As String ' url-encoded data sent by the server

Type pair
  Name As String
  Value As String
End Type

Public tPair() As pair           ' array of name=value pairs

Sub Main()
Dim Functions()
Dim i As Integer
Dim j As Integer
Dim InputRecord As String
On Error GoTo ErrorRoutine
InitCgi          ' Load environment vars and perform other initialization
GetFormData      ' Read data sent by the server
' Cgi_Main         ' Process and return data to server

Functions = GetCommandLine(99, i)

Select Case UCase(Functions(1))
Case "", "/?", "?", " "
    Send ("Format for Commands :- ")
    Send ("==================================================================")
    Send ("CLEAN Infile Outfile     { Converts from Dos to Unix file Format }")
    Send (" ")
    Send ("TO-MSDOS Infile Outfile  { Converts from Unix to Dos file Format }")
    Send (" ")
    Send ("GUI                      { Open Windows Interface for Setup etc. }")
    Send (" ")
    Send ("To7                      { Cleans and copies file to GCOS7       }")
    Send (" ")
    Send ("From7                    { Copies file from GCOS7                }")
    Send (" ")
    Send ("CopySO                   { Copies Standing Orders to }")
    Send (" ")
    Send ("==================================================================")
Case "GUI"
    Exit Sub
Case "CLEAN"
    Send ("Calling Function CLEAN")
    Call Clean(Functions(2), Functions(3))
    Send ("Calling Function TO-MSDOS")
    Call ToMsDos(Functions(2), Functions(3))
Case "TO7", "T0-7"
    Send ("Calling Function To7")
    frmDPS7.Infile = Functions(2)
    frmDPS7.OutFile = Functions(3)
    Call frmDPS7.cmdUpload_Click
Case "FROM7", "FROM-7"
    Send ("Calling Function From7")
    frmDPS7.Infile = Functions(2)
    frmDPS7.OutFile = Functions(3)
    Call frmDPS7.cmdDownload_Click
    Send ("Calling Function CopySO")
    frmDPS7.Infile = "BDC.RENTS.STANDORD"
    frmDPS7.OutFile = "c:\ohms\bull\"
    Send ("Calling download click")
    Call frmDPS7.cmdDownload_Click
    Send ("Opening file 1")
    Open "c:\ohms\bull\" For Input As #1
    Send ("Opening file 2")
    Open "c:\ohms\bull\rents\data\" For Output As #2
    Send ("Opening file 3")
    Open "c:\ohms\bull\rents\data\" For Output As #3
    Send ("Opening file 4")
    Open "c:\ohms\bull\rents\data\" For Output As #4
    Send ("Opening file 5")
    Open "c:\ohms\bull\rents\data\Standord.rep" For Output As #5
    Send ("Transfering STANDORD File")
    Do While Not EOF(1)
        Line Input #1, InputRecord
        Call OhmsStandord(InputRecord)
    Call StandordHeader
    Call RejectsHeader
    Send ("Transfer Complete")
End Select
   End           ' end program

   sErrorDesc = Err.Description & " Error Number = " & Str$(Err.Number)
   ExitProcess (1)
   Resume EndPgm
End Sub

Sub ErrorHandler()
Dim rc As Long

On Error Resume Next
' use SetFilePointer API to reset stdOut to BOF
' and SetEndOfFile to reset EOF

rc = SetFilePointer(hStdOut, 0&, 0&, FILE_BEGIN)

SendHeader "Internal Error"
Send "<H1>Error in " & CGI_ScriptName & "</H1>"
Send "The following internal error has occurred:"
Send "<PRE>" & sErrorDesc & "</PRE>"
Send "<I>Please</I> note what you were doing when this problem occurred, "
Send "so we can identify and correct it. Write down the Web page you were "
Send "using, any data you may have entered into a form or search box, "
Send "and anything else that may help us duplicate the problem."
Send "Then contact the administrator of this service: "
Send "<A HREF=""mailto:" & sEmail & """>"
Send "<ADDRESS>&lt;" & sEmail & "&gt;</ADDRESS></A>"
rc = SetEndOfFile(hStdOut)

End Sub

Sub InitCgi()

hStdIn = GetStdHandle(STD_INPUT_HANDLE)
hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)

sEmail = ""

' Get the environment variables
' Environment variables will vary depending on the server.
' Replace any variables below with the ones used by your server.
CGI_Accept = Environ("HTTP_ACCEPT")
CGI_AuthType = Environ("AUTH_TYPE")
CGI_ContentLength = Environ("CONTENT_LENGTH")
CGI_ContentType = Environ("CONTENT_TYPE")
CGI_GatewayInterface = Environ("GATEWAY_INTERFACE")
CGI_PathInfo = Environ("PATH_INFO")
CGI_PathTranslated = Environ("PATH_TRANSLATED")
CGI_QueryString = Environ("QUERY_STRING")
CGI_Referer = Environ("HTTP_REFERER")
CGI_RemoteAddr = Environ("REMOTE_ADDR")
CGI_RemoteHost = Environ("REMOTE_HOST")
CGI_RemoteIdent = Environ("REMOTE_IDENT")
CGI_RemoteUser = Environ("REMOTE_USER")
CGI_RequestMethod = Environ("REQUEST_METHOD")
CGI_ScriptName = Environ("SCRIPT_NAME")
CGI_ServerSoftware = Environ("SERVER_SOFTWARE")
CGI_ServerName = Environ("SERVER_NAME")
CGI_ServerPort = Environ("SERVER_PORT")
CGI_ServerProtocol = Environ("SERVER_PROTOCOL")
CGI_UserAgent = Environ("HTTP_USER_AGENT")

lContentLength = Val(CGI_ContentLength)   'convert to long
ReDim tPair(0)                            'initialize name/value array

End Sub

Sub GetFormData()
' Get the CGI data from STDIN and/or from QueryString
' Store name/value pairs
Dim sBuff      As String    ' buffer to receive POST method data
Dim lBytesRead As Long      ' actual bytes read by ReadFile()
Dim rc         As Long      ' return code

' Method POST - get CGI data from STDIN
' Method GET  - get CGI data from QueryString environment variable
If CGI_RequestMethod = "POST" Then
   sBuff = String(lContentLength, Chr$(0))
   rc = ReadFile(hStdIn, ByVal sBuff, lContentLength, lBytesRead, ByVal 0&)
   sFormData = Left$(sBuff, lBytesRead)
   ' Make sure posted data is url-encoded
   ' Multipart content types, for example, are not necessarily encoded.
   If InStr(1, CGI_ContentType, "www-form-urlencoded", 1) Then
      StorePairs sFormData
   End If
End If
StorePairs CGI_QueryString
End Sub

Sub StorePairs(sData As String)
' Parse and decode form data and/or query string
' Data is received from server as "name=value&name=value&"
' Names and values are URL-encoded
' Store name/value pairs in array tPair(), and decode them
' Note: if an element in the query string does not contain an "=",
'       then it will not be stored.
' /cgi-bin/pgm.exe?parm=1   "1" gets stored and can be
'                               retrieved with getCgiValue("parm")
' /cgi-bin/pgm.exe?1        "1" does not get stored, but can be
'                               retrieved with urlDecode(CGI_QueryString)
Dim pointer    As Long      ' sData position pointer
Dim n          As Long      ' name/value pair counter
Dim delim1     As Long      ' position of "="
Dim delim2     As Long      ' position of "&"
Dim lastPair   As Long      ' size of tPair() array
Dim lPairs     As Long      ' number of name=value pairs in sData

lastPair = UBound(tPair)    ' current size of tPair()
pointer = 1
  delim1 = InStr(pointer, sData, "=")
  If delim1 = 0 Then Exit Do
  pointer = delim1 + 1
  lPairs = lPairs + 1

If lPairs = 0 Then Exit Sub  'nothing to add

' redim tPair() based on the number of pairs found in sData
ReDim Preserve tPair(lastPair + lPairs) As pair

' assign values to tPair().name and tPair().value
pointer = 1
For n = (lastPair + 1) To UBound(tPair)
   delim1 = InStr(pointer, sData, "=") ' find next equal sign
   If delim1 = 0 Then Exit For         ' parse complete

   tPair(n).Name = UrlDecode(Mid$(sData, pointer, delim1 - pointer))
   delim2 = InStr(delim1, sData, "&")

   ' if no trailing ampersand, we are at the end of data
   If delim2 = 0 Then delim2 = Len(sData) + 1
   ' value is between the "=" and the "&"
   tPair(n).Value = UrlDecode(Mid$(sData, delim1 + 1, delim2 - delim1 - 1))
   pointer = delim2 + 1
Next n
End Sub

Public Function UrlDecode(ByVal sEncoded As String) As String
' Accept url-encoded string
' Return decoded string

Dim pointer    As Long      ' sEncoded position pointer
Dim pos        As Long      ' position of InStr target

If sEncoded = "" Then Exit Function

' convert "+" to space
pointer = 1
   pos = InStr(pointer, sEncoded, "+")
   If pos = 0 Then Exit Do
   Mid$(sEncoded, pos, 1) = " "
   pointer = pos + 1
' convert "%xx" to character
pointer = 1

On Error GoTo errorUrlDecode

   pos = InStr(pointer, sEncoded, "%")
   If pos = 0 Then Exit Do
   Mid$(sEncoded, pos, 1) = Chr$("&H" & (Mid$(sEncoded, pos + 1, 2)))
   sEncoded = Left$(sEncoded, pos) _
             & Mid$(sEncoded, pos + 3)
   pointer = pos + 1
On Error GoTo 0     'reset error handling
UrlDecode = sEncoded
Exit Function

' If this function was mistakenly called with the following:
'    UrlDecode("100% natural")
' a type mismatch error would be raised when trying to convert
' the 2 characters after "%" from hex to character.
' Instead, a more descriptive error message will be generated.
If Err.Number = 13 Then      'Type Mismatch error
   Err.Raise 65001, , "Invalid data passed to UrlDecode() function."
   Err.Raise Err.Number
End If
Resume Next
End Function

Function GetCgiValue(cgiName As String) As String
' Accept the name of a pair
' Return the value matching the name
' tPair(0) is always empty.
' An empty string will be returned
'    if cgiName is not defined in the form (programmer error)
'    or, a select type form item was used, but no item was selected.
' Multiple values, separated by a semi-colon, will be returned
'     if the form item uses the "multiple" option
'     and, more than one selection was chosen.
'     The calling procedure must parse this string as needed.
Dim n As Integer
For n = 1 To UBound(tPair)
    If UCase$(cgiName) = UCase$(tPair(n).Name) Then
       If GetCgiValue = "" Then
          GetCgiValue = tPair(n).Value
       Else             ' allow for multiple selections
          GetCgiValue = GetCgiValue & ";" & tPair(n).Value
       End If
    End If
Next n
End Function

Sub SendHeader(sTitle As String)
Send "Status: 200 OK"
Send "Content-type: text/html" & vbCrLf
Send "<HTML><HEAD><TITLE>" & sTitle & "</TITLE></HEAD>"
End Sub

Sub SendFooter()
' standardized footers can be added
Send "</BODY></HTML>"
End Sub

Sub Send(s As String)
' Send output to STDOUT
Dim lBytesWritten As Long

s = s & vbCrLf
WriteFile hStdOut, s, Len(s), lBytesWritten, ByVal 0&
End Sub

Sub SendB(s As String)
' Send output to STDOUT without vbCrLf.
' Use when sending binary data. For example,
' images sent with "Content-type image/jpeg".
Dim lBytesWritten As Long

WriteFile hStdOut, s, Len(s), lBytesWritten, ByVal 0&
End Sub


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
I stand corrected!  STEWA YOU RULE!

gencross, Take stewa's comment as the answer!  It deserves an A+!

gencrossAuthor Commented:
Excellent.  I will need to add a little to it, but the functionality is there.
Steve, you are a PUTER God!
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic Classic

From novice to tech pro — start learning today.