17

注: Windows Vista で PowerShell 2.0 を使用しています。

ビルド引数をpsakeに指定するためのサポートを追加しようとしていますが、特に Export-ModuleMember を使用してエクスポートされた関数の呼び出しを処理する奇妙な PowerShell 変数スコープ動作に遭遇しました (これは 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 の第一人者ではありませんが、誰かがこの動作を説明してくれませんか?

4

3 に答える 3

10

これが PowerShell チームによってバグであると見なされているとは思いませんが、少なくともそのしくみを明らかにすることはできます。

スクリプトまたはスクリプト モジュールで定義されているスクリプト ブロック (リテラル形式で、動的に作成されていないもの[scriptblock]::Create())) は、そのモジュールのセッション状態 (または、スクリプト モジュール内で実行されていない場合は「メイン」セッション状態) にバインドされます。スクリプト ブロックの元のファイルに固有の情報もあるため、スクリプト ブロックが呼び出されたときにブレークポイントなどが機能します。

このようなスクリプト ブロックをスクリプト モジュールの境界を越えてパラメーターとして渡すと、モジュール内から呼び出した場合でも、元のスコープにバインドされたままになります。

この特定のケースでは、最も簡単な解決策は、次の呼び出しによってバインドされていないスクリプト ブロックを作成することです[scriptblock]::Create()(パラメーターとして渡されたスクリプト ブロック オブジェクトのテキストを渡します)。

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

ただし、現在、別の方向にスコープの問題が発生する可能性があることに注意してください。そのスクリプト ブロックが、元のスコープで使用可能であったが、それを呼び出したモジュールからではなく、変数または関数を解決できることに依存している場合、失敗します。

このブロックの目的は変数を設定することだけであるように見えるので、スクリプト ブロックの代わりにorオブジェクト$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
于 2014-12-16T00:00:22.523 に答える
8

私が取り組んでいるプロジェクトで発生したこの問題を調査しており、次の 3 つのことを発見しました。

  1. この問題はモジュールに固有です。
    • を呼び出すコードscriptBlockが物理的に .psm1 ファイル内の任意の場所にある場合、この動作が見られます。
    • また、 を呼び出すコードがscriptBlock別のスクリプト ファイル (.ps1) にある場合や、がモジュールからscriptBlock渡された場合にも、この動作が見られます。
    • を呼び出したコードがスクリプト ファイル (.ps1) のどこかにある場合、がモジュールから渡されていない限り、動作は確認されません。scriptBlockscriptBlock
  2. scriptBlock必ずしもグローバル スコープで実行されるわけではありません。むしろ、モジュール関数が呼び出されたスコープに関係なく、常に実行されているように見えます。
  3. 問題は「.」に限定されません。演算子 (ドットソース)。を呼び出す 3 つの異なる方法をテストしましscriptBlockた。演算子、「&」演算子、およびscriptBlockオブジェクトのinvoke()メソッド。後者の 2 つのケースでは、scriptBlockが間違ったスコープで実行されます。これは、たとえば呼び出しを試みることで調査できます。{set-variable -name "message" -scope 1 -value "From scriptBlock"}

回避策を提案するにはまだ十分ではありませんが、これで問題がさらに明らかになることを願っています。

まだ PowerShell 1 をインストールしている人はいますか? もしそうなら、それが同じ動作を示すかどうかを確認できると便利です。

これが私のテストケースのファイルです。それらを実行するには、4 つのファイルすべてを同じディレクトリに作成し、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'"

モジュール.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"
于 2010-05-20T20:20:08.147 に答える
4

渡された scriptblock の $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 リストを調べます。

于 2010-02-03T18:16:37.963 に答える