Error handling in Exchange Remote PowerShell sessions

​I’m hardly an expert of PowerShell, but this topic seems to be greatly overlooked by the community. Most people seem to agree that the preferred way of error handling in PowerShell is via the try-catch-finally statement. The method is easy to use and it allows you to capture specific error types and perform a different action depending on the exception type. Here’s a simple example:

PS C:\> Try { Get-process non-existant-process -ErrorAction Stop } catch { Write-Host "Oh noes! You made a boo boo." -ForegroundColor DarkGray}

Oh noes! You made a boo boo.

However, when it comes to using remote PowerShell with Exchange, things get ugly fast. Let’s run the above example with the Get-Mailbox cmdlet:

PS C:\> Try { Get-Mailbox non-existant-mailbox -ErrorAction Stop } catch { Write-Host "Oh noes! You made a boo boo." -ForegroundColor DarkGray}

The operation couldn't be performed because object 'non-existant-mailbox' couldn't be found on 'DBXPR03A001DC01.EURPR03A001.prod.outlook.com'.
+ CategoryInfo          : NotSpecified: (:) [Get-Mailbox], ManagementObjectNotFoundException
+ FullyQualifiedErrorId : [Server=DBXPR03MB617,RequestId=04c5f21e-b716-4a67-ac67-52f3c19af39c,TimeStamp=3/1/2015 2:52:47 PM] [FailureCategory=Cmdlet-ManagementObjectNotFoundException] B5614317,Microsoft.Exchange.Management.RecipientTasks.GetMailbox
+ PSComputerName        : outlook.office365.com

The result is much different this time! Not only the ‘standard’ error message is displayed, but our custom error handling code is completely ignored. There are several reasons behind this, number one being the fact that you are not actually running the cmdlet – you are instead running a ‘proxy’ function in a implicit remoting session. See for yourself, get the definition of the Get-Mailbox cmdlet:

Get-Command Get-Mailbox | fl

OK, so how does knowing that small fact help us? It doesn’t, not really. It offers a workaround for the simplest of cases, and just that. Instead of using the proxy function, you can use Invoke-Command to ‘wrap’ it:

PS C:\> $ses = Get-PSSession | ? {$_.ConfigurationName -eq 'Microsoft.Exchange' -and ($_.Runspace.ConnectionInfo.ConnectionUri.Host -eq "outlook.office365.com") -and $_.Availability -eq "Available"}

PS C:\> try { Invoke-Command -Session $ses -ScriptBlock { Get-Mailbox non-existant-mailbox } -ErrorAction Stop } catch {Write-Host "Oh noes! You made a boo boo." -ForegroundColor DarkGray}
Oh noes! You made a boo boo.

Don’t get excited, this doesn’t make any difference. It all boils down to another small fact – the remote session you have opened with Exchange is run in ‘No Language‘ mode. This means no variables, no script blocks, no assignments, nothing at all, but the pipeline. No tab completion either. So while you can get the error handling to work when providing the identity of the mailbox as string, even passing a simple variable to the script block in Invoke-Command will not work due to the ‘No language’ mode. It’s completely useless.

Until the PowerShell team sits down with the Exchange team and give us a better option, don’t feel bad about not following the best practices when it comes to error handling.

18 thoughts on “Error handling in Exchange Remote PowerShell sessions

  1. Faris Malaeb says:

    Nice Article,
    Thanks for sharing, but they’re still one issue as I find, the Exception Category will remain System.Management.Automation.RemoteException
    so capturing certain error is a bit challenging as all the errors will have the same category

    Reply
    1. Vasil Michev says:

      Yup, but you can usually look at additional information and act accordingly. Sad thing is that “newer” cmdlets seem to ignore the good practices and all spill out some generic undistinguishable codes…

      Reply
  2. donlassini says:

    This is great!

    I used to do stuff like
    $OldErrorActionPreference = $Global:ErrorActionPreference
    $Global:ErrorActionPreference =”SilentlyContinue”

    If ($? -eq $False) {

    }
    $Global:ErrorActionPreference = $OldErrorActionPreference

    But your solution is simple, elegant and just neat!!

    Reply
  3. NickC says:

    Thanks so much for this article!

    I’m on PowerShell 5.1 and found when I tried to do a Set-User and forgot to add ‘-ErrorAction:Stop’ I had to catch a different exception:
    System.Management.Automation.Remoting.PSRemotingTransportException

    Reply
    1. Vasil Michev says:

      Oh well, it’s been few years since I posted this, Microsoft has actually improved error handling for remote sessions quite a bit now 🙂

      Reply
  4. Jeremy says:

    Thanks for the article! I found this while Googling for some hints on this same issue as I’ve been using some long-running scripts recently and struggling with EXO PowerShell error handling. I ended up realizing I could do the following successfully in PowerShell 5.1 (surely some earlier versions too):

    try {Get-Mailbox asdfasdf -ErrorAction:Stop}
    catch [System.Management.Automation.RemoteException] {“caught”}

    And it works! I’ve done this before in on-premises Exchange Mgmt Shell, using a different type then this one [Syste….RemoteException]. It was a long time ago so I took a while to remember. Then it was just a matter of doing:
    $Error[0].Exception.GetType().FullName

    …to get the type name (after first running Get-Mailbox asdfasdf to generate the exception).

    I also recently learned that Connect-ExoPSSession with MFA will survive authentication renewals seamlessly (for a while) if you specify -UserPrincipalName . I’ve dug into the Exo PS module and now have my own Connect-Exchange function using the same process as Connect-ExoPSSession.

    Reply
    1. Vasil Michev says:

      Yup, that’s what I’ve been doing as well. Unfortunately, there are tons and tons of cmdlets that don’t have proper exceptions coded and return something generic.

      The MFA module with -UserPrincipalName uses the PRT token afaik, so the machine must be joined to Azure AD. But it’s indeed useful/more reliable.

      Reply
    2. Jeffrey says:

      Do you know if this also works for Hybrid Exchange?

      I tried the following:
      Import-PSSession $session -DisableNameChecking -CommandName Get-RemoteMailbox
      try {Get-RemoteMailbox asdfasdf -ErrorAction Stop}
      catch [System.Management.Automation.RemoteException] {“caught”}

      but it didn’t work.
      I verified the exception type was the same with your $Error[0].Exception.GetType().FullName command.

      Do I still need to set $ErrorActionPreference = ‘Stop’ before that code as well?

      Reply
  5. Brendan says:

    Thanks for this, really helped me get some code sorted!

    Reply
  6. Philipp Foeckeler says:

    # Simple demo:
    try
    {
    Get-Mailbox -Identity bogusAlias -ErrorAction ‘Stop’
    }
    catch
    {
    Write-Host “This should trip, but it never does! The error action is ignored.”
    }

    # This works:
    try
    {
    $Global:ErrorActionPreference = ‘Stop’
    Get-Mailbox -Identity bogusAlias -ErrorAction ‘Stop’
    }
    catch
    {
    Write-Host “Exception caught!”
    }

    Reply
  7. Vasil Michev says:

    What I usually do is to put the result in a variable and check against it. For example if I’m looping over a list of users, I’d do something like:

    $checkuser = Get-Mailbox $user -ErrorAction SilentlyContinue

    if (!$checkuser) { Write-Error “User $user not found”; continue }

    where of course you’d replace Write-Error with the appropriate action or remove it altogether if you don’t want to clutter the output with all the error messages.

    Reply
    1. Christophe Niel says:

      eh, now try doing that on office365 with a “restricted language” active, meaning you CANNOT use any variables at all.

      Nice article, but I’m personally stuck in trying to get the warning and error message from a “invoke-command -asJob ….” on a o365 exchange session
      equivalent of a remote start-job, and tricky on the try-catch block…

      Reply
  8. FrenchSpeaker says:

    And here I am thinking I’m doing something wrong with my Try-Catch for weeks… I’m trying to get the Mailbox properties for users in a CSV file & export the results (all is working fine), but those where the mailbox is not in the cloud, obviously got this ugly red error message all other the PS console.

    At this point in time (Jan 2017), is there something we can do to log those errors in another file for users where the the mailbox is not in O365?
    If not, is there a way to avoid displaying this error?

    Thanks!

    Reply
    1. Christophe Niel says:

      I’m just answering on “is there a way to avoid displaying this error?” : yes
      “Get-Mailbox non-existant-mailbox -ErrorAction SilentlyContinue” will simply not display anything
      (-ea for short instead of -ErrorAction, thers is also -warningaction)

      if you pipe multiple command that could all give an error, you’ll need a “-ErrorAction” for each command
      “get-mailbox -ea silentlycontinue |get-mailboxstatistics -ea silentlycontinue”
      you also have a global variable $ErrorActionPreference that you can set to SilentlyContinue (but that’s not really a good idea most of the time.)

      Reply

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.