Python and COM

Home People COM Emulation
Python

Abstract

I have been playing with Python and COM on Windows 2000. Whilst the built-in help in PythonWin does go some way towards explaining the bizarre details of COM and the various common interfaces one might find, it is really necessary to go looking for concrete interface information. There are a few sources of such information, and the aim of this document is to help readers use such sources, or even to help them avoid the need to use such sources.

Prerequisites

Perhaps the easiest way of obtaining Python for Windows platforms is to go to ActiveState and download their ActivePython package. You can alternatively visit the Python site, download Python (see the Python support for Windows page) and then go looking for the win32all extensions (see Mark Hammond's Python for Windows Extensions page for details). I just did what was easiest, of course!

For the activities given below, you may need extra software such as Microsoft Outlook, although you probably won't be interested in playing with Outlook and Exchange if you don't have them.

Outlook and Exchange

One of the supposedly irritating things about Microsoft Exchange Server is the way that it hides a lot of its internals and requires a fair amount of programming to let you get to things such as contact lists and address books. It's interesting to note that much advice is to be found on importing other systems' address books into Exchange but not as much on getting the addresses back out and into something else. Fortunately, Microsoft provide enough of an API to become its own "undoing", since this API provides a reasonable mechanism to access the internals and to extract the locked-in information.

Anyway, this led me to write a small program to access Exchange's message lists, contacts and appointments through Outlook. If your organisation is running Exchange, you're probably required to run Outlook as well, and therefore automating Outlook is a valid strategy in accessing Exchange. One alternative for accessing certain types of information is to use the Active Directory Services API and/or LDAP, but I haven't been successful in that area.

Example: Outlook Explorer

Note: Before starting this application, build the "Microsoft Outlook 9.0 Object Library" using the "COM Makepy utility" (see "Initialising Type Libraries").

The Outlook Explorer program (which you can download) uses MAPI (Messaging API) within Outlook to access mail resources. You can navigate the object hierarchy using a simple command-line interface, and extract information to existing directories in the filesystem.

Activities

Initialising Type Libraries

Whilst the win32com package provides a number of ways of querying and accessing COM interfaces with few obvious differences between them (provided you know the interface), it is usually more convenient when experimenting (particularly in PythonWin with its attribute completion feature) to know what methods and properties are really available for a given interface. This is where the "COM Makepy utility", accessible from the "Tools" menu in PythonWin, is useful. By selecting a particular type library and building the Python interfaces using this tool, much more information becomes available in PythonWin, and casual investigation of object interfaces becomes much easier.

Writing Clients

All client COM access starts with the following statement in your Python program:

import win32com.client

Typically, your program will then try and find an object to use. For example:

object = win32com.client.Dispatch("Outlook.Application")

This object will then refer to a "COM object" which can have methods invoked upon it and its attributes examined like any other Python object, although we may be "automating" a Windows application (such as Outlook) when we do this.

Finding the Object Hierarchy Root

Once we have a reference to an object which is "automating" an application, the next thing to do is to find the root of the object hierarchy where all the interesting information is stored. With the Outlook application's automation object, the easiest way to achieve this is to enter the MAPI namespace:

ns = object.GetNamespace("MAPI")

This allows us to access the "folder" hierarchy accessible within Outlook. You can see this hierarchy yourself by opening Outlook manually and bringing up the folder menu (which typically says "Inbox" or "Outlook Today" or something).

The ns object can now be treated like a normal Python object, with special attributes leading to other parts of the object hierarchy.

Folders and Items

The Outlook Explorer program uses some fancy Python tricks to access the attributes of the "COM objects" it references, but in essence it looks up the folders and items available within each object:

# Refer to the folders within a given object...
ns_list = ns.Folders
# Refer to the items within a given object...
ns_list = ns.Items

Querying Interfaces

Now, how does one investigate the detail of each object? For example, how does one access the name of a folder? Firstly, it helps to know the interface that the object exposes, and this information can be found in several places:

Hopefully, however, the object that you are accessing is known well enough by PythonWin to permit some kind of attribute or method completion on it. You should only need to resort to the above when more detailed knowledge about a method or attribute is required. You can also try something like this:

dir(object.__class__)

The name of a folder can be accessed as follows:

object.Name # Where object refers to a folder.

Sequences

Of course, there can be many folders or items in a folder. How do we traverse this collection? The recommended way is arguably to use Python's sequence index notation, but beware: the first element's index apparently is not standard and can be 0 or 1. In Outlook's object model, the first element in such sequences is indeed indexed by 1, resulting in the following observations:

To access objects which behave like sequences, the following Python mechanisms can be used:

object[1]   # The first element.
len(object) # Return the number of elements.

The following mechanisms do not seem to work:

object[-1]  # Should be the last element, but instead we get an exception.
object[1:4] # Should slice the sequence, but instead we get an exception.

Summary

With the above information, the example program can access different levels of folders, query items and extract information. By using the Parent attribute on any given folder, we can navigate upwards through the hierarchy without needing to keep references to places we have already explored.

Translations of this Document