PowerShell: Is there an automatic variable for the last execution result?

Powershell

Powershell 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:


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.
  • 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 call Out-Default. The simplest workaround is to pipe to Write-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, or Format-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 capture Format-* 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, since Format-* cmdlets are involved behind the scenes even if you don't use them explicitly, the output of commands without Format-* calls is then captured twice - once as data, in $__, and again as formatting objects, in the other variable.
  • 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 $nulls 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".

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKFLView Question on Stackoverflow
Solution 1 - PowershellmanojldsView Answer on Stackoverflow
Solution 2 - Powershellmklement0View Answer on Stackoverflow
Solution 3 - PowershellShay LevyView Answer on Stackoverflow
Solution 4 - PowershellBartekBView Answer on Stackoverflow
Solution 5 - PowershellEli AlgrantiView Answer on Stackoverflow
Solution 6 - PowershellPaul MasekView Answer on Stackoverflow
Solution 7 - PowershellsrbrillsView Answer on Stackoverflow
Solution 8 - Powershelljs2010View Answer on Stackoverflow
Solution 9 - PowershellJonathan GilbertView Answer on Stackoverflow
Solution 10 - PowershellIttayDView Answer on Stackoverflow
Solution 11 - PowershellRobbie PView Answer on Stackoverflow