【问题标题】:VBscript code to capture stdout, without showing console window用于捕获标准输出的 VBscript 代码,而不显示控制台窗口
【发布时间】:2011-02-07 09:13:40
【问题描述】:

这是一个 VBScript 代码示例,展示了如何捕获命令行程序发送到标准输出的任何内容。 它执行命令xcopy /? 并在消息框中显示输出。在消息框出现之前,您会在一瞬间看到控制台窗口弹出。

Set objShell = WScript.CreateObject("WScript.Shell")
Set objExec = objShell.Exec("xcopy /?")
Do
    line = objExec.StdOut.ReadLine()
    s = s & line & vbcrlf
Loop While Not objExec.Stdout.atEndOfStream
WScript.Echo s

这是另一个 VBScript 代码示例,它展示了如何在不显示控制台窗口的情况下执行脚本。

objShell.Run "c:\temp\mybatch.bat C:\WINDOWS\system32\cmd.exe", 0

objShell.Run "c:\temp\myscript.vbs C:\WINDOWS\system32\cscript.exe", 0

如您所见,它的格式为<script><space><executor>。 最后一个示例使用objShell.Run 而不是objShell.Exec

我不知道如何执行命令行程序(如果需要从批处理文件中),捕获标准输出,而不显示控制台窗口。有什么想法吗?

【问题讨论】:

  • 您可以使用.Exec() 方法,没有控制台窗口闪烁、临时文件和意外的WScript.Echo 输出静音,请检查this answer

标签: vbscript batch-file stdout


【解决方案1】:

我通常用这个:

Wscript.echo execStdOut("ping google.com")

Function execStdOut(cmd)
   Dim goWSH : Set goWSH = CreateObject( "WScript.Shell" ) 
   Dim aRet: Set aRet = goWSH.exec(cmd)
   execStdOut = aRet.StdOut.ReadAll()
End Function 

对于更高级的命令,你可以使用 comspec (cmd)

my res = execStdOut("%comspec%" & " /c " & """" & "dir /b c:\windows\*.exe" & """" & " && Echo. && Echo finished") 

【讨论】:

  • 请注意,如果您的程序输出过多(大约 4KB IIRC)并且它没有被隐藏,这将挂起。
  • @CamiloMartin 您从哪里获得 4KB 限制?这是StdOut.ReadAll() 的限制吗?我刚刚在一个 109KB 的文件上使用了StdOut.ReadLine(),没有问题,每个ss64.com/vb/stdoutread.html。尽管我可以确认“未隐藏”的观察结果,但根据 OP,对于那些将“隐藏窗口”作为严格要求的人来说,此解决方案并不理想。
  • @QZSupport 老实说,我已经没有最微弱的线索了,但可能我试过了,在 4KB 之后挂了一些东西,然后另外两个人过来投了赞成票,因为他们也挂了一些东西。如果它确实有效,那就太好了,只需确保在您支持的所有 Windows 版本上进行测试。 (另外,应该注意的是:很可能 ReadAll 会将所有内容读入 RAM,并且它本身会非常难看 - 我会说你应该改为流式传输 StdOut,但又一次我不知道怎么做;ReadLine 不适合二进制输出)。
  • @CamiloMartin 编辑:我刚刚阅读了 1145 行大约 160KB 的文本,并且在 Windows XP SP3 上运行良好(只要我记得使用 cmd /c 而不是 cmd /k)。对于读取多行输出,我认为这个解决方案对于大多数人来说是完全足够的,但我建议使用 ReadLine() 而不是 ReadAll()。关于 4KB 的限制,这里进一步解释:support.microsoft.com/en-us/kb/960246
  • @PankajJaju 谢谢。根据同一篇文章Generally, this occurs if the spawned application is writing to both the StdOut and StdErr streams。因此,不能同时写入 stderrstdout 的应用程序不应遭受此错误。
【解决方案2】:

要将输出重定向到控制台,请使用 cscript 运行脚本,例如:c:\cscript myscript.vbs

cscript 有一些命令行选项。 (对我而言)最重要的是开关 //NOLOGO。如果你使用它 (cscript //nologo myscript.vbs) 它将省略微软商品...

【讨论】:

    【解决方案3】:

    这个概念证明脚本:

    ' pocBTicks.vbs - poor man's version of backticks (POC)
    
    Option Explicit
    
    ' Globals
    
    Const SW_SHOWMINNOACTIVE =  7
    Const ForReading         =  1
    
    Dim goFS  : Set goFS  = CreateObject( "Scripting.FileSystemObject" )
    Dim goWSH : Set goWSH = CreateObject( "WScript.Shell" )
    
    ' Dispatch
    WScript.Quit demoBTicks()
    
    ' demoBTicks -
    Function demoBTicks()
      demoBTicks = 1
      Dim aCmds : aCmds = Array( _
          "dir pocBTicks.vbs" _
        , "dur pocBTicks.vbs" _
        , "xcopy /?" _
      )
      Dim sCmd
      For Each sCmd In aCmds
          WScript.Echo "########", sCmd
          Dim aRet : aRet = BTicks( sCmd )
          Dim nIdx
          For nIdx = 0 To UBound( aRet )
              WScript.Echo "--------", nIdx
              WScript.Echo aRet( nIdx )
          Next
      Next
      demoBTicks = 0
    End Function ' demoBTicks
    
    ' BTicks - execute sCmd via WSH.Run
    '  aRet( 0 ) : goWSH.Run() result
    '  aRet( 1 ) : StdErr / error message
    '  aRet( 2 ) : StdOut
    '  aRet( 3 ) : command to run
    Function BTicks( sCmd )
      Dim aRet    : aRet     = Array( -1, "", "", "" )
      Dim sFSpec2 : sFSpec2  = goFS.GetAbsolutePathName( "." )
      Dim sFSpec1 : sFSpec1  = goFS.BuildPath( sFSpec2, goFS.GetTempName() )
                    sFSpec2  = goFS.BuildPath( sFSpec2, goFS.GetTempName() )
    
      aRet( 3 ) = """%COMSPEC%"" /c """ + sCmd + " 1>""" + sFSpec1 + """ 2>""" +  sFSpec2 + """"""
      Dim aErr
     On Error Resume Next
      aRet( 0 ) = goWSH.Run( aRet( 3 ), SW_SHOWMINNOACTIVE, True )
      aErr      = Array( Err.Number, Err.Description, Err.Source )
     On Error GoTo 0
      If 0 <> aErr( 0 ) Then
         aRet( 0 ) = aErr( 0 )
         aRet( 1 ) = Join( Array( aErr( 1 ), aErr( 2 ), "(BTicks)" ), vbCrLf )
         BTicks    = aRet
         Exit Function
      End If
    
      Dim nIdx : nIdx = 1
      Dim sFSpec
      For Each sFSpec In Array( sFSpec2, sFSpec1 )
          If goFS.FileExists( sFSpec ) Then
             Dim oFile : Set oFile = goFS.GetFile( sFSpec )
             If 0 < oFile.Size Then
                aRet( nIdx ) = oFile.OpenAsTextStream( ForReading ).ReadAll()
                goFS.DeleteFile sFSpec
             End If
          End If
          nIdx = nIdx + 1
      Next
      BTicks = aRet
    End Function
    

    展示了如何使用 .Run 和临时文件来获得带有隐藏控制台的反引号之类的东西。体面的文件处理、在 sCmd 中引用、清理返回的字符串以及处理编码将需要更多的工作。但也许您可以使用该策略来实现适合您需求的东西。

    【讨论】:

    • 一个巧妙的解决方案。可惜临时文件。但它符合要求,所以积分是你的。谢谢,马塞尔。
    【解决方案4】:

    如果您不介意出现 任务栏 按钮,您可以在启动前将控制台窗口移出屏幕。

    如果存在HKCU\Console\WindowPosition 键,Windows 将使用它的值来定位控制台窗口。如果密钥不存在,您将获得一个系统定位的窗口。

    所以,保存此键的原始值,设置您自己的值以将其置于屏幕外,调用Exec() 并捕获其输出,然后恢复该键的原始值。

    WindowPosition 键需要 32 位值。高位字是 X 坐标,低位字是 Y 坐标(XXXXYYYY)。

    With CreateObject("WScript.Shell")
    
        ' Save the original window position. If system-positioned, this key will not exist.
        On Error Resume Next
        intWindowPos = .RegRead("HKCU\Console\WindowPosition")
        On Error GoTo 0
    
        ' Set Y coordinate to something crazy...
        .RegWrite "HKCU\Console\WindowPosition", &H1000, "REG_DWORD"
    
        ' Run Exec() and capture output (already demonstrated by others)...
        .Exec(...)
    
        ' Restore window position, if previously set. Otherwise, remove key...
        If Len(intWindowPos) > 0 Then
            .RegWrite "HKCU\Console\WindowPosition", intWindowPos, "REG_DWORD"
        Else
            .RegDelete "HKCU\Console\WindowPosition"
        End If
    
    End With
    

    如果你真的想确保坐标在屏幕外,你可以通过 VBScript 使用 IE 或其他工具获取屏幕尺寸。

    【讨论】:

    • 但是任务栏还是有图标显示,怎么办?
    【解决方案5】:

    在 VBA 中返回 G:\OF 中的所有子文件夹

    sub M_snb()
      c00= createobejct("wscript.Shell").exec("cmd /c Dir G:\OF\*. /s/b").stdout.readall
    end sub
    

    将返回的字符串拆分成一个数组

    sub M_snb()
      sn=split(createobejct("wscript.Shell").exec("cmd /c Dir G:\OF\*. /s/b").stdout.readall,vbCrLf)
    
      for j=0 to ubound(sn)
         msgbox sn(j)
      next
    End Sub
    

    【讨论】:

    • -1 代表:createobejctcmd 之前缺少引号,并且未解决“隐藏”规范。
    【解决方案6】:

    这是你可以得到命令行 StdOut(结果)的方式,而不会在 vbscript 中看到这个弹出的黑色 dos 窗口:

    Set Sh = CreateObject("WScript.Shell")
    tFile=Sh.ExpandEnvironmentStrings("%Temp%")&"\t.txt"
    Sh.Run "cmd.exe /c xcopy /? > """&tFile&""" ",0,False
    Wscript.echo CreateObject("Scripting.FileSystemObject").openTextFile(tFile).readAll()
    

    【讨论】:

      【解决方案7】:

      考虑使用Win32_ProcessstartupInfo.ShowWindow = 0 来代替WScript.Shell 来启动带有SW_HIDE 的进程。我在VBS Run cmd.exe output to a variable; not text file下贴了一个详细的例子。

      【讨论】:

      • 有趣的代码。但我并没有真正看到这种情况下的优势,因为您仍然依赖于临时文件。
      猜你喜欢
      • 2010-10-21
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-09
      相关资源
      最近更新 更多