Reader Laura Moloney tells how she figured out the answer to a particularly vexing problem, and gives some kudos to SearchVB.com 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!
FORM LOAD EVENT: Public Sub Form_Load() On Error GoTo errHandler Dim commandText As String Dim dTaskID As Double Dim fsuccess As Integer Dim csScreenBuffer As CONSOLE_SCREEN_BUFFER_INFO 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" Else 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 that '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 FreeConsole Exit Sub errHandler: MsgBox Err.Number & " " & Err.Source & " " & Err.Description CloseHandle hConsoleOut FreeConsole End Sub MODULE.BAS: '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 NORMAL_PRIORITY_CLASS = &H20& Public Const INFINITE = -1& Public Const STD_OUTPUT_HANDLE = -11& Public Const STD_INPUT_HANDLE = -10& Public Type STARTUPINFO 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 Public Type PROCESS_INFORMATION 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 Public Type CONSOLE_SCREEN_BUFFER_INFO 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 _ PROCESS_INFORMATION) As Long 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 proc As PROCESS_INFORMATION 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
This was first published in December 2000