tech.guitarrapc.cóm

Technical updates

PowerShell における Windows Workflow Foundation 4.0 (WF) 利用のすすめ

Windows PowerShell での並列実行 というと、 PipeLine処理かWorkflow が利用しやすいところです。

また、 Workflow は通常の Implicit Scripting と異なり、 manageな仕組みです。

Workflowは Windows PowerShell だけでは困難な 長時間にわたるスクリプト処理 の並列化、エラー発生時の再開処理などをくみこみやすい優れた方法と言えます。

但し Workflow は PowerShell のサブセットです。

一見すると function と記述が似ておりとっつき安い反面、同じ記法と思いきや色々と制限があります。

今回は、Workflow の概要、サンプル、そしてハマり易い制限について紹介しましょう。 Workflow について、それなりに長い記事となっていますのでご注意を....! 

Workflowについて

Workflow は Windows PowerShell 3.0 (Windows 8, Windows Server 2012~標準) から日常のシーンでも利用しやすくなりました。 たぶん、きっと。

私もちょくちょく使っています。

さて、 PowerShell 3.0 で利用されている Windows Workflow Foundation 4.0 (WF) に関してはTechNet の説明が比較的わかりやすいかと思います。

TechNet - Windows PowerShell ワークフローについて

ワークフローとは、時間のかかるタスクを実行する手順や、複数のコンピューター間で複数の手順の調整を必要とする手順をプログラミングで連結した手順のシーケンスです。

Windows PowerShell ワークフローでは、IT 担当者や開発者は、頻繁に発生して時間のかかる、反復可能、並行化可能、中断可能、停止可能、および再起動可能という特徴を備えた、複数のコンピューターの管理アクティビティのシーケンスまたはワークフロー内の個別の手順をワークフローとして作成することができます。

設計上、ワークフローは、ネットワークの停止、コンピューターの再起動、停電など、予期しない停止や中断により、中断および再開される場合があります。ワークフローは移植可能です。

つまり、XAML ファイルにエクスポートしたり、XAML からインポートしたりすることができます。

カスタム セッション構成により、ワークフローまたはワークフロー内のアクティビティは、委任された IT 担当者または下位の IT 担当者が実行できます。

セッション構成には、必要に応じて、ワークフローの各段階にある管理対象ノードに渡される異なるユーザー アカウントの資格情報を含めることができます。

日本語の記事は数が少ないのですが、勉強になる記事として紹介します。

てすとぶろぐ - Powershell 3.0 と Workflow Foundation

てすとぶろぐ - PowerShell Workflow をもうちょっと触ってみる

【PowerShell】長時間バッチ処理中に停電でサーバーがシャットダウン!でも Workflow ならば安心です

私個人としては、この動画がわかりやすく感じます。

Bruce Payette - PowerShell Workflows

ここで 従来の Implicit Scripting と Workflow の違いが対比されています。

「クラウドコンピューティングな世界、複数のノードに対して長時間処理を並行して行いエラーでも継続可能にする」、これが Workflow を PowerShell に取り込んだ開発者の意図です。

Workflow Script
Indivisual activities are isolated Indivisual activities share state
State is managed allowing for persistence State is unmanaged, persistence is not supported
Flow of data is explicit, activities have well defined inpits and outputs Lots of implicit coupling
Strongly typed, early-bound, lexical scoping Slushy typing, late-bound, dynamic scoping
Plannned Ad hoc

PowerShell Workflow(PSW)の目的、利点、仕組みに関しては、 Windows PowerShell Blog で詳しく解説されています。

この記事が、日本語で紹介されていないのは非常に悲しいですね。

When Windows PowerShell Met Workflow High Level Architecture of Windows PowerShell Workflow (Part 1) High Level Architecture of Windows PowerShell Workflow (Part 2)

Workflowをどんな時に使うのか

スクリプト記述では 困る時、それが Workflow を活用するチャンスです。

PowerShell開発者の、この一言に利用する機会が示されています。 (Windows PowerShell Blog - When Windows PowerShell Met Workflow)

Reliably executing long-running tasks across multiple computers, devices or IT processes

TechNetでは以下のように表現されています。

  1. 一連の時間のかかるタスクの自動化
  2. 時間のかかるタスクの調整およびリモート監視。
    アクティビティ (ワークフロー内の手順) の状態および進行状況をいつでも表示できます。
  1. エラー回復の自動化
  2. ワークフローは、コンピューターの再起動など、計画された中断も計画外の中断も切り抜けます。実行中のワークフローを一時停止した後、一時停止した時点からワークフローを再開することができます。永続化により、ワークフローの状態およびメタデータは、特定の時点 (開始時、終了時、および作成者が指定した任意の時点) で保存 (または保持) されるため、ワークフローを最初から再開するのではなく、最後に保持されたタスクから再開することができます。
  3. 接続と切断
  4. ユーザーは、ワークフローを実行したままで、ワークフロー セッションとの接続を確立したり切断したりすることができます。たとえば、クライアント コンピューターからログオフしたりクライアント コンピューターを再起動したりして、ワークフローを中断することなく、別のコンピューター (自宅のコンピューターなど) から実行中のワークフローを監視できます。ユーザーは、ワークフローを実行したままで、サーバーとの接続を確立したり切断したりすることができます。
  5. 複数コンピューターの管理
  6. 多数の管理対象ノードにワークフロー タスクを同時に適用できます。Windows PowerShell ワークフローでは、PSComputerName ワークフロー共通パラメーターなどの共通パラメーターが自動的にワークフローに追加され、複数コンピューターの管理が可能になります。そのままの状態でワークフローで使用できるパラメーターの詳細については、「付録」を参照してください。
  7. 単一のタスクによる複雑なエンド ツー エンドのプロセスの実行
  8. シナリオ全体を実装する関連スクリプトを単一のワークフローに集約することができます。
  9. タスクのスケジュール設定
  10. ワークフロー タスクは、他の Windows PowerShell コマンドレットまたはスクリプトと同様に、スケジュールを設定し、特定の条件を満たしたときにトリガーすることができます。

そして、ゴールについても PowerShell開発者は 述べています。

- Minimizing the complexity of automating across a large number of cloud or datacenter computers and devices.
- Creating an ecosystem where ISVs and partners can build solutions on top of Windows PowerShell Workflow and the artifacts can be shared with the community and used in any solution.

簡潔にいうとこういうことです。

  • 数多くの DCにおける PC や機器のAutomation (自動化) の複雑さを最小限にとどめる
  • パートナーで Windows PowerShell Workflow を軸としたエコシステムの構築と、その内容をコミュニティで共有、各場面での解決策に利用できるようにすること

これは、 Jeffrey Snover 自身が Key Notesなどでも繰り返し述べている Windows PowerShell で求める姿でもあることは、お分かりになるかと思います。

NICconf Opening and Keynote: Modernizing Windows Server/Modernizing Ourselves - by Jeffrey Snover

PowerShell は、それ単独でも クラウド上 (Azure や AWS などの IaaS)、あるいはオンプレミス や ローカル環境に複数の Windows Server をおいて管理しなくてはいけない時に強力な力を発揮します。

が、PowerShell Cmdletはそれ自体では処理速度に難があるのは事実です。 このような場合に、Workflow と組み合わせることで PowerShell はより 安定して高速に処理が可能となります。

Workflowを使ってみよう

どんな時に、どんな風に Workflow を使うのか、考えてみましょう。

Workflowをどんな時に使っているの?

私自身、現職において PowerShell で サーバー管理 Module を作成しています。

近日公開しますが、この Moduleにおいても Workflow は取り入れており、AWSクラウド上の 数多くの Windows サーバーを管理、維持する中で、その力がいかんなく発揮されています。

Workflow の持つ制限や特性から、全てを Workflow で行っているわけではありませんが、その恩恵に日々預かっています。

従来の Scripting を知っていれば Workflow は簡単に利用できる

従来の Windows PowerShell Scripting とは、 funcntion や ScriptBlock、 foreach 、 変数 など基本的な構文です。

この知識さえあれば、Workflow の利用に際して新しく覚えることはわずかなものです。

Workflow 独自の構文として、よく使うところでは以下の構文です。

workflow
inlinescript { }
foreach –parallel
parallel { }
sequence { }
# - XAML: もし WF や既存の XAML workflows を持ってたりコミュニティで見つけら場合は、そのまま PowerShell Workflow で利用可能です。

特に inlinescript {} と foreach -parallel はとても大事です。

細かい使い方は本記事では扱いませんが、このリンクが大事な情報の多くを網羅しているので、ぜひ目を通して試していただければと思います。

TechNet - Windows PowerShell ワークフローの実行 Writing a Script Workflow Running Windows PowerShell Commands in a Workflow

サンプルコード

参考までに、PowerShell の foreach ではできない、 parallel な foreach を Workflow を使って書いてみましょう。 サンプルはGitHubに挙げておきます。

https://github.com/guitarrapc/PowerShellUtil/tree/master/WorkflowTest

通常の PowerShell による foreach

これは、10.0.3.100 と 10.0.3.150 の PCに対して Test-Connection を行った場合です。

foreach ($pc in @("10.0.3.100","10.0.3.150"))
{
    Test-Connection -ComputerName $pc
}

当然、10.0.3.100 を実行した後に、 10.0.3.150 が実行されています。

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms) 
------        -----------     -----------      -----------                              -----    -------- 
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0        
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0        
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0        
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0        
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0        
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0        
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0        
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0        

Workflow による ふつーの foreach

簡単に workflow で ふつーのforeach をしてみましょう。

workflow test-workflowforeach{

    param(
    $computerName
    )

    foreach ($pc in $computerName)
    {
        Test-Connection -ComputerName $pc
    }

}

test-workflowforeach -computerName ("10.0.3.100","10.0.3.150")

workflow 構文で書いても、PowerShell の foreach と同様の結果になります。

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)  PSComputerName                                
------        -----------     -----------      -----------                              -----    --------  --------------                                
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge 

Workflow による parallel な foreach

workflow で parallel な foreach をするのは簡単です。 foreach に -parallel スイッチを付けます。

workflow test-workflowforeachparallel{

    param(
    $computerName
    )

    foreach -parallel ($pc in $computerName)
    {
        Test-Connection -ComputerName $pc
    }

}

test-workflowforeachparallel -computerName ("10.0.3.100","10.0.3.150")

parallel を付けた foreach 内では、実行順序は保障されません。

そのため、両者の結果が入り混じって返ってきます。

今回は10.0.3.150 と10.0.3.100交互に返ってきていますが、必ずしもそうなる保証はどこにもなく偶々です。

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)  PSComputerName                                
------        -----------     -----------      -----------                              -----    --------  --------------                                
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       1         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga

Workflow に対応していない一部の PowerShell Cmdlet を InlineScript構文 で実行する

Workflowに対応していない PowerShell Cmdlet を実行するには、 InlineScript 構文を用います。

この構文内では、ほぼすべての Windows PowerShell Cmdlet が 「Workflow に管理されていない通常の PowerShell Session 」として実行されます。

たとえば、 .ps1 ファイルを Workflow で実行する時にも InlineScript構文を用います。

InlineScript構文は Workflow のみで有効であり PowerShell の function構文では利用できません。

Workflow 内の InlineScript構文内で実行された 通常の PowerShell Session の動作、出力は Session完了時に InlineScript処理 を終えて、結果をWorkflow に返します。

例えば、先ほどのforeach -parallel 処理内部で InlineScript構文 を利用してみましょう。

workflow test-workflowforeachparallelInlineScript{

    param(
    $computerName
    )

    foreach -parallel ($pc in $computerName)
    {
        # InlineScript を付けると Invoke-Command や Invoke-Expression など 通常の PowerShell Cmdletの多くが利用できるようになる
        InlineScript
        {
            # InlineScript内で、InlineScript 外の変数を取得する場合は using: 構文を用いる (ScriptBlock と同様)
            Test-Connection $using:pc
        }
    }

}

# Connection テスト
test-workflowforeachparallelInlineScript -computerName @("10.0.3.100","10.0.3.150")

InlineScript構文を付けると、構文内部で PowerShell Sessionが実行されている間は、 InlineScript 実行中とポップアップが出ます。

なお、今回示す例の実行結果はparallel を付けた foreach と変わらず、対象としたPCそれぞれの応答結果が実行順序の保障なく入り混じって返ってきます。

Source        Destination     IPV4Address      IPV6Address                              Bytes    Time(ms)  PSComputerName                                
------        -----------     -----------      -----------                              -----    --------  --------------                                
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       1         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga
WHITE-DEPL... 10.0.3.150      10.0.3.150       ::1                                      32       0         hoge
WHITE-DEPL... 10.0.3.100      10.0.3.100                                                32       0         fuga

Workflow の foreach -parallel と 通常の foreach での速度差

単純に 2つのPCに対しての比較を見てみましょう。

workflow test-workflowforeachparallelInlineScript{

    param(
    $computerName
    )

    foreach -parallel ($pc in $computerName)
    {
        # InlineScript を付けると Invoke-Command や Invoke-Expression など 通常の PowerShell Cmdletの多くが利用できるようになる
        InlineScript
        {
            # InlineScript内で、InlineScript 外の変数を取得する場合は using: 構文を用いる (ScriptBlock と同様)
            Test-Connection $using:pc
        }
    }

}

# 通常の foreach の場合
# TotalSeconds      : 6.1301668 sec
Measure-Command {
    @("10.0.3.100","10.0.3.150") | %{Test-Connection $_}
}

# foreach -parallel の場合
# TotalSeconds      : 3.1872539 sec
Measure-Command {
    test-workflowforeachparallelInlineScript -computerName @("10.0.3.100","10.0.3.150")
}

このように、 foreach -parallel のほうが高速に完了していることがわかります。

どうでしょうか、簡単にですが function を workflow に変えるだけで使えそうな気分になりましたか?

Workflowの注意点

一見するとメリットが目につく Workflow ですが、通常の PowerShell Scripting と全く同じではなく注意すべき点が複数あります。

それを見てみましょう。

Workflow はマルチスレッドではない

Workflow を行う理由として、 「foreach の sequential な実行を避けて parallel に並列/非同期実行したい」というのがあります。

この時に、マルチスレッドないことを把握しておかないと 落とし穴になりかねません。

@Ahf さんが詳しくまとめていらっしゃるので、foreach -parallel を利用する場合は必ず目を通すことをおすすめします。

てすとぶろぐ - ワークフロー上のアクティビティを非同期に複数動作させるには てすとぶろぐ - PowerShell 3.0 の ForEach –parallel はマルチスレッドではない

私の経験事例では、シングルスレッドでの非同期 => これはマルチスレッド上での非同期と動作が異なり処理の途切れるタイミングがない場合は、 シングルスレッド上で5つずつ走ることになります。

そのため、5つを超える多数のノードに対する 実行の場合は、 workflow ではうまく動作しない場合があります。

  • 例:処理と処理の間に Sleep を仕込んだ ScriptBlock を workflow で5を超える複数ノードに一斉実行時で、 一斉にコマンドを送信してほしくとも制限された数ずつ送り、 sleep での切り替えもスムーズにいかず詰まる場合など

この場合は、 Workflow ではなく Asyncに動作する非同期モジュールを作成して利用する方法があります。(これはまたの機会に記事にします。)

Workflow の実行プロセス数の制限

Workflow を実行して、Powershell.exe を終了して、また立ち上げて Workflow を実行して...... を繰り返していると、 突如、リモート PCへの接続が出来なくなることがあります。

これは、PowerShell の Job同様 同時に WmiPrvSE.exe プロセスで管理するWSManInstance数が上限に達したために発生します。

※ このコネクションは再利用されるため、 一度 Workdlow でつないだ PowerShell.exe を落とさずに再利用している場合は、まず発生しません。

WSManInstanceセッションの取得

workflow 内部などでこのコードを仕込むことでWSManInstanceセッションが取得できます。 .count でセッション数も取得可能です。

$WSManInstance = Get-WSManInstance shell -Enumerate
$WSManInstance.count

WSManInstance数の上限

WSManInstance数の上限は 25 のため、 22程度で削除やリセットを書けることも視野に入れたほうがいいでしょう。 ただし、削除/リセット時に持っている Workflow sessionも切れるため、そもそも再利用するようにすべきですが。

WSManInstanceセッションの削除

現在のWSMainInstance セッションは以下のコードで削除も可能です。

# Will remove specific session you select include current. (In this command will be all session)
$WSManInstance | %{Remove-WSManInstance -ConnectionURI http://localhost:5985/wsman shell @{shellid=$_.ShellId}}

現在の WSManInstance数をリセットする

WSManInstance数の削除は、都度選択が大変なため、リセットも視野に入れるかと思います。 リセットはWinRM サービスのリスタートで行えます。

Restart-Service -Name WinRM -Force -PassThru -ErrorAction Stop 

Workflow構文内での変数指定

少し癖があるので、気を付けてください。 構文と参照の組み合わせにより変わります。

Using Variables in Script Workflows

構文

  • Loops/Control 構文
  • Parallel / Sequence 構文
  • InlineScipt構文

参照

  • Workflow変数の参照
  • Workflow変数の変更
  • Workflow内部変数がWorkflowスコープで見れるか
Rules for Nested Statements in Workflows View workflow variable values Change workflow variable values Internal variables are visible in workflow scope
Loops/Control Statements Yes Yes Yes
Parallel / Sequence Yes No. Use $Workflow. No, return the variable.
InlineScript No. Use $Using. No. Return the variable. $Workflow is invalid. No, return the variable.

InlineScript でも実行できない Cmdlet

実はこれが問題です。 Workflow でサポート対象外の PowerShell 動作一覧があります。

Hey, Scripting Guy! Blog PowerShell Workflows: Restrictions
Other cmdlets that haven’t been turned into workflow activities are shown in the following table. You can use them in InlineScript blocks.
Unsupported cmdlet (group) Reason
*Alias, *FormatData, *History, *Location, *PSDrive, *Transcript, *TypeDate, *Variable, Connect/Disconnect-Wsman Only change Windows PowerShell session so not needed in workflow
Show-Command, Show-ControlPanelItem, Get-Credential, Show-EventLog, Out-Gridview, Read-Host, Debug-Process Workflows don’t support interactive cmdlets
*BreakPoint, Get-PSCallStack, Set-PSDebug Workflows don’t support script debugging
*Transaction Workflows don’t support transactions
Format* No formatting support
*PSsession, *PSsessionoption Remoting controlled by workflow
Export-Console,Get-ControlPanelItem, Out-Default, Out-Null, Write-Host, Export-ModuleMember, Add-PSSnapin, Get-PSSnapin, Remove-PSSnapin, Trace-Command  

たとえば、 Write-host や Read-Host が当てはまります。

例でみましょう。

workflow test-workflow{

    param(
    $hoge
    )

    Write-Host "hoge"

}

実行しようとすると、エラーが出てそもそも workflow として構文登録自体できません。

発生場所 行:7 文字:5
+     Write-Host "hoge"
+     ~~~~~~~~~~~~~~~~~
'Write-Host' コマンドを呼び出すことができません。このモジュールからの他のコマンドはワークフロー アクティビティとしてパッケージ化されていますが、このコマンドは除外されました。原因として、コマンドで Windows PowerShell の対話セッションが必要か、コマンドの動作がワークフローに適していないことが考えられます。このコマンドを実行するには、コマンドが独立して呼び出されるインライン スク
リプト (InlineScript { Write-Host }) にコマンドを含めてください。
    + CategoryInfo          : ParserError: (:) , ParseException
    + FullyQualifiedErrorId : CommandActivityExcluded

例外として、localhost での実行可否について記述があります。

There are a number of cmdlets that are local execution only in workflows.
Add-Member               Compare-Object                ConvertFrom-Csv         ConvertFrom-Json               
ConvertFrom-StringData Convert-Path ConvertTo-Csv ConvertTo-Html
ConvertTo-Json ConvertTo-Xml ForEach-Object Get-Host
Get-Member Get-Random Get-Unique Group-Object
Measure-Command Measure-Object New-PSSessionOption New-PSTransportOption
New-TimeSpan Out-Default Out-Host Out-Null
Out-String Select-Object Sort-Object Update-List
Where-Object Write-Debug Write-Error Write-Host
Write-Output Write-Progress Write-Verbose  

WriteHostは、あいにくと InlineScript に囲んで localhost に対して行ってもできない場合があるため、既存のコードを workflow で使おうと思った時は検証を要します。

workflow test-workflow{

    param(
    $hoge
    )

    InlineScript
    {
        Write-Host "hoge"
    }

}

test-workflow -hoge 1 -PSComputerName localhost

だめですね。

ユーザーにダイアログを表示するコマンドの実行に失敗しました。ホスト プログラムまたはそのコマンドの種類では、ユーザーの操作はサポートされていません。ユーザーの操作をサポートしているホスト プログラム (Windows PowerShell コンソール、Windows PowerShell ISE など) 
を使用し、ユーザーの操作をサポートしていないコマンドの種類 (Windows PowerShell ワークフローなど) からダイアログの表示に関連するコマンドを削除してください。
    + CategoryInfo          : NotImplemented: (:) [Write-Host], HostException
    + FullyQualifiedErrorId : HostFunctionNotImplemented,Microsoft.PowerShell.Commands.WriteHostCommand
    + PSComputerName        : [localhost]

Write-Host は使えませんが、 Write-Output や Write-Warning 、 Write-Verbose は利用可能です。

このworkflow に対応していない cmdletの問題は、Workflow 自体が リモート実行使えない要因になるため Cmdlet の利用には気を付けてください。

あいにくと PowerShell V4 でも改善報告がないため、難しい問題です。

InlineScript を用いた Invoke-Command の利用にはScriptBlockの生成が必要

InlineScriptを用いるとInvoke-Command が利用可能ですが ScriptBlock を渡す際に一段階処理が必要です。

例えば、 workflow に {Invoke-RestMethod -Method Get -Uri google.com} をScriptBlock として渡す処理を見てみましょう。

workflow Invoke-WorkflowParallel{

    [CmdletBinding(DefaultParameterSetName = "ScriptBlock")]
    param(
        [parameter(
            Mandatory = 1,
            Position = 0,
            ParameterSetName = "ScriptBlock")]
        [scriptblock]
        $scriptBlock,
        
        [parameter(
            Mandatory = 1,
            Position = 0,
            ParameterSetName = "Expression")]
        [string]
        $expression,

        [parameter(
            Mandatory = 1,
            Position = 0,
            ParameterSetName = "other")]
        [string]
        $argumentlist,
        
        [parameter(
            Mandatory = 1,
            Position = 1)]
        [array]
        $array
    )

    if ($scriptBlock)
    {
        foreach -Parallel ($a in $array)
        {
            inlinescript
            {
                # これはだめ
                # Invoke-Command -ScriptBlock {&$usingScriptBlock}
                # これもだめ
                # Invoke-Command -ScriptBlock {$usingScriptBlock}
                # これもだめ
                # Invoke-Command -ScriptBlock $usingScriptBlock

                # ScriptBlock は生成してから行う
                $WorkflowScript = [ScriptBlock]::Create($using:ScriptBlock)
                Invoke-Command -ScriptBlock {&$WorkflowScript} -ErrorAction Stop
            }
        }
    }
    elseif ($expression)
    {
        foreach -Parallel ($a in $array)
        {
            inlinescript
            {
                # Invoke-Command とことなり生成が不要
                Invoke-Expression -Command $using:expression
            }
        }
    }
    elseif ($argumentlist)
    {
        # ScriptBlock でも Expression でもなく直接 PowerShell cmdlet を利用することは可能
        Write-Verbose -message "$argumentlist"
        Invoke-RestMethod -Method Get -Uri google.com
    }
}

# ScriptBlock で実行する場合
Invoke-WorkflowParallel -scriptBlock {Invoke-RestMethod -Method Get -Uri google.com} -array 1,2,3,4,5

# Expression で実行する場合
Invoke-WorkflowParallel -expression "Invoke-RestMethod -Method Get -Uri google.com" -array 1,2,3,4,5

# 直接 workflow に記述してある内容を実行する
Invoke-WorkflowParallel -argumentlist "直接Workflow内部に記述した内容を実行する" -array 1,2,3,4,5 -Verbose

このように、 Workflow 内部で Invoke-Command を実行するときは、 [ScriptBlock]::Create({$scriptblock}) すると素直に渡せます。

Workflow 内部では パラメータ名の省略ができない

繰り返しますが、 Workflow は PowerShell のサブセットです。

そのため、 Workflowでは PowerShell Scripting で行っているような 位置パラメータによる パラメータ名の省略が出来ません。

日頃触っていると ついパラメータ名を省略して 手早く書いてしまいますが、Workflow ではきっちり書いてください。

 

このようなコードで分かります。

workflow test-workflowOmitParameterName{

    # パラメータ名が省略されているので workflow として登録できない。
    Write-Warning "hoge"
}

実行するとエラーが出ます。

At line:3 char:19
+     Write-Warning "hoge"
+                   ~~~~~~
Positional parameters are not supported in a Windows PowerShell Workflow. To invoke this command, use explicit parameter names with all values. For example: "Command -Parameter <value>".
    + CategoryInfo          : ParserError: (:) , ParseException
    + FullyQualifiedErrorId : PositionalParametersNotSupported

この場合は、 -message パラメータを宣言します。

workflow test-workflowOmitParameterNameCorrect{

    # パラメータ名をきっちり宣言すれば問題ない
    Write-Warning -message "hoge"
}

test-workflowOmitParameterNameCorrect

実行できました。

WARNING: [localhost]:hoge

最後に

先日公開した PS-SumoLogicAPI Module でも parallel スイッチで workflow による foreach -parallel (){} を呼び出しています。

@Ahf さんが指摘している通り、すべからく workflow にしたり、maxthrottle を変更しても効果が薄いためどういう時に使うか考慮は必要です。

が、PowerShell としては簡単に使える強力な手法ではあります。 目的に合わせて利用すると良いでしょう。

特に、複数ノードがターゲットであったり、長時間実行する処理 などで効果を発します。

ぜひ活用していただけるとよいかと思います。