使用正确的退出代码报告从PowerShell调用批处理文件,同时避免插入符号加倍

使用正确的退出代码报告从PowerShell调用批处理文件,同时避免插入符号加倍,powershell,batch-file,cmd,parameter-passing,exit-code,Powershell,Batch File,Cmd,Parameter Passing,Exit Code,我怀疑没有好的解决办法,但也许我忽略了一些事情: 我想要的是一种方法: (a) 从PowerShell调用批处理文件的方式应能在PowerShell的automatic$LASTEXITCODE变量中可靠地反映其隐式或显式退出代码 值得注意的是,调用退出的批处理文件(例如,whoami-nosuch | | exit/b)应导致$LASTEXITCODE反映whoami的退出代码,即1。当您从PowerShell调用批处理文件(按名称或路径)时,情况并非如此:退出代码为0(相反,在cmd.e

我怀疑没有好的解决办法,但也许我忽略了一些事情:

我想要的是一种方法:

  • (a) 从PowerShell调用批处理文件的方式应能在PowerShell的automatic
    $LASTEXITCODE
    变量中可靠地反映其隐式或显式退出代码

    • 值得注意的是,调用退出的批处理文件(例如,
      whoami-nosuch | | exit/b
      )应导致
      $LASTEXITCODE
      反映
      whoami
      的退出代码,即
      1
      。当您从PowerShell调用批处理文件(按名称或路径)时,情况并非如此:退出代码为
      0
      (相反,在
      cmd.exe
      会话中
      %ERRORLEVEL%
      设置为
      1

    • 还请注意,调用应该与PowerShell的输出流保持集成,因此我不寻找基于的解决方案

    • 此外,我不知道或无法控制调用的批处理文件-我正在寻找一个通用的解决方案

  • (b) 没有以任何方式更改传递给批处理文件的双引号参数,也没有以任何方式修改
    cmd.exe
    的行为;值得注意的是:

    • ^
      字符不应加倍(见下文)
    • 使用
      /V:ON
      启用延迟扩展不是一个选项

我知道如何解决(a)的唯一方法是通过
cmd/c call
调用批处理文件

不幸的是,这违反了要求(b),因为在参数中使用
call
似乎总是将
^
字符加倍。(相反,不使用
call
则无法可靠地报告退出代码)

有没有办法同时满足这两个要求

请注意,PowerShell在这里只是信使:问题在于
cmd.exe
,任何从
cmd.exe
会话外部调用批处理文件的人都会面临同样的问题


示例(PowerShell代码):

输出应为:

["a ^ 2"]
1
然而,在现实中,退出代码并未报告:

["a ^ 2"]
0          # !! BROKEN
如果我使用
cmd/c调用。\test.cmd
进行调用,则退出代码是正确的,但
^
字符会加倍:

PS> cmd /c call .\test.cmd "a ^ 2"; $LASTEXITCODE
["a ^^ 2"]  # !! BROKEN
1           # OK

我不知道为什么会这样,但它确实:

cmd/c'.\test.cmd“a^2”退出(&E)
$LASTEXITCODE
输出:

[“a^2”]
1.
中找到了有效的解决方法,这是我的荣幸;让我补充一些背景信息和指导:

  • 首先,明确地说,不需要任何变通办法
    cmd.exe的行为显然是一个bug

  • cmd/c.\test.cmd“a^2”| | | exit”
    ——即
    | |
    而不是
    &
    ——也是一种有效的解决方法。事实上,只有对命令进行无条件排序的
    &
    ,才起作用,这表明即使是
    cmd.exe
    (批处理文件的内部故障状态)在同一条语句中还不为人所知(仅在之后),这似乎是该错误的另一种表现形式

    • 为什么作为同一语句的一部分,批处理文件调用之后的显式
      exit
      调用会正确地传递批处理文件的(零或非零)退出代码,这是任何人的猜测,但似乎是可行的
  • 幸运的是,对于不包含显式
    exit/b
    /
    exit
    调用的批处理文件中的相关退出代码问题,该解决方法也很有效-请参阅

语法注意事项

  • 在PowerShell中,传递单个命令字符串的替代方法是传递单个参数并将
    &
    字符转义为
    `&
    (使用
    `
    ,PowerShell的转义字符“backtick”),以防止PowerShell对其进行解释(将其引用为
    `&
    也可以):

  • 在不涉及shell的环境中,例如从任务调度器启动时,
    `
    -不需要转义
    &
    (且不得使用)

不必将整个for-
cmd.exe
命令括在引号中,这样可以更容易地传递(a)单独需要双引号的参数,以及(b)涉及对PowerShell变量和/或表达式的引用的参数,因为后者需要使用
“…”
,而不是
“…”

# Passing *individual* arguments makes double-quoting easier.
PS> cmd /c .\test.cmd "Version = $($PSVersionTable.PSVersion)" `& exit; $LASTEXITCODE
["Version = 7.2.0-preview.4"]
1
在这种情况下,使用整个for-
cmd.exe
命令的引号会很麻烦,因为需要转义特定于参数的
字符:

# Embedded double quotes must now be `-escaped.
PS> cmd /c ".\test.cmd `"Version = $($PSVersionTable.PSVersion)`" & exit"
["Version = 7.2.0-preview.4"]
1

模块(由我编写;使用
安装模块本机
进行安装)带有功能
ie
,它:

  • 自动应用上述解决方法

  • 通常补偿由于PowerShell的断开参数传递到外部程序而引起的问题(请参阅)


希望函数
ie
所做的将成为PowerShell本身的一部分,作为即将推出的(在PowerShell v7.2中)
PSNativeCommandArgumentPassing
实验功能的一部分,该功能旨在作为中断参数传递的选择性加入修复-请参见

这对我来说很有效:
cmd'/c.\test.cmd“a^2“&退出/b”
。它从测试批处理文件返回正确的输出,如果我在那里乱说,我会在
$LASTEXITCODE
MSG\u DIR\u BAD\u COMMAND\u或\u file
)中得到
9009
。太好了,@beatcracker-请将其作为答案发布,我会接受它。我已经试过了
| |退出/b
,它应该可以工作;我甚至没有想到尝试
&exit/b
——这表明
cmd.exe
本身没有意识到批处理
# Passing *individual* arguments makes double-quoting easier.
PS> cmd /c .\test.cmd "Version = $($PSVersionTable.PSVersion)" `& exit; $LASTEXITCODE
["Version = 7.2.0-preview.4"]
1
# Embedded double quotes must now be `-escaped.
PS> cmd /c ".\test.cmd `"Version = $($PSVersionTable.PSVersion)`" & exit"
["Version = 7.2.0-preview.4"]
1
# After having run Install-Module Native:
# Use of function `ie` applies the workaround behind the scenes.
PS> ie .\test.cmd "Version = $($PSVersionTable.PSVersion)"; $LASTEXITCODE
["Version = 7.2.0-preview.4"]
1