是否有任何方法可以检测STDIN是否已在VBscript中重定向?

时间:2014-10-31 21:37:05

标签: vbscript stdin io-redirection

我正在尝试在VBscript中处理/过滤输入,但是如果输入已经通过管道传输到脚本中,则。我不希望脚本处理用户/键盘输入。我想将其编码为:

stdin_is_tty = ...
if not stdin_is_tty then
   ...
   input = WScript.StdIn.ReadAll
end if

否则,脚本将挂起,在执行WScript.StdIn.ReadAll时等待用户输入(如果我使用WScript.StdIn.AtEndOfStream测试流,则会更早。)

在C#中,我会使用:

stdin_is_tty = not System.Console.IsInputRedirected // NET 4.5+

accepted answer Q: "How to detect if Console.In (stdin) has been redirected?"显示了如何通过P / Invoke使用Win32调用构建该结果,适用于早于.NET 4.5的.NET版本。但我不知道有任何方法可以将该方法转换为VBscript。

我使用SendKeys构建了一个笨拙的部分解决方案,将流末尾序列发送到脚本的键盘缓冲区。但是如果STDIN 重定向,解决方案会将密钥保留在缓冲区中,除非我知道STDIN被重定向,否则我无法清理...所以,同样的问题。

我更喜欢将脚本保存在一个打包的部分中,所以我宁愿避免单独的包装脚本或通用Windows 7+安装中没有的任何内容。

任何出色的想法或解决方法?

编辑:添加了初始解决方案的副本

我在这里添加了我改进的初始解决方案的副本(诚然,这是一个“黑客”),它现在已经清理完毕,但仍然有几个底片:

input = ""
stdin_is_tty = False
test_string_length = 5 ' arbitrary N (coder determined to minimize collision with possible inputs)
sendkey_string = ""
test_string = ""
for i = 1 to test_string_size
   sendkey_string = sendkey_string & "{TAB}"
   test_string = test_string & CHR(9)
next
sendkey_string = sendkey_string & "{ENTER}"
wsh.sendkeys sendkey_string ' send keyboard string signal to self
set stdin = WScript.StdIn
do while not stdin.AtEndOfStream
   input = input & stdin.ReadLine
   if input = test_string then
       stdin_is_tty = True
   else
       input = input & stdin.ReadAll
   end if
   exit do
loop
stdin.Close
if not stdin_is_tty then
   set stdin = fso.OpenTextFile( "CON:", 1 )
   text = stdin.ReadLine
   stdin.Close
end if

这个解决方案有三个问题:

  1. 在命令行留下可见的痕迹(虽然现在只有一条空白线,但能见度低)

  2. 测试字符串(一系列N [编码器确定的] TAB后跟NEWLINE)的可能冲突与任何重定向输入的第一行导致误报重定向确定。由于可以修改TAB的数量,编码器可以任意降低这种可能性。

  3. 竞争条件,如果另一个窗口在执行SendKeys部分之前获得焦点,则错误的窗口将接收代码字符串,从而导致错误的否定重定向确定。我的估计是这种情况发生的可能性非常低。

1 个答案:

答案 0 :(得分:5)

简而言之,不,但......

我已经测试了我能想到的一切,并且没有找到合理的方法来做到这一点。

使用WScript.StdInfso.GetStdStream检索的TextStream包装器公开的属性/方法都没有提供足够的信息来确定输入是否被重定向/管道传送。

尝试从衍生进程的行为/环境中获取信息(如何创建可执行文件是其他故事)也不太可能有用,因为

  • WshShell.Execute总是在其输入和输出句柄重定向的情况下生成进程

  • WshShell.Run创建一个不继承当前句柄的新流程

  • Shell.Application.ShellExecuteWshShell.Run

  • 存在同样的问题

因此,这些方法都不允许生成的进程继承当前进程的句柄,以检查它们是否被重定向。

使用WMI从正在运行的进程中检索信息不会返回任何可用的内容(当有重定向时,进程的HandleCount属性会有所不同,但它不可靠)

因此,无法从vbs代码确定是否存在重定向,其余选项为

  1. 没有检测到:如果管道输入必须存在,则表现为more命令并且在所有情况下都尝试检索它

  2. 表示:如果管道输入并非总是需要,请使用参数来确定是否需要读取stdin流。

  3. 在我的例子中,我通常使用单个斜杠/作为参数(用于与一些findstr参数的一致性,这些参数也使用斜杠来表示stdin输入)。然后在vbs代码中

    If WScript.Arguments.Named.Exists("") Then 
        ' here the stdin read part
    End If
    
    1. 检查前:在启动脚本之前确定是否有重定向。需要一个包装器.cmd,但有一些技巧,这两个文件(.cmd.vbs)可以组合成一个
    2. 保存为.cmd

      <?xml : version="1.0" encoding="UTF-8" ?> ^<!------------------------- cmd ----
      @echo off
          setlocal enableextensions disabledelayedexpansion
          timeout 1 >nul 2>nul && set "arg=" || set "arg=/"
          endlocal & cscript //nologo "%~f0?.wsf" //job:mainJob %arg% %*
          exit /b
      ---------------------------------------------------------------------- wsf --->
      <package>
        <job id="mainJob">
          <script language="VBScript"><![CDATA[
              If WScript.Arguments.Named.Exists("") Then
                  Do Until WScript.StdIn.AtEndOfStream
                      WScript.StdOut.WriteLine WScript.StdIn.ReadLine
                  Loop
              Else
                  WScript.StdOut.WriteLine "Input is not redirected"
              End If
          ]]></script>
        </job>
      </package>
      

      这是存储在.wsf内的.cmd文件。批处理部分确定输入是否被重定向(timeout命令无法获得重定向输入的控制台句柄)并将参数传递给脚本部分。

      然后,可以将该过程调用为

      < inputfile.txt scriptwrapper.cmd             input redirected
      type inputfile.txt | scriptwrapper.cmd        input piped
      scriptwapper.cmd                              no redirection
      

      虽然这是处理它的一种方便的方法,但.wsf部分从.cmd调用,虽然稳定且无问题,但仍依赖于脚本host / cmd的未记录行为组合

      当然,您可以使用两个单独的文件执行相同的操作。不那么干净,但行为记录在案。