繁体   English   中英

使用Windows脚本宿主从WshShell.Exec捕获输出

[英]Capturing output from WshShell.Exec using Windows Script Host

我编写了以下两个函数,并从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);
  }
 }

}

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

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

我能做什么?

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

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

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

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

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

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

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

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

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

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

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

zzz

至:

%comspec% /c zzz 2>&1 

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

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

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

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

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

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

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

是的,当涉及终端输出时,Exec功能似乎被打破了。

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

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

一个建议是始终从stdoutstderr读取。 您可以将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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM