PowerShell 调用命令行
PowerShell Invoke Command Line
PowerShell 的进程调用是摇摇欲坠的,充其量是漏洞百出,所以当您需要一些不会像 Start-Process 那样偶尔挂起的东西,或者捕获输出到管道,同时仍然保留 $lastexitcode 时,大多数人似乎使用Process/ProcessStartInfo。有些进程向输出写入很多内容或者可能很长 运行,因此我们不想等到它们完成后才能看到输出流(不一定是主机......可能是日志文件)。所以我做了这个功能
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors and captures all output. Writes error and info output to pipeline (so uses .NET process API).
#>
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][string]$Cmd,
[Parameter(Position=1,Mandatory=0)][ScriptBlock]$ErrorMessage = ({"Error executing command: $Cmd - Exit Code $($p.ExitCode)"}),
[Parameter()][int[]]$ValidExitCodes = @(0)
)
begin {
$p = New-Object System.Diagnostics.Process
$pi = New-Object System.Diagnostics.ProcessStartInfo
$pi.FileName = "cmd.exe"
$pi.Arguments = "/c $Cmd 2>&1"
$pi.RedirectStandardError = $true
$pi.RedirectStandardOutput = $true
$pi.UseShellExecute = $false
$pi.CreateNoWindow = $true
$p.StartInfo = $pi
$outputHandler = {
if ($EventArgs.Data -ne $null) { Write-Output $EventArgs.Data }
}
Write-Output "Executing..."
$stdOutEvent = Register-ObjectEvent -InputObject $p `
-Action $outputHandler -EventName 'OutputDataReceived'
$stdErrEvent = Register-ObjectEvent -InputObject $p `
-Action $outputHandler -EventName 'ErrorDataReceived'
}
process {
$p.Start() | Out-Null
$p.BeginOutputReadLine()
$p.BeginErrorReadLine()
$p.WaitForExit() | Out-Null
}
end {
Unregister-Event -SourceIdentifier $stdOutEvent.Name
Unregister-Event -SourceIdentifier $stdErrEvent.Name
if (!($ValidExitCodes -contains $p.ExitCode)) {
throw (& $ErrorMessage)
}
}
}
问题是我的事件处理程序中的 Write-Output 无法在与 Invoke-Cmd 本身相同的执行上下文中工作...如何让我的事件处理程序将 Write-Output 写入父函数输出流?
谢谢
这应该可以满足您的要求:
cmd /c ... '2>&1' [ | ... ]
要捕获变量中的输出:
$captured = cmd /c ... '2>&1' [ | ... ]
这个成语:
- 同步执行传递给
cmd /c
的外部命令,
- 当输出可用时,通过管道传递 stdout 和 stderr,
- 如果指定,则捕获变量
$captured
、 中的组合输出
- 在
$LASTEXITCODE
. 中保留外部命令的退出代码
$LASTEXITCODE
是一个 automatic variable,每当 外部程序 时 自动设置 完成 运行ning,并设置为该程序的退出代码。这是一个 全局单例变量,你可以在任何范围内访问而无需范围说明符(就好像 它是用 -Scope Global -Option AllScope
声明的一样,即使Get-Variable LASTEXITCODE | Format-List
不反映这一点)。这意味着只有 最近 运行 程序的退出代码反映在 $LASTEXITCODE
中,而不管它在 运行 中的什么范围。
但是请注意,在会话中启动的第一个外部命令完成 运行ning.
之前,不会创建变量
警告:由于 cmd
正在处理 2&1
重定向(由于它周围的引号),stdout 和 stderr 被合并 在源头,因此您将无法分辨出哪些输出行来自哪个流;如果您确实需要知道,请使用 PowerShell 的 2&1
重定向(省略引号)。
有关这两种方法有何不同的讨论,请参阅我的 this answer。
如果除了在变量中捕获之外还想输出到控制台(假设最后一个管道段调用一个cmdlet:
cmd /c ... '2>&1' [ | ... -OutVariable captured ]
示例:
> cmd /c ver '&&' dir nosuch '2>&1' > out.txt
> $LASTEXITCODE
1
> Get-Content out.txt
Microsoft Windows [Version 10.0.10586]
Volume in drive C has no label.
Volume Serial Number is 7851-9F0B
Directory of C:\
File Not Found
该示例演示退出代码正确反映在 $LASTEXITCODE
中,stdout 和 stderr 都被发送到输出流并由 PowerShell 在文件 out.txt
.[=28= 中捕获]
PowerShell 的进程调用是摇摇欲坠的,充其量是漏洞百出,所以当您需要一些不会像 Start-Process 那样偶尔挂起的东西,或者捕获输出到管道,同时仍然保留 $lastexitcode 时,大多数人似乎使用Process/ProcessStartInfo。有些进程向输出写入很多内容或者可能很长 运行,因此我们不想等到它们完成后才能看到输出流(不一定是主机......可能是日志文件)。所以我做了这个功能
function Invoke-Cmd {
<#
.SYNOPSIS
Executes a command using cmd /c, throws on errors and captures all output. Writes error and info output to pipeline (so uses .NET process API).
#>
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=1)][string]$Cmd,
[Parameter(Position=1,Mandatory=0)][ScriptBlock]$ErrorMessage = ({"Error executing command: $Cmd - Exit Code $($p.ExitCode)"}),
[Parameter()][int[]]$ValidExitCodes = @(0)
)
begin {
$p = New-Object System.Diagnostics.Process
$pi = New-Object System.Diagnostics.ProcessStartInfo
$pi.FileName = "cmd.exe"
$pi.Arguments = "/c $Cmd 2>&1"
$pi.RedirectStandardError = $true
$pi.RedirectStandardOutput = $true
$pi.UseShellExecute = $false
$pi.CreateNoWindow = $true
$p.StartInfo = $pi
$outputHandler = {
if ($EventArgs.Data -ne $null) { Write-Output $EventArgs.Data }
}
Write-Output "Executing..."
$stdOutEvent = Register-ObjectEvent -InputObject $p `
-Action $outputHandler -EventName 'OutputDataReceived'
$stdErrEvent = Register-ObjectEvent -InputObject $p `
-Action $outputHandler -EventName 'ErrorDataReceived'
}
process {
$p.Start() | Out-Null
$p.BeginOutputReadLine()
$p.BeginErrorReadLine()
$p.WaitForExit() | Out-Null
}
end {
Unregister-Event -SourceIdentifier $stdOutEvent.Name
Unregister-Event -SourceIdentifier $stdErrEvent.Name
if (!($ValidExitCodes -contains $p.ExitCode)) {
throw (& $ErrorMessage)
}
}
}
问题是我的事件处理程序中的 Write-Output 无法在与 Invoke-Cmd 本身相同的执行上下文中工作...如何让我的事件处理程序将 Write-Output 写入父函数输出流?
谢谢
这应该可以满足您的要求:
cmd /c ... '2>&1' [ | ... ]
要捕获变量中的输出:
$captured = cmd /c ... '2>&1' [ | ... ]
这个成语:
- 同步执行传递给
cmd /c
的外部命令, - 当输出可用时,通过管道传递 stdout 和 stderr,
- 如果指定,则捕获变量
$captured
、 中的组合输出
- 在
$LASTEXITCODE
. 中保留外部命令的退出代码
$LASTEXITCODE
是一个 automatic variable,每当 外部程序 时 自动设置 完成 运行ning,并设置为该程序的退出代码。这是一个 全局单例变量,你可以在任何范围内访问而无需范围说明符(就好像 它是用 -Scope Global -Option AllScope
声明的一样,即使Get-Variable LASTEXITCODE | Format-List
不反映这一点)。这意味着只有 最近 运行 程序的退出代码反映在 $LASTEXITCODE
中,而不管它在 运行 中的什么范围。
但是请注意,在会话中启动的第一个外部命令完成 运行ning.
警告:由于 cmd
正在处理 2&1
重定向(由于它周围的引号),stdout 和 stderr 被合并 在源头,因此您将无法分辨出哪些输出行来自哪个流;如果您确实需要知道,请使用 PowerShell 的 2&1
重定向(省略引号)。
有关这两种方法有何不同的讨论,请参阅我的 this answer。
如果除了在变量中捕获之外还想输出到控制台(假设最后一个管道段调用一个cmdlet:
cmd /c ... '2>&1' [ | ... -OutVariable captured ]
示例:
> cmd /c ver '&&' dir nosuch '2>&1' > out.txt
> $LASTEXITCODE
1
> Get-Content out.txt
Microsoft Windows [Version 10.0.10586]
Volume in drive C has no label.
Volume Serial Number is 7851-9F0B
Directory of C:\
File Not Found
该示例演示退出代码正确反映在 $LASTEXITCODE
中,stdout 和 stderr 都被发送到输出流并由 PowerShell 在文件 out.txt
.[=28= 中捕获]