9

I am a newbie in Python. I have to extract emails from Outlook and get all attributes/properties of the emails.

Retrieving properties one by one, for the attributes/properties that I know they exist works fine (.Subject, .Body, etc.).

But, I need to get all possible attributes. That's where my problem is. I have been looking for hours, the only answers I found were using:

  • vars()
  • dir()
  • inspect.getmembers(obj)
  • __dict__
  • etc.
Which does not give me the list of properties like:

  • .Subject
  • .Body
  • .SentOn
  • etc.
Could someone help ?

Here is the extract of my test Notebook :

####### Retrieve email from Outlook #######
import win32com.client
objOutlookMAPI=win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
​    
​### Define folder
objOlFolder = objOutlookMAPI.GetDefaultFolder(6)

​### Retrieve ant print email
objOlMessages = objOlFolder.Items
​
# objMessage : class 'win32com.client.CDispatch'
objMessage = objOlMessages.GetLast()
print(objMessage.Subject)    

> Are you going to Las Vegas for Black Hat, DefCon, Bsides, or Hacking Diversity? Either or join us on our adventures!

vars(objMessage)

> {'_builtMethods_': {},
 '_enum_': None,
 '_lazydata_': (<PyITypeInfo at 0x0000021EC7B7D170 with obj at 0x0000021EC7B4B2F8>,
  <PyITypeComp at 0x0000021EC7B7D620 with obj at 0x0000021EC7B4B058>),
 '_mapCachedItems_': {},
 '_oleobj_': <PyIDispatch at 0x0000021EC7B7D290 with obj at 0x0000021EC7B4AAA8>,
 '_olerepr_': <win32com.client.build.LazyDispatchItem at 0x21ec8a7ba90>,
 '_unicode_to_string_': None,
 '_username_': 'GetLast'}


    dir(objMessage)
    #import inspect
    #inspect.getmembers(objMessage)

> [`'_ApplyTypes_'`,
 `'_FlagAsMethod'`,
 `'_LazyAddAttr_'`,
 `'_NewEnum'`,
 `'_Release_'`,
 `'__AttrToID__'`,
 `'__LazyMap__'`,
 `'__bool__'`,
 `'__call__'`,
 `'__class__'`,
 `'__delattr__'`,
 `'__dict__'`,
 `'__dir__'`,
 `'__doc__'`,
 `'__eq__'`,
 `'__format__'`,
 `'__ge__'`,
 `'__getattr__'`,
 `'__getattribute__'`,
 `'__getitem__'`,
 `'__gt__'`,
 `'__hash__'`,
 `'__init__'`,
 `'__init_subclass__'`,
 `'__int__'`,
 `'__le__'`,
 `'__len__'`,
 `'__lt__'`,
 `'__module__'`,
 `'__ne__'`,
 `'__new__'`,
 `'__reduce__'`,
 `'__reduce_ex__'`,
 `'__repr__'`,
 `'__setattr__'`,
 `'__setitem__'`,
 `'__sizeof__'`,
 `'__str__'`,
 `'__subclasshook__'`,
 `'__weakref__'`,
 `'_builtMethods_'`,
 `'_enum_'`,
 `'_find_dispatch_type_'`,
 `'_get_good_object_'`,
 `'_get_good_single_object_'`,
 `'_lazydata_'`,
 `'_make_method_'`,
 `'_mapCachedItems_'`,
 `'_oleobj_'`,
 `'_olerepr_'`,
 `'_print_details_'`,
 `'_proc_'`,
 `'_unicode_to_string_'`,
 `'_username_'`,
 `'_wrap_dispatch_'`]
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
  • There is likely some magic in `__getattr__` or `__getattribute__`. – Stephen Rauch Aug 06 '18 at 01:19
  • 1
    So, after further study it appears there is no easy way to introspect the com objects. You will likely need to use the MSDN documentation, or maybe the VBA debugger. – Stephen Rauch Aug 06 '18 at 01:42
  • In general, COM objects can't enumerate their methods, so there's nothing `win32com` can do about that. But: (1) If it's an `IDispatch` interface that supports `IDispatchEx`, you can just `QueryInterface` to that and call the `GetIDsOfNames` method. (2) If it's an `IDispatch` interface that doesn't do `IDispatchEx`, you can call `GetTypeInfo` on it. (2a) If the resulting `ITypeInfo` supports `ITypeInfo2` you can QI that and enumerate it. (2b) If it doesn't, but it does support `GetTypeAttr` and `GetFuncDesc`, you can use those. (3) If it's none of the above, there is no way. – abarnert Aug 06 '18 at 02:39
  • Try searching for those method names with `win32com` to see if there are any examples. I didn't find any with a quick search—but I did find [this question](https://stackoverflow.com/questions/2112302/enumerate-com-object-idispatch-methods-using-atl) for ATL, which covers how to use all those methods, if you know how to translate from C++. – abarnert Aug 06 '18 at 02:40
  • Thanks a lot for your answers guys. I found also this http://timgolden.me.uk/pywin32-docs/html/com/win32com/HTML/PythonCOM.html and this https://www.productiverage.com/idispatch-iwastedtimeonthis-but-ilearntlots. From what I understand there could be the answer of what I am looking for. But it's way over my abilities (like porting C++ into Python). If nobody has a python code ready, I guess I will have to go through VB and explore the attributes/properties using VBA variable explorer or object explorer... Thanks again. – NewbieOneCannotBe Aug 06 '18 at 21:11

2 Answers2

2

For posterity here, I found what I needed in the AppointmentItem object on this page:

Outlook Visual Basic for Applications (VBA) reference

Just expand that object on the left side of the page, and then expand properties. Found everything I needed there. Also a MeetingItem object, still in the process of figuring out the difference (which sounds like it's obvious, but a lot of the appointments I'm getting back are actually meetings (i.e. have attendees).

0

I found a way to do it, actually:

The plan is to create a macro in a second presentation that you call from within python and that is, with the help of tlbinf32.dll, able to get all members of an object.

The Second Presentation

  1. To start, you want to download the tlbinf32.dll.
    FOR 64-bit SYSTEMS: Follow this guide to properly set up the 32-bit-DLL! I also had problems with the HP Print Scan Doctor Service: I needed to disable that service and uninstall HP Smart. (and reboot)
  2. Create a new .pptm file (pptx with macros) and open it (I named it GetMembersUtil.pptm)
  3. In that presentation, create a new macro (the name is irrelevant) and edit it (View->Macros)
  4. Add a reference to your tlbinf32.dll that you just downloaded (Tools->References)
  5. Paste the following function after the macro
Function GetMembers(obj)
    ' Declare vars
    Dim TLI
    Dim TypeInfo
    Dim Members
    
    ' Create new ArrayList since we don't know how many Members obj has
    Set Members = CreateObject("System.Collections.ArrayList")
    
    ' Initialize tlbinf
    Set TLI = CreateObject("TLI.TLIApplication")
    Set TypeInfo = TLI.InterfaceInfoFromObject(obj)
    
    ' iterate over each Member
    For Each MemberInfo In TypeInfo.Members
        Members.Add MemberInfo.Name
    Next
    
    ' Convert ArrayList back to native vba Array because you can't return an ArrayList
    GetMembers = Members.ToArray()
    
End Function

this function takes an object as input and returns all the members of that object

  1. Save your presentation in the local directory of your python file

The Python Side

To be able to use the macro we need to make sure that the presentation we just created is openend

def vba_get_members(app: win32com.client.CDispatch, obj: win32com.client.CDispatch) -> tuple[str, ...]:
    # os.path.abspath is very important
    ensure_opened(app, os.path.abspath("GetMembersUtil.pptm"))
    return app.Run("GetMembersUtil.pptm!GetMembers", obj)

def ensure_opened(app: win32com.client.CDispatch, file_path):
    # check if presentation is already opened
    for presentation in app.Presentations:
        pp_filepath = os.path.join(presentation.Path, presentation.Name)

        if pp_filepath == file_path:
            # presentation already opened
            break
    else:
        # presentation wasn't already opened
        app.Presentations.Open(file_path)
Belissimo
  • 1
  • 1
  • 2