Powershell: Consider adding an automatic variable for the last result of a computation

Created on 24 Sep 2018  路  19Comments  路  Source: PowerShell/PowerShell

Most helpful comment

Does Out-Default support -OutVariable? Wouldn't it be more pragmatic to attach it only there, such that only the final output is stored in the outvariable, rather than every step of a lengthy pipeline?

Insert testing montage here...

... I might actually use this, it works pretty dang well. Easier than proxying the whole function, too!

All 19 comments

$__ is too similar to $_

We showed how to do this, variable was $last, by wrapping Out-default in a proxy function.

see page 382 of PowerShell in Action, third edition

@Masterxilo: In addition to @RichardSiddaway's approach, the following simpler one may suffice (place it in your $PROFILE):

# UPDATE: USE THE OTHER SOLUTION BELOW INSTEAD.
$PSDefaultParameterValues['*:OutVariable'] = '__'

(Since this is a custom solution, I've stuck with your proposed name, $__, even though as a built-in feature the similarity with $_ may be too confusing, as stated.)

_Update_: The following variant, courtesy of @vexx32, is preferable in general, and it also avoids the additional processing overhead that the solution above incurs, making the performance/resource-consumption debate below moot. A fundamental limitation, however, is the inability to capture output _explicitly_ sent to Format-* cmdlets.

$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'

For more information, see this answer I've just posted to the Stack Overflow question you link to above.

@mklement0 Your suggestion doesn't save the output of the last command, it saves the output of _every single command that gets run!_ It appears to work because only the last variable binding persists. Doing this means that If you have 3 commands in a pipeline then the output of each stage is saved in an ArrayList. The result is pervasive execution overhead plus additional work for the garbage collector. I would strongly recommend against this approach.

@BrucePay:

To quote from my linked SO answer (_update_: since rewritten to recommend @vexx32's variant):

Use of this technique slows your commands down, though usually only negligibly so (the more nested pipelines a command comprises, notably ones run in a loop, the higher the performance impact).

_In practice_, I would argue, this technique works fine _in interactive sessions_ without noticeable performance impact in _typical_ scenarios.

@mklement0 I certainly run a lot of scripts from my interactive environment. In practice, this is a wasteful solution for something that can be achieved much more efficiently and with little effort. I don't recommend it in _any_ scenarios.

@BrucePay:

this is a wasteful solution

My solution is a _pragmatic_ one that is _trivial to implement_.

As for the wastefulness: My guess is that users won't notice the processing overhead in practice, while still reaping the benefits.

something that can be achieved much more efficiently and with little effort

Do tell us how. If it's what @RichardSiddaway alluded to: it won't be nowhere near as simple.


The above debate is moot, however, if we decide to make this a _built-in_ feature, which sounds like a good idea - even if details need to be worked out (limit on in-memory size, ...).

What do you suggest?

Does Out-Default support -OutVariable? Wouldn't it be more pragmatic to attach it only there, such that only the final output is stored in the outvariable, rather than every step of a lengthy pipeline?

Insert testing montage here...

... I might actually use this, it works pretty dang well. Easier than proxying the whole function, too!

@vexx32:

Excellent idea - that hadn't even occurred to me.

Aside from incurring less processing overhead, there is a functional difference, however - still, that too could be considered an improvement; depending on your preference, you may choose one over the other:

  • $PSDefaultParameterValues['*:OutVariable'] = '__' saves output in $__ even if it is captured or redirected (say with $var = ... or ... >$null).

    • _Update_: Aside from the additional processing overhead, this solution falls short in that it captures the _formatting objects_ output by the Format-* cmdlets used behind the scenes rather than the actual _data_. As an aside: Curiously, using a Format-* cmdlet _explicitly_ captures nothing (returns an empty array list).
  • $PSDefaultParameterValues['Out-Default:OutVariable'] = '__' saves only the objects _output to the terminal_; as such, what $__ contains isn't necessarily output from the most recently _executed_ command, but the (terminal-bound) output from the most recently executed command that produced terminal output.
    However, that is also how _ appears to work in Python, from what I can tell.

I tend to use this way

Get-Command *-PSRead* | Tee-Object -Variable ou

which gives the output I want to the screen and to the $ou variable

though the disadvantage is that I need to know thats what I want to happen before issuing the command

@mklement considering that it would be considered similar to $LASTEXITCODE and similar things, I would argue that in most cases, direct console use is the main time you'd want it to take effect -- but as you say, it'd be up to personal preference and use case. 馃槃

In any case, I'm definitely adding that to my own profile, very handy!

@vexx32: Agreed on all counts; $PSDefaultParameterValues['Out-Default:OutVariable'] = '__' is probably the best general-purpose choice (no additional processing overhead, and no saving of output that is saved elsewhere anyway (variable assignment, redirection to file) or suppressed on purpose (>$null, ...); and, as stated, it's how Python does it too.

@kilasuit:

though the disadvantage is that I need to know thats what I want to happen before issuing the command

Yes, having output collected _automatically_ has two advantages:

  • you don't have to plan ahead

  • there's nothing additional to type.

@vexx32: Unfortunately, while we can have our cake, we can only eat it partially:

$PSDefaultParameterValues['Out-Default:OutVariable'] = '__' doesn't work if an _explicit_ Format-* call is used (presumably because Out-Default is then not called) - _nothing_ is captured then ($__ is left untouched).
In the case of $PSDefaultParameterValues['*:OutVariable'] = '__', curiously, you get an empty array list.

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.

In short: unless there is something I'm overlooking, it seems that writing an Out-Default proxy function is the way to go _for now_ - and hopefully we'll have a built-in feature soon.

You know, that doesn't even really bother me that much. Most of the time, if I pipe to the Format-* cmdlets, I don't want to reuse that output -- if I did want it, I'd have to rewrite the line anyway to remove the format command.

Once again... very little lost. :D

@vexx32: Got it.
P.S.: I hadn't noticed that my original approach, $PSDefaultParameterValues['*:OutVariable'] = '__', actually captures _formatting objects_ rather than data, so it's of no use for that reason alone; I've also updated my SO answer to recommend your approach instead.

From my understanding, $PSDefaultParameterValues['Out-Default:OutVariable'] = '__' does not capture output from other executables (non-cmdlets).

Is there a worksaround? Or entirely impossible even by changing powershell itself?

I'm sure it's possible. I'm not fully sure why calling an external executable bypasses Out-Default entirely but I'm sure it's probably deliberate; Out-Default is where PS data is sent, but external executables likely don't need that, since in a majority of cases the output is simple unstructured text.

@vexx32, I assume it's related to the by-design behavior of passing external programs' output streams through to the console (unless captured or redirected), enabling programs such as vi to work normally.

function Out-Default { Write-Host 'here' } # All PowerShell commands will now print just 'here'

whoami   # external program: Out-Default is NOT called

# An explicit redirection routes the output through PowerShell's streams and 
# therefore calls Out-Default,
# but *still passes the output through*(!) as well.
whoami *>&1

I should add that *>&1, even though it causes Out-Default to be called, is actually _ineffective_ as a workaround (and 1>&1 fundamentally doesn't work).

Currently, the simplest (but still cumbersome) workaround is:

whoami | Write-Output  # $__ is now populated.

echo is a built-in alias for Write-Output.

whoami | echo

isn't bad at all!

Was this page helpful?
0 / 5 - 0 ratings