A simple task and then again not
A customer asked me if it was possible to grab output from a command and analyze the output afterwards.
In the particular case he needs to call a telnet session and check if there was a proper response from the server.
The easy solution and then again not
The very simple solution would be to start the command from PowerShell, redirecting the output to a file, wait for the process to finish and then read the file content.
But in this case the process would not end on its own, as the telnet server would prompt for username and password.
The complex solution and then again not
Having played around with grabbing output from processes using .NET, I knew that the System.Diagnostics.ProcessStartInfo object was able to handle redirecting of output without using files.
In order to keep a handle on the process after starting it, I would use System.Diagnostics.Process, as that object gives you access to the process during runtime and keeps data after the process exits.
So I fired up PowerShell ISE (I simply love that tool) and started typing away.
# Setup the Process startup info $pinfo = New-Object System.Diagnostics.ProcessStartInfo $pinfo.FileName = "ping.exe" $pinfo.Arguments = "localhost -t" $pinfo.UseShellExecute = $false $pinfo.CreateNoWindow = $true $pinfo.RedirectStandardOutput = $true $pinfo.RedirectStandardError = $true
Instead of using a telnet command, I just use a simple ping localhost to simulate a command that generates output, I also added the –t switch to make ping run forever.
To kick off the command, we need a Process object that uses the ProcessStartInfo object.
# Create a process object using the startup info $process= New-Object System.Diagnostics.Process $process.StartInfo = $pinfo
In some cases the command may exit on its own after doing whatever it does, but in the case of the telnet (and ping with –t) it will not, so we need to handle this, I decided that a 5 second wait would be sufficient in this case. So after waiting for 5 seconds we check to see if the process is still running, if so we just go and kill it.
# Start the process $process.Start() | Out-Null # Wait a while for the process to do something sleep -Seconds 5 # If the process is still active kill it if (!$process.HasExited) { $process.Kill() }
Now that we are sure that the command is done executing, we can grab the output from stdout and stderr, both are useful when we analyze the output from a command line tool, as some will write only to stdout while others will write normal output to stdout and errors to stderr. Remember to call the method ReadToEnd() and not just assign the value of StandardOutput to your variable. The content from StandardOutput is a stream object that must be read like a file.
# get output from stdout and stderr $stdout=$process.StandardOutput.ReadToEnd() $stderr=$process.StandardError.ReadToEnd()
Now that we have the output from the command line, we can start to analyze if things went well or not. In the case of Ping we can look for the text “Reply from” to indicate success.
A little note to remember, the $stdout contains an array of strings, and normally $stdout -contains “Reply to” would return true, but for some reason it doesn’t. However calling the array method contains will correctly return true if the array contains the text “Reply to”. I suspect this to be caused by the fact the array is a .NET object but I am not sure of this.
# check output for success information, you may want to check stderr if stdout if empty if ($stdout.Contains("Reply from")) { # exit with errorlevel 0 to indicate success exit 0 } else { # exit with errorlevel 1 to indicate an error exit 1 }
The script simply returns 0 if the Reply from text is found, and 1 if not.
You can download the complete script here
[download id=”11203″]
Great tips ! Thanks !
Great posting!
I have a similar problem running a .Net BUILD.
Here is my code:
#—————————————————————
$ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo;
$ProcessStartInfo.Arguments = $BuildArguments;
$ProcessStartInfo.FileName = $gDevEnv ;
$ProcessStartInfo.CreateNoWindow = $true
$ProcessStartInfo.RedirectStandardError = $True;
$ProcessStartInfo.RedirectStandardOutput = $True;
$ProcessStartInfo.UseShellExecute = $False;
$BuildProcess = New-Object System.Diagnostics.Process;
$BuildProcess.StartInfo = $ProcessStartInfo;
$BuildProcess.Start() | Out-Null; # ignore return value with Out-Null
$BuildProcess.WaitForExit();
$stdout = $BuildProcess.StandardOutput.ReadToEnd();
if ($stdout.Contains(“0 failed”) -eq $False)
{
Write-Output ‘***** Build failed see log ‘ ;
Exit 1;
}
The if statement throws the following error:
BuildSolutionOrProject : Build Error
“D:MyProjectFolderMyProject.Net.vdproj”, You cannot call a method on a null-valued expression.
At D:BuildScriptsTargets.ps1:157 char:6
+ BuildSolutionOrProject $gTFSDiskLocatio $TargetInfo
etc
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorExcep
tion
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExceptio
n,BuildSolutionOrProject
The error is in the if ($logBody.Contains(“0 failed”) -eq $False) line.
Thanks for any help!
The error you get, indicates that the stdout does not contain any value. You may want to check if the application you run from your code actually uses standard output.
This works great! Thanks for the time to create this tip!
[…] Capture output from command line tools with PowerShell – Now that we are sure that the command is done executing, we can grab the output from stdout and stderr, both are useful when we analyze the output from a command line …… […]
I Ronnie, the code is actually flooded with HTML tags can you please repost
Please repost the code.
# Setup the Process startup info
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = “ping.exe”
$pinfo.Arguments = “localhost -t”
$pinfo.UseShellExecute = $false
$pinfo.CreateNoWindow = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.RedirectStandardError = $true
# Create a process object using the startup info
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $pinfo
# Start the process
$process.Start() | Out-Null
# Wait a while for the process to do something
sleep -Seconds 5
# If the process is still active kill it
if (!$process.HasExited) {
$process.Kill()
}
# get output from stdout and stderr
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
# check output for success information, you may want to check stderr if stdout if empty
if ($stdout.Contains(“Reply from”)) {
# exit with errorlevel 0 to indicate success
exit 0
} else {
# exit with errorlevel 1 to indicate an error
exit 1
}
Code and download should work now