はじめに
CMD(コマンドプロンプト)から PowerShell スクリプト(.ps1)を実行する際、-File オプションを使って配列パラメータを渡そうとすると、配列が1つの文字列として扱われてしまう問題があります。
この記事では、この問題の原因と、実用的な回避策をまとめます。
問題の症状
例えば、次のような PowerShell スクリプトがあるとします。
# script.ps1
param(
[string[]] $Array
)
Write-Host "Count: $($Array.Count)"
$Array | ForEach-Object { Write-Host " - $_" }
CMD から次のように実行すると:
powershell.exe -File script.ps1 -Array "AA","BB","CC"
期待する結果は配列の要素数が 3 で、それぞれ "AA"、"BB"、"CC" が表示されることですが、実際には:
Count: 1
- AA,BB,CC
のように、1つの文字列として扱われてしまいます。
原因:PowerShell 5.1 の -File パラメータの既知の制限
この動作は、PowerShell 5.1 の -File パラメータの既知の制限事項です(バグではなく、仕様上の制約です)。
なぜこうなるのか
- CMD などの外部シェルから
powershell.exe -Fileで実行する場合、ネイティブプロセスの呼び出しでは「配列」という概念が保持されない - そのため、PowerShell プロセスは1つの平坦な文字列引数として受け取る
- 結果として、
"AA,BB,CC"という1つの文字列が、string[]型のパラメータに1要素の配列として渡される
公式ドキュメントでの言及
Microsoft の公式ドキュメントでも、-File オプションはすべての引数をリテラル文字列として扱うと明記されています。そのため、配列型のパラメータには単一の連結された文字列が渡されることになります。
他の例
あるブログ記事では、3つの値を -File 経由で渡したところ、スクリプト内の配列パラメータは "Ray of Frost,Light,Detect Magic" という1つの文字列(カンマ込み)を1要素として受け取ったという報告があります。
PowerShell の内部的な引数バインダーでは、その1つの文字列が単純に1要素の文字列配列にラップされるため、Count は 1 になります。CMD から -File を使う場合、カンマによる自動分割は行われません。
回避策とベストプラクティス
この制限は PowerShell CLI の設計によるものなので、以下のいずれかの方法で回避できます。
回避策1:-Command パラメータを使う(推奨)
配列引数を渡す必要がある場合、-Command パラメータを使うのが推奨される解決策です。
-Command オプションは、その後に続く内容を PowerShell コードとして扱うため、PowerShell セッション内で書くのと同じように配列リテラルを渡せます。
実行例
powershell.exe -ExecutionPolicy Bypass -Command "C:\path\to\script.ps1 -Array 'AA','BB','CC'"
この方法では、'AA','BB','CC' の引数は CMD ではなく PowerShell によって実際の文字列配列としてパースされます。そのため、スクリプトは期待通り3要素の配列を受け取ります。
注意点
- CMD では全体をダブルクォートで囲む
- PowerShell 内の文字列リテラルにはシングルクォートを使う
-Command を使うことで、CLI の制限を完全に回避できます。
回避策2:文字列として受け取り、スクリプト内で分割する
何らかの理由で -File を使う必要がある場合(例えば、ホストの制限や -Command が使えないバグがある場合)、実用的な回避策として、1つの区切り文字付き文字列として受け取り、スクリプト内でパースする方法があります。
実行例
powershell.exe -File script.ps1 -Array "AA,BB,CC"
スクリプト側の修正
# script.ps1
param(
[string[]] $Array
)
# カンマ区切りの文字列を配列に分割
$Array = $Array -split ','
# 必要に応じて空白をトリム
$Array = $Array.ForEach{ $_.Trim() }
Write-Host "Count: $($Array.Count)"
$Array | ForEach-Object { Write-Host " - $_" }
これにより、単一の文字列 "AA,BB,CC" が配列 ("AA","BB","CC") に変換され、スクリプト内で使用できます。
メリット・デメリット
-
メリット:
-Fileのままで対応できる - デメリット: スクリプト側で分割ロジックを実装する必要がある
ただし、カンマで分割してトリムするだけのシンプルな変更で対応できます。
回避策3:ValueFromRemainingArguments を活用する(上級者向け)
PowerShell には、残りのコマンドライン引数をすべて配列としてキャプチャするパラメータ属性があります。
スクリプト側の修正
# script.ps1
param(
[Parameter(Mandatory=$true, ValueFromRemainingArguments=$true)]
[string[]] $Array
)
Write-Host "Count: $($Array.Count)"
$Array | ForEach-Object { Write-Host " - $_" }
実行例
この属性を使う場合、-Array という名前は付けずに、位置引数として値を渡します。
powershell.exe -File script.ps1 "AA" "BB" "CC"
この場合、PowerShell は "AA"、"BB"、"CC" を $Array パラメータの要素として収集します。
注意点
- 他の名前付きパラメータがある場合、それらを配列の値の後にリストできます。バインダーは名前付きパラメータに当たると、
$Arrayへの収集を停止します - この手法は配列パラメータが位置引数で埋められる場合にのみ機能します
- 誰かが
-Array "AA" "BB"のように名前付きで呼び出そうとすると、正しくバインドされません - 上級者向けの回避策で、直感的でない場合があるため、使用する場合は使い方を明確にドキュメント化してください
まとめ
- Windows PowerShell 5.1 は
-File構文で配列型パラメータを直接渡すことをサポートしていません - これは長年存在する制限事項であり、5.1 でパッチ可能なバグではありません
- Microsoft の公式ドキュメントでも、
-Fileはすべての引数をリテラル文字列として扱うと明記されています
複数の値を確実に渡すには、上記のいずれかの回避策を選択してください。
推奨アプローチ
最もシンプルで堅牢な方法は、-Command パラメータを使ってスクリプトを呼び出すことです。これは対話的な PowerShell 呼び出しと同じように動作し、配列のセマンティクスを保持します。
-File が必要な場合は、入力に応じて処理する計画を立ててください(文字列を分割するか、パラメータバインディングのトリックを採用するか)。
これらのベストプラクティスに従うことで、配列引数が PowerShell スクリプトによって意図通りに受け取られるようになります。
参考資料
- PowerShell Documentation – About PowerShell.exe (limitations of -File with arrays)
- Stack Overflow – Discussion of array args not supported with -File and recommendation to use -Command
- Community Example – Scheduled Task using -File treating comma-separated list as one argument
- Stack Overflow – Workarounds: splitting string in script and using ValueFromRemainingArguments for array params