2017-04-01 54 views
1

我在一個綁定位... 我試圖使用HTA作爲GUI以更現代的方式運行一些命令行工具...除非它不是很完美如我所願。HTA作爲命令行GUI

到目前爲止,我已經嘗試了一些技巧來獲得我想要完成的任務,但是每一個技巧都落後於我所追求的目標。

我的目標是讓我的腳本運行一個exe,它作爲一個命令行工具執行,並實時捕獲輸出,解析每一行,並在HTA窗口中顯示某些內容以指示該工具在後面執行的操作場景,比如進度條或某種花哨的格式化日誌輸出,而不是cmd窗口的控制檯文本。

問題是,我見過讓HTA運行另一個程序的唯一方法是WScript.Shell的.Run或.Exec函數。每個人都有自己的頭痛,讓我的GUI不能完成工作。 .Exec最初似乎是我想要的,因爲我的意圖是從命令行捕獲stdout並在打印時解析每一行,但是當我嘗試使用它時,我注意到一個大的空白CMD窗口出現並阻塞大部分的屏幕直到exe完成運行。是的,HTA捕獲輸出並按照我的希望進行響應,但是空白的CMD窗口有點違背了製作GUI的目的。 我試過。運行,但雖然這讓我運行在一個隱藏的窗口的EXE,我不能實時捕獲標準輸出,最好我只能轉儲它到一個TXT文件,並嘗試在事後閱讀它。

不用說,也不是完全是我正在尋找...

作爲一個真實的世界(ISH)例如什麼我會尋找創造,想象運行運行的HTA與平像10坪。我想要顯示一個進度條來顯示已完成的Ping數量,其中一些文本顯示每次Ping的平均總數,例如ping的數量與超時的成功次數,每次命中的平均ms數量以及等等。然後當ping工具完成運行時,HTA將刪除進度條並顯示ping測試的最終總數。 使用.Exec,這在技術上是有效的,但是我一直有一個坐在屏幕上某個地方的命令窗口,這很煩人,通常會阻止HTA。 對於.Run,​​HTA在10個ping運行時似乎掛起,並且只返回0的返回錯誤代碼,然後我必須加載一個文本文件以查看最終在HTA中顯示結果之前發生了什麼。 ..肯定不是理想的結果。

我覺得我錯過了一些重要的東西......我如何獲得HTA在隱藏窗口中運行命令行可執行文件,同時仍能夠實時捕獲輸出行? (我不介意使用JScript setTimeout或類似的實時操作)我想避免出現命令提示符窗口,我希望能夠在GUI中顯示輸出,而不必等待爲它完成並浪費時間寫一個文件,讀取它,然後刪除它。

+0

檢查[此WSH VBS GUI](https://stackoverflow.com/a/47111556/2165759)解決方案和[此控制檯窗口隱藏](https://stackoverflow.com/a/32302212/2165759)方法。 – omegastripes

回答

1

如果您從隱藏控制檯內運行的cscript開始執行Exec操作,則啓動的進程也將被隱藏。但是這不會發生從.htaExec方法創建一個控制檯附加到執行的進程,並且沒有方法隱藏它沒有第三方代碼。

因此,處理命令輸出解析的通常方法是將輸出重定向到一個文件,然後讀取文件,當然會丟失寫入已啓動進程的StdIn流的選項。

要一個過程的輸出重定向到一個文件,第一個想法就是使用類似

>"outputFile" command 

但問題是,試圖執行此直接將無法正常工作。這種重定向是不是操作系統的一部分,但cmd.exe運營商,所以,我們需要像

cmd /c" >"outputFile" command " 

我們還需要一種方法來知道如果這個過程已經結束運行的東西,並沒有更多的數據將可用。我們可以使用Run方法的Wait參數,但這會使.hta接口凍結,並被Run方法阻塞。

不能使用Wait參數,我們必須啓動進程,獲取進程ID並等待它結束並讀取其所有數據,或者定期檢索輸出,直到進程結束。

你有一個基本類(ProcessOutputMonitor)在這個測試中實現了此方法.hta

<html> 
<head> 
<title>pingMonitor</title> 
<HTA:APPLICATION 
    ID="pingMonitor" 
    APPLICATIONNAME="pingMonitorHTA" 
    MINIMIZEBUTTON="no" 
    MAXIMIZEBUTTON="no" 
    SINGLEINSTANCE="no" 
    SysMenu="no" 
    BORDER="thin" 
/> 
<script type="text/vbscript" > 

Class ProcessOutputMonitor 

    Dim shell, fso, wmi 
    Dim processID, retCode, processQuery 
    Dim outputFile, inputFile 

    Private Sub Class_Initialize 
     Set fso  = CreateObject("Scripting.FileSystemObject") 
     Set shell = CreateObject("WScript.Shell") 
     Set wmi  = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2") 
     Set inputFile = Nothing 
    End Sub 

    Private Sub Class_Terminate 
     freeResources 
    End Sub 

    Public Default Function Start(ByVal commandLine) 
    Const SW_HIDE = 0 
    Const SW_NORMAL = 1 
    Const TemporaryFolder = 2 
    Dim startUp 

     Start = False 
     If Not IsEmpty(processID) Then Exit Function 

     outputFile = fso.BuildPath(_ 
      fso.GetSpecialFolder(TemporaryFolder) _ 
      , Left(CreateObject("Scriptlet.TypeLib").GUID,38) & ".tmp" _ 
     ) 

     ' "%comspec%" /c">"outputFile" command arguments " 
     commandLine = Join(_ 
      Array(_ 
       quote(shell.ExpandEnvironmentStrings("%comspec%")) _ 
       , "/c"">" & quote(outputFile) _ 
       , commandLine _ 
       , """" _ 
      ) _ 
      , " " _ 
     ) 

     ' https://msdn.microsoft.com/en-us/library/aa394375%28v=vs.85%29.aspx 
     Set startUp = wmi.Get("Win32_ProcessStartup").SpawnInstance_ 
     startUp.ShowWindow = SW_HIDE 

     retCode = wmi.Get("Win32_Process").Create(commandLine , Null, startUp, processID) 
     If retCode <> 0 Then 
      freeResources 
      Exit Function 
     End If 

     processQuery = "SELECT ProcessID From Win32_Process WHERE ProcessID=" & processID 


     Start = True 
    End Function 

    Public Property Get StartReturnCode 
     StartReturnCode = retCode 
    End Property 

    Public Property Get WasStarted 
    End Property 

    Public Property Get PID 
     PID = processID 
    End Property 

    Public Property Get IsRunning() 
     IsRunning = False 
     If Not IsEmpty(processID) Then 
      If getWMIProcess() Is Nothing Then 
       processID = Empty 
       freeResources 
      Else 
       IsRunning = True 
      End If 
     End If 
    End Property 

    Public Property Get NextLine 
     NextLine = getFromInputFile("line") 
    End Property 

    Public Property Get NextData 
     NextData = getFromInputFile("all") 
    End Property 

    Private Function getFromInputFile(what) 
    Const ForReading = 1 
     getFromInputFile = Empty 
     If Not IsEmpty(processID) Then 
      If inputFile Is Nothing Then 
       If fso.FileExists(outputFile) Then 
        Set inputFile = fso.GetFile(outputFile).OpenAsTextStream(ForReading) 
       End If 
      End If 
      If Not (inputFile Is Nothing) Then 
       If Not inputFile.AtEndOfStream Then 
        Select Case what 
         Case "line" : getFromInputFile = inputFile.ReadLine() 
         Case "all" : getFromInputFile = inputFile.ReadAll() 
        End Select 
       End If 
      End If 
     End If 
    End Function 

    Private Function quote(text) 
     quote = """" & text & """" 
    End Function 

    Private Function getWMIProcess() 
    Const wbemFlagForwardOnly = 32 
    Dim process 
     Set getWMIProcess = Nothing 
     If Not IsEmpty(processID) Then 
      For Each process In wmi.ExecQuery(processQuery, "WQL", wbemFlagForwardOnly) 
       Set getWMIProcess = process 
      Next 
     End If 
    End Function 

    Private Sub freeResources() 
    Dim process 
     Set process = getWMIProcess() 
     If Not (process Is Nothing) Then 
      process.Terminate 
     End If 
     processID = Empty 
     processQuery = Empty 
     If Not (inputFile Is Nothing) Then 
      inputFile.Close 
      Set inputFile = Nothing 
      fso.DeleteFile outputFile 
     End If 
    End Sub 

End Class 

</script> 

<SCRIPT LANGUAGE="VBScript"> 

Dim timerID 
Dim monitorGoogle, monitorMicrosoft 

Sub Window_onLoad 
    window.resizeTo 1024,400 
    Set monitorGoogle = New ProcessOutputMonitor 
     monitorGoogle.Start "ping -4 www.google.com" 
    Set monitorMicrosoft = New ProcessOutputMonitor 
     monitorMicrosoft.Start "ping -4 www.microsoft.com" 

    timerID = window.setInterval(GetRef("monitorPings"), 500) 
End Sub 

Sub monitorPings 
Dim buffer, keepRunning 
    keepRunning = False 

    buffer = monitorGoogle.NextData 
    If Not IsEmpty(buffer) Then 
     google.innerHTML = google.innerHTML & Replace(buffer, vbCrLf, "<br>") 
     keepRunning = True 
    Else 
     keepRunning = CBool(keepRunning Or monitorGoogle.IsRunning) 
    End If 

    buffer = monitorMicrosoft.NextData 
    If Not IsEmpty(buffer) Then 
     microsoft.innerHTML = microsoft.innerHTML & Replace(buffer, vbCrLf, "<br>") 
     keepRunning = True 
    Else 
     keepRunning = CBool(keepRunning Or monitorMicrosoft.IsRunning) 
    End If 

    If Not keepRunning Then 
     window.clearInterval(timerID) 
     timerID = Empty 
     alert("Done") 
    End If 

End Sub 

Sub ExitProgram 
    If Not IsEmpty(timerID) Then window.clearInterval(timerID) 
    window.close() 
End Sub 

</SCRIPT> 

</head> 

<body> 
    <input id="checkButton" type="button" value="EXIT" name="run_button" onClick="ExitProgram" align="right"> 
<br><br> 
    <span id="CurrentTime"></span> 
<br><br> 
    <table style="width:100%"> 
     <tr><th style="width:50%;">microsoft</th><th style="width:50%">google</th></tr> 
     <tr> 
      <td id="microsoft" style="font-family=courier;font-size:0.6em;vertical-align:top;"></td> 
      <td id="google" style="font-family=courier;font-size:0.6em;vertical-align:top;"></td> 
     </tr> 
    </table> 

</body> 
</html> 

這將簡單地啓動兩個ping過程,並使用window.setInterval將檢索每500毫秒這兩個過程的輸出,並追加它(不好的方法,只是測試代碼)到輸出,直到兩個進程結束。

+0

儘管這很有幫助,但我需要澄清一點,以確保我自己的版本能夠正常工作。首先,當我嘗試將輸出發送到文件時,它似乎一次將所有輸出發送到文件,但這可能是因爲該函數通常只在幾ms內完成。但是,如果我正確讀取它,則示例表明,較長的進程(如ping)會在發生每行到文件時將其輸出,並且可能會重複讀取該文件以在每個新行後面看到每條新行被附加到文件中,使腳本有機會看到進程的運行狀態。 – Ceetch

+0

@提取,正確。我包含了'ping'情況(默認情況下4個數據包在它們之間發送,包含第二個數據包)以及使用'.ReadAll'方法來清除'monitorPings'子例程以增量方式檢索數據。如果進程速度很快,它將在間隔開始之前寫入所有輸出,並且第一次讀取操作將檢索所有數據。 –

+0

這有助於瞭解。 adb進程依賴於後臺服務來連接設備。在這些情況下,運行'adb devices'將首先檢查服務是否已經運行,如果沒有運行則啓動服務。在沒有啓動的情況下,該過程將打印額外的行以顯示它正在啓動服務,並且在服務加載之前似乎掛起,然後繼續列出連接的設備。如果我能在流程結束之前閱讀輸出結果,我將有機會抓住服務器啓動並在hta的進展中顯示它。 – Ceetch