しばたテックブログ

気分で書いている技術ブログです。

Set-StrictModeについてちょっとだけ詳しく説明してみる

PowerShellの基本的なことがわかってなかったシリーズ第7弾です。
基本的にはヘルプを見ればわかる話ですが、ヘルプではわかりにくかった部分があったので補足する形でこのエントリを書きます。

Set-StrictModeについて

Get-Help Set-StrictMode -Fullでこのコマンドレットのヘルプを見ると、

式、スクリプト、およびスクリプト ブロックのコーディング規則を設定して適用します。

と書いてあります。
なんだか微妙な説明ですが、要はPowerShellスクリプト構文のエラーチェックをより厳格にする機能になります。

Set-StrictMode -Versionで指定したバージョンに応じた規則を追加し、Set-StrictMode -Offで規則を解除できます。
規則が適用されるのはSet-StrictModeを実行したスコープになります。

類似の機能にVBScriptのOption ExplicitなんかがあったりしますがSet-StrictModeはどちらかというとデバッグ用の側面が強いみたいです。

Set-StrictMode -Versionのバージョン指定について

-Versionパラメーターで指定できるのは1.02.0Latestなのですが、引数のチェック自体はPowerShellのバージョンと対応する様になっている様で3.0~5.0の値も環境に応じて指定することができます。
ただ、2.0以上のバージョンで規則は増えていないため2.0=3.0~5.0です。(追記あり)

また、Latest選んだ場合は実行しているPowerShellのバージョンと同じ値が設定される様です。

ここで一つだけ注意しておきたいのは、-Versionパラメーターに指定できる値はPowerShellのバージョンと関連のある値ですが、設定するのはあくまでも規則の厳格さに過ぎず、追加される規則はPowerShellのバージョンと連動するものでは無いということです。
例えばpowershell.exeの起動時引数の-Versionパラメーターみたいな効果は無いということです。

正直このパラメーター名は-Versionより-Levelとかの方が適切だと思います。

-- 2015/11/21追記 --

2.0以上のバージョンで規則は増えていないため2.0=3.0~5.0です。

について、公式には上記が謳われているのですがドキュメント化されていない規則が3.0にある様です。

Set-StrictMode -Version 1.0で追加される規則

ここから-Version毎に追加される規則ついて触れていきます。
ヘルプを見れば十分な部分もありますが、本エントリではちょっとした補足もありますので順に説明していきます。

Set-StrictMode -Version 1.0を実行すると以下の規則が追加されます。

1. 初期化されていない変数 (文字列の初期化されていない変数を除く) への参照を禁止します。

これはわかりやすいと思います。
$a = 123の様に変数定義時に初期化しないとエラーになる規則が追加されます。

Set-StrictMode -Offだと問題ないコード

PS C:\> Set-StrictMode -Off
PS C:\> $a
PS C:\>

が、Set-StrictMode -Version 1.0にすると以下の様にエラーになります。

PS C:\> Set-StrictMode -Version 1.0
PS C:\> $a
変数 '$a' は、設定されていないために取得できません。
発生場所 行:1 文字:1
+ $a
+ ~~
    + CategoryInfo          : InvalidOperation: (a:String) []、RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

ここで "文字列の初期化されていない変数を除く" というのは、文字列中の変数定義("$a"みたいな使い方)に対しては初期化されていない変数を使ってもエラーとはならないという事を指しています。

# 変数$bは未定義
# 文字列中の変数定義は初期化されていなくてもエラーにはならない
PS C:\> Set-StrictMode -Version 1.0
PS C:\> "$b"

PS C:\> 

Set-StrictMode -Version 2.0で追加される規則

続けてSet-StrictMode -Version 2.0を実行すると以下の規則が追加されます。

1. 初期化されていない変数 (文字列の初期化されていない変数を含む) への参照を禁止します。

これは-Version 1.0の規則と同等ですが、 "文字列の初期化されていない変数を含む" とある様に、文字列中の変数定義でも初期化されていない変数が使われるとエラーとなります。

# 変数$cは未定義
# 文字列中の変数定義も初期化されていない場合はエラーとなる
PS C:\> Set-StrictMode -Version 2.0
PS C:\> "$c"
変数 '$c' は、設定されていないために取得できません。
発生場所 行:1 文字:2
+ "$c"
+  ~~
    + CategoryInfo          : InvalidOperation: (c:String) []、RuntimeException
    + FullyQualifiedErrorId : VariableIsUndefined

2. オブジェクトに存在しないプロパティへの参照を禁止します。

これもわかりやすいと思います。
Set-StrictMode -Offだと存在しないプロパティへのアクセスは$nullを返して終わりますが、Set-StrictMode -Version 2.0ではエラーとなります。

PS C:\> Set-StrictMode -Version 2.0
PS C:\> (123).ErrorProperty
このオブジェクトにプロパティ 'ErrorProperty' が見つかりません。プロパティが存在することを確認してください。
発生場所 行:1 文字:1
+ (123).ErrorProperty
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], PropertyNotFoundException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

また、

stknohg.hatenablog.jp

のエントリで記載した非配列オブジェクトに対するCountプロパティもこの場合はエラーとなります。

PS C:\> Set-StrictMode -Version 2.0
PS C:\> (123).Count
このオブジェクトにプロパティ 'Count' が見つかりません。プロパティが存在することを確認してください。
発生場所 行:1 文字:1
+ (123).Count
+ ~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], PropertyNotFoundException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

3. メソッドを呼び出すための構文を使用した関数の呼び出しを禁止します。

これはちょっとわかりにくいのですが、ヘルプにあるサンプルを見ると理解しやすいと思います。

function add ($a, $b) {$a + $b}

というシンプルなファンクションがあった場合、通常はadd 3 4の様に引数をスペース区切りで使用しますが、間違ってadd(3,4)と記述してもエラーとならず意図しない動作になってしまいます。

# 意図した動作
PS C:\> add 3 4
7

# 意図しない動作
# add (3,4) $null => (3,4) + $null と解釈されてしまう。
PS C:\> add(3,4)
3
4

Set-StrictMode -Version 2.0にすると上記の様な()を使ったファンクション呼び出しがエラーとなります。

PS C:\> Set-StrictMode -Version 2.0
PS C:\> add(3,4)
関数またはコマンドが、メソッドのように呼び出されました。パラメーターは、スペースで区切られる必要があります。パラメータ
ーの詳細については、about_Parameters ヘルプ トピックを参照してください。
発生場所 行:1 文字:1
+ add(3,4)
+ ~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
    + FullyQualifiedErrorId : StrictModeFunctionCallWithParens

ただ、残念なことにメソッド名の直後に(があるかどうかだけで判断している様で以下の様な場合はエラーになりません。

# addの直後にスペースがあるだけでこの制約は動作しない
# まあ当然といえば当然ですが...
PS C:\> Set-StrictMode -Version 2.0
PS C:\> add (3,4)
3
4

個人的にはこの様な動作を防止するにはfunction add ([int]$a, [int]$b) {$a + $b}の様に引数にきちんと型指定をする方が良いと思います。

4. 名前のない変数 (${}) を禁止します。

これはヘルプの日本語が足りなくて*1暫く理解できなかったのですが、文字列中の変数定義の話になります。

PowerShellでは${a}の様に{}でくくる形式でも変数名を定義できます。
この形式は主に文字列内での変数定義に使われると思います。

# 変数定義を明確にするために{}でくくる
PS C:\> $a=123
PS C:\> "${a}45"
12345

PowerShell 2.0までは文字列中の変数定義に"${}"と書いてもエラーになりませんでした。

# PowerShell 2.0
PS C:\> "${}"
$

Set-StrictMode -Version 2.0にするとこれがエラーとなります。

# PowerShell 2.0
PS C:\> Set-StrictMode -Version 2.0
PS C:\> "${}"
中かっこで囲まれた変数名は、空にできません。
発生場所 行:1 文字:6
+ "${}" <<<<
    + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
    + FullyQualifiedErrorId : EmptyBracedVariableName

ただ、この仕様はPowerShell 3.0からSet-StrictMode -Offでもエラーとなる様に変更されています。

# PowerShell 3.0以降
PS C:\> Set-StrictMode -Off
PS C:\> "${}"
発生場所 行:1 文字:4
+ "${}"
+    ~
空の ${} 変数参照が見つかりました。中かっこ内に名前が必要です。
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : EmptyVariableReference

この規則は今となってはあまり意味を成さない感じになっています。

2015/11/21追記 Set-StrictMode -Version 3.0で追加される規則

公式にドキュメント化はされていないのですが、Set-StrictMode -Version 3.0で以下の規則が追加されている様です。
今のところ1つだけわかりましたが他にもあるかもしれません。

【2018/04/18追記】

現時点でPowerShell Coreのソースを検索した限りではSet-StrictMode -Version 3.0の規則はこの1つだけでした。
あと、ちょっと前のはなしですがドキュメントが無い旨をIssueに上げておきました。

github.com

【追記ここまで】

1. 配列の境界チェックの追加

http://blogs.technet.com/b/heyscriptingguy/archive/2015/07/15/use-powershell-strict-mode-for-debugging.aspxblogs.technet.com

のコメント欄にこの規則に対する指摘がされています。

-Version 2.0までは配列の境界外のアクセスは$nullを返すだけですが-Version 3.0にすると配列の境界外へのアクセスがエラーになります。

# -Version 2.0 までは配列の境界外へのアクセスは $null を返すだけ
PS C:\> Set-StrictMode -Version 2.0
PS C:\> @(1,2,3)[2]
3
PS C:\> @(1,2,3)[3]

# -Version 3.0 からは配列の境界外へのアクセスはエラーとなる
PS C:\> Set-StrictMode -Version 3.0
PS C:\> @(1,2,3)[2]
3
PS C:\> @(1,2,3)[3]
インデックスが配列の境界外です。
発生場所 行:1 文字:3
+ @(1,2,3)[3]
+   ~~~~~
    + CategoryInfo          : OperationStopped: (:) [], IndexOutOfRangeException
    + FullyQualifiedErrorId : System.IndexOutOfRangeException

# PowerShellでは負数のインデックス指定ができるがこれもエラーとなる。
PS C:\> @(1,2,3)[-3]
1
PS C:\> @(1,2,3)[-4]
インデックスが配列の境界外です。
発生場所 行:1 文字:3
+ @(1,2,3)[-4]
+   ~~~~~
    + CategoryInfo          : OperationStopped: (:) [], IndexOutOfRangeException
    + FullyQualifiedErrorId : System.IndexOutOfRangeException

日本語で出せとまでは言いませんが規則が増えたのなら公式にドキュメント化してほしいものです...

Get-StrictModeについて

Set-StrictModeがあるならGet-StrictModeもありそうなのですが、標準には存在せず、ScriptCenterに有志が作ったものがある程度となっています。

gallery.technet.microsoft.com

スクリプトの中身を見るとリフレクションを使って現在のスコープオブジェクトの非パブリックなプロパティからバージョンを取得しておりなかなか楽しい感じです。

実行例は以下の様な感じになります。

PS C:\> Set-StrictMode -Version 1.0
PS C:\> Get-StrictMode

Major  Minor  Build  Revision
-----  -----  -----  --------
1      0      -1     -1

PS C:\> Set-StrictMode -Version 2.0
PS C:\> Get-StrictMode

Major  Minor  Build  Revision
-----  -----  -----  --------
2      0      -1     -1

# PowerShell 4.0 の場合
PS C:\> Set-StrictMode -Version Latest
PS C:\> Get-StrictMode

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

PS C:\> Set-StrictMode -Off
PS C:\> Get-StrictMode

Major  Minor  Build  Revision
-----  -----  -----  --------
0      0      -1     -1

*1:英語の原文も足りてない