lørdag den 1. oktober 2011

Getting output from process spawned from powershell

Updated 02-10-2011: Fixed a small bug that could make SmartProcess consume 100%
There is several ways you can spawn a new process from within PowerShell. Start-Process for instance. If you create a new object of type System.Diagnostics.Process or call Start-Process and get such an object back, you can now get access to the Standard Input/Standard Output … there is a “catch” thou. These only get updated every time the process sends a complete line including a newline.

So lets say you spawn nslookup and want to catch every time i t expects input, it wont work.

function StartProcess([String]$FileName){
$process = New-Object "System.Diagnostics.Process"
$startinfo = New-Object "System.Diagnostics.ProcessStartInfo"
$startinfo.FileName = $FileName
#$startinfo.Arguments = $arguments
#$startinfo.WorkingDirectory = $pwd.Path
$startinfo.UseShellExecute = $false
$startinfo.RedirectStandardInput = $true
$startinfo.RedirectStandardOutput = $true
$startinfo.RedirectStandardError = $false
#$startinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
$startinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal

$process.StartInfo = $startinfo
$temp = $process.start()
return $process
}

function GetPrompt([System.Diagnostics.Process]$process, [Int]$waitSec){
$str = ''
$i = 1
while( (!([String]$str).contains("> ")) -and (!$process.HasExited) -and ($i -le $waitSec) -and ($process.StandardOutput.Peek() -eq -1) ){
# Write-Host ("Peek: " + $process.StandardOutput.Peek())
while($process.StandardOutput.Peek() -ge 0){
$iChar = $process.StandardOutput.Read()
$str = ($str + [Convert]::ToChar($iChar))
}
if(!([String]$str).contains("> ")){ Write-Host "."; Start-Sleep -s 1; }
$i = $i + 1
}
return $str
}

if($true){
#[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
#[System.Windows.Forms.SendKeys]::SendWait("skadefro.dk")

if(!$process.HasExited){ Write-Host "Killing process"; $process.kill(); $process.StandardOutput.ReadToEnd() }
$process = StartProcess 'nslookup'
GetPrompt
$process 2
$process.StandardInput.WriteLine('set q=MX')
GetPrompt
$process 2
$process.StandardInput.WriteLine('skadefro.dk')
$process.StandardInput.Flush()
#$process.StandardOutput.ReadToEnd()
GetPrompt $process 2

}
if(!$process.HasExited){ $process.kill() }


So you could write some code and hook up to the ErrorDataReceived and OutputDataReceived events. but those have the same problem, so you need to dig a bit deeper. You need to take each stream object and hook up to BaseStream.BeginRead


So that is what I have done. Inside my SuperOffice Add in I have added a new command called New-SmartProcess


And now the above code will look cleaner and actually work


function WaitForPrompt([wingu.SmartProcess]$process, [Int]$waitSec){
$str = ''
$i = 1
while( (!($process.StandardOutput.contains("> ")) -and (!$process.HasExited) -and ($i -le $waitSec))){
if(!([String]$str).contains("> ")){ Write-Host "."; Start-Sleep -s 1; }
$i = $i + 1
}
return $str
}

if($process){ if(!$process.HasExited){ Write-Host "Killing process"; $process.kill(); $process.StandardOutput.ReadToEnd() } }
$process = New-SmartProcess 'nslookup'
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
$process.WriteLine('set q=MX')
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
$process.WriteLine('skadefro.dk')
WaitForPrompt
$process 2
$process.StandardOutput
$process.StandardOutput = ''
if(!$process.HasExited){ $process.kill() }

You can find my SuperOffice PowerShell snapin inclucing Source code here


Ingen kommentarer:

Send en kommentar