Try/catch does not seem to have an effect

Viewed 112368

I am new to powershell, and I am trying to add error handling via try/catch statements, but they don't seem to actually be catching the error. This is powershell v2 CP3.

$objComputer = $objResult.Properties;
$strComputerName = $objComputer.name
write-host "Checking machine: " $strComputerName

try
{
    $colItems = get-wmiobject -class "Win32_PhysicalMemory" -namespace "root\CIMV2" -computername $strComputerName -Credential $credentials
    foreach ($objItem in $colItems) 
    {
        write-host "Bank Label: " $objItem.BankLabel
        write-host "Capacity: " ($objItem.Capacity / 1024 / 1024)
        write-host "Caption: " $objItem.Caption
        write-host "Creation Class Name: " $objItem.CreationClassName      
        write-host
    }
}
Catch 
{
    write-host "Failed to get data from machine (Error:"  $_.Exception.Message ")"
    write-host
}
finally 
{ }  

When it fails to contact a specific machine, I get this in console, and not my clean catch message:

Get-WmiObject : The RPC server is
unavailable. (Exception from HRESULT:
0x800706BA) At Z:\7.0 Intern
Programvare\Powershell\Get memory of
all computers in AD.ps1:25 char:34
+ $colItems = get-wmiobject <<<< -class "Win32_PhysicalMemory"
-namespace "root\CIMV2" -computername $strComputerName -Credential
$credentials
+ CategoryInfo : InvalidOperation: (:) [Get-WmiObject],
COMException
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

7 Answers

I was able to duplicate your result when trying to run a remote WMI query. The exception thrown is not caught by the Try/Catch, nor will a Trap catch it, since it is not a "terminating error". In PowerShell, there are terminating errors and non-terminating errors . It appears that Try/Catch/Finally and Trap only works with terminating errors.

It is logged to the $error automatic variable and you can test for these type of non-terminating errors by looking at the $? automatic variable, which will let you know if the last operation succeeded ($true) or failed ($false).

From the appearance of the error generated, it appears that the error is returned and not wrapped in a catchable exception. Below is a trace of the error generated.

PS C:\scripts\PowerShell> Trace-Command -Name errorrecord  -Expression {Get-WmiObject win32_bios -ComputerName HostThatIsNotThere}  -PSHost
DEBUG: InternalCommand Information: 0 :  Constructor Enter Ctor
Microsoft.PowerShell.Commands.GetWmiObjectCommand: 25857563
DEBUG: InternalCommand Information: 0 :  Constructor Leave Ctor
Microsoft.PowerShell.Commands.GetWmiObjectCommand: 25857563
DEBUG: ErrorRecord Information: 0 :  Constructor Enter Ctor
System.Management.Automation.ErrorRecord: 19621801 exception =
System.Runtime.InteropServices.COMException (0x800706BA): The RPC
server is unavailable. (Exception from HRESULT: 0x800706BA)
   at
System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Management.ManagementScope.InitializeGuts(Object o)
   at System.Management.ManagementScope.Initialize()
   at System.Management.ManagementObjectSearcher.Initialize()
   at System.Management.ManagementObjectSearcher.Get()
   at Microsoft.PowerShell.Commands.GetWmiObjectCommand.BeginProcessing()
errorId = GetWMICOMException errorCategory = InvalidOperation
targetObject =
DEBUG: ErrorRecord Information: 0 :  Constructor Leave Ctor
System.Management.Automation.ErrorRecord: 19621801

A work around for your code could be:

try
{
    $colItems = get-wmiobject -class "Win32_PhysicalMemory" -namespace "root\CIMV2" -computername $strComputerName -Credential $credentials
    if ($?)
    {
      foreach ($objItem in $colItems) 
      {
          write-host "Bank Label: " $objItem.BankLabel
          write-host "Capacity: " ($objItem.Capacity / 1024 / 1024)
          write-host "Caption: " $objItem.Caption
          write-host "Creation Class Name: " $objItem.CreationClassName      
          write-host
      }
    }
    else
    {
       throw $error[0].Exception
    }

If you want try/catch to work for all errors (not just the terminating errors) you can manually make all errors terminating by setting the ErrorActionPreference.

try {

   $ErrorActionPreference = "Stop"; #Make all errors terminating
   get-item filethatdoesntexist; # normally non-terminating
   write-host "You won't hit me";  
} catch{
   Write-Host "Caught the exception";
   Write-Host $Error[0].Exception;
}finally{
   $ErrorActionPreference = "Continue"; #Reset the error action pref to default
}

Alternatively... you can make your own try/catch function that accepts scriptblocks so that your try/catch calls are not as kludge. I have mine return true/false just in case I need to check if there was an error... but it doesn't have to. Also, exception logging is optional, and can be taken care of in the catch, but I found myself always calling the logging function in the catch block, so I added it to the try/catch function.

function log([System.String] $text){write-host $text;}

function logException{
    log "Logging current exception.";
    log $Error[0].Exception;
}


function mytrycatch ([System.Management.Automation.ScriptBlock] $try,
                    [System.Management.Automation.ScriptBlock] $catch,
                    [System.Management.Automation.ScriptBlock]  $finally = $({})){

    

# Make all errors terminating exceptions.
    $ErrorActionPreference = "Stop";
    
    # Set the trap
    trap [System.Exception]{
        # Log the exception.
        logException;
        
        # Execute the catch statement
        & $catch;
        
        # Execute the finally statement
        & $finally
        
        # There was an exception, return false
        return $false;
    }
    
    # Execute the scriptblock
    & $try;
    
    # Execute the finally statement
    & $finally
    
    # The following statement was hit.. so there were no errors with the scriptblock
    return $true;
}


#execute your own try catch
mytrycatch {
        gi filethatdoesnotexist; #normally non-terminating
        write-host "You won't hit me."
    } {
        Write-Host "Caught the exception";
    }

Edit: As stated in the comments, the following solution applies to PowerShell V1 only.

See this blog post on "Technical Adventures of Adam Weigert" for details on how to implement this.

Example usage (copy/paste from Adam Weigert's blog):

Try {
    echo " ::Do some work..."
    echo " ::Try divide by zero: $(0/0)"
} -Catch {
    echo "  ::Cannot handle the error (will rethrow): $_"
    #throw $_
} -Finally {
    echo " ::Cleanup resources..."
}

Otherwise you'll have to use exception trapping.

In my case, it was because I was only catching specific types of exceptions:

try
  {
    get-item -Force -LiteralPath $Path -ErrorAction Stop

    #if file exists
    if ($Path -like '\\*') {$fileType = 'n'}  #Network
    elseif ($Path -like '?:\*') {$fileType = 'l'} #Local
    else {$fileType = 'u'} #Unknown File Type

  }
catch [System.UnauthorizedAccessException] {$fileType = 'i'} #Inaccessible
catch [System.Management.Automation.ItemNotFoundException]{$fileType = 'x'} #Doesn't Exist

Added these to handle additional the exception causing the terminating error, as well as unexpected exceptions

catch [System.Management.Automation.DriveNotFoundException]{$fileType = 'x'} #Doesn't Exist
catch {$fileType='u'} #Unknown
Related