Search code examples
powershellpowershell-3.0

How do I implement output variables in powershell?


I realize "output variables" is probably the wrong terminology here which is why my google searching has failed me. I'm guessing it revolves around explicitly setting variable scope, but I've tried reading the about_Scopes doc and it's just not clicking for me.

Essentially what I'm trying to do is implement the equivalent of the -SessionVariable argument from Invoke-RestMethod in my own module's function. In other words, I need a string parameter that turns into a variable in the caller's scope.

function MyTest
{
    param([string]$outvar)

    # caller scoped variable magic goes here
    set-variable -Name $outvar -value "hello world"

    # normal function output to pipeline here
    Write-Output "my return value"
}

# calling the function initially outputs "my return value"
MyTest -outvar myvar

# referencing the variable outputs "hello world"
$myvar

For bonus points, how do things change (if at all) if I'm wrapping an existing function that has its own output variable and I want to effectively pass through the output variable name?

function MyWrapper
{
    param([string]$SessionVariable)

    Invoke-RestMethod -Uri "http://myhost" -SessionVariable $SessionVariable

    # caller scoped variable magic goes here
    set-variable -Name $SessionVariable -value $SessionVariable
}

# calling the wrapper outputs the normal results from Invoke-RestMethod
MyWrapper -SessionVariable myvar

# calling the variable outputs the WebRequestSession object from the inner Invoke-RestMethod call
$myvar

P.S. If it matters, I'm trying to keep the module compatible with Powershell v3+.


Solution

  • @Mathias R. Jessen propose solution, which work well, when not defined in module. But not, if you put it in module. So, I provide another solution, which works, when put in module.

    In advanced function (one which use [CmdletBinding()] attribute) you can use $PSCmdlet.SessionState to refer to SessionState of a caller. Thus, you can use $PSCmdlet.SessionState.PSVariable.Set('Name' ,'Value') to set variables in caller's SessionState.

    Note: this solution will not work if not defined in module or if called from the same module, where it defined.

    New-Module {
        function MyTest1 {
            [CmdletBinding()]
            param([string]$outvar)
            $PSCmdlet.SessionState.PSVariable.Set($outvar, 'Some value')
        }
        function MyTest2 {
            [CmdletBinding()]
            param([string]$outvar)
            # -Scope 1 not work, because it executed from module SessionState,
            # and thus have separate hierarchy of scopes
            Set-Variable -Name $outvar -Value 'Some other value' -Scope 1
        }
        function MyTest3 {
            [CmdletBinding()]
            param([string]$outvar)
            # -Scope 2 will refer to global scope, not the caller scope,
            # so variable with same name in caller scope will hide global variable
            Set-Variable -Name $outvar -Value 'Some other value' -Scope 2
        }
    } | Out-Null
    
    & {
        $global:a = 'Global value'
        MyTest1 a
        $a
        $global:a
        ''
        MyTest2 a
        $a
        $global:a
        ''
        MyTest3 a
        $a
        $global:a
    }