Read PST files from win32 or pypff - python

I want to read PST files using Python. I've found 2 libraries win32 and pypff
Using win32 we can initiate a outlook object using:
import win32com.client
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6)
The GetDefaultFolder(6) gets the inbox folder. And then I can use this folders functions and attribute to work with.
But what I want is to give my own pst files which pywin32(or any other library) can read. Here it only connects with my Outlook Application
With pypff I can use the below code to work with pst files:
import pypff
pst_file = pypff.file()
pst_file.open('test.pst')
root = pst_file.get_root_folder()
for folder in root.sub_folders:
for sub in folder.sub_folders:
for message in sub.sub_messages:
print(message.get_plain_text_body()
But I want attributes like the size of the message and also like to access calendars in the pst files which is not available in pypff(not that I know of)
Question
How can I read PST files to get data like the size of the email, the types of attachments it has and the calendars?
Is it possible? Is there a work around in win32, pypff or any other library?

This is something that I want to do for my own application. I was able to piece together a solution from these sources:
https://gist.github.com/attibalazs/d4c0f9a1d21a0b24ff375690fbb9f9a7
https://github.com/matthewproctor/OutlookAttachmentExtractor
https://learn.microsoft.com/en-us/office/vba/api/outlook.namespace
The third link above should give additional details about available attributes and various item types. My solution still needs to connect to your Outlook application, but it should be transparent to the user since the pst store is automatically removed using in the try/catch/finally block. I hope this helps you get on the right track!
import win32com.client
def find_pst_folder(OutlookObj, pst_filepath) :
for Store in OutlookObj.Stores :
if Store.IsDataFileStore and Store.FilePath == pst_filepath :
return Store.GetRootFolder()
return None
def enumerate_folders(FolderObj) :
for ChildFolder in FolderObj.Folders :
enumerate_folders(ChildFolder)
iterate_messages(FolderObj)
def iterate_messages(FolderObj) :
for item in FolderObj.Items :
print("***************************************")
print(item.SenderName)
print(item.SenderEmailAddress)
print(item.SentOn)
print(item.To)
print(item.CC)
print(item.BCC)
print(item.Subject)
count_attachments = item.Attachments.Count
if count_attachments > 0 :
for att in range(count_attachments) :
print(item.Attachments.Item(att + 1).Filename)
Outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
pst = r"C:\Users\Joe\Your\PST\Path\example.pst"
Outlook.AddStore(pst)
PSTFolderObj = find_pst_folder(Outlook,pst)
try :
enumerate_folders(PSTFolderObj)
except Exception as exc :
print(exc)
finally :
Outlook.RemoveStore(PSTFolderObj)

Related

Moving emails in outlook between folders while inputing the subject list, and restrictring certain conditions

I'm trying to search "All Outlook Items" and then find emails based on the subject list I input into the code. Once the email is found, it is moved to another folder and marked as "Task Complete" (The green check in the emails).
However, I'm having a couple of errors when trying to run the code. If anyone can guide me it'd be amazing.
Here's the code:
import win32com.client
Email = 'johndoe#gmail.com'
subjects = input("Enter a list of subjects separated by commas: ").split(",")
MoveToFolder = "folder1"
Iter_Folder = "folder2"
def find_and_download_case_number_related_emails():
Outlook = win32com.client.Dispatch("Outlook.Application")
Outlook_Location = Outlook.GetNamespace("MAPI")
Lookin_Folder = Outlook_Location.Folders[Email].Folders[Iter_Folder]
Out_MoveToFolder = Outlook_Location.Folders[Email].Folders[MoveToFolder]
for message in Lookin_Folder:
if message.TaskCompleted:
continue
for message in Lookin_Folder:
if message.Subject in subjects:
message.Move(Out_MoveToFolder)
for message in Out_MoveToFolder:
message.MarkAsTaskCompleted()
if __name__ == "__main__":
find_and_download_case_number_related_emails()
and here's the error I'm getting at the moment:
raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: <unknown>.Items. Did you mean: 'Item'?
The following line of code contains a wrong property call:
outlook.Folders.Items.Restrict
The Folders class doesn't provide the Items property. You need to get a Folder instance and only then use Items property.
I'd suggest using the NameSpace.GetDefaultFolder method which returns a Folder object that represents the default folder of the requested type for the current profile; for example, obtains the default Inbox folder for the user who is currently logged on.
To understand how the Restrict or Find/FindNext methods work in Outlook you may take a look at the following articles that I wrote for the technical blog:
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

Converting EML (MIME) to MSG with python

I'm trying to convert EML file to MSG (Outlook) file using python. Using various examples I was able to gather this code, but well, it doesn't work. It creates a msg file but the file is unreadable by Outlook and the size is two times bigger than the input eml file. I'm a little bit lost, any ideas?
from win32com.mapi import mapi
from win32com.mapi import mapitags
import win32com.client
import pythoncom
from win32com import storagecon
import ctypes
import platform
import winreg
import uuid
import sys
import os
mapi.MAPIInitialize((mapi.MAPI_INIT_VERSION, mapi.MAPI_MULTITHREAD_NOTIFICATIONS))
IconvOLE = ctypes.OleDLL(r'C:\Program Files (x86)\Microsoft Office\root\Office16\OUTLMIME.DLL')
clsid_class = uuid.UUID(str(mapi.CLSID_IConverterSession)).bytes_le
iclassfactory = uuid.UUID(str(pythoncom.IID_IClassFactory)).bytes_le
com_classfactory = ctypes.c_long(0)
IconvOLE.DllGetClassObject(clsid_class, iclassfactory, ctypes.byref(com_classfactory))
MyFactory = pythoncom.ObjectFromAddress(com_classfactory.value, pythoncom.IID_IClassFactory)
cs = MyFactory.CreateInstance (None, str(mapi.IID_IConverterSession))
eml = mapi.OpenStreamOnFileW(r"C:\test.eml")
stg = pythoncom.StgCreateDocfile(r"C:\test.msg",
storagecon.STGM_CREATE | storagecon.STGM_READWRITE | storagecon.STGM_TRANSACTED)
msg = mapi.OpenIMsgOnIStg(0, None, stg, None, 0, mapi.MAPI_UNICODE)
cs.MIMEToMAPI(eml, msg, win32com.mapi.mapi.CCSF_SMTP | win32com.mapi.mapi.CCSF_INCLUDE_BCC)
msg.SaveChanges(0)
mapi.MAPIUninitialize()
Firstly, sizes don't matter, especially if you compare different file formats.
Secondly, try to open the MSG file in a utility like SSView (it shows the data on the IStorage level) or OutlookSpy (I am its author - click OpenIMsgOnIStg button) - it will show the MSG file data on the MAPI level.
Perhaps most importantly, as of Outlook 2016, IConverterSession interface only works if your code is running inside the outlook.exe address space (i.e. your code is a COM/VSTO addin or Outlook VBA). Also, your code never checks that IConverterSession::MIMEToMAPI returns a success return code.
If using Redemption is an option (I am also its author), it allows to convert an EML file to MSG without using Outlook converter as easily as (in VB script):
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT 'not required
set Msg = Session.CreateMessageFromMsgFile("c:\temp\test.msg")
Msg.Sent = true
Msg.Import "c:\temp\test.eml", 1024 '1024 is olRfc822
Msg.Save
So it started to work after I have moved from Outlook x86 to x64, and I added the following registry keys:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{4E3A7680-B77A-11D0-9DA5-00C04FD65685}]
#="CLSID_IConverterSession"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{4E3A7680-B77A-11D0-9DA5-00C04FD65685}\InprocServer32]
#="C:\\Program Files\\Microsoft Office\\root\\Office16\\OUTLMIME.DLL"
"ThreadingModel"="Both"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{9EADBD1A-447B-4240-A9DD-73FE7C53A981}]
#="CLSID_IMimeMessage"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{9EADBD1A-447B-4240-A9DD-73FE7C53A981}\InprocServer32]
#="C:\\Program Files\\Microsoft Office\\root\\Office16\\OUTLMIME.DLL"
"ThreadingModel"="Both"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{9EADBD1A-447B-4240-A9DD-73FE7C53A981}\Typelib]
#="{9EADBD25-447B-4240-A9DD-73FE7C53A981}"
Keys are copies of the keys that you can find in:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Classes\CLSID
Working code:
mapi.MAPIInitialize((mapi.MAPI_INIT_VERSION, mapi.MAPI_MULTITHREAD_NOTIFICATIONS))
inf = mapi.OpenStreamOnFile(r"C:\Users\xxx\raw.eml")
stg = pythoncom.StgCreateDocfile(r"C:\Users\xxx\raw.msg",
storagecon.STGM_CREATE | storagecon.STGM_READWRITE | storagecon.STGM_SHARE_EXCLUSIVE | storagecon.STGM_TRANSACTED)
msg = mapi.OpenIMsgOnIStg(0, None, stg, None, 0, mapi.MAPI_UNICODE)
cs = pythoncom.CoCreateInstance(mapi.CLSID_IConverterSession, None, pythoncom.CLSCTX_INPROC_SERVER, mapi.IID_IConverterSession)
cs.MIMEToMAPI(inf, msg, 0)
msg.SaveChanges(0)
mapi.MAPIUninitialize()

Create a pst file with a bunch of msg files using win32com python

I have a bunch of msg files in a directory, I'd like to kinda put/zip them in a pst file. I've seen some solutions like Aspos Email which needs JVM on the machine. I want to do it with Outlook itself using win32com.client. Please post if there is a way to do it. Thanks
In Outlook Object Model, call Namespace.AddStore (or AddStoreEx) to add a new PST file, find the store in the Namespace.Stores collection (AddStore does not return the newly added store), call Store.GetRootFolder() to get the top level folder and add folders and items. Keep in mind that OOM code cannot run in a service, and that you need to log to an existing profile in Outlook first (Namespace.Logon).
If using Redemption (I am its author) is an option (it can run in a service and does not require to log to an existing profile first), but can create a PST file as well as create folders and messages there. In VB script:
set Session = CreateObject("Redemption.RDOSession")
set pstStore = Session.LogonPstStore("c:\temp\test.pst", 1, "Test PST Store" )
set RootFolder = pstStore.IPMRootFolder
set newFolder = RootFolder.Folders.OpenOrAdd("Test folder")
set newItem = newFolder.Items.Add("IPM.Note")
newItem.Sent = true
newItem.Subject = "test"
newItem.HTMLBody = "test <b>bold</b> text"
newItem.Recipients.AddEx "The user", "user#domain.demo", "SMTP", olTo
vSenderEntryId = Session.AddressBook.CreateOneOffEntryID("Joe The Sender", "SMTP", "joe#domain.demo", false, true)
set vSender = Session.AddressBook.GetAddressEntryFromID(vSenderEntryId)
newItem.Sender = vSender
newItem.SentOnBehalfOf = vSender
newItem.SentOn = Now
newItem.ReceivedTime = Now
newItem.Save

Python function for changing the subject line of an Outlook mail item

Is there a Python function for changing the subject line of an Outlook mail item?
I have seen solutions for this using VBA. Was looking for a function/ method in python.
Thanks
Update:
Here's what i am trying :
import win32com.client as win32
outlook = win32.gencache.EnsureDispatch('Outlook.Application')
mapi = outlook.GetNamespace('MAPI')
folder = mapi.GetDefaultFolder(6)
messages = folder.Items
# Change the current subject line to 'Testing subject change'
messages.GetFirst().Subject = 'Testing subject change'
However, the subject line doesn't change. Is there any specific function i should be using?
This short piece of code will replace all Subject lines of all emails in the specified folder, in this case "Drafts" (provided your Office is in English)
import win32com.client as win32
outlook = win32.Dispatch("Outlook.Application").GetNamespace("MAPI")
acc = outlook.Folders("myaddress#provider.com")
eMailFolder = acc.folders("Drafts") #This is the localized name of your folder, as it appears in Outlook's GUI
def replaceSubjectLine(email:object):
print(email.Subject)
email.Subject = "This is the new subject line"
email.Save
print(email.Subject)
for message in eMailFolder.Items:
replaceSubjectLine(message)
In Short: You read in the MailItem Object into Python, then changed one of its Properties (Subject), but you never .Save the changed MailItem to Outlook.

Odoo: How to process winmail.dat attached in conversations?

We have some customers who uses Microsoft Outlook to send attachments. However in odoo we see only winmail.dat files (while everything looks ok in mail client).
Is there any way to force odoo to expose winmail.dat content?
The problem is that Microsoft Outlook uses Transport Neutral Encapsulation Format and packs all attachments in one file.
There is a good python parser for tnef format - tnefparse. I'd suggest you to use it and write simple module to extend mail.thread model like this
from tnefparse import TNEF
from openerp.osv import osv
class MailThread(osv.Model):
_inherit = 'mail.thread'
def _message_extract_payload(self, message, save_original=False):
body, attachments = \
super(MailThread, self)\
._message_extract_payload(message, save_original=save_original)
new_attachments = []
for name, content in attachments:
new_attachments.append((name, content))
if name and name.strip().lower() in ['winmail.dat', 'win.dat']:
try:
winmail = TNEF(content)
for attach in winmail.attachments:
new_attachments.append((attach.name, attach.data))
except:
# some processing here
pass
return body, new_attachments
You can find more information on how to do custom modules here.

Categories