toasterking

_RunWaitGet() - StdOut and StdErr stream capture

3 posts in this topic

Someone told me I should post this in the examples section.  I wrote this to make it easy for me to call a Windows console application and get its output using a single line of AutoIt code.  I hope it's helpful to someone else.

 

#include <Constants.au3>

; Examples:
MsgBox(0,"Windows Version",_RunWaitGet(@ComSpec & " /c ver",1,"",@SW_HIDE))
MsgBox(0,"System Info",_RunWaitGet(@SystemDir & "\systeminfo.exe",1))

; #FUNCTION# ====================================================================================================================
; Name ..........: _RunWaitGet

; Description ...: Runs the specified process, waits for it to exit, then returns the contents of its StdOut and/or StdErr streams.
;                  Handy for running command-line tools and getting their output.
; Syntax ........: _RunWaitGet($sProgram, $nOptions, $sWorkingDir, $nShowFlag)
; Parameters ....: $sProgram            - The full path of the program (EXE, BAT, COM, or PIF) to run
;                  $nOptions            - Add options together:
;                                            1 = Capture the StdOut stream.
;                                            2 = Capture the StdErr stream.
;                                            4 = Return when the stream(s) close(s), not when the process ends.
;                  $sWorkingDir         - The working directory. Blank ("") uses the current working directory.
;                                         This is not the path to the program.
;                  $nShowFlag           - The "show" flag of the executed program:
;                                            @SW_SHOW = Show window (default)
;                                            @SW_HIDE = Hidden window (or Default keyword)
;                                            @SW_MINIMIZE = Minimized window
;                                            @SW_MAXIMIZE = Maximized window
; Return values .: String value containing the captured contents.
;                  If there was a problem running the process, @error is set to the @error value returned by Run().
;                  Otherwise, @error is 0.
; Author ........: ToasterKing

; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: MsgBox(0,"System Info",_RunWaitGet(@SystemDir & "\systeminfo.exe",1))
;                  MsgBox(0,"Windows Version",_RunWaitGet(@ComSpec & " /c ver",1,"",@SW_HIDE))
; ===============================================================================================================================
Func _RunWaitGet($sProgram,$nOptions = 0,$sWorkingDir = @SystemDir,$nShowFlag = @SW_SHOW)
    Local $nRunOptFlags = 0,$sStreamOut = "" ; Initialize variables
    ; Determine flags for parent/child interaction
    If BitAND($nOptions,1) Then $nRunOptFlags += $STDOUT_CHILD
    If BitAND($nOptions,2) Then $nRunOptFlags += $STDERR_CHILD
    Local $hRunStream = Run($sProgram,$sWorkingDir,$nShowFlag,$nRunOptFlags) ; Run the process
    If @error Then Return SetError(@error,@extended,0) ; If there was an error code, return it.  Otherwise...
    While 1 ; Loop until the end of the stream, which indicates that the process has closed it (which usually means the process ended)
        If BitAND($nOptions,1) Then ; If user specified to capture STDOUT stream...
            $sStreamOut &= StdoutRead($hRunStream) ; Append new stream contents to existing variable while removing those contents from the stream.
            If @error = 2 And BitAND($nOptions,4) Then ExitLoop ; If stream ended and user specified to return when the stream closes, stop looping.
        EndIf
        If BitAND($nOptions,2) Then ; If user specified to capture STDERR stream...
            $sStreamOut &= StderrRead($hRunStream) ; Append new stream contents to existing variable while removing those contents from the stream.
            If @error = 2 And BitAND($nOptions,4) Then ExitLoop ; If stream ended and user specified to return when the stream closes, stop looping.
        EndIf
        If Not BitAND($nOptions,4) And Not ProcessExists($hRunStream) Then ExitLoop ; If using the default setting and the process ended, stop looping.
        Sleep(100) ; To avoid overloading the CPU
    WEnd
    Return SetError(0,0,$sStreamOut) ; Return the captured contents and @error = 0
EndFunc

 

1 person likes this

Share this post


Link to post
Share on other sites



I like :)

Any way to make this show live information for like a GUI or is the stream only sent after completion?

Share this post


Link to post
Share on other sites

That's not the sort of use I intended when I wrote the function, but this is AutoIt.  Of course there is a way.™

Try my scribblings below.  If you want something wrapped up in a nice, tidy function, that's an exercise for the reader.  :)

#include <Constants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiEdit.au3>
$FormMain = GUICreate("Crappy Demo", 602, 297)
$EditStdOut = GUICtrlCreateEdit("", 16, 16, 569, 225, BitOR($ES_AUTOVSCROLL,$ES_WANTRETURN,$WS_VSCROLL))
$ButtonTraceRoute = GUICtrlCreateButton("Trace Route!", 240, 256, 115, 25)
GUISetState(@SW_SHOW)

While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
        Case $ButtonTraceRoute
            _TraceRoute()
    EndSwitch
WEnd

Func _TraceRoute()
    GUICtrlSetState($ButtonTraceRoute,$GUI_DISABLE)
    $hRunStream = Run(@SystemDir & "\tracert.exe www.autoitscript.com",@SystemDir,@SW_HIDE,$STDOUT_CHILD)
    While 1
        $sStreamOut = StdoutRead($hRunStream)
        If @error Then
            ExitLoop
        Else
            _GUICtrlEdit_AppendText($EditStdOut,$sStreamOut)
        EndIf
        Sleep(100)
    WEnd
    GUICtrlSetState($ButtonTraceRoute,$GUI_ENABLE)
EndFunc

 

1 person likes this

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now

  • Similar Content

    • Simpel
      By Simpel
      Hi.
      While programming I often use ConsoleWrite() for debugging. If the script isn’t to big I often don’t do extra logging but let my ConsoleWrite()’s inside. Sometimes difficulties appear later when @compiled and weeks are gone. So my first thought often is let’s run the script and catch the console outs of my script. So I coded a console reader.
      There are two ways to start the buggy script. First via $cmdline send to the reader and second with drag’n’drop onto the readers gui.
      #include <AutoItConstants.au3> #include <WindowsConstants.au3> #include <EditConstants.au3> #include <GUIConstantsEx.au3> #include <MsgBoxConstants.au3> #include <ColorConstants.au3> #include <StaticConstants.au3> #include <array.au3> #include <WinAPIProc.au3> #include <GuiEdit.au3> #include <GuiRichEdit.au3> Opt("GUIOnEventMode", 1) Global $data = "" Global $g_aPID = [0] Global $g_bFreeze = False Global $g_iZaehler = 0 Global $g_hGUI = GUICreate("Console: StdoutRead" , 800, 800, -1, -1, $WS_OVERLAPPEDWINDOW + $WS_CLIPCHILDREN, $WS_EX_ACCEPTFILES) GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit") Global $text = GUICtrlCreateEdit("",10,30,780,760, $ES_AUTOVSCROLL + $WS_VSCROLL + $ES_READONLY + $ES_NOHIDESEL) GUICtrlSetState(-1, $GUI_DROPACCEPTED); + $GUI_FOCUS) GUICtrlSetFont(-1, 9, -1, -1, "Lucida Console") GUICtrlSetResizing(-1, $GUI_DOCKBORDERS) GUISetOnEvent($GUI_EVENT_DROPPED, "_Dropped") _GUICtrlEdit_SetLimitText($text, 8388608) ; a filesize about 1mb Global $g_LaZeilen = GUICtrlCreateLabel("Zeilen: " & StringFormat("% 5d", $g_iZaehler), 680, 10, 100, 9, $SS_LEFTNOWORDWRAP, $WS_EX_LAYERED) GUICtrlSetFont(-1, 9, -1, -1, "Lucida Console") GUICtrlSetResizing(-1, $GUI_DOCKRIGHT + $GUI_DOCKTOP + $GUI_DOCKSIZE) Global $g_hCbFreeze = GUICtrlCreateCheckbox("&Freeze", 13, 5, 90) GUICtrlSetFont(-1, 9, -1, -1, "Lucida Console") GUICtrlSetResizing(-1, $GUI_DOCKALL) GUICtrlSetOnEvent($g_hCbFreeze, "_Freeze") Global $g_hBuCopy = GUICtrlCreateButton("&Copy All", 125, 5, 70, 20) GUICtrlSetFont(-1, 9, -1, -1, "Lucida Console") GUICtrlSetResizing(-1, $GUI_DOCKALL) GUICtrlSetOnEvent($g_hBuCopy, "_Copy") GUISetState(@SW_SHOW) If $CmdLine[0] > 0 Then _ViaCmdline() _GUICtrlEdit_AppendText($text, $CmdLine[1] & @CRLF) EndIf Global $sText = StringFormat("% 5d", $g_iZaehler) & @TAB _GUICtrlEdit_AppendText($text, $sText) Local $nextline While 1 If $g_aPID[0] > 0 Then $nextline = _ConsoleReadLine() $nextline = StringReplace($nextline, @CRLF, @CRLF & StringFormat("% 5d", $g_iZaehler) & @TAB) $sText = $nextline If $g_bFreeze = False Then _GUICtrlEdit_AppendText($text, $sText) EndIf GUICtrlSetData($g_LaZeilen, "Zeilen: " & StringFormat("% 5d", $g_iZaehler)) EndIf _ProcessExist() WEnd #region - Funcs Func _ConsoleReadLine() Local $Result,$crPos While True _ProcessExist() For $i = 1 To $g_aPID[0] $data &= StdoutRead($g_aPID[$i]) If @error Then ExitLoop Next $crPos = StringInStr($data, @CRLF) If $crPos Then $Result = StringLeft($data, $crPos) & @CRLF $data = StringRight($data, StringLen($data) - $crPos) $g_iZaehler += 1 Return $Result EndIf WEnd Return SetError(1, 1, $data) EndFunc Func _Dropped() Local $hPID = Run(@GUI_DragFile, "", Default, $STDERR_MERGED) ConsoleWrite("DROP: " & $hPID & " " & @GUI_DragFile & @CRLF) _ArrayAdd($g_aPID, $hPID) $g_aPID[0] = UBound($g_aPID) - 1 EndFunc Func _ViaCmdline() Local $hPID = Run($CmdLine[1], "", Default, $STDERR_MERGED) ConsoleWrite("CMDLINE: " & $hPID & " " & $CmdLine[1] & @CRLF) _ArrayAdd($g_aPID, $hPID) $g_aPID[0] = UBound($g_aPID) - 1 EndFunc Func _Freeze() $g_bFreeze = Not $g_bFreeze ConsoleWrite("FREEZE: " & $g_bFreeze & @CRLF) GUICtrlSetState($text, $GUI_FOCUS) EndFunc Func _Copy() ConsoleWrite("COPY" & @CRLF) ClipPut(GUICtrlRead($text)) EndFunc Func _ProcessExist() For $i = $g_aPID[0] To 1 Step - 1 If Not ProcessExists($g_aPID[$i]) Then ConsoleWrite("GONE: " & $g_aPID[$i] & @CRLF) _ArrayDelete($g_aPID, $i) $g_aPID[0] = UBound($g_aPID) - 1 EndIf Next EndFunc Func _Exit() If $CmdLine[0] = 0 Then ; if reader is started by $cmdline then no script will exit but reader For $i = 1 To $g_aPID[0] ConsoleWrite("KILL: " & $g_aPID[$i] & " " & _WinAPI_GetProcessFileName($g_aPID[$i]) & @CRLF) ProcessClose($g_aPID[$i]) Next EndIf ConsoleWrite("EXIT" & @CRLF) Exit EndFunc #endregion Funcs Maybe someone will find it useful too. 
      One last remark. If only one script is given via $cmdline to the reader no scripts will exit if consolereader exits. But otherwise all scripts dropped onto the readers gui will exit too. This is by design. If you want to change this do it inside func _exit().
      Regards, Conrad 
      P.S. Possibly some #includes are not necessary anymore but have been while scripting.
    • tremolux66
      By tremolux66
      Initial Problem
      I've written several scripts with the following sequence:
      Execute a program using Run w/stdout+stderr captured Typically processes all the files in one directory tree to populate a second tree Execute a second program (also with Run) to monitor the products of the first program and Display a progress bar (percentage of output files complete) Also monitor the first program's process and exit when it terminates The script then calls ProcessWaitClose (no timeout) on the first program's process and Checks the first program's results Kills the monitor program if it hasn't already exited on its own. Sometimes, ProcessWaitClose returns 1 with @error = 0 and @extended = 0xCCCCCCCC (actually, 0xFFFFFFFFCCCCCCCC), which seems ambiguous: the documentation says that @error = non-zero and @extended = 0xCC... means an invalid PID (unclear what the return value is), and 1 is returned for non-existent processes (but no mention of @extended). The 1/0/0xCC... result seems to occur when the first program exits very quickly (with or without an error). Since the exit value is not available, the script scans the program's output and tries to determine whether it ran successfully. This has gotten complicated and unreliable.
      Partial Fix
      I've now implemented a much simpler approach that works for most cases:
      Modify the monitor program so that it ignores the other program's process (the monitor always gets killed by the script anyway) Execute the monitor program first using Run, then execute the processing program with RunWait When RunWait returns, the child process exit value is available, so the script can ignore its output (which isn't available anyway) If the monitor program is still running, kill it. Remaining Issue
      However, there are still a couple of cases where it's necessary to get both the exit value from the processing program and its output. Since RunWait doesn't capture stdout and stderr for the parent script, it's looking like I'll have to call RunWait and redirect the 2 streams to a temp file and then scan it. Also, to do the redirect, I think I'll have to use @ComSpec to execute the processing program, which adds an undesired layer.
      Does anybody have a better (cleaner) way to handle these cases?
    • TheDcoder
      By TheDcoder
      Hello Everyone , Are you tired of searching the forum for getting both the exit code & the stdout output? Then you are in the right place!

      With this UDF you can get the both output & exit code of the command or the console app! Or you can get the exit code of another process without having to use RunWait...
      Features:
      1. Simple & Lightweight (15 KB)
      2. Detailed comments & description
      3. Flexible functions with many optional parameters
      A BIG THANKS TO PsaltyDS for the functions! 2 of the (main) functions in the UDF are his work
       
      List of functions:
      Downloads:
      Grab the latest (in development) code from GitHub
       
      Hope it may help you, TD
       
      P.S Icon made by Freepik from www.flaticon.com, Modified by TheDcoder