PowerShell异步将标准输出/误差与时间戳重定向



我正在使用powershell v2.0使用system.diarostics.diarostics.process对象执行给定的进程,拦截了stdout/stderr messages异步,将各自的$ eventargs.data写给主机(出于调试目的)并用时间戳预序,最终用于记录目的:

$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

我期望写在控制台上的Stdout/stderr消息相距一秒钟(正如Ping显然每秒写的那样),但是由于某种原因,时间戳都在彼此之间约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 = 592016-11-27 14:53:16.6796113 [execcmd]:回复172.217.17.110:bytes = 32 time = 1ms ttl = 592016-11-27 14:53:17.7548025 [execcmd]:回复172.217.17.110:bytes = 32 time = 1ms = 1ms ttl = 59等...

但是,看到这个...

2016-11-26 19:00:53.0813327 [execcmd]:pinging google.com [172.217.17.46],带有32个字节的数据:2016-11-26 19:00:53.0842700 [execcmd]:回复172.217.17.46:bytes = 32 time = 1ms = 1ms ttl = 592016-11-26 19:00:53.0860183 [ExecCMD]:回复172.217.17.46:bytes = 32 time = 1ms = 1ms ttl = 592016-11-26 19:00:53.0899003 [execcmd]:回复172.217.17.46:bytes = 32 time = 1ms = 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 Framework版本中起作用。省略$ process.waitforexit(1000)语句返回正确的时间戳值,因此我知道PS能够返回正确的值。我能得到的最接近的是用if ($Process.WaitForExit(10000))语句替换CC_1语句。这起作用了,但并不是一个可行的解决方案,因为它(可以理解)杀死了CPU - 奇怪的是,添加启动睡眠X会导致该过程不退出。我已经玩过$ Process。删除事件,但这导致该过程永远不会退出,尽管该事件开火和$ process.hasexited事件返回$ true。类似地,创建一个计时器对象并检查$ Process.hasexited会导致该过程也永远不会退出。

我已经看到了许多与PS中的过程对象异步捕获Stdout/stderr有关的问题,但找不到与我的用例有关的任何特定问题。当我对此我的脑袋挠头时,任何帮助/建议/指导都将不胜感激!预先感谢您!

西蒙

您无法使用 get-date 立即记录。您应该访问powerShell的 timegenerated

示例示例:

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

它将为您提供活动的确切时间。在您的情况下,您可以使用以下内容:

$Event.TimeGenerated.ToString("AnyFormat")

我无法弄清楚如何在控制台上实时显示stdout/err streams,并在订阅process.outputdataReceived和errordataReceived Everts上的其他值(Timestamp et et timestamp et efter),最后我改变了机智,最终做了以下操作:

Function Write-Log ($Message) {
    Write-Output $Message
    Add-Content -Path "C:temptest.txt" -Value "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss.fffffff") [ExecCmd]: $Message"
}
Invoke-Expression "ping google.com" | ForEach-Object {Write-Log $_}

这有效,但是我不确定这是最好的做法 - 尽管如此,它似乎是有效且可靠的。

最新更新