【问题标题】:Real time console output from WScript.Shell ExecWScript.Shell Exec 的实时控制台输出
【发布时间】:2015-02-05 20:35:10
【问题描述】:

我花了大部分时间寻找解决方案,我开始认为这可能无法满足我的要求

我的基本设置是运行从 excel vba 代码调用的 vbscript (.vbs)。 vba 代码必须继续运行并保持 vbscript 运行,但会不时使用Exec.Status 监控它

在 vbscript 中,我使用 WScript.StdOut.WriteLine "whatever" 来跟踪/调试它的进度,但就目前而言,我只能在 excel vba 代码完成它需要做的事情后读取它的输出。

我想要的是看到从 vbscript 到控制台的实时输出

这是 vba 代码...

Dim WSH As IWshRuntimeLibrary.WshShell   'Windows Script Host Object Model
Dim Exec As WshExec 

Set WSH = CreateObject("WScript.Shell")
Set Exec = WSH.Exec("%COMSPEC% /C CSCRIPT.EXE //nologo " _
    & VbsFileDir _
    & " " & Arg1 _
    & " " & Arg2 _
    & " " & Arg3 _
    & " " & Arg4)

我已经能够通过从WSH.Exec 转换为WSH.Run 来获得实时输出,但我确实需要访问Exec.Status,这在WSH.Run 下不可用


更新 - 2015-02-06

进一步澄清...使用@Ekkehard.Horner的回答提供的示例'...B.vbs'代码...以下excel-vba代码将向控制台显示实时输出...

WSH.Run("cscript C:\28353522-B.vbs")

...但以下内容不会向控制台显示任何内容

WSH.Exec("cscript C:\28353522-B.vbs")

我不能使用.Run(),因为我使用来自.Exec().Status 标志 此外,我不能只将 vbscript 移动到 VBA 代码中,因为 VBA 会继续与 vbscript 并行执行其他任务。

附:如果有人可以提交解释为什么无法完成的答案,那么我会将其标记为已接受。

【问题讨论】:

    标签: excel vbscript console wsh vba


    【解决方案1】:

    使用 .Stdout.ReadLine() 直到该过程完成,然后使用 .Stdout.ReadAll() 啜饮其余的输出 - 如

    28353522-A.vbs

    Option Explicit
    
    Const WshFinished = 1
    
    Dim oExc : Set oExc = CreateObject("WScript.Shell").Exec("cscript 28353522-B.vbs")
    WScript.Echo "A", "start"
    Do While True
       If oExc.Status = WshFinished Then
          WScript.Echo "A", "WshFinished"
          Exit Do
       End If
       WScript.Sleep 500
       If Not oExc.Stdout.AtEndOfStream Then WScript.Echo "A", oExc.Stdout.ReadLine()
    Loop
    If Not oExc.Stdout.AtEndOfStream Then WScript.Echo "A", oExc.Stdout.ReadAll()
    

    28353522-B.vbs

    Option Explicit
    
    Dim i
    For i = 1 To 10
        WScript.Echo "B", i, "whatever"
        WScript.Sleep 100
    Next
    

    输出:

    cscript 28353522-A.vbs
    A start
    A B 1 whatever
    A B 2 whatever
    A B 3 whatever
    A WshFinished
    A B 4 whatever
    B 5 whatever
    B 6 whatever
    B 7 whatever
    B 8 whatever
    B 9 whatever
    B 10 whatever
    

    顺便说一句 - 您是如何使用 .Run() 获得实时输出的?

    【讨论】:

    • 今天早上我会试一试你的建议,然后告诉你。只是为了确保我做对了......我的excel vba代码将执行中间人(A.vbs),它将运行我的主vbscript(B.vbs)和Echo它的输出。但是,问题......如果我的 excel vba 代码以与它已经调用 B.vbs 相同的方式调用 A.vbs,那么为什么 A.vbs 会有更好的运气?而且我没有为 .Run() 做任何特别的事情,我只是替换了 .Exec() 并且它起作用了。
    • 所以我需要复制重写为 VBA 代码的 A 代码?问题在于我的 VBA 代码还有其他工作要做,我不能让它坐着看 B.vbs。我试图在这个问题上解释这一点。
    【解决方案2】:

    为什么要运行两个文件?没必要。

    VBA,完全基础,可以写入自己的控制台。

    因此,将您的 vbs 放入 VBA(您可以将 VBS 剪切并粘贴到 VBA 中,如果您将 sub/end sub 放在它周围,它将起作用)。要让 VBS 在 VBA 中运行,请放置一个触发它的计时器。

    这是一个用于 VBA 创建/读/写控制台的类模块。

    'User global var gconsole
    Public Function WriteToConsoles(sOut As String)
        If IsConsoleAvailable() = True Then
            Dim Result As Long, cWritten As Long
            Result = WriteConsole(hConsole, ByVal sOut, Len(sOut), cWritten, ByVal 0&)
        End If
    End Function
    
    Public Sub ExecuteCommand(Cmd As String, ReturnToPrompt As Boolean)
        If IsConsoleAvailable() = True Then
            If Len(Cmd) <> 0 Then
                If ReturnToPrompt = True Then
                    Shell Environ$("comspec") & " /k " & Cmd
                Else
                    Shell Environ$("comspec") & " /c " & Cmd
                End If
            End If
        End If
    End Sub
    
    Public Sub CreateConsole()
        If IsConsoleAvailable() = False Then
            If AllocConsole() Then
                hConsole = GetStdHandle(STD_OUTPUT_HANDLE)
                If hConsole = 0 Then MsgBox "Couldn't allocate STDOUT"
            Else
                MsgBox "Couldn't allocate console"
            End If
        End If
    End Sub
    
    Public Sub CloseConsole()
        If IsConsoleAvailable() = True Then
            CloseHandle hConsole
            hConsole = 0
            FreeConsole
        End If
    End Sub
    
    Public Function IsConsoleAvailable() As Boolean
            If hConsole <> 0 Then
                IsConsoleAvailable = True
            Else
                IsConsoleAvailable = False
            End If
    End Function
    

    【讨论】:

    • 公平地说,我已经简化了这个问题......它实际上运行了超过 2 个文件......实际上 VBA 代码充当了一个控制器,启动了多个 VBS 文件(所以多个工作并行运行)。我真的不想深入细节,因为它对于我的问题的主要范围来说太复杂了。感谢您的建议,我通常只使用 VBA 的 {debug.print} 作为控制台窗口,我以前从未真正见过您的方法!我可能会尝试一下。谢谢!
    【解决方案3】:

    我已经想出了我自己问题的答案。虽然这不是首选解决方案(正如我将在下面解释的那样),所以我不会将其标记为正确,但也许有人可以通过解决方案解决问题? (如果是,请发布答案,我会标记为正确)

    首先,对@Ekkehard.Horner 的答案+1 以启发此解决方案。

    创建代表我要运行的主 vbscript 的文件“B.vbs”。

    Option Explicit
    Dim i
    For i = 1 to 10
        Wscript.Echo "B", i, "whatever"
        Wscript.Sleep 100
    Next
    

    创建文件“A.vbs”作为主 vbscript 和我的 Excel VBA 代码之间的中间人

    Option Explicit
    Dim WSH
    Set WSH = CreateObject("WScript.Shell")
    WSH.Run "cscript C:\B.vbs", , True
    Set WSH = Nothing
    

    现在是 excel VBA 代码...

    Option Explicit
    Sub Test()
        Dim WSH As IWshRuntimeLibrary.WshShell
        Dim Exec As WshExec
        Set WSH = CreateObject("WScript.Shell")
    
        Set Exec = WSH.Exec("cscript C:\A.vbs")
    
        'Showing that I can still access the Exec.Status
        While Exec.Status = WshRunning
            Debug.Print "Running"
        Wend
        'But downside is nothing is avaiable from Stdout
        Debug.Print Exec.StdOut.ReadAll
    
        Set Exec = Nothing
        Set WSH = Nothing
    
    End Sub
    

    所以 Excel VBA 调用“A.vbs”仍然使用WSH.Exec(),然后它将使用WSH.Run() 调用“B.vbs”,这将打开第二个控制台窗口,该窗口将显示实时输出

    优势

    • Excel VBA 依然可以准确监控Exec.Status
    • 可以从实时控制台输出查看“B.vbs”的进度

    缺点(我没有标记为正确的原因)

    • Exec.Terminate() 只会终止“A.vbs”(第一个控制台窗口),“B.vbs”将继续运行
    • Exec.StdOut. 无法读取“B.vbs”的输出

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-22
      • 1970-01-01
      • 2011-05-28
      • 2017-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-12
      相关资源
      最近更新 更多