Reputation: 21
I am looping through entryIds stored in a dataframe (loaded from a csv file) and accessing the messages by dispatching win32com.client to access Outlook MAPI and save email attachments to a local directory using the below code. I also am storing the attachment name, path, and entryId in a new dataframe for later analysis.
df = pd.DataFrame(columns=['attName', 'path', 'entryId'])
id = 1
for email in emailData.itertuples():
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
message = outlook.GetItemFromID(email.entryId)
if message:
receivedDate = message.ReceivedTime
if message.Attachments.Count > 0:
for attachment in message.Attachments:
if attachment.Type in {1,4,5}:
if not attachment.DisplayName.endswith('.png') and not attachment.DisplayName.endswith('.jpg') and not attachment.DisplayName.endswith('.gif'):
attName = str(attachment.DisplayName)
print('\t Attachment: %s' % attachment.DisplayName)
path = "some directory\\%s\\%s" % (receivedDate.year, attachment.DisplayName)
attachment.SaveAsFile(path) #if I remove this line, the error no longer occurs
attachment = None
df.loc[id] = ([attName, str(path), email.entryId])
id += 1
attachments = None
message.Close(1)
outlook.Logoff()
outlook = None
Once I have scanned 248 messages, I encounter the below error regardless of the particular message:
File "C:\Anaconda3\envs\myenv\lib\site-packages\win32com\client\__init__.py", line 474, in __getattr__
return self._ApplyTypes_(*args)
File "C:\Anaconda3\envs\myenv\lib\site-packages\win32com\client\__init__.py", line 467, in _ApplyTypes_
self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
pywintypes.com_error: (-2147352567, 'Exception occurred.', (4096, 'Microsoft Outlook', 'Your server administrator has limited the number of items you can open simultaneously. Try closing messages you have opened or removing attachments and images from unsent messages you are composing.', None, 0, -2147220731), None)
I am able to isolate the error specifically to this line:
attachment.SaveAsFile(path)
If I remove this line, the error goes away and will continue scanning messages. I am not sure what is causing this error and I have tried various commands to close/remove references to the attachments by setting objects to None and using outlook.Logoff() for the namespace.
Has anyone else encountered this issue or have any way to resolve it?
UPDATE: After reading Eugene Astafiev's helpful suggestions, I've made some updates to my code to help show that the issue is specifically with the attachment.SaveAsFile(path) line. Unfortunately, I still receive the exact same error. Maybe I am not understanding how to release the objects? can anyone help further?
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
for email in emailData.itertuples():
message = outlook.GetItemFromID(email.entryId)
if message:
attachments = []
for attachment in list(message.Attachments):
attachments.append(attachment)
for attachment in attachments:
attachType = int(attachment.Type)
if attachType in {1,4,5}:
attName = str(attachment.DisplayName)
if not attName.endswith('.png') and not attName.endswith('.jpg') and not attName.endswith('.gif'):
path = "somedir\\%s" % (attName)
attachment.SaveAsFile(path) #Error disappears if this line is removed
del attachment
del path
del attName
del attachType
del attachments
message.Close(1)
del message
Upvotes: 1
Views: 515
Reputation: 21
After lots of research on how to release COM objects in python, I never found a solution. To me, the SaveAsFile() method has a bug that perpetuates references to each message, thereby making this error unresolvable once I've scanned 248 messages and hit the administrator limit.
Instead, I made a workaround solution to count the number of scanned messages until 245 have been scanned and force Outlook to close and open again to resume scanning. I was able to scan ~12,000 messages with attachments in one script session. I added some time buffers to ensure smooth transition between each Outlook restart.
if i == 245:
outlook.Application.Quit()
del outlook
time.sleep(20)
os.system('taskkill /im outlook.exe /f') #bit redundant but ensures task is closed
time.sleep(30)
openR = os.system('start outlook')
time.sleep(120)
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
i = 0
Upvotes: 1
Reputation: 49395
This is a widely-spread issue when dealing with Exchange profiles in Outlook. You must release all Outlook COM references as soon as possible to prevent reaching the limit.
First, I'd suggest creating an Outlook instance once and re-use it for every message processed in the loop.
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
for email in emailData.itertuples():
message = outlook.GetItemFromID(email.entryId)
if message:
Second, you need to decrease the refence counter of all Outlook COM objects, so you will try to avoid the limit. See how to release used memory immediately in python list? for more information.
Third, break the chain of property and method calls, so collections can be released as well, for example:
if message.Attachments.Count > 0:
The Attachments
property returns an instance of the Attachments
class which should be released after. But if it is used in the code like that it will never be released and you will reach the limit easily.
Upvotes: 0