enthought / comtypes

A pure Python, lightweight COM client and server framework, based on the ctypes Python FFI package.
Other
290 stars 97 forks source link

Reflected Assignment Fails #494

Closed DragonRulerX closed 1 year ago

DragonRulerX commented 1 year ago

Hi all,

I'm trying to figure out what I'm probably doing wrong, but on the off-chance it's a bug I wanted to report it.

I'm currently attempting to write some logic for handling programmatically generating rules in Microsoft Outlook 365 (v2306). Said generated rules currently handle the condition "from fake@email.com or junk@email.com" just fine, but I want it to be a condition like "with '@marketing' or '@orders' in sender's address" and I'm now running into problems due to my nigh complete ignorance of COM stuff in general. I'm not sure where to start looking for information on how to do much of this in Python either?

In an attempt to figure things out on my own I made a rule in Outlook through its UI with a condition like "with @email.com in sender's address" and attempted to interrogate the generated rule object in code for answers on how to configure my own. However, here is where I may have found a bug or am simply showing my ignorance.


(closing remarks below code)

import comtypes.client

o = comtypes.client.CreateObject("Outlook.Application")
_rules = o.Session.DefaultStore.GetRules()

def main():
    # I can clearly find the rule I configured in Outlook
    _obj = _rules.Item(3)
    print(_obj)            # <POINTER(_Rule) ptr=0x1d537383fe8 at 1d538dcb4c0>
    print(_obj.Name)       # Delete: {TEST} @email.com

    # I can also verify the Conditions interface exists
    _conds = _obj.Conditions
    print(_conds)          # <POINTER(_RuleConditions) ptr=0x1d539da6a88 at 1d538dcb1c0>

    # I can even get to _AddressRuleCondition (though, I don't know how to make my own)
    _sender_address = _conds.SenderAddress
    print(_sender_address) # <POINTER(_AddressRuleCondition) ptr=0x1d5390de9e8 at 1d53aa12640>

    # This Address property seems to be set and is a tuple data type
    _address = _sender_address.Address
    print(_address)        # ('@email.com',)
    print(type(_address))  # <class 'tuple'>

    # However, attempting to do any of the following fail in similar ways - most
    # surprising of which (to me) is the assignment of itself reflected back also failing.
    #
    # _sender_address.Address = "abc"
    # _sender_address.Address = ["abc"]
    # _sender_address.Address = ("abc",)
    # _sender_address.Address = _sender_address.Address
    # _sender_address.__AddressRuleCondition__com__set_Address("abc")
    # _sender_address.__AddressRuleCondition__com__set_Address(["abc"])
    # _sender_address.__AddressRuleCondition__com__set_Address(("abc",))
    # _sender_address.__AddressRuleCondition__com__set_Address(_sender_address.Address)

    # Traceback (most recent call last):
    #   File "...\test.py", line 47, in <module>
    #     main()
    #   File "...\test.py", line 35, in main
    #     _sender_address.__AddressRuleCondition__com__set_Address(_sender_address.Address)
    # _ctypes.COMError: (-2147024809, 'The parameter is incorrect.', ('Sorry, something went wrong. You may want to try again.', 'Microsoft Outlook', None, 0, None))

if __name__ == "__main__":
    main()

I'm not sure if this is a bug? I have a strong feeling it's just my ignorance, but if not then at least I've reported it.

If it's not a bug - I'm hoping someone has the knowledge to help me understand what I'm doing wrong and can help point me to the correct resources so I can learn more about this on my own.

Thank you in advance!

junkmd commented 1 year ago

Hi!

I think this is not a Python or comtypes spec problem, but rather one related to the Outlook spec.

I am not that familiar with Outlook, but I often use the COM API of Excel, which also belongs to MS Office.

When dealing with Excel in comtypes, I often read the MS VBA API reference. The language of the code snippets exemplified in those documents is of course VBA, but they describe the behavior of COM objects.

And although there are some peculiar syntaxes, such as method calls sometimes not requiring (), property calls sometimes requiring (), etc., if you just read them, I think that you can easily read them if you have experience with projects using object-oriented languages such as Python or Java.

From the printed class names, the following will probably be helpful.

https://learn.microsoft.com/en-us/office/vba/api/outlook.rule.conditions https://learn.microsoft.com/en-us/office/vba/api/outlook.store.getrules https://learn.microsoft.com/en-us/office/vba/api/outlook.rule https://learn.microsoft.com/en-us/office/vba/api/outlook.ruleconditions.senderaddress https://learn.microsoft.com/en-us/office/vba/api/outlook.addressrulecondition.address

I hope this helps.

DragonRulerX commented 1 year ago

Thank you for the response!

I think this is not a Python or comtypes spec problem, but rather one related to the Outlook spec.

Could you elaborate a bit more on what you mean?

I had actually reviewed those links you mentioned prior to writing here, but I was not able to identify what I was missing to get this to work. I agree the documents aren't too difficult to navigate, but I'm assuming my current example is either a bug or a gap in understanding on my part for what I need to do to properly set this value.

Any further help/advice would be greatly appreciated!

junkmd commented 1 year ago

I have guessed the cause of this error.

Please keep in mind that comtypes are highly dependent on the user's environment, and I do not know the specifications of all COM libraries, so I cannot be certain of the cause.

I suspect that this is not a bug in comtypes, but rather a COM method/property in Outlook that was not passed the proper value argument.

First, the error code of the _ctypes.COMError is -2147024809. This is the error code for "One or more arguments are invalid". https://github.com/enthought/comtypes/blob/d1f5cd7e6c73c52f30a36c226cdde21586cdc4b7/comtypes/hresult.py#L15 https://learn.microsoft.com/en-us/windows/win32/com/com-error-codes-1

Second, the error message is quite developer friendly. This can be assumed that the Outlook COM library's error raising is carefully implemented. The error messages would not be as friendly if the cause of error was comtypes spec.

It may be just my imagination, the value to assign to _sender_address.Address should be an sequence of "appropriate strings as email addresses" (i.e., not ("abc",) but ("abc@xyz.com",) or ("abc@xyz.com", " opq@lmn.com")).

I found the following while researching the Outlook COM API. https://www.experts-exchange.com/questions/24617817/Parsing-Subject-to-retrieve-forward-email-in-Outlook-2007-without-Outlook-MailItem.html

Best regards.

junkmd commented 1 year ago

What version of Python and comtypes do you use? It may be something like #212, which was caused by a bug in older cpython.

DragonRulerX commented 1 year ago

Ah, that link for error codes I'm sure will prove useful!

However, I do believe your assumption on the values is incorrect. My reasoning is because when I created the rule from Outlook's UI and inspected it in the code I provided above we can see when I am printing _sender_address.Address (seen above as me just printing _address) you'll notice I provided output which displays ('@email.com',). Therefore, I feel it's reasonable to conclude that the UI generated an object structure which contained a tuple of strings precisely as I provided it in the UI which were simply substrings of other fully qualified email address strings. This is further to my point of why I feel this behavior is a bug or at the very least misleading and unexpected (with consideration for my COM ignorance) as I should typically expect that reflecting the value back to the variable it originated from should be a valid operation (in most designs anyway). This implies the real value is not a tuple of strings, but some other object type (of which I don't know or how to obtain/create). Yet, it presents its data type as a tuple of strings which it deems wrong for reassignment.

I have yet to read your last two links (the outlook API link and the older bug link), but I intend to follow up on those later so I'll withhold comment on those until I have more time to understand the material than I do at present.

However, regarding your last inquiry on the versions I am using - they are as follows:

Any further help/advice will be greatly appreciated!

junkmd commented 1 year ago

Could you try the same script with Python>=3.10.10 or Python>=3.11.2?

I suspect a bug in the ctypes that existed in earlier versions than these.

from https://github.com/enthought/comtypes/issues/212#issuecomment-1426573913;

gh-99952: Fix a reference undercounting issue in ctypes.Structure with from_param() results larger than a C pointer.

https://docs.python.org/release/3.10.10/whatsnew/changelog.html#library https://docs.python.org/release/3.11.2/whatsnew/changelog.html#library

DragonRulerX commented 1 year ago

Sorry for the delayed response - got busy.

Upgrading to Python 3.10.10 did seem to fix the reported issue!

I'm not entirely sure I understand why yet since I again have to run, but I'll definitely need to read through your links further very soon to get a better understanding of this and why the changes discussed in your links affects this behavior.

junkmd commented 1 year ago

Thank you for your report.

212 was a very challenging error to identify and resolve. We attempted to address it within the comtypes codebase, but every approach could have resulted in significant changes and impact.

Without finding someone with detailed knowledge of cffi and ctypes implementations, we would not have been able to resolve it on the CPython side.

If there are no other concerns, I plan to close this issue as resolved in the coming days. Or you are welcome to close it yourself if you prefer.