I would like to get progress-output from native programs like restic backup or yt-dlp in powershell, even though I'm running the program from within a block like this. Is it possible for the inner script block to output to the console like when executing it directly? I have control over the executeWithPidLock implementation.
executeWithPidLock {
.\restic.exe -r temp-repo backup 'C:\Tools\AlwaysMouseWheel'
}
I know I can run with new window like this and get console progress output, which is not that bad, but not really what I want, as I don't want window popping up when running from Task scheduler:
measure-command {
$process = Start-Process -FilePath "./restic" -ArgumentList @("-r", "temp-repo","backup",'C:\Tools\') -PassThru -Wait
$process.WaitForExit()
}
EDIT (to add some extra needed info, based on first answer):
An option is to manually pipe to out-host, as correctly suggested by mklement0.
Measure-Command { yt-dlp "https://www.youtube.com/watch?v=NS5NKykuQno" | out-host}
However this will not behave like directly in console for progress information. For 'restic' it completely skips the progress information. For 'yt-dlp' it prints each %-line, where the direct execution deletes each old line, like this.
[download] 33.3% of 242.55MiB at 6.74MiB/s ETA 00:23
[download] 33.4% of 242.55MiB at 2.00MiB/s ETA 01:20
[download] 33.4% of 242.55MiB at 2.70MiB/s ETA 00:59
[download] 33.4% of 242.55MiB at 3.75MiB/s ETA 00:43
If it is not possible in Powershell 7.3.9 to execute a native command and get unmolested progress info, that is a valid answer (though a link to some doc would be greatly appreciated).
Some general advice:
c:\path\to\some.exe ...
or & $exePath ...
). This also allows you to capture their output directly and to later query their exit code via $LASTEXITCODE
. Do not use Start-Process
(or the System.Diagnostics.Process
API it is based on) - see this answer. GitHub docs issue #6239 provides guidance on when use of Start-Process
is and isn't appropriate.Heeding this advice also solves your specific problem:
PowerShell streams the output from external (console) applications line by line to the pipeline, so you can see or process lines as they're being emitted, even from inside a script block ({ ... }
).
A script block passed to Measure-Command
is a special case, as the latter explicitly suppresses output from the commands in the script block.
You can work around this - for display-only output - with Out-Host
, as the following example shows, which also demonstrates the streaming nature of the output:
Measure-Command {
cmd /c 'echo 1 & timeout 2 >NUL & echo err >&2 & echo 2' *>&1 | Out-Host
}
1
will print right away, even though the command hasn't finished executing yet.
Note the use of redirection *>&1
to ensure that stderr output is output too:
Redirected stderr lines print in red and - in Windows PowerShell - the first stderr line prints as if it were a PowerShell error (no longer a problem in PowerShell (Core) 7+).
conhost.exe
) (as opposed to in Windows Terminal), the ANSI/VT escape-code based coloring is rendered incorrectly in a pipeline involving a native (external program) - see GitHub issue #18771, which was inappropriately closed.To avoid that, insert a ForEach-Object ToString
pipeline segment before Out-Host
.
If you invoked the script block directly (typically via &
, the call operator), you wouldn't need Out-Host
and the external program's output would be streamed to PowerShell's success output stream
-- EDIT (by OP)
So if you control your function yourself, you can do this:
function executeBlockWithCall([ScriptBlock]$Block) {
Write-Host "Do stuff before..."
# Invoke-Command $Block # this also works to get progress- output to main console
& $Block
Write-Host "...and after"
}
executeBlockWithCall { yt-dlp "https://www.youtube.com/watch?v=NS5NKykuQno" }
and the progress output will stream unmolested to your console window.