【问题标题】:Capturing output from WScript.Shell in real time实时捕获 WScript.Shell 的输出
【发布时间】:2012-06-16 08:58:04
【问题描述】:

我在 .hta 文件中使用 JScript(几乎与 VBScript 相同)来打开一个新的 shell 命令并捕获其输出。这是我在谷歌搜索后得到的结果:

var shell = new ActiveXObject("WScript.Shell")
var e = shell.Exec("%comspec% /c ping google.com 2>&1 ") 
while(!e.StdOut.AtEndofStream) {
    var line = e.StdOut.ReadLine()
    document.getElementById('log').value = line
}

这行得通。但是,它不是异步的。 while 循环导致我的 .hta 界面只是阻塞(UI 变得不可用),直到 shell 命令完成。如果我删除while loopshell.Exec 命令似乎不会阻塞,所以问题出在循环中。

我认为阻塞问题只是因为我处于 .hta 环境中而发生。如果我使用cscript.exe通过命令行运行我的脚本,这似乎不会发生

如何避免阻塞行为并实时访问我的命令输出?

【问题讨论】:

  • 循环仍然在 cscript.exe 中阻塞,但由于控制台窗口由辅助进程 (conhost/csrss) 控制,因此您不会注意到阻塞(这对于控制台应用程序来说是正常且正常的做)

标签: windows command-line vbscript hta jscript


【解决方案1】:

您可能必须通过反复调用setTimeOutfake a background thread,并且只在计时器回调中做少量工作......

【讨论】:

  • 你能扩展一下吗?哪里需要setTimeout?不是异步的吗?那么这有什么关系呢?
  • 用我链接到的代码替换 while 循环。 while 循环不允许 HTA 主机应用程序处理消息,setTimeout 将...
  • 这个问题实际上比我想象的要深。 ReadLine() 将阻塞,直到有东西要读。这就是界面停止的原因。有什么想法可以解决这个问题吗?
  • 可以用Read(1)一次读取一个字符,不知道有没有非阻塞的方式可以偷看看有没有要读的...
  • 嗯好主意,试过了,但 Read() 也会阻塞,直到有东西要读。从这里的方法来看,似乎没有其他解决方案:(这很烂TextStream Object
【解决方案2】:

正如 Anders 指出的,你需要实现伪多线程策略,因为 只要 JScript 代码(任何功能),您的 GUI 就不会对任何用户输入做出反应 被执行。

出于here 的原因,我使用了 setInterval() 而不是 setTimeout()。

这个.hta

<html>
 <head>
  <title>Ping HTA (JScript)</title>
  <HTA:APPLICATION
    APPLICATIONNAME="PingHTA"
  >
  <SCRIPT Language="JScript" src="ping.js"></SCRIPT>
 </head>
 <body onload="onLoadBody()">
  <form name="anonymous">
   <h2>Ping HTA (JScript)</h2>
   <hr>
   <input id="bttStartPing" type="BUTTON" value="StartPing" onclick="startPing()">
   <hr>
   <textarea id="taDisplay" rows="25" cols="80"></textarea>
  </form>
 </body>
</html>

和.js

//= app globals
g_sCmd      = '%comspec% /c ping.exe /n 8 google.com 2>&1';
g_taDisplay = null;
g_PSIHandle = 0;
g_PSILock   = false;
g_oExec     = null;

//= prep for log
function onLoadBody() {
  g_taDisplay = window.document.getElementById("taDisplay");
  log("OnLoadBody() done.");
}

//= log
function log(msg) {
  g_taDisplay.value = new Date().toString() + "\n   " + msg + "\n" + g_taDisplay.value;
}

//= start job
function startPing() {
  if (0 == g_PSIHandle) {
     g_PSIHandle = setInterval(stepPing, 100);
     log('setInterval(stepPing, 100)');
     g_oExec = new ActiveXObject("WScript.Shell").Exec(g_sCmd);
     log("started " + g_sCmd);
  } else {
     log('***** Ping is running!');
  }
}

//= one step of job
function stepPing() {
  if (! g_PSILock) {
     g_PSILock = true;
     if (null !== g_oExec) {
        if (g_oExec.StdOut.AtEndofStream) {
           clearInterval(g_PSIHandle);
           g_PSIHandle = 0;
           log('StdOut.AtEndofStream, terminate ping');
           g_oExec.Terminate();
           g_oExec = null;
        } else {
           log(g_oExec.StdOut.ReadLine());
        }
     }
     g_PSILock = false;
  }
}

应该证明:

  1. 在 .hta 中使用 .Exec 将打开一个 shell 窗口(对于某些人 这是一个取消资格)
  2. .hta 将开始接收 ping 输出行,在此之前 进程终止
  3. 在进程运行时单击按钮会穿插 带有 ping 输出的消息(尽管反应感觉 缓慢)

生产版本将不得不花费更多的精力来处理 .Exec 的可能状态和错误处理。

为此,请开始您的研究 here 和谷歌搜索“WshScriptExec 对象”。

【讨论】:

  • 太棒了!这行得通。有没有办法隐藏弹出的命令窗口?不过,这可能是一个不同的问题。
  • @Luca - 您可以隐藏控制台窗口(请参阅我的答案中的(1))
  • 在您的 (1) 答案中,您声明它将打开一个外壳窗口。我想避免这种情况。
  • 另外,是否有关于 new ActiveXObject("WScript.Shell").Exec(g_sCmd) 返回的文档?比如你在哪里找到Terminate() 命令和其他命令?我似乎无法在网络上找到此信息
  • @LucaMatteis,Exec 方法返回一个WshScriptExec 对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多