Trigger deletion of Outlook email attachment using VSTO AddIn from VC++ app - c++

I am attempting to delete an email in Outlook from a VC++ app. I have an Outlook Addin that is loading in Outlook and registered with the system. The VC++ app to trigger the deletion of the email makes a call to CoCreateInstance() and returns successfully.
HRESULT hr;
IFoo* pISL;
CoInitialize(NULL);
LPOLESTR lpoleStr;
StringFromCLSID(__uuidof(Foo), &lpoleStr);
hr = CoCreateInstance(
__uuidof(Foo), // CLSID of coclass
NULL, // not used - aggregation
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, // type of server
__uuidof(IFoo), // IID of interface
(void**)&pISL); // Pointer to our interface pointer
if (SUCCEEDED(hr))
{
//fire event
BSTR bstr = SysAllocString(params->szObjectPath);
pISL->FooMethod(bstr);
pISL->Release();
SysFreeString(bstr);
}
else
{
// Couldn't create the COM object. hr holds the error code.
}
CoUninitialize();
I am then using the COM object returned to call the methods in the COM interface I requested. These methods fire events that should be listened to by the Outlook Addin.
private void RegisterConnectionPoint()
{
Logger.Log("RegisterConnectionPoint():");
// Call QueryInterface for IConnectionPointContainer
Foo foo = new Foo();
IConnectionPointContainer icpc = (IConnectionPointContainer)foo;
// Find the connection point for the
// _IFoo source interface
Guid IID_IFoo = typeof(_IFoo).GUID;
icpc.FindConnectionPoint(ref IID_IFoo, out icp);
// Pass a pointer of the host to the connection point
icp.Advise(this, out cookie);
}
My problem is the Outlook Addin never receives the events from the COM object. I think it may be due to the Addin not correctly registering with the COM object, or that there are two COM objects one for the VC++ app and one for the Outlook Addin.
My questions are
Is the basic concept of creating a Outlook VSTO Addin that deletes emails upon receiving an event from a COM server whose events are triggered from a separate process feasible?
If the answer is 'yes' to the first question can anyone see what I am doing wrong?
Debugging the VC++ app side I get to this code which it seems is generated ATL code (I haven't written all the code so am guessing sometimes):
int cConnections = m_vec.GetSize();
for (int iConnection = 0; iConnection < cConnections; iConnection++)
{
pThis->Lock();
CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);
pThis->Unlock();
IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);
if (pConnection)
{
CComVariant avarParams[1];
avarParams[0] = attachmentPath;
avarParams[0].vt = VT_BSTR;
CComVariant varResult;
DISPPARAMS params = { avarParams, NULL, 1, 0 };
hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, NULL, NULL);
}
}
The problem is m_vec.GetSize() returns 0. So it seems there are no 'connections'. Which would imply I am either creating a different instance of the COM object than the one used by the Outlook Addin or the Addin is not registering correctly.

So the link supplied by #Dmitry was what I needed. By using the Running Object Table (ROT) I was able to get a reference to the Outlook addin loaded by Outlook. So allowing me to call the methods inside it to delete the mail attachment.
Some changes were required including:
Moving registration of the addin to ThisAddin_Startup() as the default OutlookAddin class uses a static constructor so I was not able to reference the Addin inside the constructor.
I also had to move my code to a user session process as this post COMException (0x800401E3): Operation unavailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE)) with scheduled task explains retrieving an COM object from the ROT is not supported for services. This was just a point I thought I should add as it could stump people trying the same thing I was.
Additionally I found the tool ROT viewer found here http://alax.info/blog/1444 useful as you can check if you addin is actually registering to the ROT.
Hope this helps people.

Related

How may I bind a Toast progress bar with my C++ Win32 application?

I'm creating a c++ Win32 application which should show some Toast notifications. One of them contains a progress bar. I need to bind its value with my application in order to update it while the status changes in the currently running operation.
The Toast interface containing the progress bar is defined as follow:
<toast>
<visual>
<binding template='ToastGeneric'>
<text>Backup in progress</text>
<progress title='Working folder' value='0' status='Starting backup...' valueStringOverride='0/1 files' />
</binding>
</visual>
</toast>
To show the Toast notification I call the following function, by passing the above interface in the toastContent string:
bool ShowToastNotification(const std::wstring toastContent)
{
if (!toastContent.length())
return false;
// build XML
::CComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> pDoc;
HRESULT hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(toastContent.c_str(), &pDoc);
if (FAILED(hr))
return false;
// create the notifier. Classic Win32 apps MUST use the Compat method to create the notifier
::CComPtr<ABI::Windows::UI::Notifications::IToastNotifier> pNotifier;
hr = DesktopNotificationManagerCompat::CreateToastNotifier(&pNotifier);
if (FAILED(hr))
return false;
// create the Toast notification (using helper method from Compat library)
::CComPtr<ABI::Windows::UI::Notifications::IToastNotification> pToast;
hr = DesktopNotificationManagerCompat::CreateToastNotification(pDoc, &pToast);
if (FAILED(hr))
return false;
// get access to IToastNotification4, which should contain the binding methods
::CComPtr<ABI::Windows::UI::Notifications::IToastNotification4> pToastData;
pToast->QueryInterface(ABI::Windows::UI::Notifications::IID_IToastNotification4, (void**)&pToastData);
ABI::Windows::UI::Notifications::INotificationData* pData = nullptr;
// FIXME how may I create a valid INotificationData here?
// bind the data with the interface
hr = pToastData->put_Data(pData);
if (FAILED(hr))
return false;
// show it
hr = pNotifier->Show(pToast);
if (FAILED(hr))
return false;
return true;
}
As you can see in the FIXME part above, there is a missing part to achieve the binding before showing the Toast notification, and unfortunately there is absolutely NO documentation from Microsoft which explain how to perform that in a simple C++ Win32 application. The below one explain clearly how to perform a such binding, but only in C#:
https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-progress-bar?tabs=builder-syntax
Even without documentation I could find that the IToastNotification4 seems to be a valid interface to attach a INotificationData container to my Toast, similar to what is done in the above mentioned documentation. However I don't know how to create a valid INotificationData instance. I saw that a CreateNotificationDataWithValuesAndSequenceNumber() function exists in the INotificationDataFactory interface, but same thing, I face a cruel miss of documentation about how to obtain a such interface.
Can someone explain me how I may bind my C++ Win32 application with my progress bar on my Toast notification, in order to send it messages to update its interface? Or can someone explain me how to get the above mentioned interfaces, in order to perform something similar to the above mentioned C# documentation? Or at least can someone point me a documentation which may give information about the INotificationData and INotificationDataFactory interfaces, and how to use them to perform the binding with my Toast progress bar?
If a WinRT class can be created from C#, it means it's activatable (or it's associated with another "statics" class which is), which you can check if you go to NotificationData documentation:
[Windows.Foundation.Metadata.Activatable(262144, "Windows.Foundation.UniversalApiContract")]
[Windows.Foundation.Metadata.Activatable(typeof(Windows.UI.Notifications.INotificationDataFactory), 262144, "Windows.Foundation.UniversalApiContract")]
[Windows.Foundation.Metadata.ContractVersion(typeof(Windows.Foundation.UniversalApiContract), 262144)]
[Windows.Foundation.Metadata.MarshalingBehavior(Windows.Foundation.Metadata.MarshalingType.Agile)]
[Windows.Foundation.Metadata.Threading(Windows.Foundation.Metadata.ThreadingModel.Both)]
public sealed class NotificationData
{...}
If it's activatable, then you just have to find it's activation class id, and call RoActivateInstance . The class id is visible in the corresponding .h file (here windows.ui.notifications.h) and it's usually very straightforward: <namespace> + '.' + <class name>:
...
extern const __declspec(selectany) _Null_terminated_ WCHAR RuntimeClass_Windows_UI_Notifications_NotificationData[] = L"Windows.UI.Notifications.NotificationData";
...
So here is how you can create it:
HRESULT CreateNotificationData(INotificationData** data)
{
if (!data)
return E_INVALIDARG;
*data = nullptr;
IInspectable* instance;
auto hr = RoActivateInstance(HStringReference(RuntimeClass_Windows_UI_Notifications_NotificationData).Get(), &instance);
if (FAILED(hr))
return hr;
hr = instance->QueryInterface(data);
instance->Release();
return hr;
}
PS: using C++/WinRT is usually easier to use than ATL, WRL or WIL for WinRT types.

Excel Automation - Filter COM events using FindConnectionPoint

I use C++ COM to listen to events IID_ApplicationEvents of an existing Excel application. My code is placed in a separate EXE file.
The Connection point is initialized with the following code:
IConnectionPointContainer* pCPC = NULL;
hr = pEventSource->QueryInterface(IID_IConnectionPointContainer,
(void**)&pCPC);
if (SUCCEEDED(hr)){
hr = pCPC->FindConnectionPoint(IID_ApplicationEvents, &m_pConnectionPoint);
if (SUCCEEDED(hr)){
hr = m_pConnectionPoint->Advise(this, &m_dwConnection);
if (FAILED(hr))
LOG_ERROR(L"m_pConnectionPoint->Advise - FAIL");
}
else
LOG_ERROR(L"FindConnectionPoint - IID_ApplicationEvents: FAIL");
pCPC->Release();
}
else
LOG_ERROR(L"IID_IConnectionPointContainer: FAIL");
return hr;
I am receiving the Excel events via the Invoke function:
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams,
VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
UINT* puArgErr)
My question is following: In order to improve the performance of my app and not slow down the running Excel, I am interested only in OnBeforeSave and OnClose events. Is there a way to filter out other Excel events in my process (I dont want my Invoke function to be called at all)?
As Igor mentioned in a comment above, you cannot simply filter events. However, you can build something yourself to do this.
Here's one way:
Create a COM Add-in that will be loaded in the Excel process. (See IDTExtensibility2).
When OnConnection is called:
Create an instance of your own homegrown event filter class (implement IDispatch to handle events and IConnectionPointContainer to raise events).
Make that event filter object "public" by assigning its IUnknown pointer to the Object property of the AddIn object supplied as the AddInIst parameter to OnConnection.
When your event filter object receives an event, filter it according to your rules. If the event isn't filtered out, raise it from the filter object.
In your external process, find your COM Add-In in the AddIns2 collection and read its Object property. You can then treat that as your event source instead of the Application object.

#import lead to a HRESULT 0x80040154 "Not registered class"

I'm trying to use a COM DLL from VC++ 2005. I created a TestCOMlib.dll with ATL, created a simple interface ISimple and added one property (type LONG, name Property01) and one method (name Method01).
The DLL seems to be correctly registered in the system (I use OleView to check the entries).
I created a simple MFC dialog app to use the COM dll. I'm using the #import directive to incorporate information from the type library. Visual studio created for me the tlh and tli file.
Then I tried to obtain the ISimple interface but I'm obtaining the error 0x80040154.
The code I'm running inside the test application is the following:
HRESULT hr = S_OK;
hr = CoInitialize(NULL);
ISimplePtr myRef(__uuidof(ISimple));
// Test prop and method
myRef->Property01 = 5;
LONG test = myRef->Property01;
LONG ret = myRef->Method01(_T("Test input"));
ret = myRef->Method01(NULL);
myRef = NULL;
CoUninitialize();
The row returning the error 0x80040154 is ISimplePtr myRef(__uuidof(ISimple)).
OleView correctly display the interface and in the registry the entries seems to be good.
What am I doing wrong? Any idea?
Regards
The underlying class for these smart COM pointers is _com_ptr_t. You are trying to use this constructor:
// Constructs a smart pointer given the CLSID of a coclass. This
// function calls CoCreateInstance, by the member function
// CreateInstance, to create a new COM object and then queries for
// this smart pointer's interface type. If QueryInterface fails with
// an E_NOINTERFACE error, a NULL smart pointer is constructed.
explicit _com_ptr_t(
const CLSID& clsid,
IUnknown* pOuter = NULL,
DWORD dwClsContext = CLSCTX_ALL
);
Key point is that you have to pass the CLSID of the coclass, you are passing the IID of the interface. That's why __uuidof(Simple) works.

IWebBrowser2: how to force links to open in new window?

The MSDN documentation on WebBrowser Customization explains how to prevent new windows from being opened and how to cancel navigation. In my case, my application is hosting an IWebBrowser2 but I don't want the user to navigate to new pages within my app. Instead, I'd like to open all links in a new IE window. The desired behavior is: user clicks a link, and a new window opens with that URL.
A similar question was asked and answered here and rather than pollute that answered post, it was suggested I open a new discussion.
The members on the related post suggested I should be able to do this by trapping DISPID_BEFORENAVIGATE2, setting the cancel flag, and writing code to open a new window, but I've found out that the browser control gets lots of BeforeNavigate2 events that seem to be initiated by scripts on the main page. For example, amazon.com fires BeforeNavigate2 events like crazy, and they are not a result of link invocation.
Replies appreciated!
What I ended up doing was using IHTMLDocument directly rather than IWebBrowser. IWebBrowser is a superset of IHTMLDocument, and the navigation model implemented by IWebBrowser isn't customizable to the degree I wanted.
I actually got MS Developer Support involved and this approach was their recommendation. They say this is what Outlook uses for HTML-based email, which is the user experience I wanted to emulate. They also confirmed that there's no reliable way to filter the OnBeforeNavigate events that result from user action from those that result from script activity.
Hope this helps anybody facing the same issues. It wasn't too hard to port the code to use IHTMLDocument. If you end up doing this, you may also find yourself looking for a way to figure out when the document is done loading. To do that, hook HTMLDocumentEvents instead of DWebBrowserEvents, and look for the DISPID_HTMLDOCUMENTEVENTS_ONREADYSTATECHANGE event. It doesn't tell you what the ready state is; you need to call IHTMLDocument::get_readyState and parse the resulting string. Goofy, but there you go.
You can bind to onclick event before document is complete while creating browser in OnCreate() using IHTMLDocument2::put_onclick():
#include <comutil.h>
ClickEvents<RootFrame> clickEvents;
_variant_t clickDispatch;
clickDispatch.vt = VT_DISPATCH;
clickDispatch.pdispVal = &clickEvents;
CComQIPtr<IDispatch> dispatch;
hr = webBrowser2->get_Document(&dispatch);
ASSERT_EXIT(SUCCEEDED(hr), "webBrowser->get_Document(&dispatch)");
CComQIPtr<IHTMLDocument2> htmlDocument2;
hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2);
ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)");
htmlDocument2->put_onclick(clickDispatch);
ClickEvents class implements IDispatch, you only need to implement Invoke method, in rest return E_NOTIMPL:
HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
HRESULT hr;
CComQIPtr<IWebBrowser2> webBrowser2;
hr = rootFrame->GetDlgControl(rootFrame->rootview.GetDlgCtrlID(), IID_IWebBrowser2, (void**) &webBrowser2);
ASSERT_EXIT(SUCCEEDED(hr), "rootframe->GetDlgControl(IID_IWebBrowser2) failed");
CComQIPtr<IDispatch> dispatch;
hr = webBrowser2->get_Document(&dispatch);
ASSERT_EXIT(SUCCEEDED(hr), "webBrowser2->get_Document(&dispatch)");
CComQIPtr<IHTMLDocument2> htmlDocument2;
hr = dispatch->QueryInterface(IID_IHTMLDocument2, (void**) &htmlDocument2);
ASSERT_EXIT(SUCCEEDED(hr), "dispatch->QueryInterface(&htmlDocument2)");
CComQIPtr<IHTMLWindow2> htmlWindow2;
hr = htmlDocument2->get_parentWindow((IHTMLWindow2**) &htmlWindow2);
ASSERT_EXIT(SUCCEEDED(hr), "htmlDocument2->get_parentWindow(&htmlWindow2)");
CComQIPtr<IHTMLEventObj> htmlEvent;
hr = htmlWindow2->get_event(&htmlEvent);
ASSERT_EXIT(SUCCEEDED(hr), "htmlWindow2->get_event(&htmlEvent)");
CComQIPtr<IHTMLElement> htmlElement;
hr = htmlEvent->get_srcElement(&htmlElement);
ASSERT_EXIT(SUCCEEDED(hr), "htmlEvent->get_srcElement(&htmlElement)");
CComBSTR hrefAttr(L"href");
VARIANT attrValue;
VariantInit(&attrValue);
hr = htmlElement->getAttribute(hrefAttr, 0 | 2, &attrValue); // 0 = case insensitive, 2 = return BSTR
ASSERT_EXIT(SUCCEEDED(hr), "htmlElement->getAttribute()");
wchar_t href[2084]; // maximum url length in IE, http://support.microsoft.com/kb/208427
wcsncpy_s(href, _countof(href), attrValue.bstrVal, _TRUNCATE);
if (!rootFrame->IsURLAllowed(href)) {
VARIANT variant;
variant.vt = VT_BOOL;
variant.boolVal = VARIANT_FALSE;
htmlEvent->put_returnValue(variant);
ShellExecute(0, L"open", href, 0, 0, SW_SHOWNORMAL);
}
return S_OK;
}
As you can see after querying some interfaces I finally have the element that got clicked, then I call IsURLAllowed() defined in my root frame to check whether to allow opening url in current webbrowser window or whether to open it using default browser on user's computer.
This handles all links even if they were appended to document using javascript.
The same should be done with "onsubmit" events for forms.
I also think I have a solution for "window.location" redirects in javascript, I haven't tested it yet, but I will soon test it and I will update this answer then. You could use a combination of "onunload" and "onbeforeunload" events along with DWebBrowserEvents2::BeforeNavigate2(), after onunload/onbeforeunload are called you will know that user is leaving current page so now in BeforeNavigate2() you can cancel it. You can attach unload events using IHTMLWindow2::put_onunload() and IHTMLWindow2::put_onbeforeunload().
See sources of a complete solution for the "onclick" below.
AttachClickEvents in BrowserFrame:
http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/browser_frame.h?r=709d00b991b5#125
Invoke in ClickEvents(IDispatch):
http://code.google.com/p/phpdesktop/source/browse/phpdesktop-msie/msie/click_events.h?r=a5b0b350c933#132
I'm hypothesising here but yet another approach could be to maintain a count of navigation events, incrementing the counter on DISPID_BEFORENAVIGATE2 and decrementing it on occurrences of DISPID_NAVIGATECOMPLETE2 and DISPID_NAVIGATEERROR. With that in place, you could speculate that whenever you get DISPID_BEFORENAVIGATE2 and your counter is at zero, it is actual user navigation / link invocation.
I have no idea whether this approach would work, or whether those are the right events you'd need to make it work, but it could be worth investigating.
You could try a different approach instead and physically add the attribute target="_blank"
to all <a> tags in the rendered document.
This approach would involve waiting for DISPID_DOCUMENTCOMPLETE and then using IHTMLDocument3::getElementsByTagName() to fetch all of the anchor elements. You would then use IHTMLElement::setAttribute() to set target="_blank" on each of them.
It seems to me, that it you want "to open all links in a new IE window", it means that you want that the opening of new windows must be done in another process. The easiest way to do so: using CreateObject("InternetExplorer.Application") way (see another question which solve a problem, which is opposite to your question: InternetExplorer.Application object and cookie container). With this way you will receive the best isolation from your application and the user who clicks on the link receive all possibilities which exist in IE. You should of cause continue usage of BeforeNavigate2 events to find out the moment when "a new IE window" should be opened.

How to prevent crashing if com dll isnt registered

From some old c++ code im trying to use a com dll, it works fine when the dll is registered, but it crahses if the dll isnt registered.
// Initialize COM.
HRESULT hr = CoInitialize(NULL);
IGetTestPtr ptest(__uuidof(tester));
"Use method from the dll"
// Uninitialize COM.
CoUninitialize();
Is it anyway to check if the dll has been registered, before calling IGetTestPtr ptest(__uuidof(tester))?
Or what is the correct way to prevent the crash?
Calling CreateInstance on your object will return an HRESULT that can be tested for success:
IGetTestPtr p = null;
HRESULT hRes = p.CreateInstance( __uuidof(tester) );
bool bSuccess = SUCCEEDED(hRes);
This assumes you've created an interface wrapper around your type library using Visual Studio, where COM Smart Pointers are used in the interface (this gives you the CreateInstance method).
If the DLL is registered, there is a record in HKCR/CLSID/{uuidof(tester)} (curly brackets do matter).
Actually, if it's not, then CoCreateInstance will return an error. Check for this error before using the pointer.
If the COM class is not registered then CoCreateInstance will return REGDB_E_CLASSNOTREG. You should check for general success or failure by using the SUCCEEDED() or FAILED() macros. You also need to create the object properly - see MDSN for an introduction to COM. For example:
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
IGetTestPtr ptr = NULL;
hr = CoCreateInstance(CLSID_MyObjectNULL, CLSCTX_INPROC_SERVER, IID_IMyInterface, ptr)
if (SUCCEEDED(hr))
{
Do something with your COM object
...
// Don't forget to release your interface pointer or the object will leak
ptr->Release();
}
hr = CoUninitialize();
}
return hr;
If I recall correctly this "#import" styled COM framework throws exceptions. Add a try-catch block around the code. Google tells me the exceptions are of type _com_error.
Unless you twiddle the params on #import to prevent this, all failing COM calls are going to throw exceptions. You're going to have to turn this off (#import "raw" interfaces I think does it - look at the #import docs) or get real familiar with these exceptions.