How to programatic filter global address list in outlook with Python?

Question:

I found a way to query all global address in outlook with python,

import win32com.client
import csv
from datetime import datetime

# Outlook
outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries

# Create a dateID
date_id = (datetime.today()).strftime('%Y%m%d')

# Create empty list to store results
data_set = list()

# Iterate through Outlook address entries
for entry in entries:
    if entry.Type == "EX":
        user = entry.GetExchangeUser()
        if user is not None:
            if len(user.FirstName) > 0 and len(user.LastName) > 0:
                row = list()
                row.append(date_id)
                row.append(user.Name)
                row.append(user.FirstName)
                row.append(user.LastName)
                row.append(user.JobTitle)
                row.append(user.City)
                row.append(user.PrimarySmtpAddress)
                try:
                    row.append(entry.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3a26001e"))
                except:
                    row.append('None')
                
                # Store the user details in data_set
                data_set.append(row)

# Print out the result to a csv with headers
with open(date_id + 'outlookGALresults.csv', 'w', newline='', encoding='utf-8') as csv_file:
    headers = ['DateID', 'DisplayName', 'FirstName', 'LastName', 'JobTitle', 'City', 'PrimarySmtp', 'Country']
    wr = csv.writer(csv_file, delimiter=',')
    wr.writerow(headers)
    for line in data_set:
        wr.writerow(line)

But it querys user one by one and it’s very slow. I only need to query user from IT department out of 100,000 users. How can I write the filter to avoid querying all users?

Asked By: Leon Ren

||

Answers:

On the Extended MAPI (C++ or Delphi) level, you can access the entries as IMAPITable MAPI interface and retrieve multiple entries in a single call (IMAPITable::QueryRows). You should still keep in mind that you are limited to 32KB on each request, so you’d need to retrieve the data in batches, which is still better than one entry at a time.

IMAPITable is also exposed as AddressEntries.RawTable property in OOM, but since it is Extended MAPI, it can be accessed in C++ or Delphi only.

If using Redemption (any language, I am its author) is an option, it exposes its own MAPITable interface, and in particular ExecSQL method, which allows to retrieve data in a single call.

In VBA:

set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set GAL = Session.AddressBook.GAL
set Table = GAL.AddressEntries.MAPITable
set Recordset = Table.ExecSQL("SELECT Name, FirstName, LastName, JobTitle, EntryID, " & _
                              """http://schemas.microsoft.com/mapi/proptag/0x3A27001f"" As BusinessAddressCity, " & _
                              """http://schemas.microsoft.com/mapi/proptag/0x3a26001f"" As BusinessAddressCountry, " & _
                              """http://schemas.microsoft.com/mapi/proptag/0x39FE001F"" As PrimarySmtpAddress, " & _
                              "from list ")
while not Recordset.EOF
    Debug.Print(Recordset.Fields("Name").Value & " - " & Recordset.Fields("PrimarySmtpAddress").Value)
    Recordset.MoveNext
wend

You mention that you prefer to query the data rather than retrieve all entries – that should be the preferred solution; you should never retrieve all data unless you are creating some kind of synchronization app and you need everything on initial sync. Unfortunately, address book interfaces in MAPI (and hence in Outlook Object Model) are not very flexible when it comes to searching, especially compared to the search capabilities for the messages in folders.

If you want a single matching GAL user, use Application.Session.CreateRecipient / Recipient.Resolve and hope the alias resolves and there are no ambiguous entries. the search will be performed in all address book containers on the search path.

If you want multiple entries, you can filter on a few properties explicitly exposed by GAL for searching (Alias, City, Company, Department, First Name, Last Name, Office, Title). This is what you see in the address book window in Outlook if you click "Advanced Find". Redemption exposes it through the RDOAddressListSearch object:

set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set AddrList = Session.Addressbook.GAL
set Search = AddrList.Search
Search.FirstName = "John"
Search.City = "Phoenix"
set AddressEntries = Search.GetResults
for each AddressEntry in AddressEntries
    Debug.Print AddressEntry.Name
next

@Dmitry Streblechenko ‘s method solved my problem.

The final code that can work is:

Set Session = CreateObject("Redemption.RDOSession")
Session.Logon
Set GAL = Session.AddressBook.GAL
Set AddrList = Session.AddressBook.GAL
Set Search = AddrList.Search
Search.DisplayName = "renlei"
Set AddressEntries = Search.GetResults
For Each AddressEntry In AddressEntries
    Debug.Print AddressEntry.Name
Next
Answered By: Leon Ren