我正在PowerShell脚本中运行DTEXEC.exe命令,尝试捕获输出并将其记录到文件中。有时输出是不完整的,我正在试图找出为什么会这样,以及可能做些什么。似乎永远不会记录的行是最有趣的:
DTEXEC: The package execution returned DTSER_SUCCESS(0)
Started: 10:58:43 a.m.
Finished: 10:59:24 a.m.
Elapsed: 41.484 seconds
对于在不到8秒的时间内执行的软件包,输出似乎总是不完整,这可能是一个线索(没有太多输出或者它们很快完成)。
我正在使用.NETs System.Diagnostics.Process和ProcessStartInfo来设置和运行命令,我将stdout和stderror重定向到事件处理程序,每个事件处理程序都附加到随后写入磁盘的StringBuilder。
这个问题感觉像是时间问题或缓冲问题。为解决时间问题,我尝试使用Monitor.Enter / Exit。如果这是一个缓冲问题,我不知道如何强制进程不缓冲stdout和stderror。
环境是 - 运行CLR版本2的PowerShell 2 - SQL 2008 32位DTEXEC.exe - 主机操作系统:XP Service Pack 3.
以下是代码:
function Execute-SSIS-Package
{
param([String]$fileName)
$cmd = GetDTExecPath
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo.FileName = $cmd
$proc.StartInfo.Arguments = "/FILE ""$fileName"" /CHECKPOINTING OFF /REPORTING ""EWP"""
$proc.StartInfo.RedirectStandardOutput = $True
$proc.StartInfo.RedirectStandardError = $True
$proc.StartInfo.WorkingDirectory = Get-Location
$proc.StartInfo.UseShellExecute = $False
$proc.StartInfo.CreateNoWindow = $False
Write-Host $proc.StartInfo.FileName $proc.StartInfo.Arguments
$cmdOut = New-Object System.Text.StringBuilder
$errorEvent = Register-ObjectEvent -InputObj $proc `
-Event "ErrorDataReceived" `
-MessageData $cmdOut `
-Action `
{
param
(
[System.Object] $sender,
[System.Diagnostics.DataReceivedEventArgs] $e
)
try
{
[System.Threading.Monitor]::Enter($Event.MessageData)
Write-Host -ForegroundColor "DarkRed" $e.Data
[void](($Event.MessageData).AppendLine($e.Data))
}
catch
{
Write-Host -ForegroundColor "Red" "Error capturing processes std error" $Error
}
finally
{
[System.Threading.Monitor]::Exit($Event.MessageData)
}
}
$outEvent = Register-ObjectEvent -InputObj $proc `
-Event "OutputDataReceived" `
-MessageData $cmdOut `
-Action `
{
param
(
[System.Object] $sender,
[System.Diagnostics.DataReceivedEventArgs] $e
)
try
{
[System.Threading.Monitor]::Enter($Event.MessageData)
#Write-Host $e.Data
[void](($Event.MessageData).AppendLine($e.Data))
}
catch
{
Write-Host -ForegroundColor "Red" "Error capturing processes std output" $Error
}
finally
{
[System.Threading.Monitor]::Exit($Event.MessageData)
}
}
$isStarted = $proc.Start()
$proc.BeginOutputReadLine()
$proc.BeginErrorReadLine()
while (!$proc.HasExited)
{
Start-Sleep -Milliseconds 100
}
Start-Sleep -Milliseconds 1000
$procExitCode = $proc.ExitCode
$procStartTime = $proc.StartTime
$procFinishTime = Get-Date
$proc.Close()
$proc.CancelOutputRead()
$proc.CancelErrorRead()
$result = New-Object PsObject -Property @{
ExitCode = $procExitCode
StartTime = $procStartTime
FinishTime = $procFinishTime
ElapsedTime = $procFinishTime.Subtract($procStartTime)
StdErr = ""
StdOut = $cmdOut.ToString()
}
return $result
}
答案 0 :(得分:0)
我的2美分......它不是powershell问题,而是System.Diagnostics.Process类和底层shell中的问题/错误。我已经看到包装StdError和StdOut没有捕获所有内容的时候,有时候'listen'包装器应用程序将因为底层应用程序如何写入控制台而无限期挂起。 (在c / c ++世界中有很多不同的方法,[例如WriteFile,fprintf,cout等])
此外,可能需要捕获超过2个输出,但.net框架仅显示这两个(假设它们是两个主要的)[请参阅有关命令重定向here的文章它开始给出提示)。
我的猜测(无论是你的问题还是我的问题)都是因为它与一些低级缓冲区刷新和/或引用计数有关。 (如果你想深入,你可以开始here)
一种(非常hacky)解决这个问题的方法是直接执行程序以实际执行包含在cmd.exe中使用2>& 1的包装,但这种方法有其自身的缺陷和问题。 / p>
最理想的解决方案是让可执行文件具有日志记录参数,然后在进程退出后解析日志文件...但大多数情况下您没有该选项。
但是等等,我们正在使用PowerShell ......为什么你首先使用System.Diagnositics.Process?你可以直接调用命令:
$output = & (GetDTExecPath) /FILE "$fileName" /CHECKPOINTING OFF /REPORTING "EWP"
答案 1 :(得分:0)
输出被截断的原因是Powershell从WaitForExit()返回并在处理队列中的所有输出事件之前设置HasExited属性。
一种解决方案,它可以通过短暂的休眠来循环任意数量的时间以允许处理事件; Powershell事件处理似乎没有先发制人,因此单次长时间睡眠不允许事件处理。
更好的解决方案是在Process上注册Exited事件(除了Output和Error事件)。此事件是队列中的最后一个事件,因此如果您在此事件发生时设置了一个标志,那么您可以使用短暂休眠循环,直到设置此标志并知道您已处理了所有输出事件。
我已经写了一个完整的解决方案on my blog,但核心代码段是:
# Set up a pair of stringbuilders to which we can stream the process output
$global:outputSB = New-Object -TypeName "System.Text.StringBuilder";
$global:errorSB = New-Object -TypeName "System.Text.StringBuilder";
# Flag that shows that final process exit event has not yet been processed
$global:myprocessrunning = $true
$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = $target
$ps.StartInfo.WorkingDirectory = Split-Path $target -Parent
$ps.StartInfo.UseShellExecute = $false
$ps.StartInfo.RedirectStandardOutput = $true
$ps.StartInfo.RedirectStandardError = $true
$ps.StartInfo.CreateNoWindow = $true
# Register Asynchronous event handlers for Standard and Error Output
Register-ObjectEvent -InputObject $ps -EventName OutputDataReceived -action {
if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
$global:outputSB.AppendLine(((get-date).toString('yyyyMMddHHmm')) + " " + $EventArgs.data)
}
} | Out-Null
Register-ObjectEvent -InputObject $ps -EventName ErrorDataReceived -action {
if(-not [string]::IsNullOrEmpty($EventArgs.data)) {
$global:errorSB.AppendLine(((get-date).toString('yyyyMMddHHmm')) + " " + $EventArgs.data)
}
} | Out-Null
Register-ObjectEvent -InputObject $ps -EventName Exited -action {
$global:myprocessrunning = $false
} | Out-Null
$ps.start() | Out-Null
$ps.BeginOutputReadLine();
$ps.BeginErrorReadLine();
# We set a timeout after which time the process will be forceably terminated
$processTimeout = $timeoutseconds * 1000
while (($global:myprocessrunning -eq $true) -and ($processTimeout -gt 0)) {
# We must use lots of shorts sleeps rather than a single long one otherwise events are not processed
$processTimeout -= 50
Start-Sleep -m 50
}
if ($processTimeout -le 0) {
Add-Content -Path $logFile -Value (((get-date).toString('yyyyMMddHHmm')) + " PROCESS EXCEEDED EXECUTION ALLOWANCE AND WAS ABENDED!")
$ps.Kill()
}
# Append the Standard and Error Output to log file, we don't use Add-Content as it appends a carriage return that is not required
[System.IO.File]::AppendAllText($logFile, $global:outputSB)
[System.IO.File]::AppendAllText($logFile, $global:errorSB)