Ci3*_*Ci3 17 powershell events asynchronous
我想从我在Powershell脚本中启动的进程捕获stdout和stderr,并将其异步显示到控制台.我已经通过MSDN和其他博客找到了一些关于这样做的文档.
创建并运行下面的示例后,我似乎无法异步显示任何输出.所有输出仅在进程终止时显示.
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = "cmd.exe"
$ps.StartInfo.UseShellExecute = $false
$ps.StartInfo.RedirectStandardOutput = $true
$ps.StartInfo.Arguments = "/c echo `"hi`" `& timeout 5"
$action = { Write-Host $EventArgs.Data }
Register-ObjectEvent -InputObject $ps -EventName OutputDataReceived -Action $action | Out-Null
$ps.start() | Out-Null
$ps.BeginOutputReadLine()
$ps.WaitForExit()
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我希望在程序执行结束之前在命令行上看到"hi"的输出,因为应该触发OutputDataReceived事件.
我已经尝试过使用其他可执行文件 - java.exe,git.exe等.所有这些都具有相同的效果,所以我不得不认为有一些我不理解或错过的简单.还需要做什么才能异步读取stdout?
Ale*_*sht 34
不幸的是,如果你想要正确地进行异步读取并不那么容易.如果你在没有超时的情况下调用WaitForExit(),你可以使用像我写的这个函数(基于C#代码):
function Invoke-Executable {
# Runs the specified executable and captures its exit code, stdout
# and stderr.
# Returns: custom object.
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$sExeFile,
[Parameter(Mandatory=$false)]
[String[]]$cArgs,
[Parameter(Mandatory=$false)]
[String]$sVerb
)
# Setting process invocation parameters.
$oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$oPsi.CreateNoWindow = $true
$oPsi.UseShellExecute = $false
$oPsi.RedirectStandardOutput = $true
$oPsi.RedirectStandardError = $true
$oPsi.FileName = $sExeFile
if (! [String]::IsNullOrEmpty($cArgs)) {
$oPsi.Arguments = $cArgs
}
if (! [String]::IsNullOrEmpty($sVerb)) {
$oPsi.Verb = $sVerb
}
# Creating process object.
$oProcess = New-Object -TypeName System.Diagnostics.Process
$oProcess.StartInfo = $oPsi
# Creating string builders to store stdout and stderr.
$oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
$oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder
# Adding event handers for stdout and stderr.
$sScripBlock = {
if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.AppendLine($EventArgs.Data)
}
}
$oStdOutEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'OutputDataReceived' `
-MessageData $oStdOutBuilder
$oStdErrEvent = Register-ObjectEvent -InputObject $oProcess `
-Action $sScripBlock -EventName 'ErrorDataReceived' `
-MessageData $oStdErrBuilder
# Starting process.
[Void]$oProcess.Start()
$oProcess.BeginOutputReadLine()
$oProcess.BeginErrorReadLine()
[Void]$oProcess.WaitForExit()
# Unregistering events to retrieve process output.
Unregister-Event -SourceIdentifier $oStdOutEvent.Name
Unregister-Event -SourceIdentifier $oStdErrEvent.Name
$oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
"ExeFile" = $sExeFile;
"Args" = $cArgs -join " ";
"ExitCode" = $oProcess.ExitCode;
"StdOut" = $oStdOutBuilder.ToString().Trim();
"StdErr" = $oStdErrBuilder.ToString().Trim()
})
return $oResult
}
Run Code Online (Sandbox Code Playgroud)
它捕获stdout,stderr和退出代码.用法示例:
$oResult = Invoke-Executable -sExeFile 'ping.exe' -cArgs @('8.8.8.8', '-a')
$oResult | Format-List -Force
Run Code Online (Sandbox Code Playgroud)
有关更多信息和替代实现(在C#中),请阅读此博客文章.
MiF*_*vil 11
根据Alexander Obersht的回答,我创建了一个使用超时和异步Task类而不是事件处理程序的函数.根据Mike Adelson的说法
不幸的是,这种方法(事件处理程序)无法知道何时接收到最后一位数据.因为一切都是异步的,所以在WaitForExit()返回后触发事件是可能的(我已经观察到了这一点).
function Invoke-Executable {
# from https://stackoverflow.com/a/24371479/52277
# Runs the specified executable and captures its exit code, stdout
# and stderr.
# Returns: custom object.
# from http://www.codeducky.org/process-handling-net/ added timeout, using tasks
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]$sExeFile,
[Parameter(Mandatory=$false)]
[String[]]$cArgs,
[Parameter(Mandatory=$false)]
[String]$sVerb,
[Parameter(Mandatory=$false)]
[Int]$TimeoutMilliseconds=1800000 #30min
)
Write-Host $sExeFile $cArgs
# Setting process invocation parameters.
$oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$oPsi.CreateNoWindow = $true
$oPsi.UseShellExecute = $false
$oPsi.RedirectStandardOutput = $true
$oPsi.RedirectStandardError = $true
$oPsi.FileName = $sExeFile
if (! [String]::IsNullOrEmpty($cArgs)) {
$oPsi.Arguments = $cArgs
}
if (! [String]::IsNullOrEmpty($sVerb)) {
$oPsi.Verb = $sVerb
}
# Creating process object.
$oProcess = New-Object -TypeName System.Diagnostics.Process
$oProcess.StartInfo = $oPsi
# Starting process.
[Void]$oProcess.Start()
# Tasks used based on http://www.codeducky.org/process-handling-net/
$outTask = $oProcess.StandardOutput.ReadToEndAsync();
$errTask = $oProcess.StandardError.ReadToEndAsync();
$bRet=$oProcess.WaitForExit($TimeoutMilliseconds)
if (-Not $bRet)
{
$oProcess.Kill();
# throw [System.TimeoutException] ($sExeFile + " was killed due to timeout after " + ($TimeoutMilliseconds/1000) + " sec ")
}
$outText = $outTask.Result;
$errText = $errTask.Result;
if (-Not $bRet)
{
$errText =$errText + ($sExeFile + " was killed due to timeout after " + ($TimeoutMilliseconds/1000) + " sec ")
}
$oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
"ExeFile" = $sExeFile;
"Args" = $cArgs -join " ";
"ExitCode" = $oProcess.ExitCode;
"StdOut" = $outText;
"StdErr" = $errText
})
return $oResult
}
Run Code Online (Sandbox Code Playgroud)
我无法让这些示例与 PS 4.0 一起使用。
\n\n我想puppet apply
从 Octopus Deploy 包(通过Deploy.ps1
)运行并“实时”查看输出,而不是等待该过程完成(一小时后),所以我想出了以下方法:
# Deploy.ps1\n\n$procTools = @"\n\nusing System;\nusing System.Diagnostics;\n\nnamespace Proc.Tools\n{\n public static class exec\n {\n public static int runCommand(string executable, string args = "", string cwd = "", string verb = "runas") {\n\n //* Create your Process\n Process process = new Process();\n process.StartInfo.FileName = executable;\n process.StartInfo.UseShellExecute = false;\n process.StartInfo.CreateNoWindow = true;\n process.StartInfo.RedirectStandardOutput = true;\n process.StartInfo.RedirectStandardError = true;\n\n //* Optional process configuration\n if (!String.IsNullOrEmpty(args)) { process.StartInfo.Arguments = args; }\n if (!String.IsNullOrEmpty(cwd)) { process.StartInfo.WorkingDirectory = cwd; }\n if (!String.IsNullOrEmpty(verb)) { process.StartInfo.Verb = verb; }\n\n //* Set your output and error (asynchronous) handlers\n process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);\n process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);\n\n //* Start process and handlers\n process.Start();\n process.BeginOutputReadLine();\n process.BeginErrorReadLine();\n process.WaitForExit();\n\n //* Return the commands exit code\n return process.ExitCode;\n }\n public static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {\n //* Do your stuff with the output (write to console/log/StringBuilder)\n Console.WriteLine(outLine.Data);\n }\n }\n}\n"@\n\nAdd-Type -TypeDefinition $procTools -Language CSharp\n\n$puppetApplyRc = [Proc.Tools.exec]::runCommand("ruby", "-S -- puppet apply --test --color false ./manifests/site.pp", "C:\\ProgramData\\PuppetLabs\\code\\environments\\production");\n\nif ( $puppetApplyRc -eq 0 ) {\n Write-Host "The run succeeded with no changes or failures; the system was already in the desired state."\n} elseif ( $puppetApplyRc -eq 1 ) {\n throw "The run failed; halt"\n} elseif ( $puppetApplyRc -eq 2) {\n Write-Host "The run succeeded, and some resources were changed."\n} elseif ( $puppetApplyRc -eq 4 ) {\n Write-Warning "WARNING: The run succeeded, and some resources failed."\n} elseif ( $puppetApplyRc -eq 6 ) {\n Write-Warning "WARNING: The run succeeded, and included both changes and failures."\n} else {\n throw "Un-recognised return code RC: $puppetApplyRc"\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n\n
归档时间: |
|
查看次数: |
10165 次 |
最近记录: |