I have a short function that compiles a list of all the emails in a specific outlook folder and then saves the attachments to a specific folder. I last used this function successfully about 3 months ago and it seems something has broken it.
The exception:
com_error: (-2147352567, 'Exception occurred.', (4096, 'Microsoft Outlook', 'Array index out of bounds.', None, 0, -2147352567), None)
I'm not familiar enough with win32.com to understand what it's trying to tell me and it's a difficult error to get information on since it seems there can be many causes. I've marked in the function where it occurs.
It runs just fine for about 5,000 emails. There are about 1,000 attachments out of 6,500 that I can't download.
def outlook_attachments() -> None:
files_list = glob.glob('sort_test/*.txt')
files_list = [x[11:] for x in files_list]
# Connect to outlook.
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
# Navigate to the Oasis folder.
oasis_folder = outlook.GetDefaultFolder(4).folders("Oasis")
# Define items inside Oasis folder.
messages = oasis_folder.Items
# Start reading messages in Oasis folder, start from 0.
message = messages.GetFirst()
# Defining path to save attachment to.
path = 'fxg-notebooks/sort_test/'
# Iterating through all Oasis messages, saving files that have not yet been saved.
new_file_count = 0
message_count = 0
while True:
# print('Messages processed: ', message_count, end='\r')
message_count += 1
# Read attachment from outlook message.
try:
attachment = message.Attachments.Item(1) <--- EXCEPTION OCCURS HERE
# If GetNext() returned None, all messages have been read, break loop.
except AttributeError:
break
# Check for attachment membership in files_list.
try:
if files_list.index(str(attachment)) >= 0:
message = messages.GetNext()
continue
# If no membership, save attachment.
except ValueError:
attachment.SaveASFile(path + str(attachment))
message = messages.GetNext()
new_file_count += 1
return None
Your code is assuming there is at least one attachment in the message. Check that message.Attachments.Count > 0 first.
Related
I am a first time user of stack overflow. I am reaching out here because I have trouble saving outlook email (.MSG) using python. The idea is to archive email as it is in physical drive.
Everything works, except the save As command which throw a very generic error. It would be great help if anyone can help me please.
Here is the code I am I Using:
import win32com.client as win32
from win32com.client import Dispatch
import os
import re
os.chdir("C:\\Users\\username\\Downloads\\RPA")
outlook = win32.gencache.EnsureDispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
print(inbox)
messages = inbox.Items
for message in messages:
message = messages.GetNext()
name = str(message.Subject)
name = re.sub('[^A-Za-z0-9]+', '', name) + '.msg'
print(name)
# message.Display(True)
message.SaveAs(os.getcwd() + '//' + name)
The Error I get executing the code
return self._oleobj_.InvokeTypes(61521, LCID, 1, (24, 0), ((8, 1), (12, 17)),Path
pywintypes.com_error: (-2147467260, 'Operation aborted', None, None)
In the code you are iterating over all items in the Inbox folder:
for message in messages:
message = messages.GetNext()
name = str(message.Subject)
name = re.sub('[^A-Za-z0-9]+', '', name) + '.msg'
If the folder contains a lot of items the operation may take some time to complete. I'd suggest processing items in chunks, so you may keep Outlook under control and prevent freezing the UI (if any). The Find/FindNext or Restrict methods can help with that. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Another point is that a file name should be unique for all items in the folder. What is the actual string argument passed to the SaveAs method?
Also you need to make sure that no forbidden symbols are used in the filename.
I am trying to automate some python code that will automatically save some attachments from certain emails with a specific title.
Below is what I currently have:
import win32com.client as client
outlook = client.Dispatch('Outlook.Application')
namespace = outlook.GetNameSpace('MAPI')
inbox = namespace.GetDefaultFolder(6)
target_subject = 'Testing attachment'
mail_items = [item for item in inbox.Items if item.Class == 43]
filtered = [item for item in mail_items if item.Subject == target_subject]
if len(filtered) != 0:
target_email = filtered[0]
if target_email.Attachments.Count > 0:
attachments = target_email.Attachments
save_path = 'C:'
for file in attachments:
file.SaveAsFile(save_path.format(file.FileName))
However I seem to be getting an error with permissions?
com_error: (-2147352567, 'Exception occurred.', (4096, 'Microsoft Outlook', "Cannot save the attachment. You don't have appropriate permission to perform this operation.", None, 0, -2147024891), None)
Not sure how to work around this, I am the Admin etc.
I am also wondering what would be the changes required to actually deploy this online and have it running, i.e. I am not passing any credentials as it's local, if operating stand alone I would like it to access my inbox every 7 days or so and download this specific attachments from this specific email.
Any help will be greatly appreciated.
Thanks!
Choose another drive or folder, for example, My Documents doesn't require admin privileges for writing. Otherwise, you will have to run Outlook with admin privileges if you want to write anything to the system drive (C:).
Also I've noticed the following lines of code:
mail_items = [item for item in inbox.Items if item.Class == 43]
filtered = [item for item in mail_items if item.Subject == target_subject]
Iterating over all items in the folder is not really a good idea, moreover, you are doing that twice!
I'd recommend using the Find/FindNextorRestrict` methods of the Items class that allow getting only items that correspond to the specified condition. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Users by default do not have write access to the root drive (C:).
Change it to something like 'c:\temp\'
Pretty new to Python. My goal is to download only email attachments from certain senders of .xls and .docx filetypes to a specified folder. I have the sender conditions working but can't get the program to filter to the specific filetypes I want. The code below downloads all attachments from the listed senders including image signatures (not desired.) The downloaded attachments contain data that will be further used in a df. I'd like to keep it within win32com since I have other working email scraping programs that use it. I appreciate any suggestions.
Partially working code:
import win32com.client
Outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
Items = inbox.Items
Item = Items.GetFirst()
def saveAttachments(email:object):
for attachedFile in email.Attachments:
try:
filename = attachedFile.FileName
attachedFile.SaveAsFile("C:\\Outputfolder"+filename)
except Exception as e:
print(e)
for mailItem in inbox.Items:
if mailItem.SenderName == "John Smith" or mailItem.SenderName == "Mike Miller":
saveAttachments(mailItem)
Firstly, don't loop through all item in a folder - use Items.Find/FindNext or Items.Restrict with a query on the SenderName property - see https://learn.microsoft.com/en-us/office/vba/api/outlook.items.restrict
As for the attachment, a image attachment is not any different from any other attachment. You can check the file extension or the size. You can also read the PR_ATTACH_CONTENT_ID property (DASL name http://schemas.microsoft.com/mapi/proptag/0x3712001F) using Attachment.PropertyAccessor.GetProperty and check if it is used in an img tag in the MailItem.HTMLBody property.
Currently you save all attached files on the disk:
for attachedFile in email.Attachments:
try:
filename = attachedFile.FileName
attachedFile.SaveAsFile("C:\\Outputfolder"+filename)
except Exception as e:
print(e)
only email attachments from certain senders of .xls and .docx filetypes to a specified folder.
The Attachment.FileName property returns a string representing the file name of the attachment. So, parsing the filename by extracting the file extension will help you to filter files that should be saved on the disk.
Also you may be interested in avoiding hidden attachments used for inline images in the message body. Here is an example code in VBA (the Outlook object model is common for all programming languages, I am not familiar with Python) that counts the visible attachments:
Sub ShowVisibleAttachmentCount()
Const PR_ATTACH_CONTENT_ID As String = "http://schemas.microsoft.com/mapi/proptag/0x3712001F"
Const PR_ATTACHMENT_HIDDEN As String = "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
Dim m As MailItem
Dim a As Attachment
Dim pa As PropertyAccessor
Dim c As Integer
Dim cid as String
Dim body As String
c = 0
Set m = Application.ActiveInspector.CurrentItem
body = m.HTMLBody
For Each a In m.Attachments
Set pa = a.PropertyAccessor
cid = pa.GetProperty(PR_ATTACH_CONTENT_ID)
If Len(cid) > 0 Then
If InStr(body, cid) Then
Else
'In case that PR_ATTACHMENT_HIDDEN does not exists,
'an error will occur. We simply ignore this error and
'treat it as false.
On Error Resume Next
If Not pa.GetProperty(PR_ATTACHMENT_HIDDEN) Then
c = c + 1
End If
On Error GoTo 0
End If
Else
c = c + 1
End If
Next a
MsgBox c
End Sub
Also you may check whether the message body (see the HTMLBody property of Outlook items) contains the PR_ATTACH_CONTENT_ID property value. If not, the attached can be visible to users if the PR_ATTACHMENT_HIDDEN property is not set explicitly.
Also you may find the Sending Outlook Email with embedded image using VBS thread helpful.
EDIT : SOLVED IT!
I added this snippet of code below, in order to trace the position of the main inbox folder
for folder in outlook.Folders:
print(folder)
This highlighted that something had changed within the underlying Outlook structure and Folder[0] was no longer valid. I will now tweak code to make it more robust and dynamically choose folder
END EDIT
I wrote some code to pull emails from Outlook and save the attachments. It worked perfectly up until a few days ago.
I had not touched the code, so I can only assume that something within Outlook has changed. I work in a corporate environment, so there is remote update of software.
Does anybody have any idea what this error means and why its suddenly cropped up ? I am very bleak, as the code worked so well before this hiccup. Alternatively, any better way to retrieve emails and attachments from Outlook, using Python ?
import win32com.client
def main():
pass
def saveAttachments():
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI") # Opens Microsoft Outlook
mailbox = outlook.Folders[0] # Based off email address
inbox = mailbox.Folders["Inbox"]
emails = inbox.Items
emails.Sort("[ReceivedTime]", True)
destPath = "\\\\servername\\path\\"
try:
for mail in emails:
if ("Detailed MTM," in mail.subject) and (mail.Attachments.Count > 0):
print(mail.Sender)
print(mail.Subject)
print(mail.Receivedtime)
attachments = mail.Attachments
for file in attachments:
if "MTMDetailed" in str(file):
file.SaveAsFile(destPath + str("MTMDetailed.xls"))
break
except:
file = open(destPath + "error.log", "w")
file.write("Problem")
file.close()
if __name__ == '__main__':
main()
saveAttachments()
File "C:\Tools\Python\lib\site-packages\win32com\client\dynamic.py", line 256, in __getitem__
return self._get_good_object_(self._oleobj_.Invoke(dispid, LCID, invkind, 1, index))
pywintypes.com_error: (-2147352567, 'Exception occurred.', (4096, 'Microsoft Outlook', 'The attempted operation failed. An object could not be found.', None, 0, -2147221233), None)
I added code to iterate through the outlook.Folders to find the one I need, without relying on specific hardcoded position
I'm using Outlook 2010 - and have my main mailbox: name#company.com
I have also added another mailbox to my profile: mb data proc
Both appear as top level folders within Outlook:
name#company.com
-Inbox
-Sent Items
-Deleted Items
mb data proc
-Inbox
-Sent Items
-Deleted Items
I cannot create a different profile for the additional mailbox. It has been added in the same profile.
How do I get a reference to the Inbox in the "mb data proc" mailbox?
This is the same problem as described here Get reference to additional Inbox but this in VBS.
How to do in python?
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
folder=outlook.Folders("mb data proc")
msg=folder.Items
msgs=msg.GetLast()
print msgs
I tried this but I get this error:
folder=outlook.Folders("mb data proc")
AttributeError: _Folders instance has no __call__ method
I had a similar doubt and as I understand it the solution stated here is for Python 2.7
I will try to make it understandable regarding how to operate it using Python 3.+ versions.
import win32com.client
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
folder = outlook.Folders.Item("Mailbox Name")
inbox = folder.Folders.Item("Inbox")
msg = inbox.Items
msgs = msg.GetLast()
print (msgs)
print (msgs.Subject)
Since _Folder is not callable, you need to use Folders.Item() method in Python 3+ to reference your mailbox.
Hope that was helpful. Thanks!
Here's a simple solution. I think the only part you missed was getting to the "Inbox" folder inside of "mb data proc".
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
folder = outlook.Folders("mb data proc")
inbox = folder.Folders("Inbox")
msg = inbox.Items
msgs = msg.GetLast()
print msgs
I was trying to access Additional Mail Boxes and read the Inbox from these Shared folders
import win32com.client
>>> outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI").Folders
>>> folder = outlook(1)
>>> inbox = folder.Folders("Inbox")
>>> message = inbox.Items
>>> messages = message.GetLast()
>>> body_content = messages.body
>>> print (body_content)
If your looking for other mailboxes or seperate PST files you have access to in outlook, try using the Store / Stores MAPI objects.
import win32com.client
for stor in win32com.client.Dispatch("Outlook.Application").Session.Stores:
print( stor.DisplayName)
PS .Session return the same reference as .GetNamespace("MAPI")
for reference https://learn.microsoft.com/en-us/office/vba/api/overview/outlook
Thank you for you Posts!
Here is a function I pulled together based on your Input, to read out the available Folders:
This is my first post, so I hope I copied the code in properly:
def check_shared(namespace,recip = None):
"""Function takes two arguments:
.) Names-Space: e.g.:
which is set in the following way: outlook = Dispatch("Outlook.Application").GetNamespace("MAPI") and
.) Recipient of an eventual shared account as string: e.g.: Shared e-Mail adress is "shared#shared.com"
--> This is optional --> If not added, the standard-e-Mail is read out"""
if recip is None:
for i in range(1,100):
try:
inbox = namespace.GetDefaultFolder(i)
print ("%i %s" % (i,inbox))
except:
#print ("%i does not work"%i)
continue
else:
print('The folders from the following shared account will be printed: '+recip)
tmpRecipient = outlook.CreateRecipient(recip)
for i in range(1,100):
try:
inbox = namespace.GetSharedDefaultFolder(tmpRecipient, i)
print ("%i %s" % (i,inbox))
except:
#print ("%i does not work"%i)
continue
print("Done")
Firstly, you can use Namespace.GetSharedDefaultFolder method.
Secondly, then line
folder=outlook.Folders("mb data proc")
needs to be
folder=outlook.Folders.Item("mb data proc")