All-in-one Get-User cmdlet: Part 3 – Error Handling Get-Mailbox

In the previous part, we explored how we can handle the errors from Get-MsolUser as part of our larger Get-MITUser function.

We would hope we could handle Get-Mailbox in the same way. Let’s give it a shot:

PS> try { 
    Get-Mailbox -Identity DoesNotExist@example.com -ErrorAction Stop
} catch {
    Write-Host "I caught an error"
}
The operation couldn't be performed because object 'DoesNotExist@example.com' couldn't be found on 
'DB3PR01A005DC04.EURPR01A005.prod.outlook.com'.
    + CategoryInfo          : NotSpecified: (:) [Get-Mailbox], ManagementObjectNotFoundException
    + FullyQualifiedErrorId : [Server=AM2PR01MB0994,RequestId=92d6d3c1-ddd3-41b7-8c1c-7284d5dd41d0,TimeStamp=01/02/ 
   2017 11:28:18] [FailureCategory=Cmdlet-ManagementObjectNotFoundException] CFDD6C4D,Microsoft.Exchange.Managemen  
  t.RecipientTasks.GetMailbox
    + PSComputerName        : outlook.office365.com

For some reason, -ErrorAction does not work with the Exchange PowerShell cmdlets. Let’s try something else; PowerShell has a built in variable $ErrorActionPreference, which dictates how non-terminating errors are handled by default in lieu of parameters like -ErrorAction. Let’s try changing this.

PS> $ErrorActionPreference
Continue

PS> $ErrorActionPreference = "Stop"

PS> try { 
    Get-Mailbox -Identity DoesNotExist@example.com
} catch {
    Write-Host "I caught an error"
}
I caught an error

And it has worked. I’ll be perfectly honest; this has actually surprised me. It’s well known that Exchange Powershell does not handle -ErrorAction properly (and -ErrorVariable, -WarningAction, etc.), but I was also under the impression that it ignored $ErrorActionPreference too. Either I was mistaken, or they have half-fixed the issue. Just in case this was due to an upgrade in PowerShell itself, I retested this in PowerShell v2 rather than v5, and it works exactly the same.

In any case this is good, as it means this will be similar to our Get-MsolUser script from Part 2. I was expecting to demonstrate a trick using redirection operators and subexpressions – while a cool trick, it’s messier and more difficult to understand, so it’s a good thing we can avoid that. The only thing we need to be mindful of is that it is best practice to set the $ErrorActionPreference back to its previous value once you are done relying upon it in this way.

This time we can see that the exception type is System.Management.Automation.RemoteException, and we have another message that we can make a regular expression for.

PS> $Error[0].Exception | gm


   TypeName: System.Management.Automation.RemoteException

PS> $Error[0].Exception.Message
The operation couldn't be performed because object 'DoesNotExist@example.com' couldn't be found on 'DB3PR01A005DC04.EURPR01A005.prod.outlook.com'.

This time I will briefly go through the creation of the regular expression. What we want is to match the following:

The operation couldn't be performed because object '[ANYTHING]' couldn't be found on '[ANYTHING]'.

The first thing that is helpful is to run the original message through regex escape, which will escape any symbols that normally have a special meaning in regex. We can do this in PowerShell:

PS> [regex]::Escape("The operation couldn't be performed because object 'Anything' couldn't be found on 'Anything'.")
The\ operation\ couldn't\ be\ performed\ because\ object\ 'Anything'\ couldn't\ be\ found\ on\ 'Anything'\.

Strangely, it suggests that we need to escape the spaces with backslashes, when really the .NET regex parser doesn’t require this, so for readability we will leave the spaces unescaped. We can see that the only other symbol that has been escaped with a backslash is the final dot “.” at the end of the string. The dot is a regex character that means “any one character”, whereas here we want to match the actual dot.

We only need the following regex symbols

  • ^ – “Caret”. This tells regex to match from the start of the string.
  • .* – “Dot-star”. This tells regex to match zero or more of any character, except new lines (by default, anyway).
  • $ – “Dollar”. This tells regex to match to the end of the string.

We can now build and test our regular expression:

PS> $MailboxErrorRegex = "^The operation couldn't be performed because object '.*' couldn't be found on '.*'\.$"

PS> $Error[0].Exception.Message
The operation couldn't be performed because object 'DoesNotExist@example.com' couldn't be found on 'DB3PR01A005DC04.EURPR01A005.prod.outlook.com'.

PS> $Error[0].Exception.Message -match $MailboxErrorRegex
True

Great, it works. We can now build our final snippet. It’s fairly similar to our Get-MsolUser snippet from part 2 – the key changes are.

  1. We use Get-Mailbox instead of Get-MsolUser (obviously).
  2. We don’t have to handle two cases with -UserPrincipalName for guids and -ObjectId otherwise, just -Identity
  3. We temporarily change $ErrorActionPreference from its original value (likely “Continue”) to “Stop”. We make sure to change it back to its original value in the finally block, to ensure that no matter what* – whether we finish the try block or move to the catch block – it’s set back afterwards.

*Don’t take that literally.

try {
    
    # Exchange cmdlets do not pay attention to the -ErrorAction parameter,
    # but they do pay attention to $ErrorActionPreference.
    # Capture the old $ErrorActionPreference first as we should set it back once done.
    $ErrorActionPreferenceOld = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'
    
    $Mailbox = Get-Mailbox -Identity $Identity

    # Double check that we have actually obtained an Mailbox.
    if ($Mailbox) {
        $MailboxFound = $true
    } else {
        $MailboxFound = $false
    } #end else
 
} catch [System.Management.Automation.RemoteException] {
 
    # We may need this message for later.    
    $MailboxError = $_.Exception.Message
    $MailboxErrorRegex = "^The operation couldn't be performed because object '.*' couldn't be found on '.*'\.$"

    if ($MailboxError -match $MailboxErrorRegex) {
        # We can handle a mailbox not found, so no need to throw.
        $MailboxFound = $false
    } else {
        # We don't (yet) know how to handle this error, so rethrow for the user to see.
        throw
    } #end else
 
} finally {

    # Set $ErrorActionPreference back to its previous value.
    $ErrorActionPreference = $ErrorActionPreferenceOld

} #end finally

So the end result of this, is that we have $MailboxFound set to either $true or $false, and then either a $Mailbox or a $MailboxError.

Stay tuned for the next part where we start to put this together.

Leave a Comment

Your email address will not be published. Required fields are marked *