Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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+.

like image 670
Ryan Bolger Avatar asked Dec 07 '22 18:12

Ryan Bolger


2 Answers

There's no need to attempt to implement this yourself, -OutVariable is a Common Parameter.

Add a CmdletBinding attribute to your param block to get these for free:

function MyTest {
    [CmdletBinding()]
    param()

    return "hello world"
}

MyTest -OutVariable testvar

$testvar now holds the string value "hello world"

For your second example, where you need to set a value in the caller scope in addition to pipeline output, use the -Scope option with Set-Variable:

function MyTest {
    [CmdletBinding()]
    param([string]$AnotherVariable)

    if([string]::IsNullOrWhiteSpace($AnotherVariable)){
        Set-Variable -Name $AnotherVariable -Value 'more data' -Scope 1
    }

    return "hello world"
}

MyTest -AnotherVariable myvar

$myvar in the caller scope now contains the string value "more data". The 1 value passed to the Scope parameter means "1 level up"

like image 172
Mathias R. Jessen Avatar answered Dec 15 '22 10:12

Mathias R. Jessen


@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
}
like image 32
user4003407 Avatar answered Dec 15 '22 11:12

user4003407