Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 1 in /home/r5652521/public_html/soma-engineering.com/wp-content/themes/affinger/functions.php on line 1548
こんにちは!そーまんです。
今回はエラーハンドリングについて書いてみました。エラーハンドリングは PowerShell でスクリプトを書く上で非常に重要です。それでは何故エラーハンドリングが重要なのでしょうか?
エラーハンドリングは何故必要か
スクリプトを実行される中で、想定外のエラーが発生する場合があります。
例えばファイルの読み書きの場合、そもそもの話、そのファイルにアクセスできるとは 100 % 保証できないですよね。
理由として、具体的に言うとアクセス先のファイルが削除されたとか、ファイルがロックされたとか、ネットワーク遅延とか、そもそもアクセス権限が無いとかでアクセスができないような事があるわけです。
データベースに置き換えても同様で、アクセス先のテーブルが存在しないとか、DB がメンテナンス中とか、ネットワーク遅延とか、アクセス権限が無いとかになります。
このような問題でスクリプトの処理が失敗してしまった場合、スクリプトの処理を中断しなければなりませんよね。
こういった場合にエラーハンドリングを使ってエラーメッセージや警告を表示させたり処理を中断させたりする事ができます。
ここでは、こういったエラーが出た場合のスクリプトにおけるエラーの種類として、まずは 2 種類のパターンがある事から説明して、本題の Try-Catch の説明をします。Try-Catch をすぐ見たい方は飛ばして構いません。
エラーの種類
Termination Error
エラーが発生した場合は処理を中断します。
原因としては、構文のエラーか、メモリの容量が足りないといったような場合になります。Terminating Error はエラーハンドリングで取得されます。
Non-Terminating Error
エラーは発生しますが、PowerShell は処理を継続します。これはエラーハンドリングでは取得できません。スクリプトで発生する多くのエラーは大体 Non-Terminating Error のほうだと言われています。エラーは発生するけれども、処理は継続するというパターンです。
サンプルを見てみましょう。これは人事の DB で使う更新スクリプトになります。日本の中~大企業ですと、4 月と 10 月に大規模な人事異動があると思います。
人事部はそれに合わせて人事部で持っている DB を更新しなければなりません。誰が退職したとか、入社したとか、違う部署に異動したとかです。ここでは実際 DB がないので動作はしませんが、内容は掴めると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$UpdatedEmployee = Get-Content "\\FileServer\scripts\HR\CompanyEmployees.txt" $CurrentEmployee = Get-MyEmployee foreach ($emp in $CurrentEmployee) { if ($UpdatedEmployee -notcontains $emp) { Delete-Employee $emp # DBから社員を削除する } else { Update-Employee $emp "Add" # DBに無い社員を追加する } } |
このスクリプトですが、もしファイル (CompanyEmployees.txt) が所定の場所に存在しなかったらどうなるでしょうか、またはファイルがロックされていたらどうなるでしょうか、またはアクセス権限が無い場合はどうなるでしょうか。
ファイルが見つからなかったり、ファイルの中が見れなかったりするのでエラーは発生しますが、処理は継続しています。これが Non-Terminating Error です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 実行結果 Get-Content : パス 'C:\scripts\HR\CompanyEmployees.txt' が存在しないため検出できません。 発生場所 行:1 文字:20 + $UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" + CategoryInfo : ObjectNotFound: (C:\scripts\HR\CompanyEmployees.txt:Strin g) [Get-Content], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentComm and Get-MyEmployee : 用語 'Get-MyEmployee' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。 名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください。 発生場所 行:2 文字:20 + $CurrentEmployee = Get-MyEmployee + ~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (Get-MyEmployee:String) [], CommandNotFou ndException + FullyQualifiedErrorId : CommandNotFoundException |
では、以下のスクリプトを見てみましょう。現在 4 名従業員がいるとして、更新分の従業員のファイルと比較するとします。
実行結果を見てもらえれば分かりますが、ファイルが見つからないのですが、現在分の従業員が削除されてしまいました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
$UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" $CurrentEmployee = @("emp1", "emp2", "emp3", "emp10") foreach ($emp in $CurrentEmployee) { if ($UpdatedEmployee -notcontains $emp) { Write-Host "削除されました。" } else { Write-Host "更新されました。" } } # 実行結果 Get-Content : パス 'C:\scripts\HR\CompanyEmployees.txt' が存在しないため検出できません。 発生場所 行:1 文字:20 + $UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\scripts\HR\CompanyEmployees.txt:Strin g) [Get-Content], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentComm and 削除されました。← ファイルが見つからないのに従業員が削除されてしまっている 削除されました。← ファイルが見つからないのに従業員が削除されてしまっている 削除されました。← ファイルが見つからないのに従業員が削除されてしまっている 削除されました。← ファイルが見つからないのに従業員が削除されてしまっている |
ファイルが見つかりエラーは出力されますが、処理は継続しますので削除処理をしてしまいます。
こうなってしまうと、DB から削除された従業員は DB から復旧しなければなりませんので、それなりに大ごとになります。
至急での対応が必要とされ、バックアップしたメディアを使い、DB からリストアしたりするなどの多くの作業が必要かもしれません。ですので、これは実際はあってはならないスクリプトになります。
Non-Terminating Error の中断方法
という事で、こういう場合には処理を中断する必要がある事はお分かりいただけたかと思います。
では、Non-Terminating Error で発生したエラーを中断するにはどうしたら良いでしょうか。
-ErrorAction Stop
-ErrorAction Stop を使います。処理を中止したいところに書く事によって処理を中断する事が可能になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
$UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" -ErrorAction Stop $CurrentEmployee = @("emp1", "emp2", "emp3", "emp10") foreach ($emp in $CurrentEmployee) { if ($UpdatedEmployee -notcontains $emp) { Write-Host "削除されました。" } else { Write-Host "更新されました。" } } # 実行結果 Get-Content : パス 'C:\scripts\HR\CompanyEmployees.txt' が存在しないため検出できません。 発生場所 行:1 文字:20 + ... dEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" -ErrorAc ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\scripts\HR\CompanyEmployees.txt:String) [Get-Content], It emNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand |
もう一つは $ErrorActionPreference を使います。
$ErrorActionPreference
これは PowerShell の変数で、これを使うと全てのコマンドレットで発生したエラーを取得してどのように動作するか指定できます。
スクリプト単位で使用されるので、個々のコマンドでは使えません。その場合は ErrorAction パラメーターを使います。
デフォルトの動作は、 $ErrorActionPreference で定義されています。
通常は Continue のため、エラーをキャッチせず、処理をそのまま続行します。これを変更する場合は、Stop などをこの変数に代入します。
1 |
$ErrorActionPreference = "Stop" |
動作 | 説明 | 補足 |
---|---|---|
Continue | エラーを表示しますが、可能な場合は次の処理を続ける。 | デフォルトの動作になります。 |
SilentlyContinue | エラーを無視します。コンソールにはエラーが表示されません。 | |
Stop | エラーを表示し、スクリプトを中断します。 | |
Inquire | エラーに対しての動作をユーザーに聞いてきます。 | 処理を継続するか中断するかを聞いてきます。 |
それでは、実際に Stop を指定して使ってみましょう。結果は -ErrorAction Stop と同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$ErrorActionPreference = "Stop" $UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" $CurrentEmployee = @("emp1", "emp2", "emp3", "emp10") foreach ($emp in $CurrentEmployee) { if ($UpdatedEmployee -notcontains $emp) { Write-Host "削除されました。" } else { Write-Host "更新されました。" } } # 実行結果 Get-Content : パス 'C:\scripts\HR\CompanyEmployees.txt' が存在しないため検出できません。 発生場所 行:3 文字:20 + $UpdatedEmployee = Get-Content "C:\scripts\HR\CompanyEmployees.txt" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (C:\scripts\HR\CompanyEmployees.txt:String) [Get-Content], It emNotFoundException + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand |
$ErrorActionPreference = "Inquire" の場合はポップアップが表示され、エラーに対しての動作を選択するように聞いてきます。
Try Catch を使う
それではここから本題となります。Try Catch を使い、エラーハンドリングをします。
try -> catch -> finally の順で処理されます。
まずは try で処理をして、エラーが発生したら catch で処理をして、finally は省略可能ですが、エラーの有無関係なく、最後に行う処理です。構文としては以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
try { 処理 } catch { tryの中でエラーが発生した場合は、ここで処理 } finally エラーの有無関係なく、最後に行われる処理 } |
では、実際にどんな処理ができるのか、サンプルを見てみましょう。とりあえず finally は省略して try と catch だけで書いています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
arg = @() $file Get-Content "sample.txt" try { # 処理 Get-Help $help -ErrorAction Stop -ErrorVariable $arg } catch { # tryの中でエラーが発生した場合は、ここで処理 $ErrorMessage = $_.Exception_Message $ErrorMessage Write-Host "申し訳ございません。お探しのコマンドレット($help)は存在しません。" -ForregroundColor Yellow Write-Host "エラー: $error" -ForegroundColor Yellow } |
では、もう少し実践的なものを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$path = "C:\temp\" $file = "C:\temp\CompanyEmployees.txt" try { if (Test-Path $path) { Write-Output "ファイルを読み込んでいます。" Get-Content $file -ErrorAction Stop -ErrorVariable "err" } else { # throw でエラーメッセージを作って強制終了する。 throw "$pathにあるファイルが見つかりません。" } } catch [System.IOException] { Write-Output "$pathに対してI/Oエラーが発生しました。" } catch { $ErrorMessage = $_.Exception.Message Write-Host "申し訳ございません。お探しのファイル($file)は存在しません。" -ForregroundColor Yellow Write-Host "エラー: $err" -ForegroundColor Yellow } finally { Write-Host "アクセス先のファイル($file)は$(Get-Date)に作成されました。" -ForegroundColor green } # 実行結果 Reading File 浦島太郎 乙姫 ビルゲイツ アクセス先のファイル(C:\temp\CompanyEmployees.txt)は07/03/2018 14:42:55に作成されました。 |
こんな感じで書いてゆきます。なかなか慣れないと難しいですね。
まとめ
いかがでしょうか。
エラーハンドリングを使う事によって、想定外のエラーに対応する事ができ、実行されてはならない処理を防止する事ができます。
ですので、スクリプトを書く時にはエラーハンドリングが必要かどうかよく考えて、必要であればどこでどのように処理させるか考えて書くようにすれば良いでしょう。
では最後までお読みいただきありがとうございました!