Sunday, June 07, 2009

Downloading Attachments and Exporting Email as eml files in the EWS Managed API using Powershell

A common and useful thing you may want to do in Powershell when working with Exchange Email automation is to download an attachment. When working with Exchange Web Services you first need to use a GetItem operation to work out what attachments are on a message then use a GetAttachment operation which returns a Byte Array of each attachments content. So all you need to do with this Byte array is write it out to a file using the System.IO.FileStream Class.

To do this you first need a message object that you would normally get doing a GetItem operation in EWS in the managed API its just one line once you know the MessageID. Eg

$msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$MessageID)

Once you have the message object you will have a list of attachments and the attachmentID you need to make a GetAttachment operations. Again this is pretty easy to do using the Managed API which extrapolates the GetAttachment operation as the load method on an attachment object.

$fileName = “C:\temp”
foreach($attach in $msMessage.Attachments){
$fiFile = new-object System.IO.FileStream(($fppath + “\” + $attach.Name.ToString()), [System.IO.FileMode]::Create)
$fiFile.Write($attach.Content, 0, $attach.Content.Length)
write-host "Downloaded Attachment : " + (($fppath + “\” + $attach.Name.ToString())

In Exchange Web Services you have the ability to export the whole message in a serialized EML format (this is different from a Compound message format msg file which contains all the Mapi properties related to the rich types of the Exchange store). To do this when you do the original Get-Item operation request for the message you need to make sure you also request the MIME content of the message. This MIME content will contain the RFC serialized copy of the message which you can write out to a file. Eg

$psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent)
$msMessage = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$MessageID,$psPropset)
Then similar to the above attachment example you can write that MIME content out to filestream. Eg
$fileName = “C:\temp\exportedmail.eml”
$fiFile = new-object System.IO.FileStream($fileName, [System.IO.FileMode]::Create) $fiFile.Write($msMessage.MimeContent.Content, 0,$msMessage.MimeContent.Content.Length)


Pratik said...

Hi Glen,

I dont see any Content Property in Attachment Class...Am i missing something..???

Glen said...

Is this a embeeded message ? try a normal attachment.


Sam said...

Is it possible to have a transport rule that looks for attachments over a certain size, and when found moves that attachment to a file share? At the same time allowing the original email to go through (without the attachment) and alerting the recipient of the location of the attachment?

Glen said...

No you need to use a transport agent for that level of logic.


John said...

Great blog.

In your first example you seem to have changed the varible name for the file path so it should be:
$fppath = “C:\temp” also the parentheses for the write-host should be
write-host ("Downloaded Attachment : " + $fppath + “\” + $attach.Name.ToString())



Anonymous said...

The Attachment class doesn't have Content property which means the program cannot even compile.

Glen said...

The FileAttachment class does which is what this is using in powershell you don't see the types but it just a very simple sample


Brecht Gijbels said...

Awesome blog! Helped me a lot in my struggles with e-mails. The only thing I'm really missing is the ability to download an attachment of an e-mail that is at the same time also an e-mail.

Sometimes we have users e-mailing us with a mail in attachment. I can't seem to figure out how to download this mail in attachment.. There seems to be no MimeContent.Content on those attachment mails.

As a bonus, it's a good idea to check the validity of the file name:
$Mail.Subject -replace ("[{0}]" -f ([RegEx]::Escape([String][System.IO.Path]::GetInvalidFileNameChars())))

Nick McDermaid said...

Thanks, this solved all my problems. I searched around the EWS API reference all day and found no absolutely no reference to the Content property anywhere. But you seem to have found it. Thanks for posting the info.