PowerShell: Is there an automatic variable for the last execution result?
PowershellPowershell Problem Overview
I'm looking for a feature comparable to Python interactive shell's "_" variable. In PowerShell I want something like this:
> Get-Something # this returns an object and display it to the output.
# Now I want to assign that object to some variable
> $anObj = ???
Powershell Solutions
Solution 1 - Powershell
No there is not automatic variable like that.
You have to do:
$output = Get-Something
$output
$anObj = $output
to get the behaviour
Solution 2 - Powershell
As stated, there's no built-in support for this, but here's a simple, but suboptimal PSv3+ custom solution:
Note:
-
For a proper, but nontrivial solution, see BartekB's helpful answer.
-
GitHub feature request #7853 asks for building this functionality into a future PowerShell Core version (the current version as of this writing is PowerShell (Core) 7.2).
Add the following to your $PROFILE
file:
# Store previous command's output in $__
$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'
What to name the variable - such as $__
(2 underscores) in this example - is up to you, but beware of name collisions, notably with $_
, the automatic variable that represents the input object at hand in a number of contexts.
This will capture the terminal-bound output of the most recently executed PowerShell command [that produced terminal output] in variable $__
in your interactive sessions,
by way of PowerShell's ability to globally preset parameter defaults - see Get-Help about_Parameters_Default_Values
.
-OutVariable
is a common parameter designed to collect a cmdlet's / advanced function's output objects in a variable, and the above definition applies this parameter implicitly to all Out-Default
calls, which in turn is called behind the scenes whenever PowerShell outputs something to the terminal - however, note the exceptions mentioned below.
Caveats:
-
If needed, use
$saved = $__.Clone()
to save the captured output for later use, given that$__
is reassigned to on every command (of course, if you know ahead of time that you want to keep a command's output, use an assignment to begin with:$saved = <command>
).- Note that just
$saved = $__
does not work, because that makes$saved
point to the same[ArrayList]
instance as$__
, which gets repopulated on the next command.
- Note that just
-
Output is not captured in the following cases:
-
Output from external programs, such as
git
, because by design PowerShell passes the output streams from external programs directly to the terminal (unless they're redirected or captured), and therefore doesn't callOut-Default
. The simplest workaround is to pipe toWrite-Output
(something like*>&1
to explicitly route through PowerShell streams doesn't work); e.g.:whoami.exe | Write-Output # $__ is now populated
-
Output from commands that explicitly call a formatting cmdlet -
Format-Custom
,Format-Hex
,Format-List
,Format-Table
, orFormat-Wide
.- It's tempting to attempt to fix that with
$PSDefaultParameterValues['Format-*:OutVariable'] = '__'
, but, unfortunately, this would collect formatting objects (instructions) rather than the original data in$__
, which is undesired. An unsatisfying workaround is to captureFormat-*
output in a different variable, which not only requires you to think about which variable you need to target, but you'll still only see formatting objects rather than data, and, sinceFormat-*
cmdlets are involved behind the scenes even if you don't use them explicitly, the output of commands withoutFormat-*
calls is then captured twice - once as data, in$__
, and again as formatting objects, in the other variable.
- It's tempting to attempt to fix that with
-
-
Due to a design quirk,
$__
always contains an array list (of type[System.Collections.ArrayList]
), even if the previous command output only a single object. When in doubt, use$($__)
(or$__[0]
) to get a single output object as such. -
Beware of commands producing very large output sets, because
$__
will collect them in memory. -
$__
will only capture objects output to the terminal - just like_
does in Python; a command that produces no output or$null
/ an array of$null
s leaves any previous$__
value intact.
Solution 3 - Powershell
You can also print the result of the command and capture the output object(s) using the OutVariable parameter, later on then use $anObj to display the variable content.
Get-Something -OutVariable anObj
Solution 4 - Powershell
Last option that requires most work but IMO gives you what you ask for: create proxy that will overwrite Out-Default (that is always called implicitly at the end of pipeline, if you don't out-* to something else).
Jeffrey Snover gave presentation on it during one of PowerShell Deep Dives (I reckon it was the first one) - you can find scripts he used (including above mentioned out-default) on Dmitry Sotnikov blog. You can also watch video from it to understand whole concept.
Solution 5 - Powershell
Not exactly. There's the $_ automatic value which contains the current object in the pipe-line.
The pipeline is the usual way to pass a result from one cmdlet to the next, and cmdlets are set to accept parameters from the pipeline or from the properties of objects in the pipeline making the use of a "last result" variable irrelevant.
Still some situations do require a specific reference to the "piped" object and for those there's the $_ automatic value.
Here's an example of its usage: Using the Where-Object Cmdlet and here's a list of powershell's automatic variables: Chapter 4. PowerShell Automatic Variables
Scripting in powershell requires a different style than programming in Python (same as Python requires a different style than C++.)
Powershell is built so pipes are used extensively, if you want to break up a pipeline into a more procedural step by step structure you'll need to save your results in named variables not automatic ones.
Solution 6 - Powershell
How about invoking the last command with "r" (alias for Invoke-History) and wrapping it in parentheses() to execute it first?
Yes this reruns the last command, but in my use cases this is by far the most streamlined solution especially for times when I don't realize that I'll need the last commands output at first.
It also keeps the object structure intact.
PS C:\Users\user> Get-NetAdapter -InterfaceIndex 3 | Where {$_.State -match "2"}
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
Ethernet Intel(R) Ethernet Connection I217-LM 3 Up XX-XX-XX-XX-XX-XX 100 Mbps
PS C:\Users\user> (r) |where {$_.LinkSpeed -eq "100 Mbps"}
Get-NetAdapter -InterfaceIndex 3 | Where {$_.State -match "2"}
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
Ethernet Intel(R) Ethernet Connection I217-LM 3 Up XX-XX-XX-XX-XX-XX 100 Mbps
PS C:\Users\user> (r).MacAddress
Get-NetAdapter -InterfaceIndex 3 | Where {$_.State -match "2"}
XX-XX-XX-XX-XX-XX
Solution 7 - Powershell
For my specific use case, I was running a batch file from PowerShell, and I wanted to print the output of that batch file in real-time AND save the output into a variable. I was able to accomplish this by piping the output of my call operator to Tee-Object
:
$args = @('-r', '-a');
& "C:\myFile.bat" $args | Tee-Object -Variable output;
$output | Set-Clipboard;
The first command sets up my arguments for the batch file. The second command runs the batch file using the call operator with my arguments, and it pipes the output to the Tee-Object
command, which prints the output in real-time from the call operator, but also saves all the information into a new variable called output. The last command simply copies the contents of $output
to the clipboard.
Tee-Object
also allows saving output to a file (Unicode encoding), and if I need to save to a file and a variable (in addition to printing to the console), I can chain multiple calls to Tee-Object
together in one pipeline. See this link for more information:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/tee-object
Solution 8 - Powershell
This is from Windows Powershell in Action. Source this in your $profile, and it makes $last contain the output of the last command. It also will change directories by typing the name of the directory a la zsh, or go to a website in a browser by typing the url of the website. It makes an Out-Default function that overrides the Out-Default cmdlet and calls it. I added the "start-process $__command" line so going to a website would work in osx.
This is fairly invasive. I later disabled it in my $profile for some reason.
#
# Wrapping an exiting command with a function that uses
# steppable pipelines to "remote-control" the wrapped command.
function Out-Default
{
[CmdletBinding(ConfirmImpact="Medium")]
param(
[Parameter(ValueFromPipeline=$true)]
[System.Management.Automation.PSObject] $InputObject
)
begin {
$wrappedCmdlet = $ExecutionContext.InvokeCommand.GetCmdlet(
"Out-Default")
$sb = { & $wrappedCmdlet @PSBoundParameters }
$__sp = $sb.GetSteppablePipeline()
$__sp.Begin($pscmdlet)
}
process {
$do_process = $true
if ($_ -is [System.Management.Automation.ErrorRecord]) {
if ($_.Exception -is
[System.Management.Automation.CommandNotFoundException]) {
$__command = $_.Exception.CommandName
if (test-path -path $__command -pathtype container) {
set-location $__command
$do_process = $false
}
elseif ($__command -match '^http://|\.(com|org|net|edu)$') {
if ($matches[0] -ne "http://") {$__command = "HTTP://" +
$__command }
# [diagnostics.process]::Start($__command)
start-process $__command
$do_process = $false
}
}
}
if ($do_process) {
$global:LAST = $_;
$__sp.Process($_)
}
}
end {
$__sp.End()
}
}
Solution 9 - Powershell
An approach that has worked for me is to process the command's output with Select-Object
as a pass-through. For instance, I had a console EXE that output verbose output to stderr
and meaningful output to stdout
, of which I wanted to capture only the last line.
$UtilityPath = ""
(UpdateUtility.exe $ArgList 2>&1) | % {
If ($_ -Is [System.Management.Automation.ErrorRecord]) {
$_.Exception.Message
}
Else {
$_
$UtilityPath = $_
}
}
The way this script was being invoked, having output on PowerShell's Error
output stream was considered a hard error, which didn't mesh nicely with the way PowerShell takes external apps' stderr
output and turns it into Error
output. This approach of wrapping the output allowed me to control how it passed through, as well as capture the stdout
line I wanted. It seems to me that this would be a fairly flexible approach that would allow you to intercept output and do anything you want with it as it passes through. For instance:
$CommandOutput = ""
SomeOtherCommand | % {
$CommandOutput += "$_`r`n"
$_
}
Solution 10 - Powershell
Use the module PowerShellCookbook and add a call to Add-ObjectCollector to your startup script
Solution 11 - Powershell
You could try "(Get-History)[-1].CommandLine".