以前のエントリで軽く触れたPowerShell 3.0における破壊的変更について一覧をまとめてみました。
元ネタはこちら(docx)。
はじめに
去年アナウンスされましたがPowerShell 2.0は既に非推奨です。
一応PowerShell 2.0のサポートはOSのサポートとセットになっているためOSがサポート切れにならない限りはPowerShell 2.0を使い続けても問題ないと言えますが、できることなら早めにWPowerShell 5.1(状況によってはPowerShell Coreへ)移行しておくと良いでしょう。
とくにクライアントOS(Windows 10)での利用においては、PowerShell 2.0が有効になっているとPowerShell 5.1に内蔵されているAMSIを回避するダウングレードアタックの対象ともなりセキュリティ的にも脆弱になるためできる限り新しいバージョンを使うべきです。
PowerShellの互換性
基本的にPowerShellのバージョンが上がる際の互換性は極力保たれる様になっているのですが、PowerShell 2.0から3.0においてはベースとなるランタイムが.NET Framework 2.0から.NET 4になったことやPowerShellの基本オブジェクトであるPSObject
の実装がDLRベースに差し替えられたこともあり、残念ながらそれなりに破壊的変更が生じています。
そしてその破壊的変更の一覧もリリースノート(docx)に軽く記載されているだけであまりきちんとした資料がありません。
本エントリではこの破壊的変更の一覧をまとめ軽く補足を入れていますのでPowerShell 2.0からの移行に役立てていただければと思います。
破壊的変更の一覧
1. finally ブロックでの return
PowerShell 2.0ではfinally
ブロック内でreturn
することができましたが、PowerShell 3.0からこれはエラーとなります。
# OK : PowerShell 2.0 try {} finally { return } # NG : PowerShell 3.0 ~ try {} finally { return }
finallyブロックを抜けた後でreturn
文を実行することでエラーを回避できます。
# Workaround : PowerShell 3.0 ~ try { # do something } finally { if ($cond) { $retval = 1 $hasretval = $true } } if ($hasretval) { return $retval }
2. 実行アセンブリ
PowerShell 2.0までのPowerShell.exeの実行アセンブリはSystem.Management.Automation.dll
でしたが、これがPowerShell 3.0からは内部生成される動的なアセンブリに変更されます。
# PowerShell 2.0 PS C:\> [Reflection.Assembly]::GetExecutingAssembly() | Select-Object FullName FullName -------- System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 # PowerShell 3.0 ~ PS C:\> [Reflection.Assembly]::GetExecutingAssembly() | Select-Object FullName FullName -------- Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
この変更により影響を受ける箇所はリリースノートでも
Various errors or incorrect behavior, depending on the use.
としか書かれていないため何とも言えません。
個人的にはリフレクションを扱うコードで影響を受けそうな雰囲気を感じるので、そういったコードを移行する際にはこの点に気を付けてみるのが良さそうです。
3. 演算子で使用される変数のスコープ
これは地味に嫌らしい変更になります。
加減乗除などの演算(+=
, -=
, *=
, %=
, ++
, --
といったものも含む)で読み書きされる変数は、PowerShell 2.0まではダイナミックスコープだったのがPowerShell 3.0からはローカルスコープになります。
# スコープの異なる $x 変数を使った例 $x = 5 & { $x += 1; echo $x } # PowerShell 2.0では & { $x += 1; echo $x } は 6 を返す PS C:\> & { $x += 1; echo $x } 6 # PowerShell 3.0以降は & { $x += 1; echo $x } は 1 を返す PS C:\> & { $x += 1; echo $x } 1
4. 数値と文字列の比較
ちょっと細かい仕様まで調べきることが出来なかったのですが、PowerShell 3.0からは数値の文字列の等値比較の結果が異なります。
PowerShell 2.0まででは、たとえば"1.1"
の様な文字列の数字を単純にキャストして比較しており、PowerShell 3.0からは"1.1"
といった文字列が整数なのか小数なのかといった部分までを考慮して比較する様になっている感じです。
リリースノートの例を以下に転記しておきます。
# PowerShell 2.0 # ※おそらく単純に[int]型へのキャストをしている PS C:\> 1 -eq "1.1" True # 1kbなどの単位付き文字列は数値変換できず PS C:\> 1024 -eq "1kb" False
# PowerShell 3.0 ~ # 文字列が小数であることが考慮されて比較される PS C:\> 1 -eq "1.1" False # 1kb = 1024 といった内容が考慮して比較される PS C:\> 1024 -eq "1kb" True
5. System.Core.dll への参照
PowerShell 3.0からPSObject
を利用するC#のコードに対してSystem.Core.dll
への参照を追加する必要があります。
PSObject obj = val as PSObject;
6. Add-Typeの挙動
こちらは説明がしにくいのでリリースノートの記述をそのまま引用します。
Add-Member and PSTypeNames now apply to the underlying base object, not to PSObject
要は、PowerShell 3.0からはAdd-Memberの結果はPSObjectに内包されるBaseObjctに付くということなのですが、こちらは実際の例を見てもらう方が早いでしょう。
# PowerShell 2.0では以下の様な場合 $x.Test プロパティは何も返さない PS C:\> $x = @{} PS C:\> $x | Add-Member –MemberType NoteProperty -Name Test -Value 42 PS C:\> $x.Test # 回避策として -PassThru パラメーターの指定が必要 PS C:\> $x = @{} PS C:\> $x | Add-Member –MemberType NoteProperty -Name Test -Value 42 -PassThru PS C:\> $x.Test 42 # PowerShell 3.0からは -PassThru パラメーターなしで $x.Test プロパティが設定される PS C:\> $x = @{} PS C:\> $x | Add-Member –MemberType NoteProperty -Name Test -Value 42 PS C:\> $x.Test 42
7. ストリームの追加による挙動の変化
PowerShell 3.0からWarning output stream(3>)
~Debug output stream(5>)
のストリームが増え演算子となったことで一部のコマンドに対して実行結果が異なる様になりました。
たとえば以下のコードはPowerShell 3.0からはエラーとなります。
# PowerShell 3.0からエラー echo 1> 1>> 3> 3>> 4> 4>> 5> 5>> 6> 6>>
ストリームの細かい話はこちらをご覧ください。
8. スクリプトブロックの IsFilter プロパティ
PowerShell 3.0から無名スクリプトブロックのIsFilter
を設定するとエラーになります。
# PowerShell 3.0からは以下のコードはエラーとなる $sb = {$_*5} $sb.IsFilter = $true $sb2.IsFilter # 回避策としては名前付きスクリプトブロックを使用する {process {$_*5}}
9. foreach文の列挙変数
PowerShell 3.0からはforeach文
の列挙変数は独自のスコープを持たなくなりました。
# PowerShell 2.0では ($n in 'z') の$nが独自のスコープを持つためエラーにならない [int[]]$n = 1,2,3 foreach ($n in 'z') {} # PowerShell 3.0からは [int[]]$n を参照するためエラーとなる [int[]]$n = 1,2,3 foreach ($n in 'z') {}
10. 配列要素のプロパティに対するアクセス
こちらはPowerShell 3.0の新機能でも紹介されているものですが、PowerShell 3.0からは配列の各要素のプロパティに直接アクセスできる様になっています。
# Get-Processの結果は [System.Diagnostics.Process]型 の配列 PS C:\> $ps = Get-Process PS C:\> $ps.Count 243 # PowerShell 3.0からは[System.Diagnostics.Process]型のNameプロパティにアクセスできる PS C:\> $ps.Name ApplicationFrameHost armsvc backgroundTaskHost ・・・(後略)・・・ # これは以下のコードと概ね等価 $ps | ForEach-Object { $_.Name }
11. スカラ値に対するインデックスアクセス
PowerShell 3.0からはスカラ値に対してもインデックスアクセス([0]
)が可能になっています。
PowerShell 2.0では実行時エラーとなります。
# PowerShell 3.0からはスカラ値に対してもインデックスアクセスが可能 PS C:\> $i = 123 PS C:\> $i[0] 123
12. 複数の #requires -version
PowerShell 3.0から複数のバージョンが異なる#requires -version
文が記述されている場合はエラーになります。
#requires -version 2.0 #requires -version 3.0
13. デリゲート代わりに使われるスクリプトブロックのスコープ
PowerShell 3.0からデリゲート代わりに使われるスクリプトブロックが独自のスコープを持つようになっています。
たとえば以下の様な例だとPowerShell 2.0では1
を、PowerShell 3.0からは0
を返す様になります。
# PowerShell 2.0では 1、PowerShell 3.0からは 0 を返す Add-Type @" public class Invoker { public static void Invoke(System.Action<int> func) { func(1); } } "@ $a = 0 [Invoker]::Invoke({$a = 1}) $a