Javascript 使用 Windows Script Host 从 WshShell.Exec 捕获输出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2075886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-22 22:40:43  来源:igfitidea点击:

Capturing output from WshShell.Exec using Windows Script Host

stdoutjavascriptwsh

提问by Eric Johnson

I wrote the following two functions, and call the second ("callAndWait") from JavaScript running inside Windows Script Host. My overall intent is to call one command line program from another. That is, I'm running the initial scripting using cscript, and then trying to run something else (Ant) from that script.

我编写了以下两个函数,并从 Windows Script Host 中运行的 JavaScript 调用第二个函数(“callAndWait”)。我的总体意图是从另一个调用一个命令行程序。也就是说,我正在使用 cscript 运行初始脚本,然后尝试从该脚本运行其他内容(Ant)。

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 don't get immediate feedback about what the called program is doing. Instead, the output seems to come in fits and starts, sometimes waiting until the original program has finished, and sometimes it appears to have deadlocked. What I really want to do is have the spawned process actually share the same StdOut as the calling process, but I don't see a way to do that. Just setting oExec.StdOut = WScript.StdOut doesn't work.

不幸的是,当我运行我的程序时,我没有立即得到关于被调用程序正在做什么的反馈。相反,输出似乎断断续续地开始,有时等到原始程序完成,有时似乎已经死锁。我真正想做的是让生成的进程实际上与调用进程共享相同的 StdOut,但我看不出有什么方法可以做到这一点。只是设置 oExec.StdOut = WScript.StdOut 不起作用。

Is there an alternate way to spawn processes that will share the StdOut & StdErr of the launching process? I tried using "WshShell.Run(), but that gives me a "permission denied" error. That's problematic, because I don't want to have to tell my clients to change how their Windows environment is configured just to run my program.

是否有另一种方法可以生成共享启动进程的 StdOut 和 StdErr 的进程?我尝试使用“WshShell.Run(),但这给了我一个“权限被拒绝”的错误。这是有问题的,因为我不想告诉我的客户为了运行我的程序而改变他们的 Windows 环境的配置方式。

What can I do?

我能做什么?

回答by Ben

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

您不能以这种方式从脚本引擎中的 StdErr 和 StdOut 读取,因为没有代码大师 Bob 所说的非阻塞 IO。如果在您尝试从 StdOut 读取时被调用的进程填满了 StdErr 上的缓冲区(大约 4KB),反之亦然,那么您将死锁/挂起。在等待 StdOut 时你会饿死,它会阻塞等待你从 StdErr 读取。

The practical solution is to redirect StdErr to StdOut like this:

实际的解决方案是将 StdErr 重定向到 StdOut,如下所示:

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

In other words, what gets passed to CreateProcess is this:

换句话说,传递给 CreateProcess 的是:

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

This invokes CMD.EXE, which interprets the command line. /S /Cinvokes a special parsing rule so that the first and last quote are stripped off, and the remainder used as-isand executed by CMD.EXE. So CMD.EXE executes this:

这将调用 CMD.EXE,它解释命令行。/S /C调用一个特殊的解析规则,以便去掉第一个和最后一个引号,其余部分按原样使用并由 CMD.EXE 执行。所以 CMD.EXE 执行这个:

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

The incantation 2>&1redirects prog.exe's StdErr to StdOut. CMD.EXE will propagate the exit code.

咒语2>&1prog.exe的 StdErr重定向到 StdOut。CMD.EXE 将传播退出代码。

You can now succeed by reading from StdOut and ignoring StdErr.

您现在可以通过从 StdOut 读取并忽略 StdErr 来成功。

The downside is that the StdErr and StdOut output get mixed together. As long as they are recognisable you can probably work with this.

缺点是 StdErr 和 StdOut 输出混合在一起。只要它们是可识别的,您就可以使用它。

回答by Charles Wicksteed

Another technique which might help in this situation is to redirect the standard error stream of the command to accompany the standard output. Do this by adding "%comspec% /c" to the front and "2>&1" to the end of the execStr string. That is, change the command you run from:

在这种情况下可能有帮助的另一种技术是重定向命令的标准错误流以伴随标准输出。通过在 execStr 字符串的前面添加“%comspec% /c”并将“2>&1”添加到末尾来执行此操作。也就是说,更改您运行的命令:

zzz

to:

到:

%comspec% /c zzz 2>&1 

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

“2>&1”是一个重定向指令,它导致 StdErr 输出(文件描述符 2)被写入 StdOut 流(文件描述符 1)。您需要包含“%comspec% /c”部分,因为它是理解命令行重定向的命令解释器。请参阅http://technet.microsoft.com/en-us/library/ee156605.aspx
使用“%comspec%”而不是“cmd”可移植到更广泛的 Windows 版本。如果您的命令包含带引号的字符串参数,将它们弄对可能会很棘手:cmd 如何处理 "/c" 之后的引号的规范似乎不完整。

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

有了这个,您的脚本只需要读取 StdOut 流,并且将接收标准输出和标准错误。我将它与“net stop wuauserv”一起使用,它在成功时(如果服务正在运行)写入 StdOut,在失败时写入 StdErr(如果服务已经停止)。

回答by Code Master Bob

First, your loop is broken in that it always tries to read from oExec.StdOutfirst. If there is no actual output then it will hang until there is. You wont see any StdErroutput until StdOut.atEndOfStreambecomes true (probably when the child terminates). Unfortunately, there is no concept of non-blocking I/O in the script engine. That means calling readand having it return immediately if there is no data in the buffer. Thus there is probably no way to get this loop to work as you want. Second, WShell.Rundoes not provide any properties or methods to access the standard I/O of the child process. It creates the child in a separate window, totally isolated from the parent except for the return code. However, if all you want is to be able to SEE the output from the child then this might be acceptable. You will also be able to interact with the child (input) but only through the new window (see SendKeys).

首先,你的循环被破坏了,因为它总是试图从oExec.StdOut第一个读取。如果没有实际输出,那么它将挂起直到有。你不会看到任何StdErr输出,直到StdOut.atEndOfStream变为真(可能当孩子终止时)。不幸的是,脚本引擎中没有非阻塞 I/O 的概念。这意味着read如果缓冲区中没有数据,则立即调用并使其返回。因此,可能没有办法让这个循环按照你的意愿工作。第二,WShell.Run不提供任何属性或方法来访问子进程的标准 I/O。它在一个单独的窗口中创建子窗口,除了返回代码之外,完全与父窗口隔离。但是,如果您只想看到孩子的输出,那么这可能是可以接受的。您还可以与孩子互动(输入),但只能通过新窗口(请参阅 参考资料SendKeys)。

As for using ReadAll(), this would be even worse since it collects all the input from the stream before returning so you wouldn't see anything at all until the stream was closed. I have no idea why the exampleplaces the ReadAllin a loop which builds a string, a single if (!WScript.StdIn.AtEndOfStream)should be sufficient to avoid exceptions.

至于 using ReadAll(),这会更糟,因为它在返回之前从流中收集所有输入,因此在流关闭之前您根本看不到任何内容。我不知道为什么该示例ReadAll放在构建字符串的循环中,单个if (!WScript.StdIn.AtEndOfStream)应该足以避免异常。

Another alternative might be to use the process creation methods in WMI. How standard I/O is handled is not clear and there doesn't appear to be any way to allocate specific streams as StdIn/Out/Err. The only hope would be that the child would inherit these from the parent but that's what you want, isn't it? (This comment based upon an idea and a little bit of research but no actual testing.)

另一种替代方法可能是使用 WMI 中的进程创建方法。标准I / O的处理是不明确的,有没有出现任何的方式来分配特定的流为StdIn/ Out/ Err。唯一的希望是孩子会从父母那里继承这些,但这就是你想要的,不是吗?(此评论基于一个想法和一点研究,但没有实际测试。)

Basically, the scripting system is not designed for complicated interprocess communication/synchronisation.

基本上,脚本系统不是为复杂的进程间通信/同步而设计的。

Note: Tests confirming the above were performed on Windows XP Sp2 using Script version 5.6. Reference to current (5.8) manuals suggests no change.

注意:确认上述内容的测试是在 Windows XP Sp2 上使用脚本版本 5.6 执行的。参考当前 (5.8) 手册表明没有变化。

回答by Anders

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

是的,当涉及到终端输出时,Exec 函数似乎被破坏了。

I have been using a similar function function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());}that I call in a loop similar to yours. Not sure if checking for EOF and reading line by line is better or worse.

我一直在使用一个类似的函数function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());},我在类似于你的循环中调用它。不确定检查 EOF 和逐行阅读是好还是坏。

回答by klaus triendl

You might have hit the deadlock issue described on this Microsoft Supportsite.

您可能遇到了此Microsoft 支持站点上描述的死锁问题。

One suggestion is to always read both from stdoutand stderr. You could change readAllFromAnyto:

一个建议是始终读取 fromstdoutstderr。你可以readAllFromAny改为:

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;
}