Skip to content

Outlook: inProcess implementation to report replied / replied all / forwarded status on mail items in the message list#8756

Merged
michaelDCurran merged 12 commits into
masterfrom
i6911
Sep 19, 2018
Merged

Outlook: inProcess implementation to report replied / replied all / forwarded status on mail items in the message list#8756
michaelDCurran merged 12 commits into
masterfrom
i6911

Conversation

@michaelDCurran

@michaelDCurran michaelDCurran commented Sep 17, 2018

Copy link
Copy Markdown
Member

Link to issue number:

Fixes #6911

Summary of the issue:

Outlook's message list shows if a mail item has been replied to or forwarded, however NVDA does not report this information.
Previous tries at implementing this which failed were:

  • Fetch the info from UI Automation: UIA does expose text denoting replied/forwarded via the value pattern. However, this text is localized and therefore is impossible to match on programmatically with 100% accuracy. It is also included with other text such as read/unread status and mail item type, which we must strip.
  • Although the Outlook object model itself does not have a specific property on mail items conveying the last executed action (replied to, forwarded), it does have a propertyAccessor property which allows fetching MAPI properties. Fetching the status this way worked for some users, but for those who had set very high security policies, the fetching of the PropertyAccessor property caused a security dialog to appear. Further testing showed that even if ths property was fetched inProcess the security restrictions still applied.

Description of how this pull request fixes the issue:

The Outlook object model provides direct access to the MAPI object via a mapiObject property on mail items. However, as the IMAPIProp interface cannot be marshalled by COM, all calls must be made on the object directly, in the object's own COM apartment (the Outlook GUI thread).
This PR implements a new outlook_getMAPIProperty RPC call which marshals the given MAPI object back into its own thread, and fetches the requested property from there.

Testing performed:

  • Outlook 2016/365.
  • NVDA installed (UIAccess), portable, and running from source.
  • Outlook is set to block any insecure access (I.e. if NVDA fetches MailItem.PropertyAccessor, the security dialog appears).
  • Focus a mail item in the message list. NVDA reports if it is forwarded, replied, or replied all. No security dialog ever appears.

Known issues with pull request:

  • Although security restrictions are not currently applied to MailItem.mapiObject, there is no reason why they may not get applied in a future version of Outlook.
  • MailItem.mapiObject is deprecated in current versions of Outlook, though still functions at this point in time. A future version of Outlook may remove support completely. If this occurs, this code is already ready to handle a COM error or a NULL COM pointer. Worse case, NVDA simply stops reporting these statuses.

Change log entry:

New features:

Section: New features, Changes, Bug fixes

…s in the message list, by fetching info from the mail item's MAPI object in-process, bypassing security restrictions.

@LeonarddeR LeonarddeR left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a C++ expert, so it might be good to have @feerrenrut have a quick look at the NVDAHelper code, especially the new outlook module.

Comment thread nvdaHelper/common/libraryLoader.h Outdated
#include <windows.h>

// A Smart Library handle.
// Constrcut it with a handle returned by LoadLibrary or similar.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constrcut > Construct

pGIT->GetInterfaceFromGlobal(cookie,IID_IUnknown,(void**)&mapiObject);
if(res!=S_OK) {
LOG_ERROR(L"Could not marshal object");
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be return res?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently execInThread only takes callables that return void. I made res be passed into the lambda by reference thus once execInThread returns, res contains what ever error was inside. However, I should at least be returning res from the outer rpc function, which I forgot to do.

@LeonarddeR

Copy link
Copy Markdown
Collaborator
I'm getting the following error while arrowing through an outlook messages list INFO - RPC process 11932 (OUTLOOK.EXE) (08:33:23.914): Thread 1356, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 43: MAPI object in rpc thread: 0000019A74245B80

ERROR - RPC process 11932 (OUTLOOK.EXE) (08:33:23.921):
Thread 1356, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 62:
Could not locate function HrGetOneProp in mapi32.dll

This is outlook '16.0.10730.20088 X64

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

Copy link
Copy Markdown
Collaborator

This was a standalone clikc to run installer

Strange enough, the HrGetOneProp function is there, so I don't get why neither ctypes nor NVDAHelper can't get it:

HrGetOneProp@12	0x1000d2d0	0x0000d2d0	135 (0x87)	mapi32.dll	C:\Windows\syswow64\mapi32.dll	Exported Function	

HrGetOneProp@12	0x1000d2d0	0x0000d2d0	135 (0x87)	mapi32.dll	C:\Windows\system32\mapi32.dll	Exported Function	

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Actually, the HrGetOneProp docs indicate that this support is limited to Outlook >=2013.

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Yes, My office is 64 bit indeed.

dllex is reporting the @12 suffix for both the x86 and x64 instance on my system. File version of mapi32.dll is 1.0.2536.0. Product version is 10.0.17134.1. This one looks like to be bundled with the OS, so I wonder whether Outlook version installed makes a difference.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

A plain Windows 7 x86 VM without outlook installed also has the mapi32.dll variant with the @12 suffix.

IN this article, Microsoft explains how to link to MAPI functions. There's also an article to choose a specific version

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Yes, this works like a charm.

I just pushed a try build for this, so I can test this on an Outlook 2010 system.

@LeonarddeR

LeonarddeR commented Sep 17, 2018

Copy link
Copy Markdown
Collaborator

This definitely causes heavy crashes when used with Outlook 2010. The outlook process is terminated as soon as a message gets focus.

Error log
ERROR - RPC process 8236 (nvda_slave.exe) (11:12:53.848):
__main__.main:
slave error
Traceback (most recent call last):
  File "nvda_slave.pyw", line 94, in main
  File "comHelper.pyc", line 22, in _lresultFromGetActiveObject
  File "comtypes\client\__init__.pyc", line 180, in GetActiveObject
  File "comtypes\__init__.pyc", line 1247, in GetActiveObject
  File "_ctypes/callproc.c", line 950, in GetResult
WindowsError: [Error -2147221021] Bewerking is niet beschikbaar
INFO - RPC process 11776 (OUTLOOK.EXE) (11:12:56.298):
Thread 14208, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 43:
MAPI object in rpc thread: 0000000010BB8430

INFO - RPC process 11776 (OUTLOOK.EXE) (11:12:56.298):
Thread 4112, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp::<lambda_d6a1fe29f2f3f1f2a36dfe801802c01b>::operator (), 98:
MAPI object in GUI thread: 1

ERROR - RPC process 8808 (nvda_slave.exe) (11:13:09.305):
main.main:
slave error
Traceback (most recent call last):
File "nvda_slave.pyw", line 94, in main
File "comHelper.pyc", line 22, in lresultFromGetActiveObject
File "comtypes\client_init
.pyc", line 180, in GetActiveObject
File "comtypes_init_.pyc", line 1247, in GetActiveObject
File "_ctypes/callproc.c", line 950, in GetResult
WindowsError: [Error -2147221021] Bewerking is niet beschikbaar
INFO - RPC process 3008 (OUTLOOK.EXE) (11:13:12.579):
Thread 7516, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 43:
MAPI object in rpc thread: 0000000010486920

INFO - RPC process 3008 (OUTLOOK.EXE) (11:13:12.585):
Thread 2188, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp::<lambda_d6a1fe29f2f3f1f2a36dfe801802c01b>::operator (), 98:
MAPI object in GUI thread: 1

ERROR - RPC process 5100 (nvda_slave.exe) (11:13:25.823):
main.main:
slave error
Traceback (most recent call last):
File "nvda_slave.pyw", line 94, in main
File "comHelper.pyc", line 22, in lresultFromGetActiveObject
File "comtypes\client_init
.pyc", line 180, in GetActiveObject
File "comtypes_init_.pyc", line 1247, in GetActiveObject
File "_ctypes/callproc.c", line 950, in GetResult
WindowsError: [Error -2147221021] Bewerking is niet beschikbaar
INFO - RPC process 11572 (OUTLOOK.EXE) (11:13:39.536):
Thread 15284, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 43:
MAPI object in rpc thread: 000000000A2DC1A0

INFO - RPC process 11572 (OUTLOOK.EXE) (11:13:39.536):
Thread 14540, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp::<lambda_d6a1fe29f2f3f1f2a36dfe801802c01b>::operator (), 98:
MAPI object in GUI thread: 1

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

…erfaceTable::getInterfaceFromGlobal, and return on error.
@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Still crashes:

ERROR - RPC process 76 (nvda_slave.exe) (13:27:34.888):
__main__.main:
slave error
Traceback (most recent call last):
  File "nvda_slave.pyw", line 94, in main
  File "comHelper.pyc", line 22, in _lresultFromGetActiveObject
  File "comtypes\client\__init__.pyc", line 180, in GetActiveObject
  File "comtypes\__init__.pyc", line 1247, in GetActiveObject
  File "_ctypes/callproc.c", line 950, in GetResult
WindowsError: [Error -2147221021] Bewerking is niet beschikbaar
INFO - RPC process 5740 (OUTLOOK.EXE) (13:27:39.892):
Thread 3540, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp, 43:
MAPI object in rpc thread: 00000000109B4150

INFO - RPC process 5740 (OUTLOOK.EXE) (13:27:39.903):
Thread 3972, build\x86_64\remote\outlook.cpp, nvdaInProcUtils_outlook_getMAPIProp::<lambda_8eeec166f96c7951c3b21da844cd2873>::operator (), 98:
MAPI object in GUI thread: 1

@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

…er than PM_REMOVE, so check for PM_REMOVE specifically rather than PM_NOREMOVE. This stops messages being processed more than once if fetched with peakMessage with no PM_REMOVE but other flags.
@michaelDCurran

michaelDCurran commented Sep 17, 2018 via email

Copy link
Copy Markdown
Member Author

@michaelDCurran

michaelDCurran commented Sep 18, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

Copy link
Copy Markdown
Collaborator

The Outlook 2010 system is actually my colleague's system, but it indeed has Outlook 2010 X64. With the last try build, there are no crashes and things are reported as expected.

LeonarddeR
LeonarddeR previously approved these changes Sep 18, 2018

@LeonarddeR LeonarddeR left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strange errors in the Outlook 2010 instance NVDA log either.

Comment thread nvdaHelper/remote/outlook.cpp Outdated

// Our RPC function
error_status_t nvdaInProcUtils_outlook_getMAPIProp(handle_t bindingHandle, const long threadID, IUnknown* mapiObject, const unsigned long mapiPropTag, VARIANT* retVal) {
LOG_INFO(L"MAPI object in rpc thread: "<<mapiObject);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to make this debug before merging.

Comment thread nvdaHelper/remote/outlook.cpp Outdated
LOG_ERROR(L"Could not unmarshal object, code "<<res);
return;
}
LOG_INFO(L"MAPI object in GUI thread: "<<static_cast<IUnknown*>(mapiObject));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

//GetMessage hook callback
LRESULT CALLBACK inProcess_getMessageHook(int code, WPARAM wParam, LPARAM lParam) {
if(code<0||wParam==PM_NOREMOVE) {
if(code<0||!(wParam&PM_REMOVE)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this equal to
if(code<0||wParam~PM_REMOVE) {
I consider that to be somewhat more readble, but that's just a personal preference.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~ in c++ is a unary operator I think. So it would have to be wParam&~PM_REMOVE.

Comment thread nvdaHelper/remote/outlook.cpp Outdated
// The following declarations come from MAPIDEFS.h which is no longer included in the Windows SDK

#define PT_LONG ((ULONG)3)
#define MAPI_E_NOTFOUND 0x8004010f

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than #defines, can these be changed constexpr

Comment thread nvdaHelper/remote/outlook.cpp Outdated
// Right now this function only supports MAPI properties with a type of long.
// To support more types, we would need to know how to correctly pack them into a VARIANT.
LOG_ERROR(L"Unsupported MAPI prop type");
return -1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than -1 can we have a named constant please.

Comment thread nvdaHelper/remote/outlook.cpp Outdated
execInThread(threadID,[=,&res](){
// Unmarshal the IUnknown pointer from the COM global interface table.
IUnknownPtr mapiObject=nullptr;
res=pGIT->GetInterfaceFromGlobal(cookie,IID_IUnknown,(void**)static_cast<IUnknown**>(&mapiObject));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(void**)static_cast<IUnknown**>(&mapiObject) looks wrong to me. Why cast to IUnknown** and then c-style cast to void**

Comment thread nvdaHelper/remote/outlook.cpp Outdated
res=HrGetOneProp(mapiObject,mapiPropTag,&_propValue);
propValue.reset(_propValue);
}
if(res!=S_OK||!propValue) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, res could be returned by the outer function with S_OK even though there was an error due to !propValue

Comment thread source/appModules/outlook.py Outdated
if mapiObject:
v=comtypes.automation.VARIANT()
res=NVDAHelper.localLib.nvdaInProcUtils_outlook_getMAPIProp(self.appModule.helperLocalBindingHandle,self.windowThreadID,mapiObject,PR_LAST_VERB_EXECUTED,ctypes.byref(v))
if res==0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a name for 0 instead.

@michaelDCurran

michaelDCurran commented Sep 19, 2018 via email

Copy link
Copy Markdown
Member Author

@feerrenrut

Copy link
Copy Markdown
Contributor

Actually, no cast is necessary to cast to void** I believe this will happen implicitly. However, if its important to ensure that &mapiObject is dynamically convertible to IUnknown** then I would prefer this is done separately with a comment to explain. Like so:

// static_cast to ensure that the smart pointer's address-taking operator (&) correctly uses the encapsulated IUnknown* pointer
auto mapiObjPtr = static_cast<IUnknown**>(&mapiObject) 
res=pGIT->GetInterfaceFromGlobal(cookie, IID_IUnknown, mapiObjPtr);

That said, it's not necessary at all, mapiObject is a pointer type and thus can't override the & operator, so the result of &mapiObject has to be IUnknown**. So I think my preference would be to simplify the code to:

res=pGIT->GetInterfaceFromGlobal(cookie, IID_IUnknown, &mapiObject);

Comment thread nvdaHelper/remote/outlook.cpp Outdated
LOG_ERROR(L"NULL MAPI object");
return E_INVALIDARG;
}
if((mapiPropTag&0xffff)!=PT_LONG) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having 0xffff hard coded here, it might be better to give it a name. This ties in with the comment in nvdaInProcUtils right? Perhaps this could be called tagMask

Comment thread source/appModules/outlook.py Outdated
mapiObject=None
if mapiObject:
v=comtypes.automation.VARIANT()
res=NVDAHelper.localLib.nvdaInProcUtils_outlook_getMAPIProp(self.appModule.helperLocalBindingHandle,self.windowThreadID,mapiObject,PR_LAST_VERB_EXECUTED,ctypes.byref(v))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since this line is quite long, please split the arguments onto multiple lines.

@michaelDCurran

michaelDCurran commented Sep 19, 2018 via email

Copy link
Copy Markdown
Member Author

@michaelDCurran

michaelDCurran commented Sep 19, 2018 via email

Copy link
Copy Markdown
Member Author

feerrenrut
feerrenrut previously approved these changes Sep 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NVDA not inform about if an e-mail in Outlook 2013 is answered or forwarded

4 participants