PowerShell スクリプトのエラー処理について

Windows PowerShell 5.1 で確認。

## 構文エラー

パース段階でエラーが起きた場合、スクリプト全体が実行されない。例えば次のスクリプトを実行しようとしたとき、「###Start###」も「###End###」も表示されない。後述の `trap` ブロックや `catch` ブロックがあっても実行されない。

```powershell:error_test.ps1
Write-Output "###Start###"
$syntaxError = (1/)
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
発生場所 C:\error_test.ps1:2 文字:19
+ $syntaxError = (1/)
+                   ~
'/' 演算子に続けて値の式を指定する必要があります。
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : ExpectedValueExpression

```

## 実行時エラー

### ErrorAction

デフォルトでは、実行時エラーが起きてもスクリプトの実行が継続する。これは、`ErrorAction` パラメーターのデフォルト値が `Continue` であるため。

```powershell:error_test.ps1
Write-Output "###Start###"
$divideByZero = (1/0)
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
0 で除算しようとしました。
発生場所 C:\error_test.ps1:2 文字:1
+ $divideByZero = (1/0)
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

###End###
```

`ErrorAction` パラメーターの値を `Stop` にすると、エラーが起きた時点でスクリプトが停止する。

```powershell:error_test.ps1
$ErrorActionPreference = "Stop"
Write-Output "###Start###"
$divideByZero = (1/0)
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
0 で除算しようとしました。
発生場所 C:\error_test.ps1:3 文字:1
+ $divideByZero = (1/0)
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : RuntimeException

```

「###End###」が表示されなくなった。例外のクラスが `RuntimeException` から `ParentContainsErrorRecordException` に変わっているが、なぜかは私にはわからない。

個々のコマンドレットで発生する実行時エラーに対しては、コマンドレットごとに `ErrorAction` の値を設定することもできる。

```powershell:error_test.ps1
Write-Output "###Start###"
Get-ChildItem "NotExist" -ErrorAction "Stop"
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:2 文字:1
+ Get-ChildItem "NotExist" -ErrorAction "Stop"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

```

### Trap

スクリプトの任意の位置に `trap` ブロックを置くと、エラーが起きたときに、`trap` ブロックの中身が実行されたのちエラーメッセージが出力される。`trap` ブロックの中では、エラーの内容を `$_` で参照できる。

```powershell:error_test.ps1
Write-Output "###Start###"
$divideByZero = (1/0)
Write-Output "###End###"
trap {
  Write-Output "###Trap start###"
  Write-Output $_
  Write-Output "###Trap end###"
}
```
```
PS C:\> .\error_test.ps1
###Start###
###Trap start###
0 で除算しようとしました。
発生場所 C:\error_test.ps1:2 文字:1
+ $divideByZero = (1/0)
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], DivideByZeroException
    + FullyQualifiedErrorId : RuntimeException

###Trap end###
0 で除算しようとしました。
発生場所 C:\error_test.ps1:2 文字:1
+ $divideByZero = (1/0)
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

###End###
```

`ErrorAction` の値が `Continue` なので「###End###」が表示されている。`trap` で捕捉された例外とエラーメッセージに表示される例外が異なるが、なぜかは私にはわからない。

デフォルトでは、つまり `ErrorAction` の値が `Continue` の場合、個々のコマンドレットで発生する実行時エラーは捕捉されない。

```powershell:error_test.ps1
Write-Output "###Start###"
Get-ChildItem "NotExist"
Write-Output "###End###"
trap {
  Write-Output "###Trap start###"
  Write-Output $_
  Write-Output "###Trap end###"
}
```
```
PS C:\> .\error_test.ps1
###Start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:2 文字:1
+ Get-ChildItem "NotExist"
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###End###
```

個々のコマンドレットでの実行時エラーを捕捉するには、そのコマンドレットで `ErrorAction` の値を `Stop` にする。この場合も、スクリプトの実行はエラーが起きた後も継続する。

```powershell:error_test.ps1
Write-Output "###Start###"
Get-ChildItem "NotExist" -ErrorAction "Stop"
Write-Output "###End###"
trap {
  Write-Output "###Trap start###"
  Write-Output $_
  Write-Output "###Trap end###"
}
```
```
PS C:\> .\error_test.ps1
###Start###
###Trap start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:2 文字:1
+ Get-ChildItem "NotExist" -ErrorAction "Stop"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Trap end###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:2 文字:1
+ Get-ChildItem "NotExist" -ErrorAction "Stop"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###End###
```

スクリプト全体で `ErrorAction` の値を `Stop` にすると、エラーを捕捉できるだけでなく、エラーが起きたときにスクリプトの実行が止まる。

```powershell:error_test.ps1
$ErrorActionPreference = "Stop"
Write-Output "###Start###"
Get-ChildItem "NotExist"
Write-Output "###End###"
trap {
  Write-Output "###Trap start###"
  Write-Output $_
  Write-Output "###Trap end###"
}
```
```
PS C:\> .\error_test.ps1
###Start###
###Trap start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:3 文字:1
+ Get-ChildItem "NotExist"
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Trap end###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:3 文字:1
+ Get-ChildItem "NotExist"
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

```

### Try Catch Finally

`try` ブロックの中で実行時エラーが起きると、処理が `catch` ブロックに移る。`trap` と同様、`catch` ブロックの中でもエラーの内容を `$_` で参照できる。ただし、`trap` と異なり、エラーメッセージは出力されない。

```powershell:error_test.ps1
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  $divideByZero = (1/0)
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
###Catch start###
0 で除算しようとしました。
発生場所 C:\error_test.ps1:4 文字:3
+   $divideByZero = (1/0)
+   ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

###Catch end###
###End###
```

`trap` と同様、個々のコマンドレットで起きる実行エラーは捕捉されず、エラーメッセージが出力される。

```powershell:error_test.ps1
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  Get-ChildItem "NotExist"
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:4 文字:3
+   Get-ChildItem "NotExist"
+   ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Try end###
###End###
```

`ErrorAction` の値を `Stop` にすると、エラーを捕捉できるようになる。

```powershell:error_test.ps1
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  Get-ChildItem "NotExist" -ErrorAction "Stop"
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
###Catch start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:4 文字:3
+   Get-ChildItem "NotExist" -ErrorAction "Stop"
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Catch end###
###End###
```

スクリプト全体で `ErrorAction` の値を `Stop` にしていても、`try` ブロックの中でエラーが起きた場合、`catch` ブロックの後にあるスクリプトが実行される。

```powershell:error_test.ps1
$ErrorActionPreference = "Stop"
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  Get-ChildItem "NotExist"
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
###Catch start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:5 文字:3
+   Get-ChildItem "NotExist" -ErrorAction "Stop"
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Catch end###
###End###
```

エラーが起きた後にスクリプトの実行を止めたい場合、`catch` ブロックの末尾に `break` を置く。

```powershell:error_test.ps1
$ErrorActionPreference = "Stop"
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  Get-ChildItem "NotExist"
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
  break
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
###Catch start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:5 文字:3
+   Get-ChildItem "NotExist"
+   ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Catch end###
```

エラーが起きても起きなくても実行するスクリプトがある場合は、`finally` ブロックに記述する。

```powershell:error_test.ps1
$ErrorActionPreference = "Stop"
Write-Output "###Start###"
try {
  Write-Output "###Try start###"
  Get-ChildItem "NotExist"
  Write-Output "###Try end###"
} catch {
  Write-Output "###Catch start###"
  Write-Output $_
  Write-Output "###Catch end###"
  break
} finally {
  Write-Output "###Finally###"
}
Write-Output "###End###"
```
```
PS C:\> .\error_test.ps1
###Start###
###Try start###
###Catch start###
Get-ChildItem : パス 'C:\NotExist' が存在しないため検出できません。
発生場所 C:\error_test.ps1:5 文字:3
+   Get-ChildItem "NotExist"
+   ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\NotExist:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

###Catch end###
###Finally###
```

## 外部ツールのエラー

PowerShell スクリプトから呼び出した外部ツールでエラーが起きた場合、PowerShell スクリプトではエラーを捕捉できない。`try` ブロック内で結果を判定して、期待した結果でなければ `throw` すると、その例外を捕捉できる。外部プロセスの結果は `$LastExitCode` に書き込まれるので、この値を見て結果を判定できる。具体的な方法は外部ツールによって異なる。