PowerShell 5.1 系で利用可能な PowerShell Workflow
ThreadJob
module 使った方が楽かも
現時点での感覚として ThreadJob
使った方がなんかもろもろ楽な気がしています。
こちらもご覧いただければと思います。
PowerShell 5.1 系で利用可能な PowerShell Workflow
この記事は PowerShell 5.1 系での PowerShell Workflow の話をしますので、2023 年の時点であまり参考になる人は少ないんじゃあないかとは祈っています。
とはいえ Windows Server に PowerShell 7.x 系が install できる環境も少ないかもしれないのでもしかしたら需要があるのかもしれません。
PowerShell Workflow を使ってみる
めっちゃ簡単なものだとこんな感じです。
workflow Test-Workflow {
Get-NetAdapter
}
で、実行してみます。
> Test-Workflow
Name InterfaceDescription ifIndex Status MacAddress LinkSpeed
---- -------------------- ------- ------ ---------- ---------
Bluetooth Network Conn... Bluetooth Device (Personal Area Netw... 22 Disconnected 2C-33-58-xx-xx-xx 3 Mbps
Ethernet 3 Lenovo USB Ethernet 21 Disconnected 48-2A-E3-xx-xx-xx 0 bps
Wi-Fi Intel(R) Wi-Fi 6 AX201 160MHz 11 Up 2C-33-58-xx-xx-xx 1.2 Gbps
まぁこんな感じで、関数みたいなのを定義して、それを呼べば実行できる、シンプルな感じです。
PowerShell Workflow での並列化
そもそも PowerShell Workflow を使おう、というのは並列化が目的なことが多いでしょうから、こんな感じになるわけですね。
workflow Test-Workflow {
$NICs = Get-NetAdapter
foreach -parallel ($NIC in $NICs) {
Write-Output "$($NIC.Name)'s Link Speed is $($NIC.LinkSpeed)"
}
}
実行してみるとこんな感じです。
> Test-Workflow
Wi-Fi's Link Speed is 1.2 Gbps
Ethernet 3's Link Speed is 0 bps
Bluetooth Network Connection's Link Speed is 3 Mbps
確かに、Get-NetAdpater
で取得したネットワーク インターフェース (のオブジェクト) それぞれに対して Write-Output
が実行されていることがわかります。
PowerShell Workflow の定義を取得する
今実行しようとしている PowerShell Workflow の定義を確認する場合には Get-Command
を使います。
Definition
に定義が入っているので、それを見ればわかるという感じです。
> Get-Command -CommandType workflow -Name Test-Workflow -ShowCommandInfo
Name : Test-Workflow
ModuleName :
Module : @{Name=}
CommandType : Workflow
Definition :
$NICs = Get-NetAdapter
foreach -parallel ($NIC in $NICs) {
Write-Output "$($NIC.Name)'s Link Speed is $($NIC.LinkSpeed)"
}
ParameterSets : {@{Name=__AllParameterSets; IsDefault=False; Parameters=System.Management.Automation.PSObject[]}}
Workflow の中で素直に使えない PowerShell module がある
現時点で法則性とかがちょっとわかっていないのですが、具体例として Import-Module -Name Az
はそのままでは使えないようです。
> workflow Test-Workflow {
>> Import-Module -Name Az
>> }
At line:2 char:5
+ Import-Module -Name Az
+ ~~~~~~~~~~~~~~~~~~~~~~
Cannot call the 'Import-Module' command. Other commands from this module have been packaged as workflow activities,
but this command was specifically excluded. This is likely because the command requires an interactive Windows
PowerShell session, or has behavior not suited for workflows. To run this command anyway, place it within an
inline-script (InlineScript { Import-Module }) where it will be invoked in isolation.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : CommandActivityExcluded
これは foreach -parallel
の中に入れても同様です。
> workflow Test-Workflow {
>> $VMs = Get-AzVM
>>
>> ForEach -Parallel ($_VM in $VMs) {
>> Import-Module -Name Az
>> }
>> }
At line:5 char:9
+ Import-Module -Name Az
+ ~~~~~~~~~~~~~~~~~~~~~~
Cannot call the 'Import-Module' command. Other commands from this module have been packaged as workflow activities,
but this command was specifically excluded. This is likely because the command requires an interactive Windows
PowerShell session, or has behavior not suited for workflows. To run this command anyway, place it within an
inline-script (InlineScript { Import-Module }) where it will be invoked in isolation.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : CommandActivityExcluded
Error message に書かれているとおりですが、InlineScript
で囲むと実行ができるようになります。
このままではあまり意味がないのですが、サンプルとして。。
workflow Test-Workflow {
$VMs = Get-AzVM -ResourceGroupName "simple-windows10"
ForEach -Parallel ($VM in $VMs) {
InlineScript {
Import-Module -Name Az
}
}
}
InlineScript
の使い方に少し癖がある
で、InlineScript
で囲まれた部分で、その外にある変数を使う場合には $using:
を付ける必要があります。
ただ、なぜか少し深い property にアクセスしようとすると意図どおりには動いてくれません。
workflow Test-Workflow {
$VMs = Get-AzVM
ForEach -Parallel ($_VM in $VMs) {
InlineScript {
Write-Warning "`$Using:_VM.ResourceGroupName: $($Using:_VM.ResourceGroupName)"
Write-Warning "`$Using:_VM.Name: $($Using:_VM.Name)"
Write-Warning "`$Using:_VM.HardwareProfile.VmSize: $($Using:_VM.HardwareProfile.VmSize)"
}
}
}
結果は以下のとおりです。
> Test-Workflow
WARNING: [localhost]:$Using:_VM.ResourceGroupName: simple-windows10
WARNING: [localhost]:$Using:_VM.Name: vm-hub00
WARNING: [localhost]:$Using:_VM.HardwareProfile.VmSize:
なんと、$Using:_VM.HardwareProfile.VmSize
では値が取れていないんですね。
ただ、$Using:_VM.ResourceGroupName
と $Using:_VM.Name
は値が取れているので、それをもとに Get-AzVM
を実行してみます。
workflow Test-Workflow {
$VMs = Get-AzVM
ForEach -Parallel ($_VM in $VMs) {
InlineScript {
$VM = Get-AzVM -ResourceGroupName $Using:_VM.ResourceGroupName -Name $Using:_VM.Name
Write-Warning "`$VM.ResourceGroupName: $($VM.ResourceGroupName)"
Write-Warning "`$VM.Name: $($VM.Name)"
Write-Warning "`$VM.HardwareProfile.VmSize: $($VM.HardwareProfile.VmSize)"
}
}
}
結果は以下のとおりです。
> Test-Workflow
WARNING: [localhost]:$VM.ResourceGroupName: simple-windows10
WARNING: [localhost]:$VM.Name: vm-hub00
WARNING: [localhost]:$VM.HardwareProfile.VmSize: Standard_B2ms
ということで、やや冗長ではあるのですが、引数として渡されてきた $_VM
を使って Get-AzVM
を実行すればなんとなく用途には耐えそうです。
で、ふと、以下のような script を実行し、型がどうなっているかを確認したら理由が分かった気がします。
workflow Test-Workflow {
$VMs = Get-AzVM
ForEach -Parallel ($_VM in $VMs) {
InlineScript {
Write-Warning "`$Using:_VM: $(($Using:_VM).GetType().FullName)"
$VM = Get-AzVM -ResourceGroupName $Using:_VM.ResourceGroupName -Name $Using:_VM.Name
Write-Warning "`$VM: $($VM.GetType().FullName)"
}
}
}
結果は以下のとおりです。
> Test-Workflow
WARNING: [localhost]:$Using:_VM: System.Management.Automation.PSObject
WARNING: [localhost]:$VM: Microsoft.Azure.Commands.Compute.Models.PSVirtualMachine
ということで、$Using:_VM
は System.Management.Automation.PSObject
となってしまっており、本来の型である Microsoft.Azure.Commands.Compute.Models.PSVirtualMachine
ではない、ということが分かります。
で、何とか cast (型変換) ができないかなぁと思ったのですが、どうもうまくいかず、冗長ですが Get-AzVM
を再度実行するのが一番手っ取り早いかな、という感じです。
ForEach -Parallel -ThrottleLimit
での並列実行数制御
PC のスペックにもよると思いますが、ForEach -Parallel
での並列実行数はデフォルトで 5 となっているようです。
どこにも明記はないのですが、いろいろ調べていると 5 以上にできないのか、などという stackoverflow などの質問が散見されるので、みんな悩んでいるんでしょう。。
で、例えばそれよりも小さくしたいという場合には -ThrottleLimit
を使います。
何をやっているんだ、という意味の分からない script ですが、以下の内容を実行してみてください。
workflow Test-Workflow {
$VMs = Get-AzVM
ForEach -Parallel ($_VM in $VMs) {
InlineScript {
Write-Output "Start-Sleep -Seconds 10"
Start-Sleep -Seconds 10
}
}
}
結果は静的に示しても仕方がないので省きますが、Azure VM が 10 台とかある環境で実行していただくとたぶん 5 連続で Start-Sleep -Seconds 10
が表示され、その後 10 秒待ってからまたぽつぽつと続きが表示される、、という感じになると思います。
ちなみに Task Manager で見ても、powershell.exe が全部で 6 つくらいは動いている状況が確認できるかと思います。
これに対し、以下のように -ThrottleLimit
を指定すると、指定した数だけしか並列実行されないようになります。
workflow Test-Workflow {
$VMs = Get-AzVM
ForEach -Parallel -ThrottleLimit 2 ($_VM in $VMs) {
InlineScript {
Write-Output "Start-Sleep -Seconds 10"
Start-Sleep -Seconds 10
}
}
}
この場合には、並列数を 2 に指定しているので、Start-Sleep -Seconds 10
が 2 つ表示され、その後 10 秒待ってからまたぽつぽつと、、という感じになると思います。
Task Manager で見ても、powershell.exe が全部で 3 つくらいは動いている状況が確認できるかと思います。
Workflow session
docs によれば、なんか Workflow session ってのがおすすめらしいですね。
ただ、localhost 宛てだとしても WinRM が必要っぽいので、エンタープライズだとたぶん無理な気がします。
もしやる場合には、Enable-PSRemoting
で WinRM を有効化するんだろうけど、そもそも有効化されてるのか、を確認する方法はこちらです。
> Test-WSMan -ComputerName localhost
Test-WSMan : <f:WSManFault xmlns:f="http://schemas.microsoft.com/wbem/wsman/1/wsmanfault" Code="2150858770"
Machine="DESKTOP-09TD89K"><f:Message>The client cannot connect to the destination specified in the request. Verify
that the service on the destination is running and is accepting requests. Consult the logs and documentation for the
WS-Management service running on the destination, most commonly IIS or WinRM. If the destination is the WinRM service,
run the following command on the destination to analyze and configure the WinRM service: "winrm quickconfig".
</f:Message></f:WSManFault>
At line:1 char:1
+ Test-WSMan -ComputerName localhost
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (localhost:String) [Test-WSMan], InvalidOperationException
+ FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.TestWSManCommand
参考
- 大体ここからたどっていきます
-
ForEach -Parallel
についてはこちらです。ForEach-Object -Parallel
は PowerShell 7.x 移行で使えるものでこれとは異なりますのでご注意ください
https://learn.microsoft.com/powershell/module/psworkflow/about/about_Foreach-Parallel??wt.mc_id=MVP_391314&iew=powershell-5.1
-
InlineScript
についてはこちらです
Discussion