I need to extract attachments from an Outlook 365 mailbox.
I'm following this example here (Powershell)
https://gallery.technet.microsoft.com/office/Export-Email-Messages-from-1419bbe9
I've got it running fine. This example extracts the entire message and saves to a .EML file.
To get attachments I can run some other script over the .EML file afterwards (I don't really want to do this) or I can use Powershell to extract the attachment directly
So I'm trying to use powershell but the doco on the API that is being used is sparser than what I'm used to
Basically the problem I have is that the sample code saves an email by calling this (reduced code)
# not sure if this is relevant. I think so
$itemPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties,
[Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::MimeContent)
$emailMsg.Load($itemPropertySet)
# This actually saves it
$emailContent = $emailMsg.MimeContent.Content
[System.IO.File]::WriteAllBytes("$Path\$fileName.eml",$emailContent)
But when I try the same type of code against an attachment, MimeContent.Content
results in NULL
I think it's because I need to call Attachment.Load
with the correct parameters to drag the attachment locally? But the documentation is not helping.
So anyway here's some reduced sanitised code.
This code prints out the attachment names but for each attachment I get these errors
Exception calling "Load" with "1" argument(s): "Empty path name is not legal.
Which is because I'm not calling Load correctly
Export-OSCEXOEmailAttachment : Exception calling "WriteAllBytes" with "2" argument(s): "Value cannot be null.
Which is because the variable that's meant to hold the attachment bytes is empty.
Also can anyone tell me - every time I make a code change do I need to Remove-Module
and Import-Module
to refresh? I suspect there is a way to run the script directly without importing it
Code is below
Function Export-OSCEXOEmailAttachment
{
[cmdletbinding()]
Param
(
#Define parameters
[Parameter(Mandatory=$true,Position=1,ValueFromPipeline=$true)]
[Microsoft.Exchange.WebServices.Data.SearchFolder]$SearchFolder,
[Parameter(Mandatory=$true,Position=2)]
[string]$Path,
[Parameter(Mandatory=$false,Position=3)]
[int]$PageSize=100,
[Parameter(Mandatory=$false)]
[switch]$AllowOverwrite,
[Parameter(Mandatory=$false)]
[switch]$KeepSearchFolder
)
Begin
{
#Verify the existence of exchange service object
#This bit of code (removed)
#validates that variable $exService is initialised
#Load necessary properties for email messages
#Not certain what this is for. Does this indicate which particular properties are loaded?
$itemPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(`
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties,
[Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::MimeContent)
#Load properties for attachments. Do we need to do this to get Mime.Content??
$attachmentPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(`
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties,
[Microsoft.Exchange.WebServices.Data.Attachment]::MimeContent)
}
Process
{
#Define the view settings in a folder search operation.
$itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView($PageSize)
#Iterate each item in the search folder
do
{
$findResults = $SearchFolder.FindItems($itemView)
foreach ($findResult in $findResults) {
#Bind each email with a small set of PropertySet
$emailMsg = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind(`
$exService,$findResult.Id)
$emailMsg.Load($itemPropertySet)
# Addition to original function: now iterate through attachments
foreach ($attachment in $emailMsg.Attachments) {
$ext = [System.IO.Path]::GetExtension($attachment.Name)
if($ext -eq ".xlsx") {
$attachment.Load($attachmentPropertySet)
$exportPath=$Path + "\" + $attachment.Name
Write-Host $exportPath
$attachmentContent = $attachment.MimeContent.Content
#Export attachment
Try
{
[System.IO.File]::WriteAllBytes($exportPath,$attachmentContent)
}
Catch
{
$PSCmdlet.WriteError($_)
}
}
}
}
} while ($findResults.MoreAvailable)
}
End
}
Here's a sample of code saving attachments in C# using the API from here https://msdn.microsoft.com/EN-US/library/office/dn726695(v=exchg.150).aspx
public static void SaveEmailAttachment(ExchangeService service, ItemId itemId)
{
// Bind to an existing message item and retrieve the attachments collection.
// This method results in an GetItem call to EWS.
EmailMessage message = EmailMessage.Bind(service, itemId, new PropertySet(ItemSchema.Attachments));
foreach (Attachment attachment in message.Attachments)
{
if (attachment is ItemAttachment)
{
ItemAttachment itemAttachment = attachment as ItemAttachment;
itemAttachment.Load(ItemSchema.MimeContent);
string fileName = "C:\\Temp\\" + itemAttachment.Item.Subject + ".eml";
// Write the bytes of the attachment into a file.
File.WriteAllBytes(fileName, itemAttachment.Item.MimeContent.Content);
Console.WriteLine("Email attachment name: "+ itemAttachment.Item.Subject + ".eml");
}
}
}
Whats with all these 'schemas'? the problem is there but I don't understand it