System.InvalidCastException HResult=0x80004002 Message=Unable to cast COM - vb.net

I am trying to read the sent items folder of my outlook, with the following code:
Imports Microsoft.Office.Interop
Imports Microsoft.Office.Interop.Outlook
Dim moApp As Outlook.Application
Dim moNS As Outlook.NameSpace
moApp = New Outlook.Application
moNS = moApp.GetNamespace("MAPI")
Dim oFolder As Outlook.MAPIFolder
Dim oEmail As Outlook.MailItem
oFolder = moNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail)
For Each i As Object In oFolder.Items
If TypeOf i Is MailItem Then
MessageBox.Show("From: " & i.To)
End If
Next
Outlook folders
outlook folders
when I try to read the information of the MailItem object I get the error
"System.InvalidCastException
HResult=0x80004002
Message=Could not convert COM object of type 'System.__ComObject' to interface type 'Microsoft.Office.Interop.Outlook.MailItem'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{00063034-0000-0000-C000-0000000000000046}' failed due to the following error: No such interface is supported (0x80004002 (E_NOINTERFACE)).
Source=SendMail"
But when I try it with the inbox folder, it works without any problem.
I am also validated to receive a MailItem object.
Has this error happened to anyone else?

Try to explore the problematic item in any low-level property explorer tool such as MFCMAPI or OutlookSpy. Is there any difference between other items in the folder? Is it a true mail item?
You may also try to use the late binding technology represented in .Net by reflection. The Type.InvokeMember allows to get property value without casting objects.
Note, you can use the "is" and "as" operators in C#. See How to: Programmatically Determine the Current Outlook Item for more information. If the is operator throws an exception you may try to use the as instead.
Also the Outlook object model provides the MessageClass property - a string representing the message class for the Outlook item. Under the hood the message class is used to identify what inspector to use in Outlook for displaying the item.

Related

Retrieve Outlook Attachment Document Properties using VBA

I am attempting to retrieve some built-in (even custom?) properties from Word/Excel documents in attachments without saving temp files and opening them in the respective Application. I tried attaching them in a MailItem and a DocumentItem.
The properties I am interested in would be: Author, Title, LastSaveDtm, etc. Outlook seems to be able to get them, because the Author name appears at the top of the Preview Pane of a DocumentItem.
The only way I could find to get those properties is using the following methods as described here:
varProp = MailItem.PropertyAccessor.GetProperties(SchemaName)
varProp = DocumentItem.PropertyAccessor.GetProperties(SchemaName)
varProp = MailItem.Attachments(1).PropertyAccessor.GetProperties(SchemaName)
varProp = DocumentItem.Attachments(1).PropertyAccessor.GetProperties(SchemaName)
The SchemaName's are defined in here: [MS-OXPROPS]: Exchange Server Protocols Master Property List
Some interesting definitions from the specs:
named property: A property that is identified by both a GUID and either a string name or a 32-bit identifier
Document object: A Message object that represents a single file, such as a document generated by a word-processing application. The
Message object contains the file as an Attachment object and includes
additional properties to describe the file.
The properties I am trying to retrieve do not have a MAPI tag syntax (canonical name like PidTagPropName) to be used with the proptag namespace (which works from what I have tested*) nor a MAPI id syntax (canonical name like PidLidPropName) to be used with the id namespace, but only have a MAPI string syntax (canonical name like PidNamePropName) to be used with the string namespace.
Here is what I tried for the SchemaName:
"http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/Author"
"http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/Title"
"urn:schemas-microsoft-com:office:office#Author"
"urn:schemas-microsoft-com:office:office#Title"
None of them works.
This document says that "named properties are defined by clients and occasionally service providers"
I have also seen nomewhere that document properties should be "automatically published in MAPI".
So what I am doing wrong?
(*) SchemaName's that work (PidTagSubject):
"http://schemas.microsoft.com/mapi/proptag/0x0037001E"
"http://schemas.microsoft.com/exchange/subject-utf8"
The Outlook object model, nor Redemption, doesn't provide any property or method for that.
You need to save attachments on the disk and then retrieve document properties from files. The Attachment.SaveAsFile method saves the attachment to the specified path.
Sub SaveAttachment()
Dim myInspector As Outlook.Inspector
Dim myItem As Outlook.MailItem
Dim myAttachments As Outlook.Attachments
Set myInspector = Application.ActiveInspector
If Not TypeName(myInspector) = "Nothing" Then
If TypeName(myInspector.CurrentItem) = "MailItem" Then
Set myItem = myInspector.CurrentItem
Set myAttachments = myItem.Attachments
'Prompt the user for confirmation
Dim strPrompt As String
strPrompt = "Are you sure you want to save the first attachment in the current item to the Documents folder? If a file with the same name already exists in the destination folder, it will be overwritten with this copy of the file."
If MsgBox(strPrompt, vbYesNo + vbQuestion) = vbYes Then
myAttachments.Item(1).SaveAsFile Environ("HOMEPATH") & "\My Documents\" & myAttachments.Item(1).DisplayName
End If
Else
MsgBox "The item is of the wrong type."
End If
End If
End Sub
Note, you can automate Office applications where you can open the saved document and read the properties programmatically. See How to use a single VBA procedure to read or write both custom and built-in Document Properties for more information.

Recipient.AutoResponse property returns empty string

Using Outlook 2016, I'm trying to retrieve the AutoResponse (https://learn.microsoft.com/en-us/office/vba/api/outlook.recipient.autoresponse) property for a recipient who is sending an out of the office message.
I get an empty string.
I check that the recipient is resolved through the .Resolved property and I see the auto message displayed if I pass the objMail.Display command. Am I missing something or is this a bug in Outlook?
Sub test()
Dim objMail As Outlook.MailItem
Dim objRecipient As Outlook.Recipient
Set objMail = Application.CreateItem(olMailItem)
Set objRecipient = objMail.Recipients.Add("alias#domain.com")
objRecipient.Resolve
MsgBox objRecipient.AutoResponse
End Sub
The related thread, as well as the answer it references, suggest alternatives (EWS and Redemption) that are not feasible due to access restrictions, and don't explore the potential root cause for .AutoResponse not behaving as described in the documentation.
Recipient.AutoResponse property is local to the message - it returns PR_RECIPIENT_AUTORESPONSE_PROP (0x5FFA001F) property from the message recipient table. It does not returns the OOF status.
The Recipient.AutoResponse property returns or sets a string representing the text of automatic response for a Recipient. So, you may try to set the property to any string and then check it later.
The Display method of the MailItem class displays a new Inspector object for the item. It doesn't show up an auto-response string.

Late Binding vs Early Binding in VBA - (CreateObject() vs New)

I'm trying to use VBA code to invoke a protected API which need authentication with OAuth2. Once I try to open a URL, I'm redirected to the ADFS page for authentication and than I come back.
Now for some applications using CreateObject("InternetExplorer.Application") and the .Navigate URL works fine, for other web app I need to use New InternetExplorerMedium in order to have the code working.
Can you tell me the differences between these objects and why some websites work with one and some work with the other?
Thank you
This way of referring to objects is called "Early" and "Late Binding". From MSDN:
The Visual Basic compiler performs a process called binding when an object is assigned to an object variable.
An object is early bound when it is assigned to a variable declared to be of a specific object type. Early bound objects allow the compiler to allocate memory and perform other optimizations before an application executes.
By contrast, an object is late bound when it is assigned to a variable declared to be of type Object. Objects of this type can hold references to any object, but lack many of the advantages of early-bound objects.
You should use early-bound objects whenever possible, because they allow the compiler to make important optimizations that yield more efficient applications. Early-bound objects are significantly faster than late-bound objects and make your code easier to read and maintain by stating exactly what kind of objects are being used.
TL DR:
The difference is that in early binding you get intellisense and compiling time bonus, but you should make sure that you have added the corresponding library.
Example usage of late binding:
Sub MyLateBinding()
Dim objExcelApp As Object
Dim strName As String
'Definition of variables and assigning object:
strName = "somename"
Set objExcelApp = GetObject(, "Excel.Application")
'A Is Nothing check:
If objExcelApp Is Nothing Then Set objExcelApp = CreateObject("Excel.Application")
'Doing something with the Excel Object
objExcelApp.Caption = strName
MsgBox strName
End Sub
Example usage of early binding:
First make sure that you add the MS Excel 16.0 Object Library from VBE>Extras>Libraries:
Sub MyEarlyBinding()
Dim objExcelApp As New Excel.Application
Dim strName As String
'Definition of variables and assigning object:
strName = "somename"
'A IsNothing check:
If objExcelApp Is Nothing Then Set objExcelApp = CreateObject("Excel.Application")
'Doing something with the Excel Object
objExcelApp.Caption = strName
MsgBox strName
End Sub
Related articles:
Early and Late Binding (my site)
Early Binding vs Late Binding (ex-SO Documentation)

How to get the email address of the current logged-in user?

I'm new to VBA and trying to get an automated word document working. At the moment there is a Button in the document that which upon pressing, will fire off an email with the document attached.
However I need to also get the email address of the current user sending the email, so I can place it inside the document before sending it off. My searches on the internet have not resulted in any usable code that meets my situation. My current code is below.
Set OL = CreateObject("Outlook.Application")
Set EmailItem = OL.CreateItem(olMailItem)
Set Doc = ActiveDocument
Doc.Save
With EmailItem
.Subject = "Requesting Authorization Use Overtime"
.Body = "Please review the following request for overtime" & vbCrLf & _
"" & vbCrLf & _
"Thanks"
.To = "toemail#test.com"
.Importance = olImportanceNormal
.Attachments.Add Doc.FullName
.Send
End With
Not sure if this is relevant, but when the document is being used, the Outlook application will always be open with a user signed in. Im used to having intellisense help in these sorts of situations so I can fool around with methods and properties, but there seems to be very little help from intellisense.
It all depends on the definition of "the current user address".
The address of the primary account in Outlook can be retrieved from Appication.Session.CurrentUser (returns Recipient object). Use Recipient.Address property. Note however that for an Exchange account (Recipient.AddressEntry.Type == "EX") you will receive an EX type address. To retrieve the SMTP address, use Recipient.AddressEntry.GetExchangeUser().PrimarySmtpAddress. Be prepared to handle nulls/exceptions in case of errors. This is what you most likely need in your particular case.
On the Extended MAPI level (C++ or Delphi), use IMAPISession::QueryIdentity (you can test it in OutlookSpy (I am its author) - click IMAPISession button, then QueryIdentity). You can then read the PR_ADDRTYPE property ("EX" vs "SMTP") and PR_EMAIL_ADDRESS (when PR_ADDRTYPE = "SMTP") or (in case of Exchange) PR_SMTP_ADDRESS (not guaranteed to be present) and PR_EMS_AB_PROXY_ADDRESSES (multivalued property will Exchange addresses, including all proxy (alias) addresses).
In case of multiple accounts in the profile, an email can be sent or received through multiple accounts. In that case use MailItem.SendUsingAccount (returns Account object, can be null - in that case use Application.Session.CurentUser). This is valid both for received, sent or emails being composed (Application.ActiveInspector.CurrentItem or Application.ActiveExplorer.ActiveInlineResponse).
All accounts in a given profile can be accessed using the Namespace.Accounts collection (Application.Session.Accounts). Account's address can be accessed using Account.SmtpAddress property.
Note that the Outlook Object Model only exposes mail accounts. Some store accounts (such as PST) are not in the collection since they do not have an intrinsic user identity even if some other accounts (such as POP3/SMTP) can deliver to that store. If you want to access all accounts, you can use Redemption (I am its author) and its RDOSession.Accounts collection (RDOAccounts object).
On the Extended MAPI level, the accounts are exposed through the IOlkAccountManager interface. You can play with it in OutlookSpy if you click the IOlkAccountManager button.
In case of delegate Exchange stores, the store owner is not exposed through the Outlook Object Model. You can either use Extended MAPI (note that the PR_MAILBOX_OWNER_ENTRYID property is only exposed by the online store, it is not available in a cached store). You can parse the Exchange store entry id and extract the EX type address from it. You can then construct the GAL object entry id given the EX address. You can also access the store owner using Redemption and its RDOExchangeMailboxStore object and its Owner property.
Usually, the email address is the name assigned to Outlook Mail Folders.
So try this:
'~~> add these lines to your code
Dim olNS As Outlook.NameSpace
Dim olFol AS Outlook.Folder
Set olNS = OL.GetNamespace("MAPI")
Set olFol = olNS.GetDefaultFolder(olFolderInbox)
MsgBox olFol.Parent.Name '~~> most cases contains the email address
This is assuming your are using Early Bind with the object reference properly set.
Another way to access such info is directly use Namespace properties.
MsgBox olNS.Accounts.Item(1).DisplayName '~~> usually email address
MsgBox olNS.Accounts.Item(1).SmtpAddress '~~> email address
MsgBox olNS.Accounts.Item(1).UserName '~~> displays the user name
I hope any of the above somehow helps.
This answer is for Late Binding so you don't need to have reference libraries. Place the following code in a module:
Dim OL As Object, olAllUsers As Object, oExchUser As Object, oentry As Object, myitem As Object
Dim User As String
Set OL = CreateObject("outlook.application")
Set olAllUsers = OL.Session.AddressLists.Item("All Users").AddressEntries
User = OL.Session.CurrentUser.Name
Set oentry = olAllUsers.Item(User)
Set oExchUser = oentry.GetExchangeUser()
msgbox oExchUser.PrimarySmtpAddress
Functional Approach
To make this a bit more reusable, try any return the email from a function.
Late Binding Example
''
' Creates a new instance of Microsoft Outlook to get the current users
' email address.
' Late Binding Demo.
'
' #exception If any errors it will return an optional parameter for fallback values
''
Public Function GetUsersOutlookEmail(Optional ByVal errorFallback As String = "") As String
On Error GoTo catch
With CreateObject("outlook.application")
GetUsersOutlookEmail = .GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Parent.Name
End With
Exit Function
catch:
GetUsersOutlookEmail = errorFallback
End Function
Early Binding Example
''
' Creates a new instance of Microsoft Outlook to get the current users
' email address.
' Late Binding Demo.
'
' #reference Microsoft Outlook 16.0 Object Reference
' #exception If any errors it will return an optional parameter for fallback values
''
Public Function GetUsersOutlookEmail(Optional ByVal errorFallback As String = "") As String
On Error GoTo catch
With New Outlook.Application
GetUsersOutlookEmail = .GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Parent.Name
End With
Exit Function
catch:
GetUsersOutlookEmail = errorFallback
End Function
Error Handling
Anytime you are making an API call like this, there is always a potential for errors to occur. The method I choose for these demos is to provided an optional parameter for a fallback email. This make is dynamic as you can check to see if it is null, or you could provide something such as username Environ("Username") & "#outlook.com"

Typecast exception from ShellBrowserWindow object to ShellFolderView object

Looking for help trying to figure out why this typecast is not working on my machine.
This code was provided as an answer to another question I had and it's not working for me. It works for the answer poster on their machine, but I'm get a an exception on the line trying to typecast from ShellBrowserWindow to ShellFolderView.
I am using Visual Studio Express 2013, running on Windows 7 Pro X64 Sp1. The target framework for the project is .Net Framework 4. I've added references to Microsoft Internet Controls and Microsoft Shell Controls and Automation and I've added the Imports statements for Shell32 and SHDocVw. DLL versions are as follows: shell32.dll = 6.1.7601.18517 and shdocvw.dll = 6.1.7601.1822 I'm not sure what I could be missing.
The code looks like this. (This code is in a form object)
Imports EdmLib
Imports Shell32
Imports SHDocVw
Public Class BlankForm
Private Sub BlankForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim bar As String() = GetExplorerSelectedFiles()
Exit Sub
'The rest of my program is below this line - I'm just trying to test this one function right now...
End Sub
'Uses the windows shell to get the files selected in explorer
Public Function GetExplorerSelectedFiles() As String()
Dim ExplorerFiles As New List(Of String)
Dim exShell As New Shell32.Shell
Dim SFV As Shell32.ShellFolderView
For Each window As SHDocVw.ShellBrowserWindow In DirectCast(exShell.Windows, SHDocVw.IShellWindows)
If (window.Document).GetType.Name <> "HTMLDocumentClass" Then
SFV = CType(window.Document, ShellFolderView) '<--This is where it fails!!
For Each fi As FolderItem In SFV.SelectedItems
ExplorerFiles.Add(fi.Path)
Next
End If
Next
Return ExplorerFiles.ToArray
End Function
End Class
The line SFV = CType(window.Document, ShellFolderView) results in the following message:
An unhandled exception of type 'System.InvalidCastException' occurred
in LaunchTemplateEPDM.exe
Additional information: Unable to cast COM object of type
'System.__ComObject' to interface type 'Shell32.ShellFolderView'. This
operation failed because the QueryInterface call on the COM component
for the interface with IID '{29EC8E6C-46D3-411F-BAAA-611A6C9CAC66}'
failed due to the following error: No such interface supported
(Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
I've taken a screenshot of a quickwatch on the window object. A quickwatch on the window.document object shows an error saying it's either undefined or inaccessible.
I ran the query Microsoft.VisualBasic.Information.TypeName(window.document) and it returns "IShellFolderViewDual3".
I fixed it.
Not sure why this happens on my system and not yours.
What I found was that GetType.Name always just returns "System.__ComObject", regardless of whether the object is of type ShellFolderView, HTMLDocumentClass or something else. So what was happening was no matter what the actual type of the object was, it was passing the <>"HTMLDocumentClass" test because it was always evaluating to "System.__ComObject". Then when we tried to run the CType function on an object that didn't implement the ShellFolderView interface, it would throw that exception.
I eventually stumbled upon this article which led me to experiment with the TypeName Function which seems to return the actual type, and so I ended up with the working code below:
Public Function GetExplorerSelectedFiles() As String()
Dim ExplorerFiles As New List(Of String)
Dim exShell As New Shell32.Shell
For Each window As SHDocVw.ShellBrowserWindow In DirectCast(exShell.Windows, SHDocVw.IShellWindows)
If TypeName(window.Document) Like "IShellFolderViewDual*" Then
For Each fi As FolderItem In DirectCast(window.Document, ShellFolderView).SelectedItems
ExplorerFiles.Add(fi.Path)
Next
End If
Next
Return ExplorerFiles.ToArray
End Function