しばたテックブログ

PowerShellを中心に気分で書いている技術ブログです。

PowerShell 3.0での破壊的変更まとめ

以前のエントリで軽く触れた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からこれはエラーとなります。

# OK : PowerShell 2.0
try {} finally { return }

# NG : PowerShell 3.0 ~
try {} finally { return }

f:id:stknohg:20181130131235j:plain

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>>

ストリームの細かい話はこちらをご覧ください。

blog.shibata.tech

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') {}

f:id:stknohg:20181201105500j:plain

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

PowerShellで全角文字を入力すると表示がおかしくなる問題について の補足

本日サポートチームのブログで以下のエントリが公開されました。

blogs.technet.microsoft.com

こちらについて、内容は間違ってはいないものの若干説明が雑だと思ったので本ブログで改めて説明してみます。

発生している問題

サポートブログでの説明にある通り、PowerShellコンソール上で""''で囲まれたPowerShellの文字列に日本語が混じっている際に""''を除去すると文字の描画とキャレット位置が狂ってしまう現象が発生します。

言葉で説明するより以下のGifを見てもらう方がイメージは掴みやすいかと思います。

f:id:stknohg:20181114191648g:plain

原因

こちらもサポートブログの説明の通りPSReadlineの不具合によるものです。

PSReadlineについて

github.com

PSReadlineは元々GNU Readlineに影響を受けて作られたオープンソースのソフトウェアで、PowerShellコンソールに対してシンタックスハイライトやキーバインドのカスタマイズ等といった機能強化をするためのモジュールになります。

このモジュールはWindows 10およびWindows Server 2016からOSに標準搭載される様になっているためサポートブログでの対象OSがWindows 10およびWindows Server 2016となっています。

本件に関連するIssue

PSReadlineはオープンソースでGitHubでIssue管理されています。
本件に関連すると思しきものは以下の2件あり、2015年にはすでに問題が出ていた様です。

最初のIssueはPSReadline 1.0.0.13で報告されており、厳密に対象バージョンを特定できていないのですが、私の知る限りではPSReadline 1.x系は全滅の様です。

そしてIssueを最後まで読むとわかるのですがPSReadline 2.0.0(現在はBeta.3が最新)ではこの問題は解消されています。

対象OS

PSReadlineはWindows 10やWindows Server 2016以外のOSでも追加インストールすることが可能です。
原因がPSReadlineにあるため本現象が発生するのはOSを問わずPSReadline 1.x系を使っている環境全般のハズです。

また、Windows 10以降のOSにおいてはそのバージョンによってインストールされているPSReadlineのバージョンが異なります。

以下にOSバージョンごとにインストールされているPSReadlineを列挙していきます。
この中でPSReadline 1.xを使用している環境が問題になります。

Windows 10

リリース PowerShell PSReadline 対象
Windows 10 初期リリース (1507) 5.0 1.1 ×
Windows 10 November Update (1511) 5.0 1.1 ×
Windows 10 Anniversary Update (1607) 5.1 1.2 ×
Windows 10 Creators Update (1703) 5.1 1.2 ×
Windows 10 Fall Creators Update (1709) 5.1 1.2 ×
Windows 10 April 2018 Update (1803) 5.1 1.2 ×
Windows 10 October 2018 Update (1809) 5.1 2.0.0-beta.2

Windows Server

リリース PowerShell PSReadline 対象
Windows Server 2016 5.1 1.2 ×
Windows Server 2019 5.1 2.0.0-beta.2

【補足】PowerShell Core

こちらは補足ですがPowerShell Coreに同梱されているPSReadlineは以下の通りとなります。

バージョン PSReadline 対象
6.0.x 1.2 ×
6.1.0 2.0.0-beta.3

対処方法

対処方法は正確には2つ存在します。

1. PSReadlineを使用しない

こちらはサポートブログにある通りの対処方法です。
Remove-Moduleを使えばモジュールをアンロードできますのでこの不具合も起きなくなります。

Remove-Module PSReadline

ただし、この変更は永続化されませんので変更を永続化したいときは上記コードをプロファイルに仕込むと良いでしょう。

2. PSReadline 2.0.0をインストールする

もう一つの対処方法です。
原因はPSReadline 1.xにあるのでPSReadlineのバージョンを上げてしまえば問題を回避できます。

ただし、最新OSで既に導入済みであるもののPSReadline 2.0.0はまだベータ版です。
ベータ版のモジュールを追加でインストールするためにはモジュールの管理基盤であるPowerShellGetの更新も同時に必要であり、この対処を行うのは少し敷居が高いかもしれません。

本ブログの

blog.shibata.tech

でPowerShellGetとPSReadlineの更新方法を記載していますので詳しい手順についてはこちらをご覧ください。

【補足】PSReadline 2.0.0の注意点

本件とは直接関係しないのですが、Windows 10以前のOSにPSReadline 2.0.0をインストールする場合は以下の問題がありますので注意してください。

blog.shibata.tech

PowerShell Core 6.1.0で日本語が正しく表示されない現象について

事の発端はteratailのこちらの質問から。

teratail.com

Windows 10以前の日本語Windows環境にPowerShell Core 6.1.0をインストールするとコンソールの日本語表示がおかしくなるという現象です。

f:id:stknohg:20181113191609j:plain

影響のある環境

きちんと確認できたのは質問にあるWindows 8.1環境だけですが、おそらく、

  • Windows 7
  • Windows Server 2008 R2 ~ Windows Server 2012 R2

でも同じ現象が発生するハズです。

原因

直接の原因はPowerShell Core 6.1.0に同梱されているPSReadlineです。

PowerShell Core 6.1.0から同梱されるPSReadlineのバージョンが1.2→2.0.0-bata.3に上がり、PSReadline 2.0.0-bata.1~2.0.0-bata.3においてはコンソールのコードページをUTF-8(65001)に強制するコードが入ってており、Windows 10以前の古いOSでは日本語の描画がおかしくなってしまいます。

f:id:stknohg:20181113192103j:plain

問題がPowerShell本体ではなくPSReadline側にあるので、Windows PowerShellにPSReadline 2.0.0をインストールしても同様の現象が発生してしまいます。

f:id:stknohg:20181113193107j:plain

(Windows PowerShellにPSReadline 2.0.0をインストールする手順は割愛)

対処方法

この問題に対する対処方法は2つあります。

1. PSReadline 1.2を使用する

PSReadline 2.0.0が問題なのでひとつ前のバージョンを利用する様にすれば現象を回避することができます。

次のコマンドでPSReadline 1.2を追加インストールしてからPowerShell Coreを再起動するとPSReadline 1.2を先にロードする様になりこの問題を回避できます。

# PSReadline 1.2を追加インストール後、コンソールを再起動
Install-Module PSReadLine -Scope CurrentUser -RequiredVersion 1.2 -Force -SkipPublisherCheck

f:id:stknohg:20181113193829j:plain

2. 最新ビルドのPSReadline 2.0.0を使用する

PSReadlineの最新ビルドではコードページの強制に対して以下の修正が入っています。

github.com

この修正自体は別件に対する対応と思われるのですが、コードページの変更を強制しなくなるため本件の対応としても有用です。

ただし、この修正が適用されたPSReadlineはまだリリースされていません。
(おそらくPSReadline 2.0.0-beta.4に含まれるでしょう...)
このため手っ取り早い方法としてはPSReadineの最新ビルドをインストールしてやるのが良いでしょう。

以下にざっくりとですが手順を記載しておきます。

  1. AppVeyor上の最新ビルドから最新のビルド(Zipファイル)をダウンロードします。
    f:id:stknohg:20181113200234j:plain

  2. Zipファイルを展開し、展開した内容を[マイドキュメント]\PowerShell\Modules\PSReadLine\2.0.0に保存します。
    f:id:stknohg:20181113200729j:plain
    (上図の構成になる様にしてください)

これで最新のPSReadlineがロードされ問題を回避することができます。

f:id:stknohg:20181113201010j:plain