Jump to content
toasterking

_RunWaitGet() - StdOut and StdErr stream capture

Recommended Posts

toasterking

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

 

  • Like 1

Share this post


Link to post
Share on other sites
ViciousXUSMC

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
toasterking

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

 

  • Like 1

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

    • Kevin Finnegan
      By Kevin Finnegan
      Hi all,
      Long time lurker and now forum poster! I'm writing a relatively simple backup script for my firm that automates the copy, compression and organization of Leaver's data on one of our secured NAS systems. I personally found the best method to do this so far was to use 7zG.exe (GUI version of 7Zip which can use command-line too) and it functions quite well!
      I would like to retrieve more info on whether any warnings or errors happen in 7Zip during the backup, but I can't quite get my head around the syntax and switches for reading out, it seems any adjustment I make to the RunWait call's string seems to break the backup or give unexpected repercussions! Hopefully its something silly I'm doing as I don't code very often.
      Here is the working version:
      ; Compress the directories one by one in the zip using the listfile.... Local $iPID = RunWait(@ScriptDir & "\bin\7zG.exe a -mx" & $compressionQuality & " -v" & $compressSplitFileSize & " -wc:\temp " _ & $backupToLocation & "\" & $userDirectory & ".7z @bin\listfile.txt -x@bin\excludefile.txt", "", @SW_SHOWDEFAULT, $STDOUT_CHILD) Ultimately I would love to switch entirely to 7za.exe (standalone) so that I can read the progress percentage, current file being uploaded and any warnings or errors could be processed and output to the AutoIT script's GUI I've created rather than jumping in and out of two applications per se.
       
    • 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
×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.