Home | People | COM | Emulation |
---|---|---|---|
Python |
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.
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.
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.
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.
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.
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.
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.
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
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:
win32com/gen_py
directory within your Python distribution, or invoke an erroneous method
or access a non-existent attribute on an object with that interface in
order to see what the name of the interface file is.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.
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:
len(object)
gives 34
then the last element does
not have the index 33 - it has the index 34. Very un-Pythonic,
we might claim, but apparently there isn't anything that can really be
done about it.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.
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.