Thursday, December 6, 2007

Automating Outlook with Ruby: Saving File Attachments

A while back, we looked at accessing the Outlook Inbox and managing messages. A reader recently asked how to iterate over the messages and save file attachments to their hard drive. Specifically, they wanted to save only those file attachments that exceeded 500k in size.

In Outlook, a message (MailItem) object's file attachments are accessed via the Attachments collection:

message.Attachments.each do |attachment|
# Do something here
end

The Attachment object has few properties and methods, but one you will use is the FileName property, which returns the, uh, filename.

To save the attachment, call its SaveAsFile method, passing it a path and filename:

attachment.SaveAsFile("c:\\attachments\\#{attachment.FileName}")

The attachment object does not offer a method/property for determining the file size. If you were looking to save only files of a certain size, you could save the file, then check the saved file's size and delete it if necessary. This is not optimal, but it does the trick.

So, putting this all together, we'd have something like the following code which iterates over your Inbox messages and saves all attachments of 500,000 bytes or larger:

require 'win32ole'

outlook = WIN32OLE.new('Outlook.Application')
mapi = outlook.GetNameSpace('MAPI')
inbox = mapi.GetDefaultFolder(6)
inbox.Items.each do |message|
message.Attachments.each do |attachment|
filename = "c:\\attachments\\#{attachment.FileName}"
attachment.SaveAsFile(filename)
File.delete(filename) if File.size(filename) < 500000
end
end

That's our show for today. Questions? Comments? Suggestions? Post a comment here or send me an email.

Thanks for stopping by!

Digg my article

11 comments:

  1. Hey David,

    Nice work on promoting this under-appreciated aspect of Ruby...

    Keep on keepin' on!

    ReplyDelete
  2. Hi David,

    I'm the guy who asked this question :) Although attachments don't have a size property, messages do. Perhaps better,

    If Message.Size >= 500000
    message.Attachments.each do |attachment|
    filename = c:\\attachments\\#{attachment.FileName}"
    attachment.SaveAsFile(filename)
    end

    #Then remove the attachments from the message
    message.Attachments.remove

    end

    Just a thought--the email box I'm working with is 500 MB, so saving all those attachments would take an eon.

    Ron

    ReplyDelete
  3. Another great post Dave!

    When is the Rindows Redux book coming out? Sing me up!

    Paul

    ReplyDelete
  4. Your blog on using WIN32OLE is extremely useful. Can you tell me how to access the 'Saved Items' folder in Outlook? Better yet, can you post a list of all the numbers that can go into the GetDefaultFolder method?

    ReplyDelete
  5. @Anon: "can you post a list of all the numbers that can go into the GetDefaultFolder method?"

    I sure can...

    Outlook GetDefaultFolder Constants:

    3 Deleted items
    4 Outbox
    5 Sent Items
    6 Inbox
    9 Calendar
    10 Contacts
    11 Journal
    12 Notes
    13 Tasks

    @Alex, Ron, and Paul: Thanks for your comments!

    @Paul: I've no book in the works (yet), though the popularity of this blog would seem to indicate a potential market, eh?

    David

    ReplyDelete
  6. how can I check if a message has an attachment? i dont want to do anything with it... i just want to say something like

    if message.Attachments
    puts "there are attachments..."
    else
    puts "there are no attachments..."
    end

    i have tried many variations of this, but to no avail. when i look at the attachment the object with
    attachment = items.getfirst.Attachments

    it returns something like this
    puts "attachment = #{attachment}"
    attachments = #

    any help is much appreciated!

    ReplyDelete
  7. @Anonymous:

    A message object has an Attachments collection. If there are attachments (or embedded images), the Attachments.Count property will be greater than 0:

    if message.Attachments.Count > 0
    ...
    end

    -David

    ReplyDelete
  8. Dave,

    First of all I wanted to say thank you for the answer above regarding 'checking for attachments' and for putting together this blog... it has been very useful.

    I do have a question though. I am trying to connect to exchange and save the attachment of a message. I dont have any problems connecting, but when I go to save the attachment, I get the following error message:

    Saving attachments to disk...
    save_attachment.rb:93:in `method_missing': (in OLE method `SaveAsFile': ) (WIN32OLERuntimeError)
    OLE error code:4096 in Microsoft Office Outlook
    Cannot save the attachment. Path does not exist. Verify the path is correct.
    HRESULT error code:0x80020009
    Exception occurred.
    from save_attachment.rb:93:in `block (2 levels) in '
    from save_attachment.rb:89:in `each'
    from save_attachment.rb:89:in `block in '
    from save_attachment.rb:36:in `each'
    from save_attachment.rb:36:in `'

    I am guessing that this has something to do with me running this from a virtual machine... I say that because the code runs fine on a non-VM system. Do you have any suggestions, or advice on what I might be able to do to get this to work in a VM? Thank you in advance!!

    ReplyDelete
  9. @Anonymous:

    You may be right with regard to the virtual machine. I'd be interested in seeing your code, specifically the line(s) that call the SaveAs() method and how the path is constructed.

    -David

    ReplyDelete
  10. not sure what happened.... i was messing around trying to fix it and i am not getting the error anymore. i think it had something to do with me trying to reference the attachment.filename when performing an operation, instead of using the 'saved' attachment on disk... sorry i do not have the 'non' functioning code anymore... i'll play around with it and see if I can get it to reproduce the error.

    ReplyDelete
  11. For the error message featured above, I had the exact same problem. After a few minutes I realized ruby will not create the "c:\attachments" directory if it does not already exist, and that is why the path is not valid. Manually created the folder, and suddenly it worked. Guess my next job is to figure out how to create a directory.

    ReplyDelete