注意:我在Windows Vista上使用PowerShell 2.0。

我试图增加对为psake指定构建参数的支持,但是我遇到了一些奇怪的PowerShell变量作用域行为,专门用于处理使用Export-ModuleMember导出的调用函数(这是psake公开它的主要方法)。以下是一个简单的PowerShell模块进行说明(名为repoCase.psm1):

function Test {
    param(
        [Parameter(Position=0,Mandatory=0)]
        [scriptblock]$properties = {}
    )

    $defaults = {$message = "Hello, world!"}

    Write-Host "Before running defaults, message is: $message"

    . $defaults

    #At this point, $message is correctly set to "Hellow, world!"
    Write-Host "Aftering running defaults, message is: $message"

    . $properties

    #At this point, I would expect $message to be set to whatever is passed in,
    #which in this case is "Hello from poperties!", but it isn't.
    Write-Host "Aftering running properties, message is: $message"
}

Export-ModuleMember -Function "Test"

要测试该模块,请运行以下命令序列(确保您与repoCase.psm1位于同一目录中):
Import-Module .\repoCase.psm1

#Note that $message should be null
Write-Host "Before execution - In global scope, message is: $message"

Test -properties { "Executing properties, message is $message"; $message = "Hello from properties!"; }

#Now $message is set to the value from the script block.  The script block affected only the global scope.
Write-Host "After execution - In global scope, message is: $message"

Remove-Module repoCase

我预期的行为是因为我传递给Test的脚本块会影响Test的本地范围。它是“点源”的,因此所做的任何更改都应在调用者的范围之内。但是,这不是正在发生的事情,它似乎正在影响声明它的范围。这是输出:
Before execution - In global scope, message is:
Before running defaults, message is:
Aftering running defaults, message is: Hello, world!
Executing properties, message is
Aftering running properties, message is: Hello, world!
After execution - In global scope, message is: Hello from properties!

有趣的是,如果我不将Test导出为模块,而是声明函数并调用它,那么一切都会像我期望的那样工作。脚本块仅影响Test的范围,而不会修改全局范围。

我不是PowerShell专家,但是有人可以向我解释此行为吗?

最佳答案

我一直在研究这个问题,这个问题出现在我正在研究的项目中,发现了三件事:

  • 该问题特定于模块。
  • 如果调用scriptBlock的代码实际上位于.psm1文件内的任何位置,我们将看到此行为。
  • 如果从模块传入了scriptBlock,则调用scriptBlock的代码位于单独的脚本文件(.ps1)中时,也会看到该行为。
  • 如果未从模块传递scriptBlock,则如果调用scriptBlock的代码位于脚本文件(.ps1)中的任何位置,则看不到此行为。
  • scriptBlock不一定在全局范围内执行。相反,它似乎总是在调用模块函数的任何范围内执行。
  • 问题不仅限于“。”运算子(dotsource)。我已经测试了三种不同的方式来调用scriptBlock:“”。运算符,“&”运算符以及scriptBlock对象的invoke()方法。在后两种情况下,scriptBlock在错误的父范围内执行。可以通过尝试调用{set-variable -name "message" -scope 1 -value "From scriptBlock"}
  • 进行调查

    我希望这能为问题提供更多的启示,尽管我还没有提出足够的建议。

    是否还有人安装了PowerShell 1?如果是这样,则可以检查它是否显示相同的行为,这将很有用。

    这是我的测试用例的文件。要运行它们,请在同一目录中创建所有四个文件,然后在PowerShell ISE命令行中执行“./all_tests.ps1”

    script_toplevel.ps1
    param($script_block)
    
    set-alias "wh" write-host
    
    $message = "Script message"
    wh "  Script message before:      '$message'"
    . $script_block
    wh "  Script message after:       '$message'"
    

    script_infunction.ps1
    param($script_block)
    set-alias "wh" write-host
    
    function f {
        param($script_block)
        $message = "Function message"
        wh "  Function message before:    '$message'"
        . $script_block
        wh "  Function message after:     '$message'"
    }
    
    $message = "Script message"
    wh "  Script message before:      '$message'"
    f -script_block $script_block
    wh "  Script message after:       '$message'"
    

    module.psm1
    set-alias "wh" write-host
    
    function simple_test_fun {
        param($script_block)
    
        $message = "ModFunction message"
        wh "  ModFunction message before: '$message'"
        . $script_block
        wh "  ModFunction message after:  '$message'"
    }
    
    function ampersand_test_fun {
        param($script_block)
    
        $message = "ModFunction message"
        wh "  ModFunction message before: '$message'"
        & $script_block
        wh "  ModFunction message after:  '$message'"
    }
    
    function method_test_fun {
        param($script_block)
    
        $message = "ModFunction message"
        wh "  ModFunction message before: '$message'"
        $script_block.invoke()
        wh "  ModFunction message after:  '$message'"
    }
    
    function test_mod_to_script_toplevel {
        param($script_block)
    
        $message = "ModFunction message"
        wh "  ModFunction message before: '$message'"
        & .\script_toplevel.ps1 -script_block $script_block
        wh "  ModFunction message after:  '$message'"
    }
    
    function test_mod_to_script_function {
        param($script_block)
    
        $message = "ModFunction message"
        wh "  ModFunction message before: '$message'"
        & .\script_infunction.ps1 -script_block $script_block
        wh "  ModFunction message after:  '$message'"
    }
    
    export-modulemember -function "simple_test_fun", "test_mod_to_script_toplevel", "test_mod_to_script_function", "ampersand_test_fun", "method_test_fun"
    

    all_tests.ps1
    remove-module module
    import-module .\module.psm1
    
    set-alias "wh" write-host
    
    wh "Test 1:"
    wh "  No problem with . at script top level"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -amp-calls-> Script -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: Script message after:       'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    & .\script_toplevel.ps1 -script_block {$message = "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 1 showed expected behavior"
    wh
    wh
    wh "Test 2:"
    wh "  No problem with . inside function in script"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -amp-calls-> Script -calls-> Function -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: Function message after:     'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    & .\script_infunction.ps1 -script_block {$message = "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 2 showed expected behavior"
    wh
    wh
    wh "Test 3:"
    wh "  Problem with with . with function in module"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -calls-> ModFunction -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    simple_test_fun -script_block {$message = "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 3 showed problem behavior"
    wh
    wh
    wh "Test 4:"
    wh "  Confirm that problem scope is always scope where ScriptBlock is created"
    wh "    ScriptBlock created at 'f1' scope"
    wh "    TopScript -calls-> f1 -calls-> f2 -amp-calls-> ModFunction -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  f1 message after:           'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    function f1 {
        $message = "f1 message"
        wh "  f1 message before:          '$message'"
        f2 -script_block {$message = "Script block message"}
        wh "  f1 message after:           '$message'"
    }
    function f2 {
        param($script_block)
    
        $message = "f2 message"
        wh "  f2 message before:          '$message'"
        simple_test_fun -script_block $script_block
        wh "  f2 message after:           '$message'"
    }
    
    f1
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 4 showed problem behavior"
    wh
    wh
    wh "Test 4:"
    wh "  Confirm that problem scope is always scope where ScriptBlock is created"
    wh "    ScriptBlock created at 'f1' scope"
    wh "    TopScript -calls-> f1 -calls-> f2 -amp-calls-> ModFunction -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  f1 message after:           'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    function f1 {
        $message = "f1 message"
        wh "  f1 message before:          '$message'"
        f2 -script_block {$message = "Script block message"}
        wh "  f1 message after:           '$message'"
    }
    function f2 {
        param($script_block)
    
        $message = "f2 message"
        wh "  f2 message before:          '$message'"
        simple_test_fun -script_block $script_block
        wh "  f2 message after:           '$message'"
    }
    
    f1
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 4 showed problem behavior"
    wh
    wh
    wh "Test 5:"
    wh "  Problem with with . when module function invokes script (toplevel)"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -calls-> ModFunction -amp-calls-> Script -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    test_mod_to_script_toplevel -script_block {$message = "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 5 showed problem behavior"
    wh
    wh
    wh "Test 6:"
    wh "  Problem with with . when module function invokes script (function)"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -calls-> ModFunction -amp-calls-> Script -calls-> function -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    test_mod_to_script_function -script_block {$message = "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 6 showed problem behavior"
    wh
    wh
    wh "Test 7:"
    wh "  Problem with with & with function in module"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -calls-> ModFunction -amp-calls-> Script -calls-> function -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    ampersand_test_fun -script_block {set-variable -scope 1 -name "message" -value "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 7 showed problem behavior"
    wh
    wh
    wh "Test 8:"
    wh "  Problem with with invoke() method with function in module"
    wh "    ScriptBlock created at 'TopScript' scope"
    wh "    TopScript -calls-> ModFunction -amp-calls-> Script -calls-> function -dot-calls-> ScriptBlock:"
    wh
    wh "  Expected behavior: ModFunction message after:  'Script block message'"
    wh "  Problem behavior:  TopScript message after:    'Script block message'"
    wh
    wh "Results:"
    $global:message = "Global message"
    $message = "Top script message"
    wh "  Global message before:      '$global:message'"
    wh "  TopScript message before:   '$message'"
    method_test_fun -script_block {set-variable -scope 1 -name "message" -value "Script block message"}
    wh "  TopScript message after:    '$message'"
    wh "  Global message after:       '$global:message'"
    
    wh
    wh "Test 8 showed problem behavior"
    

    10-08 00:26