Strange behavior with Powershell scriptblock variable scope and modules, any suggestions?

后端 未结 3 543
我在风中等你
我在风中等你 2020-12-09 04:47

NOTE: I\'m using PowerShell 2.0 on Windows Vista.

I\'m trying to add support for specifying build arguments to psake, but I\'ve run into some strange PowerShell var

3条回答
  •  独厮守ぢ
    2020-12-09 05:01

    I have been investigating this problem, which has come up in a project I am working on, and discovered three things:

    1. The issue is specific to modules.
      • If the code that invokes the scriptBlock is physically located anywhere within a .psm1 file, we see the behavior.
      • We also see the behavior if the code that invokes the scriptBlock is located in a separate script file (.ps1), if the scriptBlock was passed in from a module.
      • We do not see the behavior if the code that invoked the scriptBlock is located anywhere in a script file (.ps1), as long as the scriptBlock was not passed from a module.
    2. The scriptBlock will not necessarily execute in the global scope. Rather, it always appears to execute in whatever scope the module function was called from.
    3. The issue is not limited to the "." operator (dotsource). I have tested three different ways to invoke the scriptBlock: the "." operator, the "&" operator, and the scriptBlock object's invoke() method. In the latter two cases, the scriptBlock executes with the wrong parent scope. This can be investigated by trying to invoke, for example {set-variable -name "message" -scope 1 -value "From scriptBlock"}

    I hope this sheds some more light on the problem, although I haven't quite gotten far enough to propose a workaround.

    Does anyone still have PowerShell 1 installed? If so, it would be useful if you can check whether it displays the same behavior.

    Here are the files for my test cases. To run them, create all four files in the same directory, and then execute "./all_tests.ps1" at the PowerShell ISE command line

    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"
    

提交回复
热议问题