mojomatt

Question regarding StdinWrite and Stdoutread when used with Console apps

4 posts in this topic

Question regarding StdinWrite  and Stdoutread…

Here’s my setup…
I’m opening a powershell session using the AutoIT Run function with the $STDIN_CHILD  and $STDOUT_CHILD optional flags.

I’m then sending a command using the STDINWRITE function to connect to a remote server which is a long distance away and it takes a “long” time to return results.

I’m then trying to read the STDOUT stream.

After this there are other STDINWRITE statements that I need to make so the STREAM hasn’t closed at this point.

My question is this…How do I know for sure when all the data has been returned from the remote server after my first STDINWRITE?

Things I’ve tried and don’t work…
Use a loop which waits for @error to be non-zero.  This doesn’t work because as long as the STREAM is open then @error never is set to non-zero.

Use a loop which waits for @extended to be 0.  This doesn’t work because the remote server sends data back in bursts and I don’t know how much time will pass in-between bursts.

The only thing that is somewhat reliable is utilizing very long sleep statements – like 5 minutes – which really slows down my script and doesn’t ensure success either.

What I really need is a way to tell that control has been returned to the command prompt.  What I mean by this is if you open a command prompt and issue the command DIR C:\ /s which lists all the files and folders on your C: drive you know you can’t issue any more commands until you get a blinking cursor at the command prompt again.  How do I programatically know when I get a blinking cursor again?

Sample code that isn’t 100% foolproof…

This code uses the logic looking for an @error to be non-zero...

#include <constants.au3>
Dim $StrOutput
$StrPowerShellPID = Run('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe  -command - ',@SystemDir, @SW_HIDE, $STDIN_CHILD + $STDERR_MERGED)
StdinWrite($StrPowerShellPID, "Dir C:\" & @CRLF) ;this is just an example command
; I can't close the STDOUT stream at this point because I need to issue additional commands into the existing STDIN stream later in the script
While 1 ;Wait for EOF as indicated by @error being non-zero
    $StrOutput &= StdoutRead($StrPowerShellPID)
    If @error <> 0 Then ExitLoop
    sleep (500)
WEnd
Consolewrite($StrOutput)

The above code doesn't work because @error will never be set to non-zero since the stream isn't closed.  I can't close the stream because I need to issue additional commands into it.
 

 

This code uses logic looking for an @extended value of 0 indicating no data was returned to the stream...

#include <constants.au3>
Dim $StrOutput
$StrPowerShellPID = Run('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe   -command - ',@SystemDir, @SW_HIDE, $STDIN_CHILD + $STDERR_MERGED)
StdinWrite($StrPowerShellPID, "Dir ""C:\program files\common files"" -recurse" & @CRLF)
While 1 ;This loop detects when data starts to stream in and then exits the loop
    $StrOutput &= StdoutRead($StrPowerShellPID)
    If @extended > 0 Then ExitLoop
    sleep(1000)
WEnd

While 1 ;Now that data has begun to stream in I wait until I can't read any more data as indicated by 0 bytes read in the value of @extended
    $StrOutput &= StdoutRead($StrPowerShellPID)
    If @extended = 0 Then ExitLoop
;~  sleep (1000) ; this is the only way to get this specific sample to work but this won't work for me because I don't know the time in-between bursts of data coming into the STDIN stream
WEnd
ConsoleWrite($StrOutput) ;If you look at the output you'll see it's not complete because @extended was set to 0 during the second loop

The above code doesn't always work because if the remote server doesn't send any data for several seconds, then the StdoutRead thinks it's done reading when in reality it may not be.

 

Share this post


Link to post
Share on other sites



I don't know if powershell allows to concatenate 2 commands on the same line (as allowed in dos prompt by using the & separator), If this is allowed then you could use this "workaround" by issuing a command like this:

StdinWrite($StrPowerShellPID, "Dir ""C:\program files\common files"" -recurse  & TheEnd!" @CRLF)


where TheEnd! is deliberately a non existing command that will generate an error on the StdErr stream, and that error will be generated only after the first part of the command (the correct one) has finished. In this way you could know that your command has finished when you get "TheEnd!" string as first part on the StdErr stream.

of course you should manage also the StdErr stream by using the $STDERR_CHILD flag in the Run command.
You will trap the "TheEnd!" string that will signal you the end of your command. Here is working example in a DOS environment (hope it can work the same way in  powershell?)

#include <Constants.au3>
Local $cmd_Pid, $Output

; execute a permanent DOS prompt with all streams redirected
$cmd_Pid = Run(@ComSpec & " /k", "", @SW_HIDE, $STDIN_CHILD + $STDOUT_CHILD + $STDERR_CHILD)

; issue a command + issue an error at the end of the first command
StdinWrite($cmd_Pid, "ping localhost & TheEnd!" & @CRLF)

Do
    $Output &= StdoutRead($cmd_Pid)
Until StringLeft(StderrRead($cmd_Pid), 9) = '"TheEnd!"' ; wait till the deliberately generated error

ConsoleWrite($Output & @CRLF) ; show result of first command
StdinWrite($cmd_Pid, "exit" & @CRLF) ; close the permanent command promp

p.s.

here a link about Running multiple DOS commands in one line https://www.autoitscript.com/forum/topic/54864-run-multiple-dos-commands-in-one-line/


small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Share this post


Link to post
Share on other sites

Wow Chimp that's pretty close to being brilliant!  :D

The semicolon is used to concatenate multiple commands on the same line in Powershell.

Here's my working code:

#include <constants.au3>
Dim $StrOutput
$StrPowerShellPID = Run('C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe   -command - ',@SystemDir, @SW_HIDE, $STDIN_CHILD + $STDERR_MERGED)
StdinWrite($StrPowerShellPID, "Dir ""C:\program files\common files"" -recurse; write-host ""All Done!""" & @CRLF)
While 1 ;This loop detects when data starts to stream in and then exits the loop
    $StrOutput &= StdoutRead($StrPowerShellPID)
    If @extended > 0 Then ExitLoop
    sleep(1000)
WEnd

While 1 ;Now that data has begun to stream in I wait until I see the text "All Done!" which tells me my first command has completed
    $StrOutput &= StdoutRead($StrPowerShellPID)
    If StringInStr($StrOutput, "All Done!") Then ExitLoop
    Sleep (1000)
WEnd
ConsoleWrite($StrOutput)

Thanks a ton for the help!!!

Share this post


Link to post
Share on other sites

You are welcome

glad to be of help  :)


small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

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

    • careca
      By careca
      #include <Constants.au3> ListLetter() Func ListLetter() ;$DSK = Run("cmd", '', '', $STDIN_CHILD + $STDOUT_CHILD) $DSK = Run(@ComSpec & " /c diskpart.exe start", '', '', $STDIN_CHILD + $STDOUT_CHILD) ConsoleWrite('$DSK - '& $DSK&' - Error - '& @error &' - '&@MSEC&@CRLF) Sleep(2000) $Read = StdoutRead($DSK, True, False) ConsoleWrite(' - '& @error &' - '&@MSEC&@CRLF) ConsoleWrite('StdoutRead ' &$Read&' - '&@MSEC&@CRLF) EndFunc ;==>ListLetter So, this works with normal console "cmd" why does it error with diskpart? how can i make it read from diskpart?
    • dubi
      By dubi
      Hi,


       
      Background: i have a number of instances of the same application that I want to automate in parallel.

      Unfortunately, I cannot completely automate these instances in the background. So, from time to time these instances need to have the focus so that I can interact with the controls via “send” directly.

      Each of the application instances is controlled by a au3 complied script. Each script (called with a parameter) manages the automation of the respective application-instance. Each of the (complied) script (instances) is called by a (central) front end with a gui. The front end controls if the “focus” is available to do the “send” and “mouseclick” modifications. The central front end either allows a child to have the focus or prevents it to get the focus (in which case the child will wait and checks again). The code for the front end is included. Apologies for the lengthy explanation.


      #RequireAdmin #include <ButtonConstants.au3> #include <GUIConstantsEx.au3> #include <WindowsConstants.au3> #include <EditConstants.au3> #include <GUIEdit.au3> #include <ScrollBarConstants.au3> #include <Array.au3> Global Const $APF_ALLOWMULTILINE = 1 Global Const $APF_RIGHTALIGN = 6 Global Const $APF_RIGHTALIGNCOL = 2 Global Const $APF_RIGHTALIGNDATA = 4 Global Const $APF_PRINTROWNUM = 8 Global Const $APF_DEFAULT = $APF_PRINTROWNUM Global $PID[9], $FocusAvailable = True, $previousEditMsg Global $PID_waiting[0][2], $logfile $logfile = "D:\MultiInstance\Logfiles\FrontEnd.txt" If FileExists($logfile) Then FileDelete($logfile) #Region ### START Koda GUI section ### Form= $hGui_1 = GUICreate("Instanzenmanager", 493, 1226, 1807, 93) $grpInst1 = GUICtrlCreateGroup(" 1 ", 8, 8, 233, 121) $btnPause01 = GUICtrlCreateCheckbox("Pause", 16, 64, 50, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $tbnStop01 = GUICtrlCreateCheckbox("Stop", 16, 96, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnMin01 = GUICtrlCreateCheckbox("Minimize", 72, 64, 66, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnWdh01 = GUICtrlCreateCheckbox("Restore", 144, 64, 90, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnStart01 = GUICtrlCreateCheckbox("Start", 16, 32, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) GUICtrlSetBkColor(-1, 0x00FF00) GUICtrlCreateGroup("", -99, -99, 1, 1) $grpInst3 = GUICtrlCreateGroup(" 3 ", 8, 136, 233, 121) $btnPause03 = GUICtrlCreateCheckbox("Pause", 16, 192, 50, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $tbnStop03 = GUICtrlCreateCheckbox("Stop", 16, 224, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnMin03 = GUICtrlCreateCheckbox("Minimize", 72, 192, 66, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnWdh03 = GUICtrlCreateCheckbox("Restore", 144, 192, 90, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnStart03 = GUICtrlCreateCheckbox("Start", 16, 160, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) GUICtrlSetBkColor(-1, 0x00FF00) GUICtrlCreateGroup("", -99, -99, 1, 1) $grpInst2 = GUICtrlCreateGroup(" 2 ", 248, 8, 233, 121) $btnPause02 = GUICtrlCreateCheckbox("Pause", 256, 64, 50, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $tbnStop02 = GUICtrlCreateCheckbox("Stop", 256, 96, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnMin02 = GUICtrlCreateCheckbox("Minimize", 312, 64, 66, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnWdh02 = GUICtrlCreateCheckbox("Restore", 384, 64, 90, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnStart02 = GUICtrlCreateCheckbox("Start", 256, 32, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) GUICtrlSetBkColor(-1, 0x00FF00) GUICtrlCreateGroup("", -99, -99, 1, 1) $grpInst4 = GUICtrlCreateGroup(" 4 ", 248, 136, 233, 121) $btnPause04 = GUICtrlCreateCheckbox("Pause", 256, 192, 50, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $tbnStop04 = GUICtrlCreateCheckbox("Stop", 256, 224, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnMin04 = GUICtrlCreateCheckbox("Minimize", 312, 192, 66, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnWdh04 = GUICtrlCreateCheckbox("Restore", 384, 192, 90, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnStart04 = GUICtrlCreateCheckbox("Start", 256, 160, 218, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) GUICtrlSetBkColor(-1, 0x00FF00) $Edit1 = GUICtrlCreateEdit("", 8, 720, 473, 497) $btnPauseAll = GUICtrlCreateCheckbox("Pause all", 8, 656, 474, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) $btnStopAll = GUICtrlCreateCheckbox("Stop all", 7, 688, 474, 25, BitOR($GUI_SS_DEFAULT_CHECKBOX, $BS_PUSHLIKE)) GUISetState(@SW_SHOW) #EndRegion ### END Koda GUI section ### While 1 CheckGuiMsg() FileWrite($logfile, @HOUR & ":" & @MIN & ":" & @SEC & "> " & "CheckGuiMsg" & @CRLF) CheckClientMessages() FileWrite($logfile, @HOUR & ":" & @MIN & ":" & @SEC & "> " & "CheckClientMessages" & @CRLF) WEnd Func CheckGuiMsg() $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE Exit Case $btnStart01 AddTextToEdit("Starting Instance 1") $PID[0] = Run("D:\XVM05\Entwicklung_FilterTest_Multi_ToFileV001.exe 1", @ScriptDir, Default, 3) Case $btnStart02 AddTextToEdit("Starting Instance 2") $PID[1] = Run("D:\XVM05\Entwicklung_FilterTest_Multi_ToFileV001.exe 2", @ScriptDir, Default, 3) Case $btnStart03 AddTextToEdit("Starting Instance 3") $PID[2] = Run("D:\XVM05\Entwicklung_FilterTest_Multi_ToFileV001.exe 3", @ScriptDir, Default, 3) Case $btnStart04 AddTextToEdit("Starting Instance 4") $PID[3] = Run("D:\XVM05\Entwicklung_FilterTest_Multi_ToFileV001.exe 4", @ScriptDir, Default, 3) Case $btnPause01 AddTextToEdit("Send Pause to Instance 1") StdinWrite($PID[0], "Pause") Case $btnPause02 StdinWrite($PID[1], "Pause") Case $btnPause03 StdinWrite($PID[2], "Pause") Case $btnPause04 StdinWrite($PID[3], "Pause") Case $tbnStop01 StdinWrite($PID[0], "Stop") Case $tbnStop02 StdinWrite($PID[1], "Stop") Case $tbnStop03 StdinWrite($PID[2], "Stop") Case $tbnStop04 StdinWrite($PID[3], "Stop") Case $btnPauseAll AddTextToEdit(@CRLF & "************Pause All not yet implemented**************" & @CRLF) Case $btnStopAll AddTextToEdit(@CRLF & "************Stop All not yet implemented***************" & @CRLF) EndSwitch EndFunc ;==>CheckGuiMsg Func CheckClientMessages() For $i = 0 To 3 FileWrite($logfile, @HOUR & ":" & @MIN & ":" & @SEC & "> " & $i & @CRLF) Local $a = TimerInit() $p = $PID[$i] $streamRead = StdoutRead($p) If $streamRead <> "" Then Switch $streamRead Case StringInStr($streamRead, "Focus Needed") > 0 If $FocusAvailable Then $FocusAvailable = False StdinWrite($p, "Focus Granted") Else EndIf Case StringInStr($streamRead, "Release Focus") > 0 StdinWrite($p, "Release Focus Received") $FocusAvailable = True Case Else EndSwitch EndIf FileWrite($logfile, @HOUR & ":" & @MIN & ":" & @SEC & "> " & $i & " " & round(TimerDiff($a),2) & @CRLF) Next EndFunc ;==>CheckClientMessages Func AddTextToEdit($text) If $previousEditMsg <> $text Then $previousEditMsg = $text GUICtrlSetData($Edit1, GUICtrlRead($Edit1) & @YEAR & "." & @MON & "." & @MDAY & " - " & @HOUR & ":" & @MIN & ":" & @SEC & "> " & $text & @CRLF) _GUICtrlEdit_Scroll($Edit1, $SB_SCROLLCARET) EndIf EndFunc ;==>AddTextToEdit



      My issue now is that the mechanism with with StdoutRead and StdinWrite is not efficient at all. The more instances I start the slower it gets. This is not just a bit slower (like a fraction of a second), but to the degree that the front end is not responding at all any longer (with 3 instances handling).

      So my questions are:

      1.       Is there a flaw in my implementation with StdoutRead and StdinWrite? (note that all works fine with 1 and also (slower) with 2 instances running) but actually breaks down with 3 instances running.

      2.       Can I optimize the currently used implementation so that I can control 30+ instances?

      3.       What other implementation do you see suitable for this approach?

      a.       I have already tried it with communication through files but observed that this is not sufficiently reliable with multiple instances.

      b.       Is Named Pipes a more performant approach (I am a bit scared of the effort to learn and implement this)

      c.       Any other method?


       
      Many thanks in advance

      -dubi





    • nill
      By nill
      $user = "root" $password ="" $host ="5.0.0.1" $port ="22" $puty_exe = @ScriptDir & "\putty.exe"; putty salve local folder script $command = Run(@comspec & " /C "&$puty_exe&" -ssh -l "&$user&" "&$host&" p "&$port&" -pw "&$password,@ScriptDir, @SW_HIDE, 1) While 1 $data = StdoutRead($command) ConsoleWrite($data&@CRLF) If @error Then ExitLoop Wend Why StdoutRead cant read what putty.exe write in console ?
    • Jeemo
      By Jeemo
      Hello AutoIt community,
      I'm working on a project where I need to copy the bits of a file to a child process's StdIn stream. It's almost complete, but I have one final snag. 
      The script uses DLLStructCreate to create a 64K buffer that holds the file's binary contents in memory. It iterates through the contents of the desired file in 64K segments, first pulling the bits into the buffer, then dumping the contents of the buffer out to the child process's StdIn stream. The child process then ultimately reconstitutes the file on a remote system.
      The child process does build the file in the target directory, but the problem is that the reconstituted file is always slightly larger than the original because it's always a multiple of 64K (when you look at the file properties, the size is always identical to the "size on disk" value). Storage consumption is not a concern since it's technically not occupying more space than the original, but file integrity is a concern. Not every file shows any signs of corruption when opening it, but some of the reconstituted files are completely useless because of this.
      I know that the problem lies within the last iteration of reading the source file; for example, if there are only 24K bits remaining to be read, that data gets stored in the DLLStruct along with 40K of zeros to fill the entire 64K buffer. When the file is reconstituted, this padding of zeroes is unfortunately also included. So my challenge is to try to figure out how to ignore these trailing zeroes while reading the final <64K bits of the file, or at least only send part of the contents of the DLLStruct buffer to StdIn. Does anyone know how to go about doing this?
      Thanks in advance,
      Jeemo