vba WScript.Shell Exec 的实时控制台输出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28353522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-12 06:07:26  来源:igfitidea点击:

Real time console output from WScript.Shell Exec

excel-vbavbscriptconsolewshvba

提问by Skytunnel

I spent most of the day searching for a solution to this, I'm starting to think its maybe not possible for my requirements

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

My basic setup is to run a vbscript (.vbs) called from an excel vba code. The vba code has to continue on and leave the vbscript running, but will monitor it from time to time using Exec.Status

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

In the vbscript I'm using WScript.StdOut.WriteLine "whatever"to track/debug it's progress, but as it stands I can only read it's output after the excel vba code is finished what it needs to do.

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

What I want is to see a real time output to the console from the vbscript

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

Here's the vba code...

这是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)

I have been able to get a real time output by converting from WSH.Execto WSH.Run, but I do need the access to Exec.Status, which is not available under WSH.Run

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



UPDATE - 2015-02-06

更新 - 2015-02-06

To clarify further... Using the example '...B.vbs' code provided by @Ekkehard.Horner's answer... The following excel-vba code WILL display a real-time output to the console...

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

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

...but the following WILL NOT display anything to the console

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

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

I can't use the .Run()because I use the .Statusflag from .Exec()Also I can't just move the vbscript into the VBA code because the VBA goes on to do other tasks in parallel with the vbscript.

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

P.s. If anyone can submit an answer explaining why it can't be done, then I will mark that as accepted.

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

回答by Ekkehard.Horner

Use .Stdout.ReadLine() until the process has finished and .Stdout.ReadAll() to slurp the rest of the output - as in

使用 .Stdout.ReadLine() 直到该过程完成并使用 .Stdout.ReadAll() 来处理剩余的输出 - 如

28353522-A.vbs

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

28353522-B.vbs

Option Explicit

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

output:

输出:

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

BTW - How did you get real-time output with .Run()?

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

回答by user4532213

Why are you running two files? There is no need.

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

VBA, being full basic, can write to it's own console.

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

So put your vbs into VBA (you can cut and paste VBS into VBA and it will work if you put sub/end sub around it). To have the VBS run in VBA put a timer that fires it.

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

Here's a class module for VBA to create/read/write consoles.

这是 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

回答by Skytunnel

I've come up with an answer to my own question. Though this isn't a preferred solution (as I will explain below), so I'll not be marking as correct, but maybe someone can fix the issues with solution? (if so, post an answer and I'll mark as correct)

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

First off, +1 to @Ekkehard.Horner's answer for inspiring this solution.

首先,+1 到@Ekkehard.Horner 的回答以激发这个解决方案。

Create the file 'B.vbs' representing my main vbscript to be run.

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

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

Create the file 'A.vbs' to act as a middle man between the main vbscript and my Excel VBA code

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

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

Now the excel VBA code...

现在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

So the Excel VBA calls the 'A.vbs' still using WSH.Exec(), then that will call the 'B.vbs' using WSH.Run(), which opens a second console window which will display the real-time output

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

Advantages

好处

  • Excel VBA can still monitor the Exec.Statusaccurately
  • Progress of 'B.vbs' can be viewed from real-time console output
  • Excel VBA 仍可Exec.Status准确监控
  • 'B.vbs' 的进度可以从实时控制台输出中查看

Disadvantages(reasons I'm not marking as correct)

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

  • The Exec.Terminate()will only terminate the 'A.vbs' (first console window), the 'B.vbs' will remain running
  • The Exec.StdOut.cannot read the output from 'B.vbs'
  • Exec.Terminate()只会终止“A.vbs”(第一个控制台窗口)中,“B.vbs”将保持运行
  • Exec.StdOut.无法读取“B.vbs”输出