def get_default_cf(self): """Returns a tuple (IMAPIFolder, IMAPITable) that can be used to manipulate the Contacts folder and the associated meta information""" if self.def_cf: return self.def_cf msgstore = self.get_default_msgstore() if self.def_inbox is None: self.def_inbox = self.get_default_inbox() PR_IPM_CONTACTS_ENTRYID = 0x36D10102 hr, props = self.def_inbox.GetProps((PR_IPM_CONTACTS_ENTRYID), 0) (tag, cf_id) = props[0] # check for errors if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: raise TypeError('got PT_ERROR: %16x' % tag) elif mapitags.PROP_TYPE(tag) == mapitags.PT_BINARY: pass cf = msgstore.OpenEntry(cf_id, None, MOD_FLAG) return cf
def print_prop(self, tag, value): prop_type = mapitags.PROP_TYPE(tag) prop_id = mapitags.PROP_ID(tag) if prop_type & mapitags.MV_FLAG: print "Tag: 0x%16x (Multi Value); Value: %s" % (long(tag), value) else: print "Tag: %s; Value: %s" % (mapiutil.GetPropTagName(tag), value)
def _populate_folders(self, fid=None, depth=" "): """Recurse through the entire folder hierarchy of the message store and collect the folders that we are interested in and populate the current MessageStore's folders object.""" msgstore = self.get_obj() if not fid: fid = self.get_ipm_subtree_eid() if not fid: return folder = msgstore.OpenEntry(fid, None, MOD_FLAG) htable = folder.GetHierarchyTable( (mapi.CONVENIENT_DEPTH | mapi.MAPI_UNICODE)) htable.SetColumns((mapitags.PR_ENTRYID, mapitags.PR_DISPLAY_NAME, mapitags.PR_FOLDER_TYPE, mapitags.PR_SUBFOLDERS, mapitags.PR_CONTENT_COUNT, mapitags.PR_DEPTH), 0) hr = htable.SeekRow(mapi.BOOKMARK_BEGINNING, 0) cnt = 0 while True: rows = htable.QueryRows(1, 0) if len(rows) != 1: break ((eidt, eid), (dnt, dn), (ftt, ft), (sft, sf), (cct, cc), (dt, d)) = rows[0] cnt += 1 if mapitags.PROP_TYPE(eidt) != mapitags.PT_ERROR: logging.debug( '%sEID: %s Name: %-25s Type: %2d Has Sub: %5s ' 'Depth: %2d Count: %d', depth, base64.b64encode(eid), dn, ft, sf, d, cc) if ft == mapi.FOLDER_GENERIC: ftype, f = OLFolder.get_folder_type(msgstore, eid) if ftype == Folder.CONTACT_t: ff = OLContactsFolder(self.ol, eid, dn, f, self) elif ftype == Folder.TASK_t: ff = OLTasksFolder(self.ol, eid, dn, f, self) elif ftype == Folder.NOTE_t: ff = OLNotesFolder(self.ol, eid, dn, f, self) elif ftype == Folder.APPT_t: ff = None logging.info('Appointments not supported. Ignoring.') else: ff = None if ff: self.add_to_folders(ff) if sf and eid != self.get_del_items_eid(): self._populate_folders(fid=eid, depth=(depth + ' ')) else: logging.error('Hm, Error... cnt: %2d', cnt)
def all_entries(self): """Return an array of entries in the current folder along with the corresponding google IDs in a format that can be directly written to the app_state.json file. The value from this for all folders will be written to the file as an array field.""" ret = {'folder': self.name, 'store': self.store.name} entries = [] ctable = self.get_contents() ctable.SetColumns( (self.prop_tags.valu('GOUT_PR_GCID'), mapitags.PR_ENTRYID), 0) while True: rows = ctable.QueryRows(1, 0) #if this is the last row then stop if len(rows) != 1: break (gid_tag, gid), (entryid_tag, entryid) = rows[0] if mapitags.PROP_TYPE(entryid_tag) == mapitags.PT_ERROR: logging.error('all_entries(): Error returned while iterating') gid = entryid = None else: entryid = base64.b64encode(entryid) if mapitags.PROP_TYPE(gid_tag) == mapitags.PT_ERROR: # Was not synced for whatever reason. logging.debug( ('Folder:all_contents(): Prepped unsynched ' + 'items for b64encoded entryid: %s'), entryid) gid = None entries.append({'eid': entryid, 'gcid': gid}) ret.update({'entries': entries, 'entrycnt': len(entries)}) return ret
def _clear_tag(self, tags, dryrun=False): """Clear any property whose property tag is the provided array.""" logging.info('Querying MAPI for all data needed to clear flag') ctable = self.get_contents() cols = tuple([mt.PR_ENTRYID, mt.PR_DISPLAY_NAME]) + tuple(tags) ctable.SetColumns(cols, 0) logging.info('Data obtained from MAPI. Clearing one at a time') cnt = set() errs = set() i = 0 store = self.get_msgstore().get_obj() hr = ctable.SeekRow(mapi.BOOKMARK_BEGINNING, 0) while True: rows = ctable.QueryRows(1, 0) # if this is the last row then stop if len(rows) != 1: break (entryid_tag, entryid), (name_tag, name) = rows[0][:2] i += 1 for j in range(2, len(rows[0])): (gid_tag, gid) = rows[0][j] if mt.PROP_TYPE(gid_tag) != mt.PT_ERROR: if not dryrun: entry = store.OpenEntry(entryid, None, mapi.MAPI_BEST_ACCESS) hr, ps = entry.DeleteProps([gid_tag]) if winerror.FAILED(hr): logging.debug( 'Could not delete sync tag for: %s ' '(%s), due to: %s', name, base64.b64encode(entryid), winerror.HRESULT_CODE(hr)) errs.add(entryid) else: entry.SaveChanges(0) cnt.add(entryid) logging.info('Entries cleared: %d. Errors: %d; i: %d', len(cnt), len(errs), i) ctable.SetColumns(self.get_def_cols(), 0) return (len(errs) == 0)
def test_read_emails (self, itemid): eid = base64.b64decode(itemid) olcf = self.deff prop_tag = olcf.get_proptags().valu('ASYNK_PR_EMAIL_1') store = olcf.get_msgstore() item = store.get_obj().OpenEntry(eid, None, mapi.MAPI_BEST_ACCESS) hr, props = item.GetProps([prop_tag], mapi.MAPI_UNICODE) (tag, val) = props[0] if mt.PROP_TYPE(tag) == mt.PT_ERROR: print 'Prop_Tag (0x%16x) not found. Tag: 0x%16x' % (prop_tag, (tag % (2**64))) else: print 'Email address found: ', val
def update_prop (self, prop_tag, prop_val, action): self.ol_item = self.get_ol_item() try: hr, props = self.ol_item.GetProps([prop_tag, mapitags.PR_ACCESS, mapitags.PR_ACCESS_LEVEL], mapi.MAPI_UNICODE) (tag, val) = props[0] if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: logging.debug('update_prop(): Prop %s (0x%16x) not found', self.prop_tags.name(tag), prop_tag) val = '' # This could be an int. FIXME except Exception, e: val = '' # This could be an int. FIXME
def open(self): """ Ensures we have a MAPI Session to the exchange server. Returns whether or not a new connection was required (True or False). """ if self.connection: # Nothing to do if the connection is already open. return False try: mapi.MAPIInitialize(None) if self.MAPIProfile <> None: MAPIProfile = self.MAPIProfile else: MAPIProfile = "" session = mapi.MAPILogonEx( 0, MAPIProfile, None, mapi.MAPI_EXTENDED | mapi.MAPI_USE_DEFAULT) messagestorestable = session.GetMsgStoresTable(0) messagestorestable.SetColumns( (mapitags.PR_ENTRYID, mapitags.PR_DISPLAY_NAME_A, mapitags.PR_DEFAULT_STORE), 0) while True: rows = messagestorestable.QueryRows(1, 0) #if this is the last row then stop if len(rows) != 1: break row = rows[0] #if this is the default store then stop if ((mapitags.PR_DEFAULT_STORE, True) in row): break # unpack the row and open the message store (eid_tag, eid), (name_tag, name), (def_store_tag, def_store) = row msgstore = session.OpenMsgStore( 0, eid, None, mapi.MDB_NO_DIALOG | mapi.MAPI_BEST_ACCESS) # get the outbox hr, props = msgstore.GetProps((mapitags.PR_IPM_OUTBOX_ENTRYID), 0) (tag, eid) = props[0] #check for errors if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: raise TypeError('got PT_ERROR instead of PT_BINARY: %s' % eid) self.connection = msgstore.OpenEntry(eid, None, mapi.MAPI_BEST_ACCESS) return True except: if not self.fail_silently: raise
def verify_google_id (self): """Internal Test function to check if tag storage works. This is intended to be used for debug to retrieve and print the value of the Google Contacts Entry ID that is stored in MS Outlook. """ prop_tag = self.cf.prop_tags.valu('GOUT_PR_GCID') hr, props = self.ol_item.GetProps([prop_tag], mapi.MAPI_UNICODE) (tag, val) = props[0] if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: print 'Prop_Tag (0x%16x) not found. Tag: 0x%16x' % (prop_tag, (tag % (2**64))) else: print 'Google ID found for contact. ID: ', val
def DumpItemProp(item, prop, outfile): if type(prop) != type(0): # see if a mapitags contant try: prop = mapitags.__dict__[prop] except KeyError: # resolve as a name props = ((mapi.PS_PUBLIC_STRINGS, prop), ) propIds = obj.GetIDsFromNames(props, 0) prop = mapitags.PROP_TAG(mapitags.PT_UNSPECIFIED, mapitags.PROP_ID(propIds[0])) hr, data = item.GetProps((prop, ), 0) prop_tag, prop_val = data[0] # Do some magic rtf conversion if mapitags.PROP_ID(prop_tag) == mapitags.PROP_ID( mapitags.PR_RTF_COMPRESSED): rtf_stream = item.OpenProperty(mapitags.PR_RTF_COMPRESSED, pythoncom.IID_IStream, 0, 0) html_stream = mapi.WrapCompressedRTFStream(rtf_stream, 0) chunks = [] while 1: chunk = html_stream.Read(4096) if not chunk: break chunks.append(chunk) prop_val = "".join(chunks) elif mapitags.PROP_TYPE(prop_tag)==mapitags.PT_ERROR and \ prop_val in [mapi.MAPI_E_NOT_ENOUGH_MEMORY,'MAPI_E_NOT_ENOUGH_MEMORY']: prop_tag = mapitags.PROP_TAG(mapitags.PT_BINARY, mapitags.PROP_ID(prop_tag)) stream = item.OpenProperty(prop_tag, pythoncom.IID_IStream, 0, 0) chunks = [] while 1: chunk = stream.Read(4096) if not chunk: break chunks.append(chunk) prop_val = "".join(chunks) outfile.write(prop_val)
def save(self): """Saves the current (new) contact to Outlook so it is persistent. Returns the itemid for the saved entry. Returns None in case of an error""" ## FIXME: This only takes care of new insertions. In-place updates are ## not taken care of by this. As of this time (May 2012) this method ## is only invoked for new contact creations. Updates are handld ## differently - see folder_ol:batch_update(), so this is not a bug, ## just that it would be good to have a single method deal with both ## cases. fobj = self.get_folder().get_fobj() msg = fobj.CreateMessage(None, 0) if not msg: return None olprops = self.get_olprops() hr, res = msg.SetProps(olprops) if (winerror.FAILED(hr)): logging.critical( 'push_to_outlook(): unable to SetProps (code: %x)', winerror.HRESULT_CODE(hr)) return None msg.SaveChanges(mapi.KEEP_OPEN_READWRITE) # Now that we have successfully saved the record, let's fetch the # entryid and return it to the caller. hr, props = msg.GetProps([mt.PR_ENTRYID], mapi.MAPI_UNICODE) (tag, val) = props[0] if mt.PROP_TYPE(tag) == mt.PT_ERROR: logging.error('save: EntryID could not be found. Weird') return None else: logging.debug('Successfully Wrote contact to Outlook : %-32s', self.get_name()) return self.set_entryid(val)
def enumerate_all_folders(self, folder_eid=None, depth=' '): """Walk through the entire folder hierarchy of the message store and print one line per folder with some critical information. This is a recursive function. If you want to start enumerating at the root folder of the current message store, invoke this routine without any arguments. The defaults will ensure the root folder if fetched and folders will be recursively enumerated. """ msgstore = self.get_obj() folder = msgstore.OpenEntry(folder_eid, None, MOD_FLAG) htable = folder.GetHierarchyTable( (mapi.CONVENIENT_DEPTH | mapi.MAPI_UNICODE)) htable.SetColumns((mapitags.PR_ENTRYID, mapitags.PR_DISPLAY_NAME, mapitags.PR_FOLDER_TYPE, mapitags.PR_SUBFOLDERS, mapitags.PR_CONTENT_COUNT, mapitags.PR_DEPTH), 0) hr = htable.SeekRow(mapi.BOOKMARK_BEGINNING, 0) cnt = 0 while True: rows = htable.QueryRows(1, 0) if len(rows) != 1: logging.debug('\tbreaking... %d', len(rows)) break ((eidt, eid), (dnt, dn), (ftt, ft), (sft, sf), (cct, cc), (dt, d)) = rows[0] cnt += 1 if mapitags.PROP_TYPE(eidt) != mapitags.PT_ERROR: logging.debug( '%sEID: %s Name: %-25s Type: %2d Has Sub: %5s ' 'Depth: %2d Count: %d', depth, base64.b64encode(eid), dn, ft, sf, d, cc) if sf: self.enumerate_all_folders(folder_eid=eid, depth=(depth + ' ')) else: logging.error('H, error in enumeraate! :-)')
def SendEMAPIMail(Subject="", Message="", SendTo=None, SendCC=None, SendBCC=None, MAPIProfile=None): """Sends an email to the recipient using the extended MAPI interface Subject and Message are strings Send{To,CC,BCC} are comma-separated address lists MAPIProfile is the name of the MAPI profile""" # initialize and log on mapi.MAPIInitialize(None) session = mapi.MAPILogonEx(0, MAPIProfile, None, mapi.MAPI_EXTENDED | mapi.MAPI_USE_DEFAULT) messagestorestable = session.GetMsgStoresTable(0) messagestorestable.SetColumns( (mapitags.PR_ENTRYID, mapitags.PR_DISPLAY_NAME_A, mapitags.PR_DEFAULT_STORE), 0) while True: rows = messagestorestable.QueryRows(1, 0) #if this is the last row then stop if len(rows) != 1: break row = rows[0] #if this is the default store then stop if ((mapitags.PR_DEFAULT_STORE, True) in row): break # unpack the row and open the message store (eid_tag, eid), (name_tag, name), (def_store_tag, def_store) = row msgstore = session.OpenMsgStore(0, eid, None, mapi.MDB_NO_DIALOG | mapi.MAPI_BEST_ACCESS) # get the outbox hr, props = msgstore.GetProps((mapitags.PR_IPM_OUTBOX_ENTRYID), 0) (tag, eid) = props[0] #check for errors if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: raise TypeError('got PT_ERROR instead of PT_BINARY: %s' % eid) outboxfolder = msgstore.OpenEntry(eid, None, mapi.MAPI_BEST_ACCESS) # create the message and the addrlist message = outboxfolder.CreateMessage(None, 0) # note: you can use the resolveaddress functions for this. but you may get headaches pal = [] def makeentry(recipient, recipienttype): return ((mapitags.PR_RECIPIENT_TYPE, recipienttype), (mapitags.PR_SEND_RICH_INFO, False), (mapitags.PR_DISPLAY_TYPE, 0), (mapitags.PR_OBJECT_TYPE, 6), (mapitags.PR_EMAIL_ADDRESS_A, recipient), (mapitags.PR_ADDRTYPE_A, 'SMTP'), (mapitags.PR_DISPLAY_NAME_A, recipient)) if SendTo: pal.extend([ makeentry(recipient, mapi.MAPI_TO) for recipient in SendTo.split(",") ]) if SendCC: pal.extend([ makeentry(recipient, mapi.MAPI_CC) for recipient in SendCC.split(",") ]) if SendBCC: pal.extend([ makeentry(recipient, mapi.MAPI_BCC) for recipient in SendBCC.split(",") ]) # add the resolved recipients to the message message.ModifyRecipients(mapi.MODRECIP_ADD, pal) message.SetProps([(mapitags.PR_BODY_A, Message), (mapitags.PR_SUBJECT_A, Subject)]) # save changes and submit outboxfolder.SaveChanges(0) message.SubmitMessage(0)
def prep_sync_lists(self, destid, sl, synct_sto=None, cnt=0): """See the documentation in folder.Folder""" pname = sl.get_pname() conf = self.get_config() pdb1id = conf.get_profile_db1(pname) oldi = conf.get_itemids(pname) stag = conf.make_sync_label(pname, destid) logging.info('Querying MAPI for status of Contact Entries') ## Sort the DBIds so dest1 has the 'lower' ID dest1 = self.get_db().get_dbid() if dest1 > destid: dest2 = dest1 dest1 = destid else: dest2 = destid ctable = self.get_contents() stp = self.get_proptags().sync_tags[stag] cols = (mt.PR_ENTRYID, mt.PR_LAST_MODIFICATION_TIME, mt.PR_DISPLAY_NAME, stp) ctable.SetColumns(cols, 0) i = 0 synct_str = self.get_config().get_last_sync_start(pname) if not synct_sto: synct_sto = self.get_config().get_last_sync_stop(pname) synct = iso8601.parse(synct_sto) logging.debug('Last Start iso str : %s', synct_str) logging.debug('Last Stop iso str : %s', synct_sto) logging.debug('Current Time : %s', iso8601.tostring(time.time())) logging.info('Data obtained from MAPI. Processing...') newi = {} while True: rows = ctable.QueryRows(1, 0) #if this is the last row then stop if len(rows) != 1: break ((entryid_tag, entryid), (tt, modt), (name_tag, name), (gid_tag, gid)) = rows[0] b64_entryid = base64.b64encode(entryid) newi.update({b64_entryid: gid}) if mt.PROP_TYPE(gid_tag) == mt.PT_ERROR: # Was not synced for whatever reason. logging.debug('New Outlook Contact: %20s %s', name, b64_entryid) sl.add_new(b64_entryid) else: if mt.PROP_TYPE(tt) == mt.PT_ERROR: logging.debug('Impossible! Entry has no timestamp. i = %d', i) else: if utils.utc_time_to_local_ts(modt) <= synct: sl.add_unmod(b64_entryid) else: logging.debug('Modified Outlook Contact: %20s %s', name, b64_entryid) sl.add_mod(b64_entryid, gid) i += 1 if cnt != 0 and i >= cnt: break ctable.SetColumns(self.get_def_cols(), 0) kss = newi.keys() for x, y in oldi.iteritems(): if not x in kss and not y in kss: logging.debug('Deleted Outlook Contact: %s:%s', x, y) if pdb1id == self.get_dbid(): sl.add_del(x, y) else: sl.add_del(y, x)
class PropTags: """This Singleton class represents a set of all the possible mapi property tags. In general the mt module has pretty usable constants defined. However MAPI compllicates things with 'Named Properties' - which are not static, but have to be generated at runtime (not sure what all parameters change it...). This class includes all the mt properties as well as a set of hand selected named properties that are relevant for us here.""" PSETID_Address_GUID = '{00062004-0000-0000-C000-000000000046}' PSETID_Task_GUID = '{00062003-0000-0000-c000-000000000046}' def __init__(self, def_cf, config): self.name_hash = {} self.valu_hash = {} # We use the def_cf to lookup named properties. I suspect this will # have to be changed when we start supporting multiple profiles and # folders... self.def_olcf = def_cf self.def_cf = def_cf.get_fobj() self.config = config self.sync_tags = {} self.load_proptags() def load_proptags(self): # Load up all available properties from mt module for name, value in mt.__dict__.iteritems(): if name[:3] == 'PR_': # Store both the full ID (including type) and just the ID. # This is so PR_FOO_A and PR_FOO_W are still # differentiated. Note that in the following call, the value # hash will only contain the full ID. self.put(name=name, value=mt.PROP_ID(value)) self.put(name=name, value=value) # Now Add a bunch of named properties that we are specifically # interested in. self.put(name='ASYNK_PR_FILE_AS', value=self.get_file_as_prop_tag()) self.put(name='ASYNK_PR_EMAIL_1', value=self.get_email_prop_tag(1)) self.put(name='ASYNK_PR_EMAIL_2', value=self.get_email_prop_tag(2)) self.put(name='ASYNK_PR_EMAIL_3', value=self.get_email_prop_tag(3)) self.put(name='ASYNK_PR_IM_1', value=self.get_im_prop_tag(1)) self.put('ASYNK_PR_TASK_DUE_DATE', self.get_task_due_date_tag()) self.put('ASYNK_PR_TASK_STATE', self.get_task_state_tag()) self.put('ASYNK_PR_TASK_RECUR', self.get_task_recur_tag()) self.put('ASYNK_PR_TASK_COMPLETE', self.get_task_complete_tag()) self.put('ASYNK_PR_TASK_DATE_COMPLETED', self.get_task_date_completed_tag()) self.put('ASYNK_PR_CUSTOM_PROPS', self.get_custom_prop_tag()) self.load_sync_proptags() def load_sync_proptags(self): conf = self.config mydid = self.def_olcf.get_db().get_dbid() olps = conf.get_db_profiles(mydid) for pname, prof in olps.iteritems(): db1id = conf.get_profile_db1(pname) db2id = conf.get_profile_db2(pname) stag = conf.make_sync_label(pname, db1id if db2id == mydid else db2id) prop_tag_valu = self.get_gid_prop_tag(pname) self.put(name=stag, value=prop_tag_valu) self.sync_tags.update({stag: prop_tag_valu}) # self.put(name='ASYNK_PR_GCID', value=self.get_gid_prop_tag('gc')) # self.put(name='ASYNK_PR_BBID', value=self.get_gid_prop_tag('bb')) def valu(self, name): return self.name_hash[name] def name(self, valu): return self.valu_hash[valu] ## The rest of the methods below are internal to the class. def put(self, name, value): self.name_hash[name] = value self.valu_hash[value] = name # Routines to construct the property tags for named property. Intended to # be used only once in the constructor def get_email_prop_tag(self, n): """MAPI is crappy. Email addresses of the EX type do not conatain an SMTP address value for their PR_EMAIL_ADDRESS property tag. While the desired smtp address is present in the system the property tag that will help us fetch it is not a constant and will differ from system to system, and from PST file to PST file. The tag has to be dynamically generated. The routine jumps through the requisite hoops and appends those property tags to the supplied fields array. The augmented fields array is then returned. """ if n <= 1: try: return self.valu('ASYNK_PR_EMAIL_1') except KeyError, e: prop_name = [(self.PSETID_Address_GUID, 0x8084)] prop_type = mt.PT_UNICODE prop_ids = self.def_cf.GetIDsFromNames(prop_name, mapi.MAPI_CREATE) return (prop_type | prop_ids[0]) prev_tag = self.get_email_prop_tag(n - 1) prev_tag_id = mt.PROP_ID(prev_tag) prev_tag_type = mt.PROP_TYPE(prev_tag) return mt.PROP_TAG(prev_tag_type, prev_tag_id + 1)
def check_tag_error (self, tag): if mapitags.PROP_TYPE(tag) == mapitags.PT_ERROR: raise TypeError('got PT_ERROR: %16x' % tag) elif mapitags.PROP_TYPE(tag) == mapitags.PT_BINARY: pass