Powershell scriptblock 变量范围和模块的奇怪行为,有什么建议吗?

发布于 2024-08-20 00:00:43 字数 2029 浏览 9 评论 0原文

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

我正在尝试添加对 psake 指定构建参数的支持,但我遇到了一些奇怪的 PowerShell 变量作用域行为,专门处理使用 Export-ModuleMember 导出的调用函数(这就是 psake 公开其 main 方法的方式)。下面是一个简单的 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 专家,但有人可以向我解释这种行为吗?

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 variable scoping behavior dealing specifically with calling functions that have been exported using Export-ModuleMember (which is how psake exposes it's main method). Following is a simple PowerShell module to illustrate (named 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"

To test the module, run the following sequence of commands (be sure you're in the same directory as the 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

The behavior I expected was for the script block I passed to Test to affect the local scope of Test. It is being 'dotsourced' in, so any changes it makes should be within the scope of the caller. However, that's not what's happening, it seems to be affecting the scope of where it was declared. Here's the output:

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!

Interestingly, if I don't export Test as a module and instead just declare the function and invoke it, everything works just like I would expect it to. The script block affects only Test's scope, and does not modify the global scope.

I'm not a PowerShell guru, but can someone explain this behavior to me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

狂之美人 2024-08-27 00:00:43

我不认为 PowerShell 团队认为这是一个错误,但我至少可以阐明它是如何工作的。

在脚本或脚本模块中定义的任何脚本块(以文字形式,不是使用 [scriptblock]::Create()) 动态创建的,都绑定到该模块的会话状态(或绑定到“主”会话状态(如果不在脚本模块内执行)。)还有特定于脚本块来自的文件的信息,因此断点之类的东西将在调用脚本块时起作用。

当您跨脚本模块边界传递这样的脚本块作为参数时,它仍然绑定到其原始范围,即使您从模块内部调用它也是如此。

在这种特定情况下,最简单的解决方案是通过调用 [scriptblock]::Create() 创建未绑定的脚本块(传入作为参数传入的脚本块对象的文本) :

. ([scriptblock]::Create($properties.ToString()))

但是,请记住,现在在另一个方向上可能存在范围问题。如果该脚本块依赖于能够解析原始范围内可用的变量或函数,但不能解析调用它的模块中的变量或函数,则它将失败。

由于 $properties 块的目的似乎是设置变量而不是其他,所以我可能会传入一个 IDictionaryHashtable 对象脚本块的。这样,所有的执行都发生在调用者的作用域中,并且您将获得一个简单的惰性对象来在模块内部处理,而无需担心作用域的愚蠢:

function Test {
    param(
        [ValidateNotNull()]
        [Parameter(Position=0,Mandatory=0)]
        [System.Collections.IDictionary]$properties = @{}
    )

    # Setting the default
    $message = "Hello, world!"

    Write-Host "After setting defaults, message is: $message"

    foreach ($dictionaryEntry in $properties.GetEnumerator())
    {
        Set-Variable -Scope Local -Name $dictionaryEntry.Key -Value $dictionaryEntry.Value
    }

    Write-Host "After importing properties, message is: $message"
}

调用者文件:

Import-Module .\repoCase.psm1

Write-Host "Before execution - In global scope, message is: $message"

Test -properties @{ Message = 'New Message' }

Write-Host "After execution - In global scope, message is: $message"

Remove-Module repoCase

I don't think this is considered to be a bug by the PowerShell team, but I can at least shed some light on how it works.

Any script block that's defined in a script or script module (in literal form, not dynamically created with something like [scriptblock]::Create()) is bound to the session state of that module (or to the "main" session state, if not executing inside a script module.) There is also information specific to the file that the script block came from, so things like breakpoints will work when the script block is invoked.

When you pass in such a script block as a parameter across script module boundaries, it is still bound to its original scope, even if you invoke it from inside the module.

In this specific case, the simplest solution is to create an unbound script block by calling [scriptblock]::Create() (passing in the text of the script block object that was passed in as a parameter):

. ([scriptblock]::Create($properties.ToString()))

However, keep in mind that there is potential for scope problems in the other direction, now. If that script block relies on being able to resolve variables or functions that were available in the original scope, but not from the module where you've invoked it, it will fail.

Since the intention of the $properties block appears to be to set variables and nothing else, I would probably pass in an IDictionary or Hashtable object instead of a script block. That way all of the execution takes place out in the caller's scope, and you get a simple, inert object to deal with inside the module, with no scope silliness to worry about:

function Test {
    param(
        [ValidateNotNull()]
        [Parameter(Position=0,Mandatory=0)]
        [System.Collections.IDictionary]$properties = @{}
    )

    # Setting the default
    $message = "Hello, world!"

    Write-Host "After setting defaults, message is: $message"

    foreach ($dictionaryEntry in $properties.GetEnumerator())
    {
        Set-Variable -Scope Local -Name $dictionaryEntry.Key -Value $dictionaryEntry.Value
    }

    Write-Host "After importing properties, message is: $message"
}

Caller file:

Import-Module .\repoCase.psm1

Write-Host "Before execution - In global scope, message is: $message"

Test -properties @{ Message = 'New Message' }

Write-Host "After execution - In global scope, message is: $message"

Remove-Module repoCase
飘然心甜 2024-08-27 00:00:43

我一直在调查这个问题,该问题出现在我正在从事的一个项目中,并发现了三件事:

  1. 该问题特定于模块。
    • 如果调用 scriptBlock 的代码物理上位于 .psm1 文件中的任何位置,我们就会看到该行为。
    • 如果调用 scriptBlock 的代码位于单独的脚本文件 (.ps1) 中,并且 scriptBlock 是在 中传递的,我们还会看到该行为来自模块。
    • 如果调用 scriptBlock 的代码位于脚本文件 (.ps1) 中的任何位置,只要 scriptBlock 不是从模块传递的。
  2. scriptBlock 不一定会在全局范围内执行。相反,它似乎总是在调用模块函数的任何范围内执行。
  3. 问题不仅仅限于“。”运算符(点源)。我测试了三种不同的方法来调用 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"

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"
薆情海 2024-08-27 00:00:43

看来传入的脚本块中的 $message 与全局范围相关,例如:

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" 

    # This works. Hmmm
    Write-Host "Aftering running properties, message is: $global:message" 
} 

Export-ModuleMember -Function "Test" 

输出:

Before running defaults, message is: 
Aftering running defaults, message is: Hello, world!
Executing properties, message is 
Aftering running properties, message is: Hello, world!
Aftering running properties, message is: Hello from properties!

这似乎是一个错误。我将查看 PowerShell MVP 列表,看看是否可以确认这一点。

It appears that the $message in the scriptblock passed in is tied to the global scope e.g.:

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" 

    # This works. Hmmm
    Write-Host "Aftering running properties, message is: $global:message" 
} 

Export-ModuleMember -Function "Test" 

Outputs:

Before running defaults, message is: 
Aftering running defaults, message is: Hello, world!
Executing properties, message is 
Aftering running properties, message is: Hello, world!
Aftering running properties, message is: Hello from properties!

This would appear to be a bug. I'll prod the PowerShell MVP list to see if I can confirm this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文