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

riv*_*ivy 3 vbscript stdin io-redirection

我试图处理/过滤器输入一个VBScript中,但只有在输入已通过管道输送到脚本.我不希望脚本处理用户/键盘输入.我想用这样的代码编写代码:

stdin_is_tty = ...
if not stdin_is_tty then
   ...
   input = WScript.StdIn.ReadAll
end if
Run Code Online (Sandbox Code Playgroud)

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

在C#中,我使用:

stdin_is_tty = not System.Console.IsInputRedirected // NET 4.5+
Run Code Online (Sandbox Code Playgroud)

接受答案问题:"如果Console.In(标准输入)已经被重定向如何检测?" 显示如何通过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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

MC *_* ND 5

总之,不,但......

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

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

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

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

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

  • Shell.Application.ShellExecute 有同样的问题 WshShell.Run

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

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

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

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

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

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

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

被保存为 .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>
Run Code Online (Sandbox Code Playgroud)

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

然后,可以调用该过程

< inputfile.txt scriptwrapper.cmd             input redirected
type inputfile.txt | scriptwrapper.cmd        input piped
scriptwapper.cmd                              no redirection
Run Code Online (Sandbox Code Playgroud)

虽然这是处理它的一种方便的方法,但是在稳定且无问题地工作的情况下调用.wsf部件.cmd依赖于脚本host/cmd组合的未记录的行为.

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