1

I'm using PowerShell to launch an app called GoodSync to perform a backup for me. At the end of the process, I have it email me the results. I also like to (sometimes) watch the PowerShell window to keep an eye on it and watch the status of the backup while it's processing. As my code sits right now, the stdout is going to console and that's it.

My question: Is it possible to have stdout go to console, AND save it to a variable for later processing?

You can see I'm trying something with the $output variable, but getting nowhere. Here's the error $output is returning:

You cannot call a method on a null-valued expression.
At GoodSync.ps1:119 char:42
+     $output = $proc.StandardOutput.ReadToEnd <<<< ()
    + CategoryInfo          : InvalidOperation: (ReadToEnd:String) [], Runtime
   Exception
    + FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At GoodSync.ps1:120 char:42
+     $output += $proc.StandardError.ReadToEnd <<<< ()
    + CategoryInfo          : InvalidOperation: (ReadToEnd:String) [], Runtime
   Exception
    + FullyQualifiedErrorId : InvokeMethodOnNull

Here's my code:

(And to be a little proactive from all you gurus out there, yes I'm doing a lot with this code to output to both a file and stdout so I can keep an eye on everything. This section is just a snippet of the whole file.)

###############################
## Call the GoodSync process ##
###############################

# This is the section taken from http://stackoverflow.com/questions/8925323
# This is a known working section. However, right now I don't know how to save the stdout to variable and insert into email log file.
$procInfo = New-Object System.Diagnostics.ProcessStartInfo
$procInfo.FileName = "C:\Program Files\Siber Systems\GoodSync\gsync.exe"
$procInfo.UseShellExecute = $false
$procInfo.Arguments = '/progress=yes /exit sync MyBackupJobName'
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $procInfo
$proc.Start() | Out-Host
(Get-Date -format T) + " - Loaded gsync.exe. Backup process running. Please stand by..." | Tee-Object -Variable RoboLog
$RoboLog >> $myLogFile
"" | Tee-Object -Variable RoboLog
$RoboLog >> $myLogFile
$proc.WaitForExit()
(Get-Date -format T) + " - Backup complete and gsync.exe has exited." | Tee-Object -Variable RoboLog
$RoboLog >> $myLogFile
"" | Tee-Object -Variable RoboLog
$RoboLog >> $myLogFile

# Now we take the exit code, write it to console and log file, and begin to build our email report. 
if ($proc.ExitCode -eq 0) { 
    "Success: GoodSync Reported: Analyze or Sync Successfully Completed." | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
    $subject = $RoboLog
    "" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
} elseif ($proc.ExitCode -eq 1) {
    "Failure: GoodSync Error: Analyze had Terminal Errors. Did gsync.exe close abruptly?" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
    $subject = $RoboLog
    "" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
} elseif ($proc.ExitCode -eq 2) {
    "Failure: GoodSync Error: Sync had Terminal Errors. Did gsync.exe close abruptly?" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
    $subject = $RoboLog
    "" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
} else { 
    "Failure: GoodSync Error: General Error. Check log file." | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
    $subject = $RoboLog
    "" | Tee-Object -Variable RoboLog
    $RoboLog >> $myLogFile
}

# Grab the stdout and stderr to a variable
$output = $proc.StandardOutput.ReadToEnd()
$output += $proc.StandardError.ReadToEnd()
# The ReadToEnd() makes everything 1 long string, so we break it up by carriage return, and filter
$output -split "`n"
# Write to log file for email capture
$output >> $myLogFile

"" | Tee-Object -Variable RoboLog
$RoboLog >> $myLogFile

#---------------------------------------------------------------------------------------------------
# GoodSync backup jobs are now complete.
#---------------------------------------------------------------------------------------------------
Pat
  • 1,173
  • 5
  • 19
  • 44

2 Answers2

2

The error from $proc.StandardOutput.ReadToEnd (You cannot call a method on a null-valued expression.) is because you need to set these properties on the ProcessStartInfo object:

$procInfo.RedirectStandardError = $true
$procInfo.RedirectStandardOutput = $true

Note - when setting these, output will not be displayed on the console.

And here's a way to store output in a variable and display it on the console using Tee-Object:

& ping.exe localhost -n 1 | Tee-Object -Variable output
$output

BTW - You can do a switch on $LASTEXITCODE -- e.g.

switch ($LASTEXITCODE) {
    0 {'good'}
    1 {'bad'}
    2 {'ugly'}
}
Andy Arismendi
  • 50,577
  • 16
  • 107
  • 124
  • Thank you Andy. I've added those Redirects, but now I don't have anything showing in console, and based on your example (I'm not using ping.exe but assume I'd substitute it with gsync.exe), I'm not sure where I'd stick that in the code? Is it realtime display too? – Pat Apr 23 '13 at 20:32
  • @Pat, you can't redirect *and* display using the `System.Diagnostics.Process` class. So instead of using that, just invoke your program using the call operator `&` (example given with ping.exe). You can check the exit code after using `$LASTEXITCODE`. – Andy Arismendi Apr 23 '13 at 20:34
  • Okay, I will give that a try. – Pat Apr 23 '13 at 20:40
  • Interesting. If I add the `Tee-Object -Variable output` `$output` to the script, I get nothing in the log and nothing in console. If I remove the `Tee-Object` then console shows the stdout. Thoughts? – Pat Apr 23 '13 at 20:48
  • It works in the example I provided. What is the full line you are testing? – Andy Arismendi Apr 23 '13 at 20:52
  • You're right, it is working. It's just not in real time. For example, when I use robocopy, I'm able to pipe that to `Tee-Object` and get real time stdout as well as stdout to a variable. Any trick on how to get `&`, `Invoke-Expression`, or `Start-Process` to do this (variable and realtime console)? – Pat Apr 23 '13 at 22:51
  • Ended up catching someone in chat who said the exe isn't releasing control for the Tee-Object to update (whereas Robocopy does this). So, I can't really have my cake and eat it too. Bummer. – Pat Apr 23 '13 at 23:22
1

Why not just run it as a background job? Then you can use Receive-Job to get the output (as many times as you want if you use -Keep, and do whatever you want with it.

mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • That sounds good, but I have to admit: I don't know how to do that. (at least I don't think I know). This snippet was mostly taken from the stackoverflow link in the code comment area. I'm guessing it's Start-Process? I'm doing a little reading on the side here. – Pat Apr 23 '13 at 20:37
  • 2
    Start with Get-Help About_Jobs – mjolinor Apr 23 '13 at 20:42