Mat*_*utt 16 powershell scope psake
注意:我在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"
Run Code Online (Sandbox Code Playgroud)
要测试模块,请运行以下命令序列(确保您与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
Run Code Online (Sandbox Code Playgroud)
我期望的行为是我传递给Test的脚本块以影响Test的本地范围.它是'dotsourced',因此它所做的任何更改都应该在调用者的范围内.然而,这不是正在发生的事情,它似乎正在影响其声明的范围.这是输出:
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!
Run Code Online (Sandbox Code Playgroud)
有趣的是,如果我不将Test作为模块导出而只是声明该函数并调用它,那么一切都会像我期望的那样工作.脚本块仅影响Test的范围,不会修改全局范围.
我不是PowerShell大师,但是有人可以向我解释这种行为吗?
小智 9
我不认为PowerShell团队认为这是一个错误,但我至少可以说明它是如何工作的.
脚本或脚本模块中定义的任何脚本块(以文字形式,不是通过类似动态创建的[scriptblock]::Create()))绑定到该模块的会话状态(如果不在脚本模块内执行,则绑定到"主"会话状态.)还有特定于脚本块来自的文件的信息,因此在调用脚本块时,断点等操作将起作用.
当您跨脚本模块边界传递这样的脚本块作为参数时,它仍然绑定到其原始范围,即使您从模块内部调用它.
在这种特定情况下,最简单的解决方案是通过调用[scriptblock]::Create()(传入作为参数传入的脚本块对象的文本)来创建未绑定的脚本块:
. ([scriptblock]::Create($properties.ToString()))
Run Code Online (Sandbox Code Playgroud)
但是,请记住,现在可能存在另一个方向的范围问题.如果该脚本块依赖于能够解析原始作用域中可用的变量或函数,而不是来自您调用它的模块,则它将失败.
由于$properties块的意图似乎是设置变量而没有别的,我可能会传入一个IDictionary或一个Hashtable对象而不是一个脚本块.这样所有的执行都在调用者的范围内进行,你得到一个简单的,惰性的对象来处理模块内部,没有范围愚蠢担心:
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"
}
Run Code Online (Sandbox Code Playgroud)
来电者档案:
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
Run Code Online (Sandbox Code Playgroud)
我一直在调查这个问题,这个问题出现在我正在研究的项目中,并发现了三件事:
scriptBlock实际位于.psm1文件中的任何位置,我们会看到该行为.scriptBlock位于单独的脚本文件(.ps1)中(如果scriptBlock是从模块传入的话),我们也会看到这种行为.scriptBlock位于一个脚本文件(名为.ps1)的任何地方,只要scriptBlock不是从一个模块传递.scriptBlock在全球范围内并不一定会执行.相反,它似乎总是在调用模块函数的任何范围内执行.scriptBlock:"." 运算符,"&"运算符和scriptBlock对象的invoke()方法.在后两种情况下,scriptBlock使用错误的父范围执行.例如,可以通过尝试调用来调查这一点{set-variable -name "message" -scope 1 -value "From scriptBlock"}我希望这能更好地解决这个问题,尽管我还没有提出足够的建议来解决这个问题.
还有人还安装了PowerShell 1吗?如果是这样,如果您可以检查它是否显示相同的行为将是有用的.
以下是我的测试用例的文件.要运行它们,请在同一目录中创建所有四个文件,然后在PowerShell ISE命令行中执行"./all_tests.ps1"
param($script_block)
set-alias "wh" write-host
$message = "Script message"
wh " Script message before: '$message'"
. $script_block
wh " Script message after: '$message'"
Run Code Online (Sandbox Code Playgroud)
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'"
Run Code Online (Sandbox Code Playgroud)
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"
Run Code Online (Sandbox Code Playgroud)
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"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5282 次 |
| 最近记录: |