Exchange Online のリモート PowerShell におけるエラー ハンドリング

こんにちは。Exchange サポート チームの小間です。
今回は Exchange Online に接続する PowerShell でエラー ハンドリングを行う方法をご紹介します。

Exchange Online の管理を PowerShell スクリプトとして記述して実行している管理者の方は多いと思いますが、その時に必要となるのがエラー ハンドリングです。例えば Get-Mailbox コマンドの引数に存在しないユーザー名を指定して実行してしまった時、コマンドの実行はエラーになりますのでエラー時は別の処理を行いたいという要件があると思います。その場合は以下のように try-catch 構文を使用するのが一般的ですが、Exchange Online のリモート PowerShell では意図した通りに catch ブロックに処理が移りません。Get-Mailbox コマンドに ErrorAction パラメーターで Stop を指定しても動作が変わりません。(参考として Exchange Server 2010 以降に接続するリモート PowerShell では Get-Mailbox コマンドに ErrorAction パラメーターで Stop を指定することで、この方法でも意図した通りに動作します。)

# sample01.ps1

param([Parameter(Mandatory=$true,ValueFromPipeline=$true)]$UserName)

try{    Get-Mailbox $UserName    Write-Host "成功"}catch{    Write-Warning "失敗"}

なお上記のスクリプトでは省略していますが、Exchange Online に PowerShell で接続する方法については以下の技術情報を参考にしていただきますようお願いいたします。

TITLE: リモート PowerShell による Exchange への接続
URL: https://technet.microsoft.com/library/jj984289(v=exchg.160).aspx

先ほどのスクリプトの実行結果は次のようになります。実在するユーザーの処理には問題はありませんが、実在しない UNKNOWNUSER でエラーになったにもかかわらず「失敗」ではなく「成功」と出力されてしまいました。

  

try-catch 構文については後述しますが、Exchange Online に接続する PowerShell で簡単にエラー ハンドリングを行う方法としては、$Error 変数を使用する方法があります。コマンドを実行した際にエラー出力があると、その内容が $Error に自動的に格納されますので、$Error が Null でない場合はエラーが発生していたと判断することが可能です。

例えばスクリプトと実行結果は以下のようになり、実在しないユーザーの処理で「失敗」と表示されるようになります。なお、$Error.Clear() はこれまでのエラー内容を削除するコマンドです。

# sample02.ps1

param([Parameter(Mandatory=$true,ValueFromPipeline=$true)]$UserName)

$Error.Clear()

Get-Mailbox $UserName

if($Error -ne $null){    Write-Warning "失敗"}else{    Write-Host "成功"}

try-catch 構文を使用したスクリプトを作るには、少し工夫が必要になります。先にサンプル スクリプトと実行結果を見てみましょう。今回は説明の都合上、ここまでは省略していた Exchange Online に接続するコマンドもまとめて記載しています。

# sample03.ps1

param([Parameter(Mandatory=$true,ValueFromPipeline=$true)]$UserName)

$Pass = ConvertTo-SecureString "PASSWORD" -AsPlainText -Force$Cred = New-Object System.Management.Automation.PSCredential "admin@contoso.onmicrosoft.com", $Pass$Session = New-PSSession `    -ConfigurationName Microsoft.Exchange `    -ConnectionUri https://outlook.office365.com/powershell-liveid/ `    -Credential $Cred `    -Authentication Basic `    -AllowRedirection

function ExecuteCommand($Command){    Invoke-Command `        -Session ($Session) `        -ScriptBlock ([ScriptBlock]::Create("$Command")) `        -ErrorAction Stop}

try{    ExecuteCommand "Get-Mailbox $Username" | ft Name,Alias    Write-Host "成功"}catch{    Write-Warning "失敗"}

Remove-PSSession $Session

 

このサンプル スクリプトでは、通常は Import-PSSession コマンドを実行してローカル PowerShell で Exchange Online のコマンドを使用できるようにするところを、Invoke-Command コマンドを実行して Exchange Online 上でコマンドを直接実行するように変更しています。Invoke-Command コマンドの実行自体を関数にまとめて利用しやすくした上で、try-catch 構文によるエラー ハンドリングを行います。

スクリプトがだいぶ複雑になった印象を受けますが、この方法には制限もあり、Invoke-Command コマンドで実行できる内容が Exchange Online のコマンドに限られます。これは Exchange Online 上で実行できるコマンドを制限しているためで、例えば「ExecuteCommand "$Temp = Get-Mailbox $Username"」のような変数への代入も実行できません。変数に代入したり、出力結果を基に別の作業を行ったりするには、「$Temp = ExecuteCommand "Get-Mailbox $Username"」のように Invoke-Command コマンド (さらには今回の場合は ExecuteCommand 関数) の戻り値を使用することで対応できます。

$Error 変数を使用する方法と Invoke-Command コマンドを使用する方法のどちらを採用するかは、実際の要件やメンテナンスのしやすさなどを考慮してご検討ください。また、スクリプトによる Exchange Online の管理を行う際には十分に動作を検証していただきますようお願いいたします。

今後も当ブログおよびサポート チームをよろしくお願いいたします。