请注意:
方法
PS C:\> (Get-Command Invoke-SilentlyAndReturnExitCode).ScriptBlock
param([scriptblock]$Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
& $Command > $null 2>&1
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
PS C:\>
静音命令
PS C:\> $ErrorActionPreference = "Stop"
PS C:\> $Command = { cmd /c dir xo-xo-xo }
PS C:\> & $Command > $null 2>&1
cmd : File Not Found
At line:1 char:14
+ $Command = { cmd /c dir xo-xo-xo }
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (File Not Found:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PS C:\>
如您所见,它失败并带有异常。但是我们可以轻松地将其静音,对吧?
PS C:\> $ErrorActionPreference = 'SilentlyContinue'
PS C:\> & $Command > $null 2>&1
PS C:\> $LASTEXITCODE
1
PS C:\>
一切都很好。现在,我的功能执行相同操作,因此让我们尝试一下:
PS C:\> $ErrorActionPreference = "Stop"
PS C:\> Invoke-SilentlyAndReturnExitCode $Command
-1
PS C:\>
赞!它返回-1,而不是1。
问题似乎是该函数内的$ErrorActionPreference
设置实际上并未传播到命令范围。确实,让我添加一些输出:
PS C:\> (Get-Command Invoke-SilentlyAndReturnExitCode).ScriptBlock
param([scriptblock]$Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
Write-Host $ErrorActionPreference
& $Command > $null 2>&1
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
PS C:\> $Command = { Write-Host $ErrorActionPreference ; cmd /c dir xo-xo-xo }
PS C:\> Invoke-SilentlyAndReturnExitCode $Command
Continue
Stop
-1
PS C:\>
因此,问题实际上出在$ErrorActionPreference
上-为什么它不传播? Powershell使用动态作用域,因此命令定义不应捕获其值,而应使用函数中的值。那么发生了什么?如何解决?
答案 0 :(得分:1)
tl; dr
由于您的Invoke-SilentlyAndReturnExitCode
函数是在模块中定义的,因此必须在该模块范围内重新创建脚本块,才能看到该模块- $ErrorActionPreference
的本地Continue
值:
# Use an in-memory module to demonstrate the behavior.
$null = New-Module {
Function Invoke-SilentlyAndReturnExitCode {
param([scriptblock] $Command, $Folder)
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try
{
Write-Host $ErrorActionPreference # local value
# *Recreate the script block in the scope of this module*,
# which makes it see the module's variables.
$Command = [scriptblock]::Create($Command.ToString())
# Invoke the recreated script block, suppressing all output.
& $Command *>$null
# Output the exit code.
$LASTEXITCODE
}
catch
{
-1
}
finally
{
Pop-Location
}
}
}
$ErrorActionPreference = 'Stop'
$Command = { Out-Host -InputObject $ErrorActionPreference; cmd /c dir xo-xo-xo }
Invoke-SilentlyAndReturnExitCode $Command
在Windows上,以上内容现在可以按预期打印以下内容:
Continue
Continue
1
也就是说,重新创建的$Command
脚本块看到了本地函数$ErrorActionPreference
的值,并且catch
块未 触发。
注意事项:
仅当$Command
脚本块不包含对原始作用域中的变量的引用(除了 global 作用域中的变量之外),此才起作用。
替代 避免此限制的目的是定义模块外部的功能 (假设您还从模块外部的代码中调用它。)
此行为表示您的Invoke-SilentlyAndReturnExitCode
函数是在模块 中定义的,并且每个模块都有自己的范围域(范围层次)。 / p>
您的$Command
脚本块,因为它是在模块的外部中定义的,因此绑定到 default 范围域,甚至是从模块内部执行时也是如此,它将继续查看来自定义它的作用域 的变量。
因此,$Command
仍会看到Stop
$ErrorActionPreference
的值,即使函数中模块源代码的 为Continue
,原因是在模块函数内设置了$ErrorActionPreference
的本地副本。
令人惊讶的是,控制行为的仍然是$ErrorActionPreference
内的$Command
,而不是函数局部值。
对于2>$null
的重定向,例如*>$null
有效,而Stop
是有效的$ErrorActionPreference
值,则仅存在来自外部程序的stderr输出-是否表示真正的错误不是-触发终止错误,因此触发catch
分支。
这种特殊行为-明确抑制 stderr输出的意图触发了错误-应该被视为 bug ,并已在this GitHub issue中报告。
一般行为-在定义范围内执行的脚本块-不明显,但在设计上 。
注意:此答案的其余部分是其原始形式,其中包含一般背景信息,但是并不涵盖上述模块方面。
*> $null
可用于使命令的所有输出静默-无需抑制成功输出流(>
,隐含1>
)和错误输出流( 2>
)。
通常,$ErrorActionPreference
对外部程序(例如git
)的错误输出没有影响,因为 stderr 的输出是外部程序默认情况下绕过 PowerShell的错误流。
但是有一个例外:将$ErrorActionPreference
设置为'Stop'
实际上会使诸如2>&1
和*>$null
之类的重定向引发终止错误,如果外部程序因为git
会产生任何stderr输出。
this GitHub issue中讨论了这种意外行为。
否则,对外部程序的调用永远不会触发try
/ catch
语句将处理的终止错误。只能通过自动$LASTEXITCODE
变量来推断成功或失败。
因此,如果在模块外部定义(并调用)函数,则编写如下的函数 :
function Invoke-SilentlyAndReturnExitCode {
param([scriptblock]$Command, $Folder)
# Set a local copy of $ErrorActionPreference,
# which will go out of scope on exiting this function.
# For *> $null to effectively suppress stderr output from
# external programs *without triggering a terminating error*
# any value other than 'Stop' will do.
$ErrorActionPreference = 'Continue'
Push-Location $Folder
try {
# Invoke the script block and suppress all of its output.
# Note that if the script block calls an *external program*, the
# catch handler will never get triggered - unless the external program
# cannot be found.
& $Command *> $null
$LASTEXITCODE
}
catch {
# Output the exit code used by POSIX-like shells such
# as Bash to signal that an executable could not be found.
127
} finally {
Pop-Location
}
}