HTA作为命令行GUI

时间:2017-04-01 05:43:17

标签: windows user-interface cmd hta

我有点束缚...... 我正在尝试使用HTA作为GUI以更现代的方式运行一些命令行实用程序......除了它不像我希望的那样。

到目前为止,我已经尝试了一些技巧来获得我想要的东西,但每一个都落后于我的目标。

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

麻烦的是,我看到让HTA运行另一个程序的唯一方法是WScript.Shell的.Run或.Exec函数。每个都有自己的头痛,使我的GUI不能完成它的工作。 .Exec似乎是我最初想要的,因为我的意图是从命令行捕获stdout并在打印时解析每一行,但是当我尝试使用它时,我注意到一个大的空白CMD窗口出现并阻塞大多数屏幕直到exe完成运行。是的,HTA抓住了输出并按照我的希望做出了回应,但那个空白的CMD窗口有点挫败了制作GUI的目的。 我试过.Run,​​但是虽然这让我在一个隐藏的窗口中运行exe,我无法实时捕获stdout,最多只能将它转储到txt文件中并尝试在事后读取它。

毋庸置疑,这也不是我想要的......

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

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

1 个答案:

答案 0 :(得分:1)

如果从隐藏控制台内运行的Exec启动cscript操作,则启动的进程也将被隐藏。但是.hta不会发生这种情况,Exec方法创建一个附加到执行过程的控制台,没有第三方代码就无法隐藏它。

因此,处理命令输出解析的常用方法是将输出重定向到文件然后读取文件,当然会丢失写入已启动进程的StdIn流的选项。

要将进程的输出重定向到文件,第一个想法是使用类似

的内容
>"outputFile" command

但问题是尝试直接执行此操作无效。此重定向不是操作系统的一部分,而是cmd.exe的运算符,因此,我们需要运行类似

的操作
cmd /c" >"outputFile" command "

我们还需要一种方法来了解流程是否已经结束,并且不再有可用的数据。我们可以使用Wait方法的Run参数,但这会使.hta接口冻结,被Run方法阻止。

我们不能使用Wait参数,我们必须启动进程,获取进程ID并等待它结束并读取其所有数据或定期检索输出,直到进程结束。 / p>

您有一个基本类(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两个进程的输出并将其(不好的方式,只是测试代码)附加到输出,直到两个进程结束了。