none
PowerShell - Remote Sessions, Try{}Catch{}, ErrorActionPreference

    Question

  • Hello everyone,

    I am encountering issues with Remote Exchange sessions and was wondering what I am overlooking/over-complicating.

    I am running Windows 8.1/PowerShell 4.0. Reason I mention this is because of this blog post:
    http://blogs.msdn.com/b/powershell/archive/2013/10/25/windows-management-framework-4-0-is-now-available.aspx

    The IMPORTANT section noted that Exchange 2007, 2010, 2013 are not compatible with WMF 4.0 and didn't know if that is good/bad since the OS is using PowerShell 4.0.

    1 - I am connecting to Office 365 utilizing the following:
    http://help.outlook.com/en-us/140/cc952755.aspx

    2 - I have the following function:

    function Get-SomeMailboxStatistics { [CmdletBinding()] param( [Parameter(Mandatory=$True, ValueFromPipeline=$True, HelpMessage="Identities or aliases to gather Exchange Statistics from.")] [Alias('Username')] [string[]]$Identity, [string]$ErrorLog = 'C:\MbxStatsErrorLog.txt', [switch]$LogErrors ) BEGIN{} PROCESS{ Write-Verbose "Beginning..." foreach ($alias in $Identity) { Write-Verbose "Gathering info for $alias." #trap [ManagementObjectNotFoundException] {$everythingOK = $false} Try{ # $ErrorActionPreference = "Stop" # Testing purposes. $everythingOK = $true $gms = Get-MailboxStatistics -Identity $alias -ErrorAction Stop # http://technet.microsoft.com/library/hh847884.aspx # Trap { # throw $_ # } } Catch{ $everythingOK = $false Write-Warning "Lookup on $alias failed." if ($LogErrors -eq $true){ $alias | Out-File $ErrorLog -Append Write-Warning "Logged $alias to $ErrorLog." } } if ($everythingOK){ $props = @{}; $props = @{'Alias' = $alias; 'DisplayName' = $gms.DisplayName; 'TotalItemSizeInBytes' = ($gms.TotalItemSize.Value.ToString() -replace "(.*\()|,| [a-z]*\)", ""); 'TotalDeletedItemSizeInBytes' = ($gms.TotalDeletedItemSize.Value.ToString() -replace "(.*\()|,| [a-z]*\)", ""); 'MailboxType' = $gms.MailboxType;

    }; $obj = New-Object -TypeName PSObject -Property $props Write-Output $obj } } } END{} }

    Example: Get-SomeMailboxStatistics -Identity GoodAlias1,GoodAlias2,FailAlias,GoodAlias3 -LogErrors -ErrorLog 'C:\Logs\MbxStatsErrors.txt' -Verbose

    The pipeline terminates on FailAlias, and then throws an error when it tries to pass the FailAlias to the $props.

    $gms | Get-Member on a good alias is:
    TypeName: Deserialized.Microsoft.Exchange.Management.MapiTasks.Presentation.MailboxStatistics

    I have tried setting $ErrorActionPreference = 'Stop' in various sections and think I am scope-creeping/not catching the Error where I should be or PowerShell is flat-out ignoring something.

    I have tried additional nested if{}else{} in the Try{}Catch{} and cannot get it to behave as expected.
    I have also tried to Trap{[ManagementObjectNotFoundException]} and Throw exception in various portions without success.

    However, setting $ErrorActionPreference = 'Stop' in the session window (from default of 'Continue') the script behaves as I expected it to, when encountering a non-existant mailbox, it logs the error. It doesn't seem to have issues executing on Windows 8.0/PowerShell 3.0.


    I have encountered numerous blog/forum posts with no definitive answer/resolution.
    Based some of my tests around some suggestions from this thread:
    http://stackoverflow.com/questions/1142211/try-catch-does-not-seem-to-have-an-effect
    Extras:
    http://blogs.technet.com/b/heyscriptingguy/archive/2010/03/09/hey-scripting-guy-march-9-2010.aspxhttp://stackoverflow.com/questions/19553278/powershell-catch-non-terminating-errors-with-silentlycontinue
    http://stackoverflow.com/questions/15545429/erroractionpreference-and-erroraction-silentlycontinue-for-get-pssessionconfigur
    http://social.technet.microsoft.com/Forums/scriptcenter/en-US/228a3329-f564-4daa-ad70-6d869b912246/non-terminating-error-turned-into-a-terminating-error?forum=winserverpowershell





    • Edited by stlth Saturday, November 9, 2013 7:19 PM
    Saturday, November 9, 2013 3:59 PM

Answers

  • The following works.  Use it to build a wrapper for the CmdLet.

    function Test-Trap{
        $alias='nobody'
        $saved=$global:ErrorActionPreference
        $global:ErrorActionPreference='stop'
        Try{
            
            $gms = Get-MailboxStatistics -Identity $alias        
        }
        Catch{
             Write-Warning $_
        }
        Finally{
            $global:ErrorActionPreference=$saved
    	}
    }

    If you only change the local version of erroractionpreference it won't trap. If we change the global version it will. It is very clear that the junior programmer who wrote the CmdLets missed some obvious test things.

    It is clearly a program scope bug. The CmdLet, for some reason, is not properly honoring the stop request except when it is the  global setting.  It may also be a change in the way the Net 4.x exception handler works.  I notice the exceptions seem to be much richer than in P2.


    ¯\_(ツ)_/¯

    • Proposed as answer by jrv Saturday, November 9, 2013 7:44 PM
    • Marked as answer by stlth Saturday, November 9, 2013 8:38 PM
    Saturday, November 9, 2013 7:43 PM

All replies

  • You said it works on P3 and fails on P4. You also note that Exchange is NOT compatible with WMF4.  Don't you think you should pay attention to the warning that WMF4 does not work with Exchange?

    I simplified your coed and removed a couple of logic and syntax errors.  I also removed the global error stop.  It should not be used with Try/Catch.

    If anything is going to work this should.  If the error is thrown it will catch and the code will continue.  You had an extra bracket in the script and some other things that made it hidden.  A good editor would have picked this up for you.

    I haven't tested this - got lazy - it passes muster as far as syntax checks go.

    The design is to always produce an object even if the alias is not found.  This makes the looping very flat and predictable. All compatibility comes down to the one command.  If that command will work at a prompt then it should work in the function.

    function Get-SomeMailboxStatistics {
    
        [CmdletBinding()]
        param(
            [Alias('Username')]
            [Parameter(
                Mandatory=$True,
                ValueFromPipeline=$True,
                HelpMessage='Identities or aliases to gather Exchange Statistics from.'
            )][string[]]$Identity,
            [string]$ErrorLog = 'C:\MbxStatsErrorLog.txt',
            [switch]$LogErrors
        )
        
        Begin{}
        
        Process{
    
                Write-Verbose "Beginning..."
                foreach ($alias in $Identity){
                    $props = @{
                        Alias=$alias
                        DisplayName=''
                        TotalItemSizeInBytes=''
                        TotalDeletedItemSizeInBytes=''
                    }
                    Write-Verbose "Gathering info for $alias."                    
                    Try{
                        $gms = Get-MailboxStatistics -Identity $alias -ErrorAction Stop
                        $props.DisplayName=gms.DisplayName
                        $props.TotalItemSizeInBytes=$gms.TotalItemSize.Value
                        $props.TotalDeletedItemSizeInBytes=$gms.TotalDeletedItemSize.Value
                   }
                    Catch{
                         Write-Warning "Lookup on $alias failed."
                    }
                    New-Object PsObject -Property $props
                }
        }
        
        End{}
    }
    
    
    


    ¯\_(ツ)_/¯


    • Edited by jrv Saturday, November 9, 2013 4:37 PM
    Saturday, November 9, 2013 4:36 PM
  • Here is a link to an easier to read copy.  The SkyDrive version retains proper formatting in most browsers and this forum does not.

    http://sdrv.ms/1bgUKhj


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 4:42 PM
  • The link now has enhanced code demoing how to manage error reporting in an object stream.


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 4:48 PM
  • Hey jrv, thanks for the responses and samples.

    The design of my original was to stop processing if it could not locate the mailbox and immediately trip and log the entry to the text file. How I was going about it in my head: Why create the custom PS Object if there isn't anything to grab stats from? I was using stuff from "Learn PowerShell Toolmaking in a Month of Lunches" as examples.

    I posted the first comment as I located it after I had a machine upgraded to 8.1 and also lists a bunch of other things people have found in PS 3.0 vs. PS 4.0 in Microsoft Connect.

    I have had issues in the past with other Service Packs, so I guess I shouldn't be surprised at this point.
    I don't have an 8.0 machine in front of me at the moment, but ran it on 8.1. 8.0 I will have to test later.

    Using the version from the SkyDrive posting on a fake account, it ends up returning the custom object, saying that the Alias was found (which wasn't really because the Exchange command-let wrote that out in Red-Text, below), and doesn't trip the Catch{} block which is what I was experiencing with my version as well.

    Get-SomeMailboxStatistics -Identity FailAlias -LogErrors -ErrorLog 'C:\Logs\MbxStatsErrors.txt' -Verbose

    Same return from the -ErrorAction Stop earlier in the block:
    [Get-MailboxStatistics], ManagementObjectNotFoundException

     

    The specified mailbox "FailAlias" doesn't exist.
    + CategoryInfo: NotSpecified: (:) [Get-MailboxStatistics], ManagementObjectNotFoundException
    + FullyQualifiedErrorId : [FailureCategory=Cmdlet-ManagementObjectNotFoundException] D8B3F4AA,Microsoft.Exchange.Management.MapiTasks.GetMailboxStatistics

    Found: True
    Alias:FailAlias
    TotalItemSizeInBytes: 
    TotalDeletedItemSizeInBytes: 
    DisplayName: 
    ErrorMessage: 

    Still working on it and thanks for the suggestions.

    Thanks,

    stlth

    Saturday, November 9, 2013 7:12 PM
  • What you are saying is that the CmdLet does not honor the error action variable.  If that is the case you are hosed.  There is no way to get around that. If it does NOT throw an exception then you will never be able to trap it.

    If you run just the following what is the outcome?

    $alias='nobody'
    Try{
        $gms = Get-MailboxStatistics -Identity $alias -ErrorAction Stop
    }
    Catch{
         Write-Warning "Lookup on $alias failed."
    }
    
     


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 7:18 PM
  • Oh believe me, I already tried that as well before posting this thread which is why I have been pulling my hair out. :P

    Stop doesn't stop.

    I was trying to determine a "graceful" way of working around it if it was possible without using an Invoke-Command block across the $Session.

    The specified mailbox "nobody" doesn't exist.
        + CategoryInfo          : NotSpecified: (:) [Get-MailboxStatistics], ManagementObjectNotFoundException
        + FullyQualifiedErrorId : [FailureCategory=Cmdlet-ManagementObjectNotFoundException] 77E49D,Microsoft.Exchange.Management.MapiTasks.GetMailboxStatistics

    Disconnecting from the PS Session, or running it from a new PS Window, Try{}Catch{} functions as expected:
    WARNING: Lookup on nobody failed.


    • Edited by stlth Saturday, November 9, 2013 7:28 PM
    Saturday, November 9, 2013 7:24 PM
  • Forget it.  I just ran Exchange with Posh 4.  With PowerShell 4 installed no version of PowerShell will work with Exchange with  'Stop'.  It works with SilentlyContinue but will not trap.  You can just test the error variable after the call to see if there is an error.  Trap and Try/Catch will not work with this CmdLet until they fix the shell.


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 7:26 PM
  • Here is the exception and how to test it:|

    [PS] C:\Windows\system32>$error[0].Exception.ErrorRecord.FullyQualifiedErrorId
    2C03D7BC,Microsoft.Exchange.Management.MapiTasks.GetMailboxStatistics

    To make it work you will need to clear the errors first.

    $error.Clear()

    It is a kludge but I do not see another way to do it.  Try it.  Maybe I can think up another tweak.


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 7:29 PM
  • Thanks for the double-checking. I guess I will load up an 8 VM as a temporary 'fix'.

    I tried a bunch of adjustments around $ErrorActionPreference and -ErrorAction 'Stop' before posting this.

    Try{}Catch{}Finally{} and the Trap{} do not handle non-terminating errors, which is what I was hinting at in the post title so they never pick up the problem/stop processing.


    If I remember right, same thing happened when Exchange 2010 came out. PS 3.0 (with Windows 7) didn't work until Exchange 2010 had SP2 rolled-out or something along those lines.
    • Edited by stlth Saturday, November 9, 2013 7:36 PM Typo.
    Saturday, November 9, 2013 7:32 PM
  • No - as you suspected this can be made to work:

    $alias='nobody'
    Try{
        $ErrorActionPreference='stop'
        $gms = Get-MailboxStatistics -Identity $alias
    }
    Catch{
         Write-Warning $_
    }
    
     

    The above works.  It traps to the catch block.

    We need to check to see if the variable remains isolated or we will have to return its state.

     


    ¯\_(ツ)_/¯

    Saturday, November 9, 2013 7:35 PM
  • The following works.  Use it to build a wrapper for the CmdLet.

    function Test-Trap{
        $alias='nobody'
        $saved=$global:ErrorActionPreference
        $global:ErrorActionPreference='stop'
        Try{
            
            $gms = Get-MailboxStatistics -Identity $alias        
        }
        Catch{
             Write-Warning $_
        }
        Finally{
            $global:ErrorActionPreference=$saved
    	}
    }

    If you only change the local version of erroractionpreference it won't trap. If we change the global version it will. It is very clear that the junior programmer who wrote the CmdLets missed some obvious test things.

    It is clearly a program scope bug. The CmdLet, for some reason, is not properly honoring the stop request except when it is the  global setting.  It may also be a change in the way the Net 4.x exception handler works.  I notice the exceptions seem to be much richer than in P2.


    ¯\_(ツ)_/¯

    • Proposed as answer by jrv Saturday, November 9, 2013 7:44 PM
    • Marked as answer by stlth Saturday, November 9, 2013 8:38 PM
    Saturday, November 9, 2013 7:43 PM
  • Sweet. Made a wrapper for my version, and worked on 8.1.

    Thanks for your help jrv!

    Now let's see what happens on all of the other command-lets... I expect more wrappers.
    • Edited by stlth Saturday, November 9, 2013 8:49 PM
    Saturday, November 9, 2013 8:41 PM
  • This is not the only problem with WMF 4.0

    When you use -ErrorAction 'SilentlyContinue', $Error object is not set.

    [PS] $Error.Clear()
    [PS] $objContact = Get-Contact -Identity 'CN=unknown contact,OU=Users,DC=mydomain,DC=mytopdomain' -ErrorAction 'Stop'
    The operation couldn't be performed because object 'mytopdomain.mydomain/Users/unknown contact' couldn't be found on 'mydc.mydomain.mytopdomain'.
        + CategoryInfo          : NotSpecified: (:) [Get-Contact], ManagementObjectNotFoundException    + FullyQualifiedErrorId : 482E7079,Microsoft.Exchange.Management.RecipientTasks.GetContact
        + PSComputerName        : myexchange.mydomain.mytopdomain

    [PS] $Error.Count
    1

    [PS] $Error.Clear()
    [PS] $objContact = Get-Contact -Identity 'CN=unknown contact,OU=Users,DC=mydomain,DC=mytopdomain' -ErrorAction 'SilentlyContinue'
    [PS] $Error.Count
    0

    That's why since Exchange 2010 SP3 Rollup 5 (i suppose when reading Exchange Supportability Matrix), The Exchange Management Shell link has been updated to use Powershell 2.0

    Before 2010 SP3 Rollup 5
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noexit -command ". 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto"

    After 2010 SP3 Rollup 5
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -version 2.0 -noexit -command ". 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'; Connect-ExchangeServer -auto"



    • Edited by WeetA Wednesday, July 23, 2014 10:11 AM
    Wednesday, July 23, 2014 10:09 AM
  • It's not so simple.

    the behavior is different on two Windows 2012 R2 servers.

    Both of them have Exchange 2010 SP3 Rollup 5 management Tools, Powershell 2.0, Powershell 4.0, .Net 3.5, .net 4.5 installed

    Server A:
    the EMS link contains -Version 2.0
    try catch with -errorAction Stop: OK
    $Error.Count=1 with -errorAction Silentlycontinue: OK
    force powershell -Version 4.0, try catch with -errorAction Stop: OK
    force powershell -Version 4.0, $Error.Count=1 with -erroraction SilentlyContinue: OK

    Server B
    the EMS link contains -Version 4.0
    try catch with -errorAction Stop: KO
    $Error.Count=1 with -errorAction Silentlycontinue: KO
    force powershell -Version 2.0, try catch with -errorAction Stop: OK
    force powershell -Version 2.0, $Error.Count=1 with -erroraction SilentlyContinue: OK

    I'm confused right now. I don't understand why it works on Server A with -version 4.0.
    I compared Powershell variables (Get-Variable): No difference


    • Edited by WeetA Wednesday, July 23, 2014 11:04 AM
    Wednesday, July 23, 2014 11:00 AM
  • This topic is a year old. If you have issues then please open a new topic.  You can post a link back here.

    I don't see any issues with V3 and V4 like you are seeing.  Perhaps you have a missing patch.  The framework has a few optional patches.


    ¯\_(ツ)_/¯

    Wednesday, July 23, 2014 11:04 AM
  • I got the same patches on both servers. That's why i don't understand.

    Wednesday, July 23, 2014 11:54 AM
  • Found my problem.

    Someone have modified EMS link on server B to use Powershell 4.0
    I had to delete all entries related to CAS servers from $env:USERPROFILE\AppData\Roaming\Microsoft\Exchange\RemotePowerShell\ and C:\Windows\System32\WindowsPowerShell\v1.0\Modules\

    After that even if i execute EMS with -Version 4.0 (no reason to do that), it still works as expected due to PowerShell cache.

    Wednesday, July 23, 2014 12:39 PM