如何在PowerShell中实现输出变量?

5
我意识到“输出变量”可能不是正确的术语,这就是为什么我的谷歌搜索失败了。我猜想它围绕着显式设置变量范围,但我尝试阅读关于作用域文档,但它对我来说并不直观。
本质上,我正在尝试实现与Invoke-RestMethod中的-SessionVariable参数相当的功能,即在自己模块的函数中使用一个字符串参数,该参数会变成调用者范围内的变量。
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

如果我要包装一个已有的函数,并且想有效地将输出变量名传递,那么事情如何改变(如果有的话)?
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

顺便说一句,如果有关系的话,我正在努力使模块与Powershell v3+兼容。

2个回答

9
不需要自行实现,-OutVariable是一个常见参数。将CmdletBinding属性添加到您的param块中即可免费获得这些参数:
function MyTest {
    [CmdletBinding()]
    param()

    return "hello world"
}

MyTest -OutVariable testvar

$testvar现在保存了字符串"hello world"的值。

对于您的第二个例子,如果您需要在管道输出之外设置调用者范围的值,请使用Set-Variable-Scope选项:

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在调用者范围内现在包含字符串值“more data”。传递给Scope参数的1值表示“向上1级”。


@RyanBolger 更新的答案 - Mathias R. Jessen
作用域“向上1级”。太好了。非常感谢! - Ryan Bolger
...或者“直接父级”,你懂的 :-) 欢迎使用。 - Mathias R. Jessen
1
@MathiasR.Jessen 如果函数定义在模块中,Set-Variable -Scope 1 将无法正常工作。模块使用单独的 SessionState 和单独的作用域层次结构,因此“直接父级”将不是调用者。 - user4003407
-Scope 2 很可能是指全局作用域,而不是调用者的作用域(除非它们相同)。是的,这是因为你在模块中。 - user4003407
显示剩余2条评论

5
< p > @Mathias R. Jessen 提出了一种解决方案,在未在模块中定义时可以很好地工作。但是,如果将其放在模块中,则无法正常工作。因此,我提供了另一种解决方案,可在模块中使用。

在高级函数(使用 [CmdletBinding()] 属性的函数)中,您可以使用 $PSCmdlet.SessionState 来引用调用者的 SessionState。因此,您可以使用 $PSCmdlet.SessionState.PSVariable.Set('Name' ,'Value') 在调用者的 SessionState 中设置变量。

注意:如果未在模块中定义或从定义它的同一模块调用,则此解决方案将无法正常工作。

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
}

1
感谢您的解释和演示。尽管在我个人的情况下隐藏全局变量可能是可以的,但这似乎是更正确的完成任务的方式。 - Ryan Bolger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接