PowerShell调用命令行

时间:2016-08-06 19:29:54

标签: .net powershell pipeline powershell-v4.0

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到父函数输出流?

谢谢

1 个答案:

答案 0 :(得分:1)

这应该做你想要的:

cmd /c ... '2>&1' [ | ... ]

捕获变量中的输出:

$captured = cmd /c ... '2>&1' [ | ... ]

这个成语:

  • 同步执行传递给cmd /c的外部命令
  • 通过管道传递stdout和stderr,因为输出可用,
  • 如果指定,则捕获变量$captured
  • 中的组合输出
  • $LASTEXITCODE
  • 中保留外部命令的退出代码

$LASTEXITCODE是一个automatic variable,只要外部程序完成运行,自动设置,并设置为该程序的退出码。它是一个全局单例变量,你可以在没有任何范围的范围说明符的情况下访问它(好像它已用-Scope Global -Option AllScope声明,即使Get-Variable LASTEXITCODE | Format-List没有不反映这一点。这意味着只有最近运行的程序的退出代码才会反映在$LASTEXITCODE中,无论其运行的范围如何。
但请注意,在会话中启动的第一个外部命令完成运行之前,不会创建该变量。

警告:由于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中捕获。