PowerShellでのエラーハンドリングについて

メモ。
違っている部分があればご指摘ください。

基本的には以下のMSの記事を参考に検証しているもで、こちらを確認頂いても良いかと思います。

[An Introduction to Error Handling in PowerShell](https://blogs.msdn.microsoft.com/kebab/2013/06/09/an-introduction-to-error-handling-in-powershell/)

# 環境

+ PSVersion->5.0.10586.117

# エラーメッセージをファイルに書き込みたい

リダイレクトについては以下に詳細がありました。

+ [How to capture error messages thrown by a command?](http://stackoverflow.com/questions/17420474/how-to-capture-error-messages-thrown-by-a-command)
+ [about_Redirection](https://technet.microsoft.com/en-us/library/hh847746.aspx)

上記にもあるようにPowerShellではリダイレクト時に利用できるstreamが複数あります。

>      The Windows PowerShell redirection operators use the following characters 
      to represent each output type:
        *   All output
        1   Success output
        2   Errors
        3   Warning messages
        4   Verbose output
        5   Debug messages
        6   Informational messages 

エラーは2です。
上記より、エラーをファイルに書き込む場合には以下のようにします。

```ps1
command 2>error.txt
```

Bashなどでよく使うように標準出力及びエラーをファイルに追記する場合には以下のようにします。

```ps1
command 2>&1 >> result.txt
```

実際にやってみます。

```ps1
# 存在しないファイルを確認
PS C:\Users\Administrator> ls poyo.txt 2>>result.txt
PS C:\Users\Administrator> ls poyo.txt 2>>result.txt

# catコマンドをファイルにエラーメッセージが書き込まれるかを確認
PS C:\Users\Administrator> cat .\result.txt
ls : Cannot find path 'C:\Users\Administrator\poyo.txt' because it does not exist.
At line:1 char:1
+ ls poyo.txt 2>>result.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\Administrator\poyo.txt:String) [Get-ChildItem], ItemNotFoundEx
   ception
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

ls : Cannot find path 'C:\Users\Administrator\poyo.txt' because it does not exist.
At line:1 char:1
+ ls poyo.txt 2>>result.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\Administrator\poyo.txt:String) [Get-ChildItem], ItemNotFoundEx
   ception
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
```

次にPowerShellコマンドを作成して、標準出力と標準エラー出力の確認をしてみます。

```ps1
# 空のファイルを作成
PS C:\Users\Administrator> New-Item errorTest.ps1 -type file


    Directory: C:\Users\Administrator


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        10/5/2016   1:15 AM              0 errorTest.ps1

# ファイルを開く
PS C:\Users\Administrator> Invoke-Item .\errorTest.ps1
```

```test.ps1
Get-Date
ls poyo.txt
```

実行してみます。

```ps1
# 実行。標準出力と標準エラー出力をresult.txtに追記
PS C:\Users\Administrator> powershell.exe .\errorTest.ps1 2>&1 >> result.txt

# 確認。標準出力及び標準エラー出力が確認できる
PS C:\Users\Administrator> cat .\result.txt

Wednesday, October 5, 2016 1:19:28 AM
powershell.exe : ls : Cannot find path 'C:\Users\Administrator\poyo.txt' because it does not exist.
At line:1 char:1
+ powershell.exe .\errorTest.ps1 2>&1 >> result.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ls : Cannot fin...does not exist.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

At C:\Users\Administrator\errorTest.ps1:2 char:1

+ ls poyo.txt

+ ~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (C:\Users\Administrator\poyo.txt:String) [Get-ChildItem], ItemNotFoundEx

   ception

    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

```

# $error変数について

[An Introduction to Error Handling in PowerShell](https://blogs.msdn.microsoft.com/kebab/2013/06/09/an-introduction-to-error-handling-in-powershell/)

PowerShellでエラーが発生した場合、$errorというArrayListに情報が追加されます。
直近のエラーは$error[0]に格納されます。

```ps1
# 存在しないコマンドを実行
PS C:\Users\Administrator> hogefuga
hogefuga : The term 'hogefuga' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ hogefuga
+ ~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (hogefuga:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

# 最新のエラー情報を確認
PS C:\Users\Administrator> $error[0]
hogefuga : The term 'hogefuga' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ hogefuga
+ ~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (hogefuga:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
```

$errorのArryListのそれぞれはErrorRecordになっています。
get-member(Alias gm)でメンバー情報を確認してみます。

```ps1
PS C:\Users\Administrator> $error[0] | gm


   TypeName: System.Management.Automation.ErrorRecord

Name                  MemberType     Definition
----                  ----------     ----------
Equals                Method         bool Equals(System.Object obj)
GetHashCode           Method         int GetHashCode()
GetObjectData         Method         void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System....
GetType               Method         type GetType()
ToString              Method         string ToString()
CategoryInfo          Property       System.Management.Automation.ErrorCategoryInfo CategoryInfo {get;}
ErrorDetails          Property       System.Management.Automation.ErrorDetails ErrorDetails {get;set;}
Exception             Property       System.Exception Exception {get;}
FullyQualifiedErrorId Property       string FullyQualifiedErrorId {get;}
InvocationInfo        Property       System.Management.Automation.InvocationInfo InvocationInfo {get;}
PipelineIterationInfo Property       System.Collections.ObjectModel.ReadOnlyCollection[int] PipelineIterationInfo {g...
ScriptStackTrace      Property       string ScriptStackTrace {get;}
TargetObject          Property       System.Object TargetObject {get;}
PSMessageDetails      ScriptProperty System.Object PSMessageDetails {get=& { Set-StrictMode -Version 1; $this.Except...

```

ErrorRecordの詳細はこちら

[ErrorRecord Members](https://msdn.microsoft.com/en-us/library/system.management.automation.errorrecord_members%28v=vs.85%29.aspx)

個別に取得したい場合には`Exception`や`InvocationInfo`を使っても良いでしょうし、とりあえず全部出力したい場合には`Out-String`を使って画面の出力を文字列にして表示しても良いと思われます。
上記は以下のStackOverflowの記事が参考になりました。

[How can I get powershell exception descriptions into a string?](http://stackoverflow.com/questions/3404642/how-can-i-get-powershell-exception-descriptions-into-a-string)

```ps1
# Out-Stringで文字列を取得
PS C:\Users\Administrator> $error[0] | Out-String
hogefuga : The term 'hogefuga' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ hogefuga
+ ~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (hogefuga:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

# Exception
PS C:\Users\Administrator> $error[0].Exception
The term 'hogefuga' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.

# InvocationInfo
PS C:\Users\Administrator> $error[0].InvocationInfo


MyCommand             :
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 1
OffsetInLine          : 1
HistoryId             : 3
ScriptName            :
Line                  : hogefuga
PositionMessage       : At line:1 char:1
                        + hogefuga
                        + ~~~~~~~~
PSScriptRoot          :
PSCommandPath         :
InvocationName        : hogefuga
PipelineLength        : 0
PipelinePosition      : 0
ExpectingInput        : False
CommandOrigin         : Internal
DisplayScriptPosition :

```

# Error Action Preferenceについて

[An Introduction to Error Handling in PowerShell](https://blogs.msdn.microsoft.com/kebab/2013/06/09/an-introduction-to-error-handling-in-powershell/)

[about_Preference_Variables](https://technet.microsoft.com/ja-jp/library/hh847796.aspx)

PowerShellのスクリプトででエラーが発生した場合にそのまま処理を継続したり、終了させたりすることができます。
設定できるものとして以下があり、デフォルトはエラーが発生してもエラーを出力し、そのままスクリプトを継続するようになっています。


+ Stop:エラー メッセージが表示され、実行が停止されます。
+ Inquire:エラー メッセージが表示され、続行するかどうかを確認するメッセージが表示されます。
+ Continue (既定値):エラー メッセージが表示され、実行が続行されます。
+ Suspend:さらなる調査のため、ワークフロー ジョブを自動的に中断します。調査の後で、ワークフローを再開できます。
+ SilentlyContinue:影響を与えません。エラー メッセージは表示されず、中断されることなく実行が続行されます。

```ps1
# 同じセッションの場合の挙動を変更。PowerShellを別途起動した場合にはデフォルト値に戻る
PS C:\Users\Administrator> $ErrorActionPreference
Continue
PS C:\Users\Administrator> $ErrorActionPreference = "Stop"
PS C:\Users\Administrator> $ErrorActionPreference
Stop
```

スクリプト内でも上記設定を変更することで挙動の変更が可能です。

以下を実行すると2行目でエラーとなるため、stopし、`Get-Date`は実行されません。

```stop.ps1
$ErrorActionPreference = "Stop"
ls poyo.txt
Get-Date
```

こちらは$ErrorActionPreferenceの値がデフォルトのContinueのため、エラーが発生しても継続して処理を行うため`Get-Date`が実行されます。

```continue.ps1
ls poyo.txt
Get-Date
```

なお、実行するコマンドレット単位でも-ErrorActionオプションを付与することでコマンドごとの挙動を変更することも可能です。

こちらの記事も読んでおくと良い。

[Understanding Non-Terminating Errors in PowerShell](https://devblogs.microsoft.com/scripting/understanding-non-terminating-errors-in-powershell/)


# Try/Catch/Finally ブロックについて

[An Introduction to Error Handling in PowerShell](https://blogs.msdn.microsoft.com/kebab/2013/06/09/an-introduction-to-error-handling-in-powershell/)

[PowerShell の コマンドレット例外を取得する](http://tech.guitarrapc.com/entry/2013/07/22/000738)

Javaとかにもあるtry/catch/finallyとかがPowerShellのスクリプトでも書けて、Exception発生時の処理をcatch構文に書いておくことでエラー時の処理を書けるよというような話。’
同じようにfinallyでconnectionのクローズをするとかもできそうです。

なお、リンクにもあるようにコマンドレットの例外を補足するには対象のコマンドレットで`-ErrorAction stop`オプションを指定する必要があります。

例えば以下ではcatch以降は呼びされません。

```ngtrycatch.ps1
try{
  ls poyo.txt
} catch {
  Write-Output "catch error"
}
```

```ps1
PS C:\Users\Administrator> powershell.exe .\ngtrycatch.ps1
ls : Cannot find path 'C:\Users\Administrator\poyo.txt' because it does not exist.
At C:\Users\Administrator\trycatch.ps1:2 char:3
+   ls poyo.txt
+   ~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\Administrator\poyo.txt:String) [Get-ChildItem], ItemNotFoundEx
   ception
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
```

以下のようにすることでエラーを発生させず、処理の対応が可能。

```trycatch.ps1
try{
  ls poyo.txt -ErrorAction stop 
} catch {
  Write-Output "catch error"
}
```

```ps1
PS C:\Users\Administrator> powershell.exe .\trycatch.ps1
catch error
```

$errorを使うとcatchした時にエラーを標準出力に記載したりもできる。(そもそもエラーはエラー出力に出すべきというのは理解しつつ)

```trycatchwitherror.ps1
try{
  ls poyo.txt -ErrorAction stop 
} catch {
  Write-Output "catch error"
  Write-Output $error[0]
}
```

```ps1
PS C:\Users\Administrator> powershell.exe .\trycatchwitherror.ps1
catch error
ls : Cannot find path 'C:\Users\Administrator\poyo.txt' because it does not exist.
At C:\Users\Administrator\trycatch.ps1:2 char:3
+   ls poyo.txt -ErrorAction stop
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\Administrator\poyo.txt:String) [Get-ChildItem], ItemNotFoundEx
   ception
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
```

`Out-String`と組み合わせてStringで取得し、独自のログに書くなどもできそうです。