Search code examples
powershellscoping

Referring to current object from different scope


I want to be able to refer to $_ object in catch block from the function I call in catch block like this:

function foo
{
    $something = ((Get-Variable -Name "_" -Scope 1).Value).Exception.Message
    Write-Host $something
}

I want to use this in situations like these:

foo #write-host should print empty line
try {
    throw
} catch {
    foo #write-host should print $_.Exception.Message from this catch block
}

How to do that properly? The goal is to avoid passing $_ as parameter to foo every time I use it in a catch block, and not to print anything when I call foo not in a catch block.

I have also tried this:

function foo
{
    $something = (($ExecutionContext.SessionState.PSVariable.Get("_")).Value).Exception.Message
    Write-Host $something
}

This seems to produce the result I want when working interactively, but not when launching script.


Solution

  • The clean way of doing this is, of course, an (optional) parameter on foo, called with foo $_ from catch blocks. This makes it perfectly clear what we're calling foo with, it has no issues with scoping, it makes foo testable, all that niceness. You will see this approach in most anything that prints errors (as in the answers to this question, for example).

    Even so, if you insist on plucking the error variable from the parent frame, it can be done:

    function foo {
        $lastError = (Get-PSCallStack)[1].GetFrameVariables()["_"].Value
        if ([Object]::ReferenceEquals($lastError.Exception, $error[0].Exception)) {
            $lastError.Exception.Message
        }
    }
    

    Note the extra trickery with Object.ReferenceEquals to be absolutely sure that $_ is referring to the last error record and not some arbitrary pipeline item (which also use $_). This will still fail if the error record itself is in the pipeline (e.g. $error |% { foo }), but at that point you might as well call it a feature.