以前のエントリで軽く触れたPowerShell 3.0における破壊的変更について一覧をまとめてみました。
blog.shibata.tech
元ネタはこちら(docx)。
はじめに
blogs.msdn.microsoft.com
去年アナウンスされましたが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からこれはエラーとなります。
try {} finally { return }
try {} finally { return }
finallyブロックを抜けた後でreturn
文を実行することでエラーを回避できます。
try {
}
finally {
if ($cond) {
$retval = 1
$hasretval = $true
}
}
if ($hasretval) {
return $retval
}
2. 実行アセンブリ
PowerShell 2.0までのPowerShell.exeの実行アセンブリはSystem.Management.Automation.dll
でしたが、これがPowerShell 3.0からは内部生成される動的なアセンブリに変更されます。
PS C:\> [Reflection.Assembly]::GetExecutingAssembly() | Select-Object FullName
FullName
--------
System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
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 = 5
& { $x += 1; echo $x }
PS C:\> & { $x += 1; echo $x }
6
PS C:\> & { $x += 1; echo $x }
1
4. 数値と文字列の比較
ちょっと細かい仕様まで調べきることが出来なかったのですが、PowerShell 3.0からは数値の文字列の等値比較の結果が異なります。
PowerShell 2.0まででは、たとえば"1.1"
の様な文字列の数字を単純にキャストして比較しており、PowerShell 3.0からは"1.1"
といった文字列が整数なのか小数なのかといった部分までを考慮して比較する様になっている感じです。
リリースノートの例を以下に転記しておきます。
PS C:\> 1 -eq "1.1"
True
PS C:\> 1024 -eq "1kb"
False
PS C:\> 1 -eq "1.1"
False
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に付くということなのですが、こちらは実際の例を見てもらう方が早いでしょう。
PS C:\> $x = @{}
PS C:\> $x | Add-Member –MemberType NoteProperty -Name Test -Value 42
PS C:\> $x.Test
PS C:\> $x = @{}
PS C:\> $x | Add-Member –MemberType NoteProperty -Name Test -Value 42 -PassThru
PS C:\> $x.Test
42
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からはエラーとなります。
echo 1> 1>> 3> 3>> 4> 4>> 5> 5>> 6> 6>>
ストリームの細かい話はこちらをご覧ください。
blog.shibata.tech
8. スクリプトブロックの IsFilter プロパティ
PowerShell 3.0から無名スクリプトブロックのIsFilter
を設定するとエラーになります。
$sb = {$_*5}
$sb.IsFilter = $true
$sb2.IsFilter
{process {$_*5}}
9. foreach文の列挙変数
PowerShell 3.0からはforeach文
の列挙変数は独自のスコープを持たなくなりました。
[int[]]$n = 1,2,3
foreach ($n in 'z') {}
[int[]]$n = 1,2,3
foreach ($n in 'z') {}
10. 配列要素のプロパティに対するアクセス
こちらはPowerShell 3.0の新機能でも紹介されているものですが、PowerShell 3.0からは配列の各要素のプロパティに直接アクセスできる様になっています。
PS C:\> $ps = Get-Process
PS C:\> $ps.Count
243
PS C:\> $ps.Name
ApplicationFrameHost
armsvc
backgroundTaskHost
・・・(後略)・・・
$ps | ForEach-Object { $_.Name }
11. スカラ値に対するインデックスアクセス
PowerShell 3.0からはスカラ値に対してもインデックスアクセス([0]
)が可能になっています。
PowerShell 2.0では実行時エラーとなります。
PS C:\> $i = 123
PS C:\> $i[0]
123
12. 複数の #requires -version
PowerShell 3.0から複数のバージョンが異なる#requires -version
文が記述されている場合はエラーになります。
13. デリゲート代わりに使われるスクリプトブロックのスコープ
PowerShell 3.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