2017-04-01 56 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