亀の甲羅2

今日もまた朝とく起きて励まなん窓に明るきありあけの月

PowerShell 文字列をコマンドとして実行する Invoke-Expression

1.はじめに

実行したいコマンドレットを文字列として作り込んでから実行する方法を調べてみた。

2.環境

PowerShellのバージョンは以下の通り。

PS > $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.18362.752
・・・以下、略・・・

3.目指すところ

  • Invoke-Expression の使い方を覚える

4.やってみよう

4.1 Invoke-Expression

Invoke-Expression何するものぞ

Invoke-Expressionは文字列をコマンドとして実行するコマンドレット。例を見れば早い。

PS > $cmd = "Get-Alias"        ・・・"Get-Alias"という文字列を変数に格納

PS > Invoke-Expression $cmd    ・・・文字列をコマンドとして実行

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ac -> Add-Content
Alias           asnp -> Add-PSSnapin
(略)

実行したいことが条件によって微妙に異なる場合は多い。そんなときは、実行したいことを文字列として作り込んでから実行すればいい。

Invoke-Expressionを使わない例

簡単な例として、偶数なら「実動作がGet-ChildItemとなっているエイリアスを列挙」、奇数なら「lsのとして定義されているエイリアスを表示」するスクリプトを作成してみた。どちらもGet-Aliasコマンドレットを使って実現できるが、Invoke-Expressionを使わないと、以下のようになる。

# $joken:0~1
$joken = (Get-Random) % 2

if($joken -eq 0){
    #偶数
    Get-Alias -Definition Get-ChildItem
}else{
    #奇数
    Get-Alias -Name ls
}

Invoke-Expressionを使った例

同じことをInvoke-Expressionを使って書いてみる。

# $joken:0~1
$joken = (Get-Random) % 2

$cmd = "Get-Alias "
if($joken -eq 0){
    #偶数
    $cmd += "-Definition Get-ChildItem"
}else{
    #奇数
    $cmd += "-Name ls"
}

Invoke-Expression $cmd

あまりにもシンプルな例なので「こんなの意味あるの?」と思うかもしれないが、少し複雑なスクリプトになると地味に便利なことに気づくはず。

Invoke-Expressionの注意点(つまづきやすい点)

配列変数を文字列として扱うには注意が必要。 a[0] = 1, a[1] = 2, a[2] = 3という配列をカンマ区切りで表示する例で示す。

まずは、配列を作って単純に表示させると、こんな感じ。

PS > $a = @(1,2,3)
PS > Write-Host $a
1 2 3

上記配列をカンマ区切りで表示させると、こんな感じ。

PS > Write-Host ($a -join ',')
1,2,3

では、同じことを文字列として実行してみよう。

PS > $cmd = "Write-Host ($a -join ',')"

PS > Invoke-Expression $cmd
Invoke-Expression : 発生場所 行:1 文字:15
+ Write-Host (1 2 3 -join ',')
+               ~
式またはステートメントのトークン '2' を使用できません。
発生場所 行:1 文字:14
+ Write-Host (1 2 3 -join ',')
+              ~
式の終わりの ')' が存在しません。
発生場所 行:1 文字:28
+ Write-Host (1 2 3 -join ',')
+                            ~
式またはステートメントのトークン ')' を使用できません。
発生場所 行:1 文字:1
+ Invoke-Expression $cmd
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ParserError: (:) [Invoke-Expression], ParseException
    + FullyQualifiedErrorId : UnexpectedToken,Microsoft.PowerShell.Commands.InvokeExpressionCommand

エラーになる。エラー内容を見てみると、実行されているのは Write-Host (1 2 3 -join ',') というコマンドのようだ。これではうまくいかないのも当然。

以下をみてもらえばわかるが、$cmdという変数に文字列を格納した時点で配列が展開されてしまっている

PS > $cmd = "Write-Host ($a -join ',')"
PS > Write-Host $cmd
Write-Host (1 2 3 -join ',')

これを回避するには、以下のように配列変数部分を`(バッククォータ)でくくる。バッククォータはキーボードの@マークが書かれているキーをShift押しながら入力。

PS > $cmd = "Write-Host (`$a` -join ',')"

PS > Write-Host $cmd
Write-Host ($a -join ',')

PS > Invoke-Expression $cmd
1,2,3

ということで、配列変数を文字列にしたいときには注意が必要ということは覚えておきたい。

終.獲得した知識

  • Invoke-Expressionコマンドレットの使い方
  • 定義済み配列を文字列の中で展開させない方法