Faster search of email in GAL using Python - python

I need to create a Python script which grabs different information about 1500 Outlook Contacts (out of 20000), based on their email. Until now, I managed to do this:
def grab_user_details(email):
first_name, last_name, department, location = '', '', '', ''
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
gal = outlook.Session.GetGlobalAddressList()
entries = gal.AddressEntries
for i in entries:
user = i.GetExchangeUser()
if user is not None:
if user.PrimarySmtpAddress == email:
first_name = email.split("#")[0].split(".")[0].title()
last_name = email.split("#")[0].split(".")[1].title()
department = user.Department
location = user.OfficeLocation
print(email, first_name, last_name, department, location)
break
In the end, the code just iterates through the GAL for that specific email. After finding it, it breaks, and continues searching for the next email. This method is fast for emails starting with A, or at least B... but when you have a GAL with 20000 emails, you can't just wait 2 days for it to finish the whole alphabet.
Is there a faster way of doing this?
Thanks!

Use the CreateRecipient method of the Namespace class to get an instance of the Recipient class based on the email address.
Sub TestRecipient()
Dim myNamespace As Outlook.NameSpace
Dim myRecipient As Outlook.Recipient
Set myNamespace = Application.GetNamespace("MAPI")
Set myRecipient = myNamespace.CreateRecipient("address#domain.com")
myRecipient.Resolve
If myRecipient.Resolved Then
' do some stuff here
End If
End Sub
The Recipient.AddressEntry property returns the AddressEntry object corresponding to the resolved recipient. Accessing the AddressEntry property forces resolution of an unresolved recipient name. If the name cannot be resolved, an error is returned. If the recipient is resolved, the Resolved property is True.
Then you can use the AddressEntry.GetExchangeUser method which returns an ExchangeUser object that represents the AddressEntry if the AddressEntry belongs to an Exchange AddressList object such as the Global Address List (GAL) and corresponds to an Exchange user.

Based on #Eugene Astafiev's answer (thank you!), I came up with the following code:
def grab_user_details(email):
name, department, location = '', '', ''
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
user = outlook.Session.CreateRecipient(email)
user.Resolve()
print(user.Resolved)
try:
name = user.AddressEntry.GetExchangeUser().Name
department = user.AddressEntry.GetExchangeUser().Department
location = user.AddressEntry.GetExchangeUser().OfficeLocation
print(email, name, department, location)
except:
print("user NA")
This method is way faster than searching through the GAL.
There's one more thing I need to fix: unfortunately, some users have 2 email address', and .CreateRecipient(email) returns nothing, although outlook can find it. I need to dig into this a little bit more.

Related

Python generated *.msg file

I'm Jan and it's my first post here and the following code is also my first python code. So please don't judge me, if the code is not well shaped :) and don't wonder I had to reduce my mail body.
With the following code I try to generate several msg file depending on a user list called "customer_names". The idea is to iterate through this list and to adjust the email body espacially the placeholder for "Customer". The rest of the body content is not so important. Everything works good except the iteration through the list. I have a suggestion that I may need to increment the index for the list in the loop. Do you have any ideas.
import win32com.client as win32
import datetime
import random
# List of customer names
customer_names = ['Name1','Name2','Name3']
current_customer_index = 0
# List of email providers
email_providers = ['provider1', 'provider2', 'provider3']
# set up the Outlook application
outlook = win32.Dispatch('outlook.application')
# create a new email
mail = outlook.CreateItem(0)
# set the subject and recipients
mail.Subject = "Request for Customer Information"
mail.To = "user#emailprovider.net"
# Message body for the email
message = f"Dear User,\n\nThe information we require from [Customer] is as follows:\n\n- Email Address: [Email Address] \n\nWe kindly request that you send the requested information to us within [number of days] days. \n\n Kind regards."
# set the number of days for the customer to respond
num_days = 7
# set the importance of the email to normal
mail.Importance = 1 # 0=Low, 1=Normal, 2=High
# set the sensitivity of the email to normal
mail.Sensitivity = 0 # 0=Normal, 1=Personal, 2=Private, 3=Confidential
# set the read receipt option to true
mail.ReadReceiptRequested = True
# add a reminder for the sender to follow up in 3 days
mail.FlagRequest = "Follow up"
mail.FlagDueBy = (datetime.date.today() + datetime.timedelta(days=3)).strftime('%m/%d/%Y')
# Generate a random email
for i in range(len(customer_names)):
customer = customer_names[i]
message_with_data = message.replace("[Customer]", customer)
message_with_data = message_with_data.replace("[number of days]", str(num_days))
mail.Body = message_with_data
file_name = "Request_" + customer + ".msg"
file_path = "C:/Users/user/Desktop/Email/" + file_name
mail.SaveAs(file_path)

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

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?
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

Python to retrieve Outlook email user profile

Is there a way to read the Outlook user's profile information, for example job title, Department, telephone number, and reporting manager, by given an email address?
For example I have 5 email address (internal user in the same company)
I want to quickly find out above information of each email user (probably with win32com?)
Thank you.
You may use the following code.
import win32com.client
import pandas as pd
# Outlook stuff
outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries
data_set = list()
# Iterates through your contact book and extracts/appends them to a list
for entry in entries:
if entry.Type == "EX":
user = entry.GetExchangeUser()
if user is not None:
if len(user.FirstName) > 0 or len(user.LastName) > 0:
row = list()
row.append(user.FirstName)
row.append(user.LastName)
row.append(user.PrimarySmtpAddress)
row.append(user.Department)
row.append(user.BusinessTelephoneNumber)
row.append(user.MobileTelephoneNumber)
row.append(user.CompanyName)
row.append(user.Name)
row.append(user.JobTitle)
row.append(user.OfficeLocation)
row.append(user.Alias)
row.append(user.City)
row.append(user.Comments)
row.append(user.StateOrProvince)
row.append(user.StreetAddress)
data_set.append(row)
first,second,email,dept,office,mobile,company,outlook_name,title,location,alias,city,state,postal = [],[],[],[],[],[],[],[],[],[],[],[],[],[]
for ea in data_set:
first.append(ea[0])
second.append(ea[1])
email.append(ea[2])
dept.append(ea[3])
office.append(ea[4])
mobile.append(ea[5])
company.append(ea[6])
outlook_name.append(ea[7])
title.append(ea[8])
location.append(ea[9])
alias.append(ea[10])
city.append(ea[11])
state.append(ea[13])
postal.append(ea[14])
address = pd.DataFrame()
address['Full Name']= outlook_name
address['email']=email
address['Department'] = dept
address['Office No.']=office
address['Mobile No.'] = mobile
address['Company']=company
address['City'] = city
address['Title']= title
address['Office']= location
address['Alias'] = alias
address['First Name']=first
address['Last Name']=second
address['Province'] =state
address['Postal Address'] = postal
address.to_excel('Address_book with '+str(address.shape[0])+' contacts.xlsx',index=None,freeze_panes =(1,0))
You can use the following sequence of actions in the Outlook object model:
Recipient.AddressEntry.GetContact()
The ContactItem class provides all the required properties you are interested in.
To get an instance of the Recipient class you need to use the CreateRecipient method of the Namespace class. For example, a sample VBA macro which shows how to use the method:
Sub ResolveName()
Dim myNamespace As Outlook.NameSpace
Dim myRecipient As Outlook.Recipient
Dim CalendarFolder As Outlook.Folder
Set myNamespace = Application.GetNamespace("MAPI")
Set myRecipient = myNamespace.CreateRecipient("Eugene Astafiev") ' or email address
myRecipient.Resolve
If myRecipient.Resolved Then
Call ShowCalendar(myNamespace, myRecipient)
End If
End Sub
Sub ShowCalendar(myNamespace, myRecipient)
Dim CalendarFolder As Folder
Set CalendarFolder = _
myNamespace.GetSharedDefaultFolder _
(myRecipient, olFolderCalendar)
CalendarFolder.Display
End Sub

How can I search the Outlook (2010) Global Address List for multiple names?

I've read this post How can I search the Outlook (2010) Global Address List for a name? and found a working solution for getting a name from Outlook GAL.
I have 3 questions:
I can get the contact if the search_string is an email address. When it's a name, the search doesn't work. It would return False for resolved, but True for sendable. Then I get error when using ae object. What am I doing wrong?
I don't understand the code enough to modify it for searching multiple names. I simply created a for loop but maybe there is a more efficient way? For example, can I reuse the outlook.Session object between different searches?
Is the line recipient.Resolve() necessary?
Thanks in advance!
My attempt is below.
from __future__ import print_function
import win32com.client
search_strings = ['Doe John', 'Doe Jane']
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
for search_string in search_strings:
recipient = outlook.Session.CreateRecipient(search_string)
recipient.Resolve()
print('Resolved OK: ', recipient.Resolved)
print('Is it a sendable? (address): ', recipient.Sendable)
print('Name: ', recipient.Name)
ae = recipient.AddressEntry
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)
Can't believe I found the solution so quickly after posting the question. Since it's hard to find the answer. I'm sharing my findings here.
It's inspired by How to fetch exact match of addressEntry object from GAL (Global Address List), though it's in c# rather than python.
This method uses exact match of displayname rather than relying on outlook to resolve the name. Though, it's possible to loop through the global address list and do partial match yourself.
import win32com.client
search_string = 'Doe John'
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
gal = outlook.Session.GetGlobalAddressList()
entries = gal.AddressEntries
ae = entries[search_string]
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)

How do you map an email address onto a SOA RNAME field?

Is there an existing/standard algorithm for mapping an email address onto a RNAME field of a SOA record (and its inverse)? I'm using the dnspython package but I don't see anything in their source tree to handle this. I ran into the edge case of having a period '.' in the username that needs to be escaped and wondering if there are any other edge cases that I am missing. RFC 1035 simply states:
A <domain-name> which specifies the mailbox of the person responsible for this zone.
None of the RFCs that update 1035 expand upon RNAME field aside from a brief mention in RFC 1183.
Here is what I came up with using dnspython:
from dns.name import from_text
def email_to_rname(email):
"""Convert standard email address into RNAME field for SOA record.
>>> email_to_rname('johndoe#example.com')
<DNS name johndoe.example.com.>
>>> email_to_rname('john.doe#example.com')
<DNS name john\.doe.example.com.>
>>> print email_to_rname('johndoe#example.com')
johndoe.example.com.
>>> print email_to_rname('john.doe#example.com')
john\.doe.example.com.
"""
username, domain = email.split('#', 1)
username = username.replace('.', '\\.') # escape . in username
return from_text('.'.join((username, domain)))
def rname_to_email(rname):
"""Convert SOA record RNAME field into standard email address.
>>> rname_to_email(from_text('johndoe.example.com.'))
'johndoe#example.com'
>>> rname_to_email(from_text('john\\.doe.example.com.'))
'john.doe#example.com'
>>> rname_to_email(email_to_rname('johndoe#example.com'))
'johndoe#example.com'
>>> rname_to_email(email_to_rname('john.doe#example.com'))
'john.doe#example.com'
"""
labels = list(rname)
username, domain = labels[0], '.'.join(labels[1:]).rstrip('.')
username = username.replace('\\.', '.') # unescape . in username
return '#'.join((username, domain))

Categories