64

I need to collect the standard output and error log from several processes into one single log file.

So every output must append to this log file.

I want to call all the jobs with lines like this:

$p=start-process myjob.bat -redirectstandardoutput $logfile -redirecterroroutput $logfile -wait

Where do I have to put the information to append?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
PSBeginner
  • 701
  • 1
  • 5
  • 7

4 Answers4

96

In order to append to a file you'll need to use a slightly different approach. You can still redirect an individual process' standard error and standard output to a file, but in order to append it to a file you'll need to do one of these things:

  1. Read the stdout/stderr file contents created by Start-Process
  2. Not use Start-Process and use the call operator, &
  3. Not use Start-Process and start the process with .NET objects

The first way would look like this:

$myLog = "C:\File.log"
$stdErrLog = "C:\stderr.log"
$stdOutLog = "C:\stdout.log"
Start-Process -File myjob.bat -RedirectStandardOutput $stdOutLog -RedirectStandardError $stdErrLog -wait
Get-Content $stdErrLog, $stdOutLog | Out-File $myLog -Append

The second way would look like this:

& myjob.bat 2>&1 >> C:\MyLog.txt

Or this:

& myjob.bat 2>&1 | Out-File C:\MyLog.txt -Append

The third way:

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "myjob.bat"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$output = $p.StandardOutput.ReadToEnd()
$output += $p.StandardError.ReadToEnd()
$output | Out-File $myLog -Append
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andy Arismendi
  • 50,577
  • 16
  • 107
  • 124
  • Does it works for Powershell Remoting? I have testing, I think only the second way works for PS Remoting. – Kiquenet May 06 '13 at 09:24
  • 5
    The third way causes a deadlock in some circumstances, read the MSDN or discussion here http://stackoverflow.com/a/8762068/151641 – mloskot Jun 17 '15 at 16:19
  • Excellent! `2>&1 | Out-File $file -Append -Encoding UTF8` is afaik the only way to redirect both stdout and stderr *and* use UTF-8 output. – marsze Jan 10 '19 at 10:59
24

Like Unix shells, PowerShell supports > redirects with most of the variations known from Unix, including 2>&1 (though weirdly, order doesn't matter - 2>&1 > file works just like the normal > file 2>&1).

Like most modern Unix shells, PowerShell also has a shortcut for redirecting both standard error and standard output to the same device, though unlike other redirection shortcuts that follow pretty much the Unix convention, the capture all shortcut uses a new sigil and is written like so: *>.

So your implementation might be:

& myjob.bat *>> $logfile
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Guss
  • 30,470
  • 17
  • 104
  • 128
23

Andy gave me some good pointers, but I wanted to do it in an even cleaner way. Not to mention that with the 2>&1 >> method PowerShell complained to me about the log file being accessed by another process, i.e. both stderr and stdout trying to lock the file for access, I guess. So here's how I worked it around.

First let's generate a nice filename, but that's really just for being pedantic:

$name = "sync_common"
$currdate = get-date -f yyyy-MM-dd
$logfile = "c:\scripts\$name\log\$name-$currdate.txt"

And here's where the trick begins:

start-transcript -append -path $logfile

write-output "starting sync"
robocopy /mir /copyall S:\common \\10.0.0.2\common 2>&1 | Write-Output
some_other.exe /exeparams 2>&1 | Write-Output
...
write-output "ending sync"

stop-transcript

With start-transcript and stop-transcript you can redirect ALL output of PowerShell commands to a single file, but it doesn't work correctly with external commands. So let's just redirect all the output of those to the stdout of PS and let transcript do the rest.

In fact, I have no idea why the MS engineers say they haven't fixed this yet "due to the high cost and technical complexities involved" when it can be worked around in such a simple way.

Either way, running every single command with start-process is a huge clutter IMHO, but with this method, all you gotta do is append the 2>&1 | Write-Output code to each line which runs external commands.

bviktor
  • 1,356
  • 13
  • 14
  • Because it can't, it depends precisely on what the program you called does. In this case "robocopy" works, that's not true for everything. – Luiz Felipe Jun 15 '21 at 17:37
0

Maybe it is not quite as elegant, but the following might also work. I suspect asynchronously this would not be a good solution.

$p = Start-Process myjob.bat -redirectstandardoutput $logtempfile -redirecterroroutput $logtempfile -wait
add-content $logfile (get-content $logtempfile)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 4
    It doesn't actually work because Powershell doesn't allow you to redirectstandardouput and redirecterroroutput to the same file. "Start-Process : This command cannot be run because "RedirectStandardOutput" and "RedirectStandardError" are same. Give different inputs and Run your command again." is the error I got when I tried this. – Bob Apr 08 '16 at 20:14
  • I think the poster meant to use $logFile for -redirectstandardoutput. The exe would write to the two files and then the second line appends one file to the other. That would work. But, it does not collate the lines; just appends. Often an exe writes to err only at the end of a run so that would often work. But not in the general case. – steve Dec 09 '20 at 21:35