3

かわいそうに言うつもりはありませんが、これが問題です。$env:PSModulePath の下にインストールされた PowerShell モジュール Test.psm1 で定義されている次の 2 つの関数について考えてみましょう。

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    Start-Job { Start-Test -Name $using:Name -Block $using:Block }
}

function Start-Test
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')
    # do some work here, including this:
    Invoke-Command -ScriptBlock $Block
}

モジュールをインポートした後、同期関数を実行できます...

PS> Start-Test -Name "My Test" -Block { ps | select -first 9 }

...そして、Get-Process からの適切な出力が表示されます。

ただし、非同期バージョンを実行しようとすると...

PS> $testJob=Start-TestAsync -Name "My Test" -Block { ps | select -first 9 }

...そして、その出力を確認します...

PS> Receive-Job $testJob

...パラメーターを Start-Test 関数に持ち込むだけで失敗し、文字列を ScriptBlock に変換できないと報告します。したがって、-Block $using:BlockScriptBlock ではなく文字列を渡しています。

いくつかの実験の後、回避策を見つけました。$Block パラメーターの型が [ScriptBlock] ではなく [string] になるように Start-Test を変更し、その文字列をブロックに戻して Invoke-Command にフィードすると...

function Start-Test
{
    [CmdletBinding()]
    param([string]$Block, [string]$Name = '')
    $myBlock = [ScriptBlock]::Create($Block)
    Invoke-Command -ScriptBlock $myBlock
}

上記と同じコマンドを実行すると、正しい結果が得られます。

PS> $testJob=Start-TestAsync -Name "My Test" -Block { ps | select -first 9 }
PS> Receive-Job $testJob

using私の最初の例 (ScriptBlock を文字列に変換する) で、スコープは正しく機能していますか? それに関する限られたドキュメント ( about_Remote_Variablesabout_Scopes ) は、ほとんどガイダンスを提供しません。最終的に、$Block パラメーターが [ScriptBlock] として入力されている場合に Start-Test を機能させる方法はありますか?

4

4 に答える 4

2

これは明らかに設計によるものです: https://connect.microsoft.com/PowerShell/feedback/details/685749/passing-scriptblocks-to-the-job-as-an-argument-cannot-process-argument-transformation-on-パラメータ

回避策(上記のリンクから)は、次を使用すること[ScriptBlock]::Create()です:

これは、PowerShell のシリアル化のしくみにより、$ScriptToNest スクリプト ブロックが文字列に変換されるために発生します。スクリプトブロックを明示的に作成することで、これを回避できます。$OuterScriptblock の param() ブロックを次のように置き換えます ($ip は入力です)。

[scriptblock]$OuterScriptblock = {
param($ip)
[ScriptBlock]$ScriptToRun = [ScriptBlock]::Create($ip)

これはあなたの回避策です(あなたが見つけたように):

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')
    Start-Job { Start-Test -Name $using:Name -Block $using:Block }
}

function Start-Test
{
    [CmdletBinding()]
    param($Block, [string]$Name = '')
    # do some work here, including this:
    $sb = [ScriptBlock]::Create($Block)
    Invoke-Command -ScriptBlock $sb
}
于 2014-09-08T21:20:31.473 に答える
0

私が見たのは既知の問題であることを知っておくと便利ですが (@KeithHill に感謝します)、申し訳ありませんが、「設計上」という意味でした - 私の本当の質問には答えられませんでした ( 「最終的に、作成する方法はありますか? $Block パラメーターが [ScriptBlock] として入力されている場合、Start-Test は機能しますか?" )

答えは昨夜突然私に来ました:

function Start-TestAsync
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    Start-Job {
        $myBlock = [ScriptBlock]::Create($using:Block);
        Start-Test -Name $using:Name -Block $myBlock  }
}

function Start-Test
{
    [CmdletBinding()]
    param([ScriptBlock]$Block, [string]$Name = '')   
    # do some work here, including this:
    Invoke-Command -ScriptBlock $Block
}

Start-TestAsync では、シリアル化を内部的に許可し ($using:Block)、ScriptBlock を文字列に変換し、すぐにそれを再変換 (Create) して ScriptBlock にし、安全に Start-本物の ScriptBlock としてテストします。私にとって、これは私の質問の回避策よりも大幅に改善されています。これは、両方の関数のパブリック API が正しいためです。

于 2014-09-10T16:41:49.877 に答える
0

$using の目的は、リモート システムで使用される前に、スクリプト ブロック内のローカル変数のを展開することであると考えられます。リモートセッション。スクリプトブロックが値に最も近いものは、コマンド テキストです。

于 2014-09-08T20:43:27.413 に答える
0

これはあなたの質問に完全には答えていませんが、1つの関数に入れることでこれを大幅に簡素化できると思います:

function Start-Test
{
    [CmdletBinding()]
    param(
        [ScriptBlock]$Block, 
        [string]$Name = '',
        [Switch]$Async
    )
    # do some work here, including this:
    Invoke-Command -ScriptBlock $Block -AsJob:$Async
}

Invoke-Commandすでにジョブを開始できるため、関数にスイッチを受け入れさせ、その-Async値をスイッチに渡すことができます-AsJob

同期を呼び出す

Start-Test -Block { ps | select -first 9 }

非同期呼び出し

Start-Test -Block { ps | select -first 9 } -Async

投機

あなたが見ている実際のことがなぜ起こっているのかについては、確かではありませんが、現時点では適切なテストを行うことはできませんが、スクリプト ブロックのネストに関連している可能性があると思います。

于 2014-09-08T20:04:06.963 に答える