More detailed information about Exception Error?

  • Thread starter Thread starter Don
  • Start date Start date
D

Don

When using Visual Basic .NET with a reference to Interop.Outlook, is there a
way to get more detailed information about an error other than
Exception.Message or Exception.ToString? For example, Outlook's
MailItem.SaveAs method can return an error message of "The operation
failed", but I have no idea what that means. As a test, I tried SaveAs using
a filename that contained illegal characters and got the error message
"Internal Application Error", which isn't helpful for explaining that
particular error either. I suppose I could use a series of "Catch e As"
derived Exception Types, but I'm not even sure what to include. Instead, can
the "real" error simply be found in some property related to the base
Exception Class object? Thanks!
 
Hi Don,

Managed and unmanaged code can work together to handle exceptions. If a
method throws an exception in managed code, the common language runtime can
pass an HRESULT to a COM object. If a method fails in unmanaged code by
returning a failure HRESULT, the runtime throws an exception that can be
caught by managed code.

The runtime automatically maps the HRESULT from COM interop to more
specific exceptions. For example, E_ACCESSDENIED becomes
UnauthorizedAccessException, E_OUTOFMEMORY becomes OutOfMemoryException,
and so on.

If the HRESULT is a custom result or if it is unknown to the runtime, the
runtime passes a generic COMException to the client. The ErrorCode property
of the COMException contains the HRESULT value.

In your specific case, the behavior is correct since the error returned
from MailItem.SaveAs itself doesn't contain useful information. You can
verify this by creating a simple macro in Outlook VBA and the error will be
the same "The operation failed":

Sub test()
Dim f As Folder
Set f = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
Dim i As MailItem
For Each i In f.Items
i.SaveAs ("c:::1")
Exit For
Next
End Sub


Exception thrown from COM should be available in .NET through the
IErrorInfo interface automatically. For example, if you create following
COM component in VB6 which throws a exception:

Public Sub Test()
Err.Raise 12345, "Project1.Class1.Test()", "Custom exception from VB6"
End Sub

When you call it from VB.NET:

Try
Dim x As New Project1.Class1
x.Test()
Catch e As Exception
If TypeOf e Is System.Runtime.InteropServices.COMException Then
Dim ce As System.Runtime.InteropServices.COMException =
CType(e, System.Runtime.InteropServices.COMException)
Stop
End If
End Try

You will see:

e.Message = "Custom exception from VB6"
e.Source = "Project1.Class1.Test()"
ce.ErrorCode = &H800A3039

The error code is composed of several parts, the last part is 12345
(&H3039):

#Heath Stewart's Blog : Deciphering an HRESULT
http://blogs.msdn.com/heaths/archive/2005/07/21/441391.aspx

For more information why we need to check the exception type is a
COMException or not:

#Handling COM Interop Exceptions (.NET Framework Developer's Guide)
http://msdn.microsoft.com/library/en-us/cpguide/html/cpconhandlingcominterop
exceptions.asp?frame=true


Hope this helps.

Sincerely,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications. If you are using Outlook Express, please make sure you clear the
check box "Tools/Options/Read: Get 300 headers at a time" to see your reply
promptly.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Walter,

Still having a problem... My test case calls MailItem.SaveAs using a
filename having illegal characters. The error code returned in VB6 is
x800300FC with a description of "Internal Application Error". I did find
that passing that code to FormatMessage returns "The name %1 is not valid"
which is much more useful (requires the use of the
FORMAT_MESSAGE_IGNORE_INSERTS flag), so I'll do that in VB.NET. But the
HRESULT returned in COMException.ErrorCode is different every time I run the
application. I've gotten xA72300FC, xA0F300FC, xA30300FC, xA51300FC, etc.
While the "300FC" portion of each HRESULT matches the "300FC" portion of the
VB6 Error Code, the rest does not, so the lookup in FormatMessage fails. One
solution apears to be to do a bit-wise AND of the HRESULT using x800FFFFF,
which results in the proper code of x800300FC for each different HRESULT
returned by the COMException. Is that a good solution, or is there another
method that I should use to derive the proper Error Code for use with
FormatMessage? Also, if using AND is correct, then is x800FFFFF okay, or to
preserve both severity bits should it be xC00FFFFF?

Thanks for your help,
Don
 
Hi Don,

A HRESULT is made up of following fields:

* A 1-bit code indicating severity, where zero represents success and 1
represents failure.
* A 4-bit reserved value.
* An 11-bit code indicating responsibility for the error or warning, also
known as a facility code.
* A 16-bit code describing the error or warning.

#HRESULT
http://msdn2.microsoft.com/en-us/library/ms526450.aspx


In case of COMException, you could use Marshal.GetExceptionForHR to convert
the HRESULT to an exception again. It will return correct message for it:


Imports Microsoft.Office.Interop.Outlook
Imports System.Runtime.InteropServices

Module Module1
Sub Main()
Dim app As New Application()
Dim inbox As MAPIFolder =
app.GetNamespace("MAPI").GetDefaultFolder(OlDefaultFolders.olFolderInbox)
For Each item As MailItem In inbox.Items
Try
item.SaveAs("?.msg")
Catch e As System.Exception
If TypeOf e Is COMException Then
Dim ce As COMException = CType(e, COMException)
Dim e2 As System.Exception =
Marshal.GetExceptionForHR(ce.ErrorCode)
Console.Write(e2.Message)
Else
Console.Write(e.Message)
End If
End Try
Exit For
Next
End Sub

End Module


Hope this helps.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Walter,

I'm still having a problem getting the correct message. Using my
MailItem.SaveAs test with illegal characters in the filename,
Marshal.GetExceptionForHR(ce.ErrorCode) returned an Exception.Message as
follows:

"Exception from HRESULT: 0x8E6300FC"

Ran the test again and got:

"Exception from HRESULT: 0x928300FC"

The HRESULT is different each time, and none of them appear to map to a
description. Since the VB6 Error Code for this test is &H800300FC, don't I
still need to convert ce.ErrorCode to a true COM Code when using
GetExceptionForHR? For example, I tried
Marshal.GetExceptionForHR(ce.ErrorCode And &H800FFFFF) instead, and the
resulting Exception.Message was:

"The name is not valid. (Exception from HRESULT: 0x800300FC
(STG_E_INVALIDNAME))"

which is basically what I'm looking for. So, is using "ce.ErrorCode And
&H800FFFFF" the proper solution? And is &H800FFFFF the appropriate value for
the filter?

Thanks,
Don
 
Hi Don,

Use 0x800300FC for example, its binary representation is "10000000 00000011
00000000 11111100", the facility code is FACILITY_STORAGE (3) (see
winerror.h for complete list). This means the error is related to storage
system. Other common facility code is FACILITY_WIN32 (7).

I'm curious why the exception code is random on your side, on my side, when
I used "?.msg" as the file name, the error code will always be 0x800300FC.
As you can see from the error constant STG_E_INVALIDNAME, the facility code
does matter for the error message interpretation.

To answer your question, I don't think "And &H800FFFFF" is the correct way
to do here. We need to first find out why the exception code is random.
Would you please post your code here so that I can test it on my side?
Thanks.


Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Walter,

Please see below for the code you requested. Just to make sure that some
other portion of my app wasn't causing a problem, I broke out the relevant
portion and placed it in a button sub of a new project.

For some unknown reason, each click of Button1 results in a different
COMException.ErrorCode. I'm using VS 2005 and Outlook 2000 SR-1
(9.0.0.4527), but the result is the same using Outlook 2003.

Please let me know the result of your tests.

Thanks,
Don


(project needs COM Outlook as a Reference)
(Form1 needs a Button and a Label)

Option Explicit On
Option Strict On
'******************************************************
Imports System.IO
Imports System.Runtime.InteropServices

Public Class Form1

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim lstrFailureReason As String = ""

Try
Dim oa As New Outlook.Application
Dim ons As Outlook.NameSpace = oa.GetNamespace("MAPI")
Dim f As Outlook.MAPIFolder
f = ons.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Dim i As Object
i = f.Items.GetFirst
If Not i Is Nothing Then
If TypeOf i Is Outlook.MailItem Then
Dim imi As Outlook.MailItem = CType(i, Outlook.MailItem)
imi.SaveAs("C:\?", Outlook.OlSaveAsType.olMSG)
End If
End If

Catch objEX As Exception
If TypeOf objEX Is COMException Then
Dim objComEX As COMException = CType(objEX, COMException)
Dim objEX2 As Exception =
Marshal.GetExceptionForHR(objComEX.ErrorCode)
lstrFailureReason = "ComEX: " & objEX2.Message
objComEX = Nothing
objEX2 = Nothing
Else
lstrFailureReason = "EX: " & objEX.Message
End If
Me.Label1.Text = lstrFailureReason
End Try

End Sub
End Class
 
Hi Don,

Thank you for your code.

I've reproduced the issue on a system with Outlook 2003 installed. However,
it correctly returns correct result on a system with Outlook 2007 installed:

ComEX: The name is not valid. (Exception from HRESULT: 0x800300FC
(STG_E_INVALIDNAME))


That's why I didn't reproduce the issue previously. This looks like an
issue fixed in Outlook 2007. I'm currently consulting this in our internal
discussion list to see if there's any workaround for Outlook 2000/2003.
I'll get back to you as soon as possible, thank you for your feedback.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Walter,

I'm certainly glad you were able to reproduce the problem using Outlook
2003. I'm hopeful that your discussion group can find a fix. My previous
suggestion of ANDing the HRESULT with 0x800FFFFF would only work for
Facility Codes less than or equal to 0xF.

Thanks,
Don
 
Hi Don,

After further researching and consulting, I think you could use following
workaround to get the correct HRESULT, please note this workaround is only
valid for the Outlook automation interface:

Dim e2 As System.Exception = Marshal.GetExceptionForHR(ce.ErrorCode And Not
&H7FFF0000)

It basically clears out some bits of the HRESULT which is irrelevant here.
This should work on Outlook 2000, 2003 and 2007 and above.

Let me know if you have any questions.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Hi Walter,

Doesn't the suggested value of &H7FFF0000 clear out too many bits? To
preserve at least the low value for the Facility Code, wouldn't we need to
use &H7FF00000 instead? It then works for Facility Codes 0x0 thru 0xF, which
would cover the most common codes including FACILITY_STORAGE (0x3) and
FACILITY_WIN32 (0x7).

Thanks,
Don
 
My bad, you are right, we should use:

Dim e2 As System.Exception = Marshal.GetExceptionForHR(ce.ErrorCode And Not
&H7FF00000)

This will keep the highest error bit, the facility code and the last 16-bit
error code.

Sorry for the typo.

Regards,
Walter Wang ([email protected], remove 'online.')
Microsoft Online Community Support

==================================================
When responding to posts, please "Reply to Group" via your newsreader so
that others may learn and benefit from your issue.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Back
Top