Problem solve Get help with specific problems with your technologies, process and projects.

Getting A Hold on Error Messages

Getting a hold on error messages.

Getting A Hold on Error Messages
Laura Moloney

Reader Laura Moloney tells how she figured out the answer to a particularly vexing problem, and gives some kudos to and the rest of the VB community in the process.

I started working with Visual Basic about 6 months ago when I had to pick up an application that was written by someone else and was not meeting the functional requirements of the user. So I had an opportunity to learn as I went, relying heavily on the Internet and VB sites like SearchVB that offered assistance where needed (many of my favorites were recommended by SearchVB). My next foray was to build a back-end job process logging application where a user would initiate several back-end tasks via a VB application, for example run awk to format data, use Oracle's SQL Loader to load the data to temporary Oracle tables, and then run Oracle PL/SQL code to validate and post the data to the production databases.

Throughout this process I needed to ensure that start/stop times and error messages from each step were captured in a central database table. This required me to build a console application that would manage each step of the process. Again this is where the Internet and the VB community as a whole played a significant part in helping me build the application.

One area where I could not find an answer from the VB community was in capturing the system responses to commands run in the console window. For example if I were executing an AWK process and the process could not find a file, the system would return a message 'file not found' to the console window. I needed to be able to capture that message. After many failed attempts, I was able to find the resolution to the problem.

The procedure below opens a console window via the EXECCMD function, runs an AWK process, waits for its completion, and then reads the console screen buffer twice - the first time to get the console buffer contents as a whole, and the next time to get a line at a time. I hope others find this useful!


Public Sub Form_Load()

On Error GoTo errHandler
Dim commandText As String
Dim dTaskID As Double
Dim fsuccess As Integer
Dim ConsoleBoundary As SMALL_RECT
Dim XPos As Integer, YPos As Integer

commandText = "x:packagesawkawk -f c:visual~1consol~1post.awk"
If AllocConsole() Then
    hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE)
    If hConsoleOut = 0 Then Err.Raise Number:=502, 
Description:="Couldn't allocate STDOUT"
    Err.Raise Number:=502, Description:="Couldn't allocate console"
End If
dTaskID = ExecCmd(commandText) 'start AWK processing

fsuccess = GetConsoleScreenBufferInfo(hConsoleOut, csScreenBuffer) 
'non-zero result is a success
ConsoleBoundary = csScreenBuffer.srWindow
Dim readLength As Integer
'to read console window messages as one blob of text
Dim fullMessage As String * 400
Dim fsuccess2 As Integer
readLength = 400
XPos = 0
YPos = 0
fsuccess2 = ReadConsoleOutputCharacter(hConsoleOut, fullMessage, 
readLength, XPos + YPos, vbNull) 'non-zero result is a success
MsgBox fullMessage

'To read console window messages one line at a time using the
'ypos axis I was unable to figure out how to pass in the x and y
'coordinates (x,y). All formats I used did not work. Instead I found 
'the screen buffer length is 64K, so using the ypos as a variable I
'basically multiply the row number I want with 64k to get 'the starting
'point of the row. This works because the screen buffer 'can be 
accessed as
'one contiguous string.

Dim lineMessage As String * 80
readLength = 80
XPos = 0
YPos = 0
For YPos = 0 To ConsoleBoundary.Bottom
    def = ReadConsoleOutputCharacter(hConsoleOut, lineMessage, 
readLength, YPos * 65536, vbNull)
    If Trim(lineMessage) <> "" Then MsgBox Trim(lineMessage)
Next YPos

CloseHandle hConsoleOut
Exit Sub

MsgBox Err.Number & " " & Err.Source & " " & Err.Description
CloseHandle hConsoleOut
End Sub

'Functions that support the application opening the command
'window where the subsequent execcmd will execute from,
'as well as close and free the command window.
'Also functions that support reading the console screen buffer

Public hConsoleIn As Long
Public hConsoleOut As Long

Public Const INFINITE = -1&
Public Const STD_OUTPUT_HANDLE = -11&
Public Const STD_INPUT_HANDLE = -10&

      cb As Long
      lpReserved As String
      lpDesktop As String
      lpTitle As String
      dwX As Long
      dwY As Long
      dwXSize As Long
      dwYSize As Long
      dwXCountChars As Long
      dwYCountChars As Long
      dwFillAttribute As Long
      dwFlags As Long
      wShowWindow As Integer
      cbReserved2 As Integer
      lpReserved2 As Long
      hStdInput As Long
      hStdOutput As Long
      hStdError As Long
End Type

    hProcess As Long
    hThread As Long
    dwProcessID As Long
    dwThreadID As Long
End Type

Public Type COORD
        x As Integer
        y As Integer
End Type

Public Type SMALL_RECT
        Left As Integer
        Top As Integer
        Right As Integer
        Bottom As Integer
End Type

        dwSize As COORD
        dwCursorPosition As COORD
        wAttributes As Integer
        srWindow As SMALL_RECT
        dwMaximumWindowSize As COORD
End Type

Declare Function AllocConsole Lib "kernel32" () As Long

Declare Function FreeConsole Lib "kernel32" () As Long

Declare Function GetStdHandle Lib "kernel32" (ByVal _
           nStdHandle As Long) As Long

Declare Function GetLastError Lib "kernel32" ()

Declare Function GetConsoleScreenBufferInfo Lib "kernel32" _
(ByVal hConsoleOutput As Long, _
lpConsoleScreenBufferInfo As CONSOLE_SCREEN_BUFFER_INFO) As Long

Declare Function SetConsoleCursorPosition Lib "kernel32" _
(ByVal hConsoleOutput As Long, ByVal CursorPosition As Long) As Long

Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
    hHandle As Long, ByVal dwMilliseconds As Long) As Long

Declare Function CreateProcessA Lib "kernel32" (ByVal _
    lpApplicationName As String, ByVal lpCommandLine As String, ByVal _
    lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
    ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
    ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As String, _
    lpStartupInfo As STARTUPINFO, lpProcessInformation As _

Declare Function CloseHandle Lib "kernel32" _
    (ByVal hObject As Long) As Long

Declare Function GetExitCodeProcess Lib "kernel32" _
    (ByVal hProcess As Long, lpExitCode As Long) As Long

Declare Function ReadConsoleOutputCharacter Lib "kernel32" Alias 
"ReadConsoleOutputCharacterA" _
    (ByVal hConsoleOutput As Long, ByVal lpCharacter As String, ByVal _
    nLength As Long, ByVal dwReadCoord As Long, _
    lpNumberOfCharsRead As Long) As Long

Public Function ExecCmd(cmdline$)
    Dim start As STARTUPINFO

    ' Initialize the STARTUPINFO structure:
    start.cb = Len(start)

    ' Start the shelled application:
    ret& = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
        NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)

    ' Wait for the shelled application to finish:
        ret& = WaitForSingleObject(proc.hProcess, INFINITE)
        Call GetExitCodeProcess(proc.hProcess, ret&)
        Call CloseHandle(proc.hThread)
        Call CloseHandle(proc.hProcess)
        ExecCmd = ret&
End Function

Dig Deeper on Visual Basic 6 programming language

Start the conversation

Send me notifications when other members comment.

Please create a username to comment.