Jump to content

Stdout read from cmd.exe being cut short


Rishodi
 Share

Recommended Posts

I've written a couple of wrapper functions as follows to simplify interacting with the Windows command line interpreter. The first one executes commands, and the second one reads everything that was output to stdout.

Func RunCmd($sCmd, $sDir = Default, $showFlag = Default, $optFlag = Default)
    If ($sCmd == Default) Or ($sCmd == "") Then Return SetError(1, 0, 0)
    If ($sDir == Default) Then $sDir = ""
    If ($optFlag = Default) Then $optFlag = $STDERR_MERGED
    Local $cmdPID = Run(@COMSPEC & " /c " & $sCmd, $sDir, $showFlag, $optFlag)
    Return SetError(@error, @extended, $cmdPID)
EndFunc

Func StdoutReadAll($iPID)
    Local $output = ""
    Do
        $output &= StdoutRead($iPID)
    Until $error
    StdioClose($iPID)
    Return $output
EndFunc

The command I'm currently trying to use is "fsutil fsinfo drives" which reports a list of the active drive letters. I've since realized that there's a built-in function for doing just that (DriveGetDrive), but I want to know how to resolve my issue in case it pops up again when I'm using some command for which there isn't an existing function in AutoIt. Using the functions above, all I need to do is make a couple calls like so:

Local $cmdPID = RunCmd("fsutil fsinfo drives", @SystemDir)
Local $result = StdoutReadAll($cmdPID)
MsgBox(0, "Result", $result)

The problem is that $result only includes the output from stdout up through the first drive. The remaining drives are not listed. Curious, I decided to read data from stdout in binary format instead, and I found that null characters separate the drive letters instead of spaces.

Expected output is this in hex: 0x4472697665733A20433A5C20443A5C20573A5C20

Which in string format is this: Drives: C:\ D:\ W:\

Actual output is this in hex: 0x4472697665733A20433A5C00443A5C00573A5C00

Which in string format is this: Drives: C:\

I've removed the CRLFs here (0x0D0A) to highlight the problem, which is that null characters (0x00) are where the spaces (0x20) should be. AutoIt apparently lops off the rest of the string after encountering the first null character. How would I dodge this issue? If I read from stdout in binary instead of ASCII, is there a way to manipulate the data by replacing all occurrences of 0x00 with 0x20 before converting to a String? Is there (shouldn't there be) an easier way?

Edited by Rishodi
Link to comment
Share on other sites

Well, not the best solution at all but you might try to delay the exit from your Do/Until loop.

Func StdoutReadAll($iPID)
    Local $output = "", $STD_read = "", $S_Counter = 0
    While 1
        Sleep(10)
        $STD_read = StdoutRead($iPID)
        If $STD_read = "" Then
            $S_Counter += 1
        Else
            $output &=$STD_read
        EndIf
        If $S_Counter > 5 Then ExitLoop
    WEnd
    StdioClose($iPID)
    Return $output
EndFunc

I don't particulary like it because it doesn't rely on @error but it might work in your case. What you could do is make a second function (StdoutReadAll_2) to use only in this case.

SNMP_UDF ... for SNMPv1 and v2c so far, GetBulk and a new example script

wannabe "Unbeatable" Tic-Tac-Toe

Paper-Scissor-Rock ... try to beat it anyway :)

Link to comment
Share on other sites

StdoutRead does not block, it will return immediately. In order to get all data, it must be called in a loop.

Peeking on the stream does not remove the data from the buffer, however, it does return the available data as normal.

By default, data is returned in text format. By using the binary option, the data will be returned in binary format.

That is from the help file, it gives a sample like so:

#include <Constants.au3>
Local $foo = Run(@ComSpec & " /c dir foo.bar", @SystemDir, @SW_HIDE, $STDERR_CHILD + $STDOUT_CHILD)
Local $line
While 1
$line = StdoutRead($foo)
If @error Then ExitLoop
MsgBox(0, "STDOUT read:", $line)
Wend
Edited by maqleod
[u]You can download my projects at:[/u] Pulsar Software
Link to comment
Share on other sites

Strings are terminated by a null, but not binary data. Don't forget there is a binary parameter for StdReadOut()

:)

Edit: Where did "$error" come from in this:

Until $error

Shouldn't that be:

Until @error

I don't see your binary null in the output when I test this, by the way:

#include <Constants.au3>

Global $sExtCmd = "fsutil.exe fsinfo drives"
Global $iPID = Run($sExtCmd, @SystemDir, @SW_MINIMIZE, $STDOUT_CHILD)
Global $sOut = ""

Do
    $sOut &= StdoutRead($iPID)
Until @error

ConsoleWrite("$sOut  = " & $sOut & @LF)
MsgBox(64, "Result", "$sOut   = " & $sOut)

;)

Edited by PsaltyDS
Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law
Link to comment
Share on other sites

enaiman: Thanks, but unfortunately, that won't help me. I've tested my script to see how the data comes in from stdout in this particular case, and it's always the same every time. The first iteration of the loop finds nothing, the second iteration returns all data, and on the third iteration @error is non-zero and the loop exits.

maqleod: I'm aware of that, as I've read everything from the help file I could find that was relevant. Notice that the function I wrote calls StdoutRead in a loop; the only difference is that I'm using a Do-Until and the example in the help file uses a While loop.

PsaltyDS: Yes, I am aware that there's a binary parameter for StdoutRead(). Otherwise, I couldn't have discovered the fact that the CLI is printing null characters to stdout after each drive letter. And yes, $error should be @error in the script in the OP. ($error was just left over from some troubleshooting I was doing.)

I ran your test script, and there's still a null character after the first drive (the C:\ drive), so the rest of the drive listing is lost when the data is read as a string. What version of Windows are you running? I'm on XP SP3; I could certainly appreciate it if newer releases changed the behavior so that null characters were not printed in the middle of a command's output.

AndyG: I didn't expect your suggestion to work, but it did! I had assumed that calling StdoutRead() with binary=false was truncating the string at the first null character, but that's not the case. The string is not being truncated until it's displayed (console, msgbox, etc.), so the fix is as simple as calling StringReplace. Thanks!

Link to comment
Share on other sites

Sounds like you have your solution, but my demo didn't show the symptoms (no nulls) on Vista Business 32-bit. I did look at the binary value and the nulls just weren't there.

;)

Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law
Link to comment
Share on other sites

Sounds like you have your solution, but my demo didn't show the symptoms (no nulls) on Vista Business 32-bit. I did look at the binary value and the nulls just weren't there.

;)

I believe you, which is why I asked which OS you're using. Perhaps the output of the command was adjusted in Vista?

In any case, I think we can all agree that null characters shouldn't appear in the middle of a command's output stream like this.

Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...