Replace Outlook attachments with links


The requirement: Given an Outlook message, detach any attachments to the filesystem and replace them in the message by a shortcut.

Although ostensibly two different things, Outlook and Exchange are pretty tightly coupled in reality. This code uses the CDO technique, an IDispatch-based mechanism under the application name MAPI.Session. For any work with CDO I highly recommend the CDOLive website.

The approach is to iterate over the messages in a folder, interrogating each one as to its attachments. If any of the attachments is a file (ie is not itself a link), save the file to the file system using a simple incrementing counter approach and add a new attachment of the same name which is a link to the newly-created file on the file system. Finally, delete the now unneeded original attachment. Please note the warning below: the code as presented has few safeguards and should be considered illustrative only.

The code below is pared to the bone for readability. Naturally you'll need checks galore. The only safeguards I've retained are that the code doesn't in fact delete the existing attachments: the line's commented out; and that it only tries to detach file attachments, ie you can run it again on the same folder and it won't try to create links to links!

import os, sys
import win32com.client

win32com.client.gencache.EnsureDispatch ("MAPI.Session")


def process_attachment (attachment):
  global COUNTER
  basename = attachment.Name.encode ("utf-8", "ignore").lower ()
  root, ext = os.path.splitext (basename)
  while True:
      filepath = os.path.join (ATTACHMENTS_FOLDER, "%d%s" % (COUNTER, ext))
      print "Trying", filepath
      open (filepath, "r")
    except IOError:
      COUNTER += 1

    attachment.WriteToFile (filepath)
    return 0
    attachments = attachment.Parent
    new_attachment = attachments.Add ()
    new_attachment.Position = 0
    new_attachment.Type = win32com.client.constants.CdoFileLink
    new_attachment.Source = filepath
    new_attachment.Name = attachment.Name
    ## attachment.Delete ()
    return 1

def process_message (message):
  print "Message:", message.Subject
  attachments = message.Attachments
  for n_attachment in range (1, attachments.Count + 1):
    attachment = attachments.Item (n_attachment)
    if attachment.Type == win32com.client.constants.CdoFileData:
      if process_attachment (attachment):
        message.Update ()

if __name__ == '__main__':
  session = win32com.client.gencache.EnsureDispatch ("MAPI.Session")
  session.Logon ()
  messages = session.Inbox.Messages
  message = messages.GetFirst ()
  while message:
    process_message (message)
    message = messages.GetNext ()