PlanetSquires Forums

Support Forums => General Board => Topic started by: Paul Squires on March 05, 2012, 09:18:34 PM

Title: Capture Console Output
Post by: Paul Squires on March 05, 2012, 09:18:34 PM
One of the hardest things to do reliably is capture the text output from a console application. I have been using modified VB code and it worked "okay", but sometimes it would hang, etc.

I am working with Fossil (http://www.fossil-scm.org) to see if I can add source code version control to FireFly and I need to capture the console screen output. My code was hit and miss and seemed to miss more often then it should. I simply could not rely on it.

I searched the Net for a solution and came across a great CodeProject project that does exactly what I want. It is supplied in DLL form and can be easily called. Here is the link: http://www.codeproject.com/Articles/32965/CaptureConsole-DLL-A-Universal-Console-Output-Redi

I have been using it non-stop over the past few days and it works perfectly every time.

Here is the declare for the one function that you call in the DLL (you use unicode variables when calling the function):


Declare Function ExecuteW Lib "CaptureConsole" Alias "ExecuteW" ( _
                     ByVal s_CommandLine    As wString, _
                     ByVal s32_FirstConvert As Long, _
                     ByVal s_CurrentDir     As Long, _
                     ByVal s_Environment    As Long, _
                     ByVal b_SeparatePipes  As Long, _
                     ByVal s32_Timeout      As Long, _
                     ByRef s_ApiError       As wString, _
                     ByRef s_StdOut         As wString, _
                     ByRef s_StdErr         As wString _
                     ) As Long


Hopefully this will be of use to some of you guys as well.  :)
Title: Re: Capture Console Output
Post by: José Roca on March 06, 2012, 05:47:16 AM
Your declare looks more like VB than PB :)


'typedef DWORD (WINAPI* tExecute)(const WCHAR*, DWORD, const WCHAR*, const WCHAR*, BOOL, DWORD, BSTR*, BSTR*, BSTR*);

Declare Function ExecuteW Lib "CaptureConsole" Alias "ExecuteW" ( _
                     ByRef s_CommandLine    As wStringZ, _
                     ByVal s32_FirstConvert As Dword, _
                     ByRef s_CurrentDir     As wStringZ, _
                     ByRef s_Environment    As wStringZ, _
                     ByVal b_SeparatePipes  As Long, _
                     ByVal s32_Timeout      As DWord, _
                     ByRef s_ApiError       As wString, _
                     ByRef s_StdOut         As wString, _
                     ByRef s_StdErr         As wString _
                     ) As DWord

Title: Re: Capture Console Output
Post by: Pedro Marquez on November 10, 2013, 10:10:21 AM
You can teach an example use of the function,
Title: Re: Capture Console Output
Post by: Paul Squires on November 10, 2013, 01:34:08 PM
You need to download the console package and use the 32-bit DLL. Put that DLL in the same folder as your EXE.

In the project I created, I was using a function like the following to call the ExecuteW in the DLL.


'--------------------------------------------------------------------------------
Function ExecuteConsoleApp( wCommandLine As wString, _
                            wResult      As wString _
                            ) As Long 

   Local wApiError As wString
   Local wStdOut   As wString
   Local wStdErr   As wString

   Function = ExecuteW( wCommandLine, 0, 0, _
                         1, 0, 120000, _
                         wApiError, _
                         wStdOut, _
                         wStdErr )
   
   wResult = wStdOut
                         
End Function


Example:

   ' Create the Fossil source code repository
   Local wst     As wString
   Local wResult As wString
   
   wst = gFossilExe & " init " & gFossilDB 

   ExecuteConsoleApp wst, wResult

   ShowResultW wResult

Title: Re: Capture Console Output
Post by: Pedro Marquez on November 10, 2013, 02:17:54 PM
Thanks, but do not want to use external dll
Title: Re: Capture Console Output
Post by: Elias Montoya on November 10, 2013, 05:13:37 PM

Pedro,

You can execute your console app via CreateProcess(), making sure to set the process info like this:

   pi.cb          = SizeOf (NewProcStartInfo)
   pi.dwFlags     = %STARTF_USESTDHANDLES
   pi.wShowWindow = %Null
   pi.hStdInput   = hStdOutPut
   pi.hStdOutput  = hStdInPut
   pi.hStdError   = hStdErr
   pi.lpTitle     = %Null


This redirects the output of STDOUT to the files that are opened with those 3 file handles (need to create and open the files yourself).

Then just use WaitForSingleObject() for the app to finish, and then open the contents of StdIN file, the contents of the output should be there. Im sorry i dont have an example right now, but this is the idea and you can search for examples using those terms. :)


Title: Re: Capture Console Output
Post by: Pedro Marquez on November 10, 2013, 05:43:26 PM
thanks but I'm newbie
with examples learn best
Title: Re: Capture Console Output
Post by: Paul Squires on November 10, 2013, 09:44:19 PM
This is code that I have been using inside FireFly to shell and create a .RES resource file. Probably got it from the PB Forum.


'--------------------------------------------------------------------------------------------------
Function ShellConsoleApp( CmdLine As String, _
                          StartDirectory As Asciiz, _
                          Result As String, _
                          ByVal dwMillisecondsWait As Dword _
                          ) As Long
                         
      '**** Set security attributes ****
      Local sa As SECURITY_ATTRIBUTES
      sa.nLength = SizeOf(SECURITY_ATTRIBUTES)
      sa.bInheritHandle = 1 ' TRUE

       '**** Create a new pipe ****
      Local hReadPipe As Dword, hWritePipe As Dword
      If CreatePipe(hReadPipe, hWritePipe, sa, ByVal 0) = 0 Then _
         Function = -2: Exit Function

      '**** Start the program ****
      Local SI As STARTUPINFO, pi As PROCESS_INFORMATION, ExitCode As Long
      SI.cb           = SizeOf(STARTUPINFO)
      SI.dwFlags      = %STARTF_USESHOWWINDOW Or %STARTF_USESTDHANDLES
      SI.wShowWindow  = %SW_HIDE
      SI.hStdOutput   = hWritePipe
      SI.hStdError    = hWritePipe

      If CreateProcess ("", ByVal StrPtr(CmdLine), ByVal 0&, ByVal 0&, 1,  %NORMAL_PRIORITY_CLASS, _  ' // creation flags
         ByVal 0&, StartDirectory, si, pi) = 0 Then Function = -1: Exit Function
      WaitForSingleObject PI.hProcess, dwMillisecondsWait
      GetExitCodeProcess PI.hProcess, ExitCode
      If ExitCode = %STILL_ACTIVE Then TerminateProcess pi.hProcess, 0: Function = -3 Else Function = ExitCode

      '**** Read the pipe anyway ****
      Local BytesRead As Dword, BytesWritten As Dword
      Local chBuf As Asciiz * 1024, chEOF As Asciiz * 12

      chEOF = "End Of Pipe" ' Something unique
      Result = ""
      Do
         If WriteFile (hWritePipe, chEOF, Len(chEOF), BytesWritten, ByVal 0) = 0 Then Function = -4: Exit Do
         If BytesWritten <> Len(chEOF) Then Function = -4: Exit Do
         Do
            If ReadFile(hReadPipe, chBuf, SizeOf(chBuf), BytesRead, ByVal 0) = 0 Then Function = -4: Exit Do
            If BytesRead = 0 Then Function = -4: Exit Do
            Result = Result + Left$(chBuf, BytesRead)
            If Right$(Result, Len(chEOF)) = chEOF Then Result$ = Left$(Result$, Len(Result) - Len(chEOF)): Exit Do
         Loop
         Exit Do
      Loop
      If Len(Result) Then OemToCharBuff ByVal StrPtr(Result), ByVal StrPtr(Result), Len(Result)

      '*** Close process and pipe handles ****
      CloseHandle hReadPipe
      CloseHandle hWritePipe
      CloseHandle pi.hThread
      CloseHandle pi.hProcess
End Function


Title: Re: Capture Console Output
Post by: Elias Montoya on November 11, 2013, 02:38:26 AM

Oh, sorry Pedro, i didn't knew you were new. Good thing paul came to the rescue!. :)
Title: Re: Capture Console Output
Post by: Pedro Marquez on November 11, 2013, 04:35:20 AM
hi, paul
Perfect, this is what I seek, now study the example.

thanks
Title: Re: Capture Console Output
Post by: Pedro Marquez on November 11, 2013, 10:32:41 AM
ShellConsoleApp ("netsh wlan show networks mode=bssid","",a$,5000)

Not read all output,

¿the time can be detected automatic?

Title: Re: Capture Console Output
Post by: Paul Squires on November 11, 2013, 11:39:51 AM
I ran your command on my laptop and it captured everything. Maybe you should try increasing your timeout from 5 seconds (5000) to maybe 30 seconds (30000).
Title: Re: Capture Console Output
Post by: Pedro Marquez on November 11, 2013, 02:02:24 PM
ShellConsoleApp ("netsh wlan show networks mode=bssid","",a$,30000)

No problem of time, output is cut in half


Also detected with upx -- help
output is nothing and the process is open
Title: Re: Capture Console Output
Post by: Paul Squires on November 11, 2013, 02:59:00 PM
Does using the "ExecuteW" and dll in Post#1 capture all of the text from your console?

Can you post the code you are using to display the text received from my ShellConsoleApp function? Maybe something there is cutting off your display string?
Title: Re: Capture Console Output
Post by: Pedro Marquez on November 11, 2013, 03:15:05 PM
 ShellConsoleApp ("netsh wlan show networks mode=bssid","",a$,5000)
FF_Control_SetText (HWND_FORM1_RichEdit1,a$)

Nothing is showing and is the program.exe is hidden, active process.



ShellConsoleApp ("netsh wlan show networks","",a$,5000)
FF_Control_SetText (HWND_FORM1_RichEdit1,a$)


Sample text 26 networks  of 32

Title: Re: Capture Console Output
Post by: Pedro Marquez on December 24, 2013, 09:59:40 AM
Me doy cuenta que si a.txt es de gran tamaño, el programa queda bloqueado.
I realize that if a.txt is large, the program is blocked.



Function ShellConsoleApp( CmdLine As String, _
                          StartDirectory As Asciiz, _
                          Result As String, _
                          ByVal dwMillisecondsWait As Dword _
                          ) As Long
                         
      '**** Set security attributes ****
      Local sa As SECURITY_ATTRIBUTES
      sa.nLength = SizeOf(SECURITY_ATTRIBUTES)
      sa.bInheritHandle = 1 ' TRUE

       '**** Create a new pipe ****
      Local hReadPipe As Dword, hWritePipe As Dword
      If CreatePipe(hReadPipe, hWritePipe, sa, ByVal 0) = 0 Then _
         Function = -2: Exit Function

      '**** Start the program ****
      Local SI As STARTUPINFO, pi As PROCESS_INFORMATION, ExitCode As Long
      SI.cb           = SizeOf(STARTUPINFO)
      SI.dwFlags      = %STARTF_USESHOWWINDOW Or %STARTF_USESTDHANDLES
      SI.wShowWindow  = %SW_HIDE
      SI.hStdOutput   = hWritePipe
      SI.hStdError    = hWritePipe

      If CreateProcess ("", ByVal StrPtr(CmdLine), ByVal 0&, ByVal 0&, 1,  %NORMAL_PRIORITY_CLASS, _  ' // creation flags
         ByVal 0&, StartDirectory, si, pi) = 0 Then Function = -1: Exit Function
      WaitForSingleObject PI.hProcess, dwMillisecondsWait
      GetExitCodeProcess PI.hProcess, ExitCode
      If ExitCode = %STILL_ACTIVE Then TerminateProcess pi.hProcess, 0: Function = -3 Else Function = ExitCode

      '**** Read the pipe anyway ****
      Local BytesRead As Dword, BytesWritten As Dword
      Local chBuf As Asciiz * 1024, chEOF As Asciiz * 12

      chEOF = "End Of Pipe" ' Something unique
      Result = ""
      Do
         If WriteFile (hWritePipe, chEOF, Len(chEOF), BytesWritten, ByVal 0) = 0 Then Function = -4: Exit Do
         If BytesWritten <> Len(chEOF) Then Function = -4: Exit Do
         Do
            If ReadFile(hReadPipe, chBuf, SizeOf(chBuf), BytesRead, ByVal 0) = 0 Then Function = -4: Exit Do
            If BytesRead = 0 Then Function = -4: Exit Do
            Result = Result + Left$(chBuf, BytesRead)
            If Right$(Result, Len(chEOF)) = chEOF Then Result$ = Left$(Result$, Len(Result) - Len(chEOF)): Exit Do
         Loop
         Exit Do
      Loop
      If Len(Result) Then OemToCharBuff ByVal StrPtr(Result), ByVal StrPtr(Result), Len(Result)

      '*** Close process and pipe handles ****
      CloseHandle hReadPipe
      CloseHandle hWritePipe
      CloseHandle pi.hThread
      CloseHandle pi.hProcess
     
End Function



button blocked ----- a.txt 17kb
button Command2 ---- b.txt 1kb

(https://www.planetsquires.com/protect/forum/proxy.php?request=http%3A%2F%2Fi.imgur.com%2F2GW1Kdw.jpg&hash=d2e8b53547960ea6761ff5a52f732a3f4028f50f)

Title: Re: Capture Console Output
Post by: Elias Montoya on December 24, 2013, 05:06:36 PM

What is strange to me is that the line hangs at this line:
Lo que me parece extraño es que el codigo se detiene en esta linea:

If WriteFile (hWritePipe, chEOF, Len(chEOF), BytesWritten, ByVal 0) = 0 Then Function = -4: Exit Do

Para entonces yo aun no veo ninguna diferencia de importancia en la ejecucion de codigo. Yo diria que algo le esta pasando al handle del pipe, pero no estoy seguro.
By then i still can't find anything different in code execution. I would say something is happening to the pipe handle, but im not sure.
Title: Re: Capture Console Output
Post by: Wilko Verweij on December 24, 2013, 05:59:51 PM
Hi,
Are you sure you are using the right declarations? I have been struggling with this in the past and remember the only way I could get it working was to declare the string as a byte.

  Local TB As Byte
  Local ToWrite$
 
  ToWrite$="abcdefgh"
                                             
  For I=1 To Len(ToWrite$)
    TB=Asc(Mid$(ToWrite$,I,1))
    RetValL=WriteFile(hPipeWrite,TB,1,Written,ByVal 0)
  Next I


See: http://www.powerbasic.com/support/pbforums/showthread.php?t=40600 (http://www.powerbasic.com/support/pbforums/showthread.php?t=40600)

I only have to write a small string, so I now write them byte by byte (which is clumsy, but it is not a critical part of my program...).

Maybe this helps.
Wilko
Title: Re: Capture Console Output
Post by: Pedro Marquez on December 25, 2013, 02:08:26 PM
 upload the chBuf but same problem
Local chBuf As Asciiz * 2048


master or creator have solution
Title: Re: Capture Console Output
Post by: Pedro Marquez on December 26, 2013, 01:06:51 PM
with 5kb of data stops working.
Title: Re: Capture Console Output
Post by: Pedro Marquez on January 12, 2014, 03:06:48 PM
¿TechSupport can have a solution?
Title: Re: Capture Console Output
Post by: David Kenny on January 12, 2014, 03:56:24 PM
Pedro,

Firefly is probably not the problem.  I would recommend creating a minimal Powerbasic-only version.  I think you will find the problem still exists.  If it turns out that the problem still exists, your best bet is to post your problem on the PB forums.  There will be many more people that have experience with Console output there.  They are just as eager to help as the forum members here.   

David
Title: Re: Capture Console Output
Post by: Pedro Marquez on January 12, 2014, 05:06:43 PM
Yes, I think is best.
Title: Re: Capture Console Output
Post by: Roger Garstang on February 01, 2014, 03:17:22 PM
Interesting.  I wrote a program using the exact code Paul used as reference a few months back.  I needed an easy way to call the Android ADB service and make it forward a TCP port over USB for communication with an android app wrote in Java with Eclipse.  Unfortunately it wasn't as automated as I wanted since ADB doesn't use any return codes when ran and all you have is the text to go by, but it works...and was wrote in FF/PB.
Title: Re: Capture Console Output
Post by: Pedro Marquez on August 25, 2015, 09:17:20 AM
you can convert ShellConsoleApp to FreeBasic