HTA作为命令行GUI

我在一个绑定点…我试图使用HTA作为GUI以更现代的方式运行一些命令行实用程序…除非它不像我所希望的那样。

到目前为止,我已经尝试了一些技巧来获得我想要完成的任务,但是每个技巧都在我所追求的目标上有所下降。

我的目标是让脚本运行一个作为命令行工具执行的exe文件,并实时捕获输出结果,parsing每一行,并在HTA窗口中显示某些内容,以指示该工具在后台执行的操作作为一个进度条或某种花哨的格式化日志输出,而不是cmd窗口的控制台文本。

麻烦的是,我所看到的让HTA运行另一个程序的唯一方法是WScript.Shell的.Run或.Exec函数。 每个人都有自己的头痛,让我的graphics用户界面不工作。 .Exec最初似乎是我想要的,因为我的意图是从命令行捕获stdout,并在打印时parsing每一行,但是当我尝试使用它时,我注意到一个大的空白CMD窗口出现并阻止了大多数直到exe完成运行。 是的,HTA捕获输出并按照我所希望的方式进行响应,但是空白的CMD窗口有点违背了制作GUI的目的。 我尝试过。但是,虽然这让我运行在一个隐藏的窗口中的EXE,我不能实时捕获标准输出,最好我只能转储到一个TXT文件,并试图在事后阅读。

毋庸置疑,这不是我正在寻找的…

作为一个真实的世界(ISH),我想要创build一个例子,想象一下运行一个像10 ping一样运行ping的HTA。 我想显示一个进度条来显示已经完成了多lessping,有些文本显示每个ping的发生平均总数,比如ping的次数是多less,平均ms是多less,以及等等。 然后,当ping工具结束运行时,HTA将删除进度条并显示pingtesting的最终总计。 使用.Exec,这在技术上是有效的,但是整个时间我都有一个坐在屏幕上的命令窗口,这很烦人,通常会阻塞HTA。 使用.Run,​​HTA在10个ping运行时似乎是挂起的,只能得到返回的错误代码0,然后我必须加载一个文本文件来查看最终在HTA中显示结果之前发生了什么。绝对不是理想的结果。

我觉得我错过了一些重要的东西…我怎么能得到一个HTA运行一个隐藏的窗口中的命令行可执行文件,同时仍然能够实时捕获输出线逐行? (我不介意使用JScript的setTimeout或类似的半实时)我想避免出现命令提示符窗口,我希望能够在GUI中显示输出,而不必等待为它完成,浪费时间写一个文件,读取它,然后删除它。

如果您从隐藏控制台中运行的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将检索每个500ms两个进程的输出,并将其追加(输出错误的方式,只是测试代码)到输出,直到两个进程结束。