PowerShell异步重定向带时间戳的标准输出/错误

时间:2016-11-27 15:38:54

标签: powershell asynchronous timestamp stdout stderr

我正在使用PowerShell v2.0使用System.Diagnostics.Process对象执行给定进程,异步拦截stdout / stderr消息,将相应的$ 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]:使用32个字节的数据ping google.com [172.217.17.110]: 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的回复:bytes = 32 time = 1ms TTL = 59 等...

但相反,看到这个......

2016-11-26 19:00:53.0813327 [ExecCmd]:使用32个字节的数据ping google.com [172.217.17.46]: 2016-11-26 19:00:53.0842700 [ExecCmd]:来自172.217.17.46的回复:bytes = 32 time = 1ms TTL = 59 2016-11-26 19:00:53.0860183 [ExecCmd]:来自172.217.17.46的回复:bytes = 32 time = 1ms TTL = 59 2016-11-26 19:00:53.0899003 [ExecCmd]:来自172.217.17.46的回复:bytes = 32 time = 1ms TTL = 59 等...

我使用.NET Framework v2.0在C#中重新创建了这个(因为我正在使用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#中工作,但在具有相同.NET框架版本的PS中却没有。省略$ Process.WaitForExit(1000)语句会返回正确的时间戳值,因此我知道PS能够返回正确的值。我能得到的最接近的是用if ($Process.WaitForExit(10000))语句替换while (!$Process.HasExited) {}语句。这可行,但不是一个可行的解决方案,因为它(可以理解)杀死CPU - 奇怪的是,添加Start-Sleep x会导致进程无法退出。我玩过$ Process.Exited事件,但这导致进程永远不会退出,尽管事件触发并且$ Process.HasExited事件返回$ true。类似地,创建一个Timer对象并检查$ Process.HasExited会导致进程永远不会退出。

我已经看到很多与PS中的Process对象异步捕获stdout / stderr相关的问题,但是找不到与我的用例相关的任何具体问题。任何帮助/建议/指导将非常感激,因为我正在摸着这个!提前谢谢!

西蒙

2 个答案:

答案 0 :(得分:0)

您无法使用获取日期记录即时时间。您应该访问Powershell的 TimeGenerated

示例示例:

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

它会告诉你事件的确切时间。 在您的情况下,您可以使用以下内容:

$Event.TimeGenerated.ToString("AnyFormat")

答案 1 :(得分:0)

在订阅Process.OutputDataReceived和ErrorDataReceived事件时,我无法弄清楚如何在控制台上实时显示stdout / err流并使用其他值(时间戳等)写入文件,所以最后我改变了机智并最终做了以下事情:

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 $_}

这是有效的,但是我不确定它是否是最佳实践 - 它似乎是高效可靠的。