Capturing output from WshShell.Exec using Windows Script Host

I wrote the following two functions and called the second ("callAndWait") from JavaScript running inside the Windows Script Host. My general intention is to invoke one command line from another. That is, I run the initial scripting using cscript, and then try to run something else (Ant) from this script.

function readAllFromAny(oExec) { if (!oExec.StdOut.AtEndOfStream) return oExec.StdOut.ReadLine(); if (!oExec.StdErr.AtEndOfStream) return "STDERR: " + oExec.StdErr.ReadLine(); return -1; } // Execute a command line function.... function callAndWait(execStr) { var oExec = WshShell.Exec(execStr); while (oExec.Status == 0) { WScript.Sleep(100); var output; while ( (output = readAllFromAny(oExec)) != -1) { WScript.StdOut.WriteLine(output); } } } 

Unfortunately, when I run my program, I do not receive immediate feedback on what the called program does. Instead, the conclusion seems to come in fits and starts, sometimes until the original program ends, and sometimes it seems to have reached an impasse. What I really want to do is that the spawned process actually uses the same StdOut as the calling process, but I see no way to do this. Just setting oExec.StdOut = WScript.StdOut does not work.

Is there an alternative way to create processes that will share the StdOut and StdErr of the startup process? I tried using “WshShell.Run (), but this gives me a“ permission denied ”error. This is problematic because I do not want my clients to change how their Windows environment is configured to only run my program.

What can I do?

+11
javascript stdout wsh
Jan 16
source share
5 answers

You cannot read from StdErr and StdOut in the script engine this way, since there is no non-blocking I / O, as Code Master Bob says. If the called process fills the buffer (about 4 KB) on StdErr when you try to read from StdOut or vice versa, then you will lock / hang. You will starve while waiting for StdOut, and it will block you from reading StdErr.

The practical solution is to redirect StdErr to StdOut as follows:

 sCommandLine = """c:\Path\To\prog.exe"" Argument1 argument2" Dim oExec Set oExec = WshShell.Exec("CMD /S /C "" " & sCommandLine & " 2>&1 """) 

In other words, what is passed to CreateProcess is the following:

 CMD /S /C " "c:\Path\To\prog.exe" Argument1 argument2 2>&1 " 

This calls CMD.EXE, which interprets the command line. /S /C calls a special parsing rule, so that the first and last quotes are deleted, and the remainder is used as-is and CMD.EXE is executed. So, CMD.EXE does the following:

 "c:\Path\To\prog.exe" Argument1 argument2 2>&1 

Spell 2>&1 redirects prog.exe StdErr to StdOut. CMD.EXE will distribute the exit code.

You can now succeed by reading StdOut and ignoring StdErr.

The disadvantage is that the output from StdErr and StdOut is mixed. As long as they are recognized, you can probably work with this.

+11
Jan 30 '12 at 11:30
source share

Another method that can help in this situation is to redirect the standard stream of command errors to accompany the standard output. Do this by adding "% comspec% / c" to the beginning and "2> & 1" to the end of the line execStr. That is, change the command you are working with:

 zzz 

at

 %comspec% /c zzz 2>&1 

"2> & 1" is a redirection command that causes the output of StdErr (file descriptor 2) to be written to the StdOut stream (file descriptor 1). You need to include the% comspec% / c part because it is a command interpreter that understands command line redirection. See http://technet.microsoft.com/en-us/library/ee156605.aspx
Using "% comspec%" instead of "cmd" provides portability to a wider range of versions of Windows. If your command contains quoted string arguments, it can be difficult to get them right: the specification of how cmd processes quotes after "/ c" seems incomplete.

In this case, your script only needs to read the StdOut stream and get both standard output and standard error. I used this with "net stop wuauserv", which writes StdOut about success (if the service is running) and StdErr on failure (if the service is already stopped).

+3
Jan 17 '12 at 20:13
source share

First, your loop is broken in that it always tries to read oExec.StdOut first. If there is no actual exit, it will hang until it appears. You will not see any output of StdErr until StdOut.atEndOfStream becomes true (possibly when the child finishes). Unfortunately, there is no concept of non-blocking I / O in a script block. This means calling read and immediately returning it if there is no data in the buffer. So there is probably no way to make this loop work the way you want. Secondly, WShell.Run does not provide any properties or methods for accessing the standard I / O of the child process. It creates the child in a separate window, completely isolated from the parent, with the exception of the return code. However, if all you want to be able to see the result is from a child, then this may be acceptable. You can also interact with a child (input), but only through a new window (see SendKeys ).

Regarding the use of ReadAll() , this will be even worse, since it collects all the input from the stream before returning so that you don't see anything at all until the stream has been closed. I don’t know why the example puts ReadAll in a loop that builds a string, one if (!WScript.StdIn.AtEndOfStream) should be enough to avoid exceptions.

Another alternative would be to use process creation methods in WMI. How standard I / O is handled is unclear, and there seems to be no way to allocate specific streams like StdIn / Out / Err . The only hope would be that the child inherits them from the parent, but what do you want, right? (This comment is based on an idea and a bit of research, but not really.)

In principle, the scripting system is not intended for complex interprocess communication / synchronization.

Note. Tests confirming the above were performed in Windows XP Sp2 using script version 5.6. The reference to the current (5.8) manuals does not suggest any changes.

+2
Oct 17 2018-11-11T00:
source share

Yes, the Exec function seems to be broken when it comes to terminal output.

I use a similar function function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());} , which I call in a loop like your . Not sure if checking EOF and reading line by line is better or worse.

+1
Jan 23 '10 at 11:35
source share

You may have encountered the deadlock problem described on this Microsoft support site .

One suggestion is to always read from both stdout and stderr . You can change readAllFromAny to:

 function readAllFromAny(oExec) { var output = ""; if (!oExec.StdOut.AtEndOfStream) output = output + oExec.StdOut.ReadLine(); if (!oExec.StdErr.AtEndOfStream) output = output + "STDERR: " + oExec.StdErr.ReadLine(); return output ? output : -1; } 
+1
Feb 05 '16 at 11:53 on
source share



All Articles