In my powershell script I have a variable which captures its commands output
$files= dosomething
so running dosomething alone will produce output to the stdout. My question is how do I get that same functionality out of my variable as its running? mostly as a way to see progress.
maybe best to run the command and then somehow capture that to a variable?
UPDATE 1:
here's the command I'm trying to monitor:
$remotefiles = rclone md5sum remote: --dry-run --max-age "$lastrun" --filter-from "P:\scripts\filter-file.txt"
UPDATE 2:
running rclone md5sum remote: --dry-run --max-age "$lastrun" --filter-from "P:\scripts\filter-file.txt"
directly produces output ...
The simplest solution is to enclose your assignment in (...)
, the grouping operator, which passes the result of the assignment through; e.g:
# Enclosing an assignment in (...) passes the value that is assigned through.
PS> ($date = Get-Date)
Friday, May 21, 2021 7:43:48 PM
Note:
Use of (...)
implies that all command output is captured first, before passing it through; that is, you won't see the passed-through output until after the command has terminated - see the bottom section for a solution.
An assignment by default captures only success output (from PowerShell commands) / stdout output (from external commands).
If you (also) want to capture error-stream / stderr output, use 2>&1
to merge the error-stream / stderr output into the success stream / stdout (see the conceptual about_Redirection topic); in the case of the external rclone
program (for brevity, the actual arguments are omitted and represented with placeholder ...
):
# Merge stderr into stdout and capture the combination, and also pass it through.
([string[]] $remotefiles = rclone ... 2>&1)
Note: the [string[]]
type constraint ensures that the stderr lines too are captured as strings; by default, PowerShell returns them as [System.Management.Automation.ErrorRecord]
instances. If you want pipeline logic, i.e. to only capture a [string]
scalar if there's only one output line and use an array only for multiple lines, use($remotefiles = rclone ... 2>&1 | % ToString)
instead.
As Abraham Zinala points out, PowerShell cmdlets (including advanced functions and scripts), support the common -OutVariable
parameter, which also allows capturing a cmdlet's success output in addition to outputting it to the success output stream:
# Outputs *and* saves in variable $date.
# Note that the -OutVariable argument must *not* start with "$"
PS> Get-Date -OutVariable date
Friday, May 21, 2021 7:43:48 PM
Note: Using 2>&1
does not cause the -OutVariable
to also capture error-stream output, but you can capture errors separately, using the common -ErrorVariable
parameter.
Caveat: Surprisingly, the -OutVariable
target variable[1]always receives a [System.Collections.ArrayList]
instance, which is unusual in two respects:
PowerShell generally uses regular array ([object[]]
) instances to collect multiple output objects, such as in an assignment.
While difference in (read-only) access between an array list and a regular array will rarely matter, the more problematic aspect is that an array list is used even when the cmdlet outputs just ONE object, unlike in assignments; in the case at hand this means:
$date = Get-Date
stores a [datetime]
instance in variable $date
Get-Date -OutVariable date
stores a single-element array list containing a [datetime]
instance in $date
.This unexpected behavior is the subject of GitHub issue #3154.
By contrast, Tee-Object
's -Variable
parameter, as shown in SADIK KUZU's helpful answer, does not exhibit this unexpected behavior; that is, it behaves the same way as an assignment.
However, given that use of Tee-Object
is by definition an additional call and therefore requires an extra pipeline segment, you do pay a performance penalty (though that may not matter in practice).
Solution for a long-running (external) command whose output should be passed through as it is being produced:
# Merge stderr into stdout and output in streaming fashion,
# while capturing the (stringified) lines in variable $remoteFiles
rclone ... 2>&1 | % ToString -OutVariable remoteFiles
Note: %
is the built-in alias for the ForEach-Object
cmdlet; ToString
calls the .ToString()
method on each object, which is necessary for converting the stderr output lines to strings, because PowerShell by default wraps them in[System.Management.Automation.ErrorRecord]
instances; if conversion to strings isn't desired or necessary, replace% ToString -OutVariable remoteFiles
withTee-Object -Variable remoteFiles
.
[1] The same applies to the other common -*Variable
parameters, such as -ErrorVariable
.
Tee-Object saves command output in a file or variable and also sends it down the pipeline.
Get-Process notepad | Tee-Object -Variable proc | Select-Object processname,handles
ProcessName Handles
----------- -------
notepad 43
notepad 37
notepad 38
notepad 38
This example gets a list of the processes running on the computer, saves them to the $proc
variable, and pipes them to Select-Object
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With