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:

Alex said...

Hey David,

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

Keep on keepin' on!

an interested individual said...

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

Anonymous said...

Another great post Dave!

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

Paul

Anonymous said...

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?

David Mullet said...

@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

Anonymous said...

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!

David Mullet said...

@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

Anonymous said...

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!!

David Mullet said...

@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

Anonymous said...

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.

Anonymous said...

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.