In the previous part, we explored how we can handle the errors from <code>Get-MsolUser</code> as part of our larger <code>Get-MITUser</code> function. We would hope we could handle <code>Get-Mailbox</code> in the same way.
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.
- We use
Get-Mailbox
instead ofGet-MsolUser
(obviously). - We don’t have to handle two cases with
-UserPrincipalName
for guids and-ObjectId
otherwise, just-Identity
- We temporarily change
$ErrorActionPreference
from its original value (likely “Continue”) to “Stop”. We make sure to change it back to its original value in thefinally
block, to ensure that no matter what* – whether we finish the try block or move to the catch block – it’s set back afterwards.
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.