I use the following command in Powershell to convert files in the background but would like to log the results all in one file. Now the -RedirectStandardOutput replaces the file each run.
foreach ($l in gc ./files.txt) {Start-Process -FilePath "c:\Program Files (x86)\calibre2\ebook-convert.exe" -Argumentlist "'$l' '$l.epub'" -Wait -WindowStyle Hidden -RedirectStandardOutput log.txt}
I tried with a redirect but then the log is empty. If possible I would like to keep it a one-liner.
foreach ($l in gc ./files.txt) {Start-Process -FilePath "c:\Program Files (x86)\calibre2\ebook-convert.exe" -Argumentlist "`"$l`" `"$l.epub`"" -Wait -WindowStyle Hidden *> log.txt}
CodePudding user response:
You can use redirection and append to files if you don't use Start-Process
, but a direct invocation:
foreach ($l in gc ./files.txt) {& 'C:\Program Files (x86)\calibre2\ebook-convert.exe' "$l" "$l.epub" *>> log.txt}
CodePudding user response:
If sequential, synchronous execution is acceptable, you can simplify your command to use a single output redirection (the assumption is that ebook-convert.exe
is a console-subsystem application, which PowerShell therefore executes synchronously (in a blocking manner).:
Get-Content ./files.txt | ForEach-Object {
& 'c:\Program Files (x86)\calibre2\ebook-convert.exe' $_ "$_.epub"
} > log.txt
If you want to control the character encoding, use Out-File
- which >
effectively is an alias for - with its -Encoding
parameter; or, preferably, with text output - which external-program output always is in PowerShell - Set-Content
.
Note that PowerShell never passes raw output from external programs through to files - they are first always decoded into .NET strings, based on the encoding stored in [Console]::OutputEncoding
(the system's active legacy OEM code page by default), and then re-encoded on saving to a file, using the file-writing cmdlet's own defaults, unless overridden with -Encoding
- see this answer for more information.
If you want asynchronous, parallel execution (such as via Start-Process
, which is asynchronous by default), your best bet is to:
write to separate (temporary) files:
Pass a different output file to
-RedirectStandardOutput
/-RedirectStandardError
in each invocation.Note that if you want to merge stdout and stderr output and capture it in the same file, you'll have to call your
.exe
file via a shell (possibly another PowerShell instance) and use its redirection features; for PowerShell, it would be*>log.txt
; forcmd.exe
(as shown below), it would be> log.txt 2>&1
wait for all launched processes to finish:
Pass
-PassThru
toStart-Process
and collect the process-information objects returned.Then use
Wait-Process
to wait for all processes to terminate; use the-Timeout
parameter as needed.
and then merge them into a single log file.
Here's an implementation:
$procsAndLogFiles =
Get-Content ./files.txt | ForEach-Object -Begin { $i = 0 } {
# Create a distinct log file for each process,
# and return its name along with a process-information object representing
# each process as a custom object.
$logFile = 'log{0:000}.txt' -f $i
[pscustomobject] @{
LogFile = $logFile
Process = Start-Process -PassThru -WindowStyle Hidden `
-FilePath 'cmd.exe' `
-ArgumentList '/c', "`"c:\Program Files (x86)\calibre2\ebook-convert.exe`" `"$_`" `"$_.epub`" > `"$logFile`" 2>&1"
}
}
# Wait for all processes to terminate.
# Add -Timeout and error handling as needed.
$procsAndLogFiles.Process | Wait-Process
# Merge all log files.
Get-Content -LiteralPath $procsAndLogFiles.LogFile > log.txt
# Clean up.
Remove-Item -LiteralPath $procsAndLogFiles.LogFile