簡體   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