2012-10-28 51 views
0

我正在PowerShell腳本中運行DTEXEC.exe命令,試圖捕獲並將輸出記錄到文件中。有時輸出不完整,我試圖弄清楚爲什麼會出現這種情況,以及可能會做些什麼。似乎從來沒有得到記錄的線條是最有趣:通過PowerShell運行的命令的捕獲輸出有時不完整

DTEXEC: The package execution returned DTSER_SUCCESS(0) 
Started: 10:58:43 a.m. 
Finished: 10:59:24 a.m. 
Elapsed: 41.484 seconds 

輸出似乎總是不完全對執行少於〜25秒鐘,這可能是一個線索包(沒有多大的輸出或者快速完成)。

我正在使用.NET System.Diagnostics.Process和ProcessStartInfo來設置和運行命令,並將stdout和stderror重定向到事件處理程序,每個事件處理程序附加到隨後寫入磁盤的StringBuilder。

該問題感覺像是計時問題或緩衝問題。爲了解決計時問題,我試圖使用Monitor.Enter/Exit。如果這是一個緩衝問題,我不知道如何強制進程不緩衝stdout和stderror。

環境是 - PowerShell的2運行CLR版本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

我的2美分......它不是一個PowerShell問題,而是System.Diagnostics.Process類和底層shell中的問題/錯誤。我已經看到了包裝StdError和StdOut並沒有捕捉到所有東西的時候,以及其他時候,由於底層應用程序寫入控制檯,'聆聽'包裝應用程序將無限期掛起。 (在c/C++世界中有很多不同的方式來做到這一點,[例如WriteFile,fprintf,cout等])

此外,還有2個以上的輸出可能需要捕獲,但.net框架只顯示你這兩個(因爲它們是兩個主要的)[請參閱本文有關命令重定向here,因爲它開始提示)。

我的猜測(對於你的問題以及我的問題)是它必須處理一些低級緩衝區刷新和/或參考計數。 (如果你想獲得深,你就可以開始here

一(非常哈克)的方式來解決這個問題的,而不是直接執行程序實際上在呼叫執行包裹它與2> & cmd.exe的1,但這種方法有其自己的陷阱和問題。

最理想的解決方案是讓可執行文件具有日誌記錄參數,然後在進程退出後解析日誌文件......但大多數情況下,您沒有該選項。

但是等等,我們正在使用powershell ...爲什麼你首先使用System.Diagnositics.Process?你可以直接調用命令:

$output = & (GetDTExecPath) /FILE "$fileName" /CHECKPOINTING OFF /REPORTING "EWP" 
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)