2016-11-27 141 views
0

我使用PowerShell v2.0来执行给定进程,使用System.Diagnostics.Process对象,异步截取stdout/stderr消息,将相应的$ EventArgs.Data写入主机(用于调试)和预未决它与时间戳,最终用于日志记录:PowerShell异步重定向标准输出/带时间戳错误

$ProcessInfo = New-Object System.Diagnostics.ProcessStartInfo 
$ProcessInfo.RedirectStandardOutput = $true 
$ProcessInfo.RedirectStandardError = $true 
$ProcessInfo.UseShellExecute = $false 
$ProcessInfo.CreateNoWindow = $true 
$ProcessInfo.FileName = "ping" 
$ProcessInfo.Arguments = "google.com" 

$Process = New-Object System.Diagnostics.Process 
$Process.StartInfo = $ProcessInfo 

$CaptureStandardOutputStandardError = { 
    If (![String]::IsNullOrEmpty($EventArgs.Data)) { 
     Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss.fffffff") [ExecCmd]: $($EventArgs.Data)" 
    } 
} 

Register-ObjectEvent -InputObject $Process -SourceIdentifier StdOutEvent -Action $CaptureStandardOutputStandardError -EventName 'OutputDataReceived' | Out-Null 
Register-ObjectEvent -InputObject $Process -SourceIdentifier ErrOutEvent -Action $CaptureStandardOutputStandardError -EventName 'ErrorDataReceived' | Out-Null 

$Process.Start() | Out-Null 
$Process.BeginOutputReadLine() 
$Process.BeginErrorReadLine() 

If ($Process.WaitForExit(10000)) { 
    # Ensure streams are flushed 
    $Process.WaitForExit() 

    # Do stuff 
} Else { 
    # Timeout 
} 

Unregister-Event StdOutEvent 
Unregister-Event ErrOutEvent 

我期待写入控制台标准输出/标准错误消息发送到具有时间戳的第二开的(如平写入显然每秒钟都会到管道),但由于某些原因,时间戳都在彼此的大约10毫秒之内,所以注册事件显然在过程完成时触发。所以我期待这样:

2016-11-27 14:53:15.6581302 [ExecCmd]:Pinging google.com [172.217.17.110]与32个字节的数据: 2016-11-27 14:53: 15.8778445 [ExecCmd]:172.217.17.110:bytes = 32 time = 1ms TTL = 59 2016-11-27 14:53:16.6796113 [ExecCmd]:来自172.217.17.110的回复:bytes = 32 time = 1ms TTL = 59 2016年11月27日14:53:17.7548025 [ExecCmd]:回复从172.217.17.110:字节= 32时间= 1毫秒TTL = 59 等...

但是,相反,看到此...

2016-11-26 19:00:00 53.0813327 [ExecCmd]:Pinging google.com [172.217.17.46]机智h 32个字节的数据: 2016-11-26 19:00:00 53.0842700 [ExecCmd]:172.217.17.46的回复:bytes = 32 time = 1ms TTL = 59 2016-11-26 19:00:00 53.0860183 [ExecCmd] :172.217.17.46:bytes = 32 time = 1ms TTL = 59 2016-11-26 19:00:00 53.0899003 [ExecCmd]:172.217.17.46:bytes = 32 time = 1ms TTL = 59 等等。 。

我已经在C#中使用.NET Framework v2.0重新创建了这个(因为我使用PSv2)并且它按预期工作。参考代码:

ProcessStartInfo processInfo = new ProcessStartInfo(); 
processInfo.RedirectStandardError = true; 
processInfo.RedirectStandardOutput = true; 
processInfo.UseShellExecute = false; 
processInfo.CreateNoWindow = true; 
processInfo.FileName = "ping"; 
processInfo.Arguments = "google.com"; 

Process process = new Process(); 
process.StartInfo = processInfo; 

process.OutputDataReceived += (sender, e) => { 
    if (!string.IsNullOrEmpty(e.Data)) 
    { 
     Console.WriteLine("{0} [ExecCmd]: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), e.Data); 
    } 
}; 

process.ErrorDataReceived += (sender, e) => { 
    if (!string.IsNullOrEmpty(e.Data)) 
    { 
     Console.WriteLine("{0} [ExecCmd]: {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffffff"), e.Data); 
    } 
}; 

process.Start(); 
process.BeginOutputReadLine(); 
process.BeginErrorReadLine(); 

if (process.WaitForExit(10000)) 
{ 
    // Ensure streams are flushed 
    process.WaitForExit(); 

    // Do stuff 
} 
else 
{ 
    // Timeout 
} 

我在想什么?我不明白为什么在C#中有效地使用相同的代码,而在PS中使用相同的.NET框架版本。省略$ Process.WaitForExit(1000)语句返回正确的时间戳值,所以我知道PS能够返回正确的值。我能得到的最接近的是用while (!$Process.HasExited) {}声明替换if ($Process.WaitForExit(10000))声明。这是有效的,但不是一个可行的解决方案,因为它(可以理解)杀死CPU - 奇怪的是,添加Start-Sleep x会导致进程不能退出。我玩过$ Process.Exited事件,但是这会导致进程永远不会退出,尽管事件触发并且$ Process.HasExited事件返回$ true。同样,创建一个Timer对象并检查$ Process.HasExited会导致该进程永远不会退出。

我见过很多与PS中的Process对象异步捕获stdout/stderr有关的问题,但找不到与我的用例有关的任何特定问题。任何帮助/建议/指导将非常感激,因为我用这个挠我的头!先谢谢你!

西蒙

+0

当发生事件时,PowerShell不保证立即处理。如果你想要准确的时间信息,使用'$ Event.TimeGenerated'而不是在事件动作体内调用'Get-Date' –

+0

令人惊叹 - 谢谢!像魅力一样工作!对于其他感兴趣的人,我将'Write-Host'$(Get-Date -Format“yyyy-MM-dd HH:mm:ss.fffffff”)['ExecCmd]:$($ EventArgs.Data)''改为'Write -Host“$($ Event.TimeGenerated.ToString(”yyyy-MM-dd HH:mm:ss.fffffff“))[ExecCmd]:$($ EventArgs.Data)”'。 – sthounsell

+0

我仍然有兴趣知道PS是否解决了在事件引发时触发事件的行为,而不是在流程退出时。最终,我试图在CLI中实时接收stdout/err,并将数据写入日志文件,我在其中添加时间戳。目前,这些值在进程退出时显示,这对日志文件来说很好,但对于监视CLI而言并不理想,因为控制台正在显示进度状态消息 - 如果控制台消息仅在进程关闭。 – sthounsell

回答

0

我无法弄清楚如何显示标准输出/ ERR流在控制台上的实时和编写额外的值到文件(时间戳等)时认购到Process.OutputDataReceived和ErrorDataReceived事件,所以最后我改变了机智和最终做了以下内容:

Function Write-Log ($Message) { 
    Write-Output $Message 
    Add-Content -Path "C:\temp\test.txt" -Value "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss.fffffff") [ExecCmd]: $Message" 
} 

Invoke-Expression "ping google.com" | ForEach-Object {Write-Log $_} 

这工作,但我不知道这是否是最好的做法还是不 - 这似乎是有效的而且可靠。

0

您不能使用获取最新登录立即时间。您应该访问Powershell的TimeGenerated

一个样本实施例:

(get-eventlog System | where-object {$_.EventID -eq “6005”})[0].TimeGenerated 

它会给你该事件的确切时间。 在你的情况,你可以用下面的:

$Event.TimeGenerated.ToString("AnyFormat") 
+0

我想我很困惑,因为如果在我的示例PS代码中注释掉'If($ Process.WaitForExit(10000)){}'块和'unregister event'语句,虽然在此示例中该过程从不关闭,但它至少可以正确地向控制台正确写入 - 所以感觉像Process.WaitForExit()阻止实时事件发生(不同于C#中的反向部分)。感谢您的输入 - 虽然我没有意识到Get-Date和TimeGenerated。 – sthounsell

相关问题