Microsoft Graph - Saving file attachments through C#? - c#

Is it possible to save file attachments in C# through Microsoft Graph API?
I know I can get the properties of the attachment (https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/attachment_get) - can we also save it to a certain location?

Once you have particular Microsoft Graph message, you can e.g. pass it to a method as parameter. Then you need to make another request to get attachments by message Id, iterate through attachments and cast it to FileAttachment to get access to the ContentBytes property and finally save this byte array to the file.
private static async Task SaveAttachments(Message message)
{
var attachments =
await _client.Me.MailFolders.Inbox.Messages[message.Id].Attachments.Request().GetAsync();
foreach (var attachment in attachments.CurrentPage)
{
if (attachment.GetType() == typeof(FileAttachment))
{
var item = (FileAttachment)attachment; // Cast from Attachment
var folder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var filePath = Path.Combine(folder, item.Name);
System.IO.File.WriteAllBytes(filePath, item.ContentBytes);
}
}
}

When you get the attachment properties, they will contain information about the attachment.
There are three types of attachments.
First, check the attachment type in the properties' #odata.type and handle them correspondingly.
For fileAttachment types, they contain a contentLocation attribute, which is the URI of the attachment contents.
You can download the attachment from the URI.

Related

How to programmatically save an outlook email attachment (e.g. PDF) that has been copied to clipboard by the user using CTRL-C, in C#

Context:
I am able to copy (using CTRL-C) and paste programmatically a file from the clipboard when the file copied to the clipboard is for example a file on the desktop. This is simple enough using the following syntax:
File.Copy(Clipboard.GetFileDropList()[0], savePath)
where Clipboard.GetFileDropList()[0] returns the path of the copied file and savePath is the paste location.
However, i find that the above syntax does NOT work if the copied file (using CTRL-C) is a file attachment in an Outlook email. In that scenario, Clipboard.ContainsFileDropList() returns false and Clipboard.GetFileDropList()[0] results in the following error message:
"ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index"
This is despite the fact that pressing CTRL-V does successfully paste the file, confirming that the file was initially successfully copied to the clipboard.
Question:
Sorry if i missed something very basic. My question is how to programmatically paste/save an email attachment (PDF, Word, etc...) from the clipboard to a file location when that email attachment was copied into the clipboard using CTRL-C from within Outlook.
Note that i do understand that what i am trying to do can be solved by skipping the Clipboard and interacting programmatically with Outlook to access a selected email attachment. However, my goal here is to learn how to interact programmatically with the Clipboard under different scenario.
You are using the wrong DataFormat. You can always get a list of currently present data formats by calling Clipboard.GetDataObject().GetFormats().
You need to use:
"FileGroupDescriptor" to retrieve the file names
private static async Task<List<string>> GetAttachedFileNamesFromClipboardAsync(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileGroupDescriptor"))
{
return new List<string>();
}
using (var descriptorStream = clipboardData.GetData("FileGroupDescriptor", true) as MemoryStream)
{
using (var streamReader = new StreamReader(descriptorStream))
{
var streamContent = await streamReader.ReadToEndAsync();
string[] fileNames = streamContent.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
return new List<string>(fileNames.Skip(1));
}
}
}
"FileContents" to retrieve the raw file contents
// Returns the attachment file content as string
private static async Task<string> GetAttachmentFromClipboardAsync(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return string.Empty;
}
using (var fileContentStream = clipboardData.GetData("FileContents", true) as MemoryStream)
{
using (var streamReader = new StreamReader(fileContentStream))
{
return await streamReader.ReadToEndAsync();
}
}
}
// Returns the attachment file content as MemoryStream
private static MemoryStream GetAttachmentFromClipboard(IDataObject clipboardData)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return null;
}
return clipboardData.GetData("FileContents", true) as MemoryStream;
}
Save attached file to disk
Since Windows only adds the first selected attachment to the system clipboard, this solution can only save a single attachment. Apparently the office clipboard is not accessible.
private static async Task SaveAttachmentFromClipboardToFileAsync(IDataObject clipboardData, string destinationFilePath)
{
if (!clipboardData.GetDataPresent("FileContents"))
{
return;
}
using (var attachedFileStream = clipboardData.GetData("FileContents", true) as MemoryStream)
{
using (var destinationFileStream = File.Open(destinationFilePath, FileMode.OpenOrCreate))
{
await attachedFileStream.CopyToAsync(destinationFileStream);
}
}
}
Save attached files to disk using the Office API
Requires to reference Microsoft.Office.Interop.Outlook.dll. This solution does not depend on the system clipboard. It just reaads the selected attachments from the currently open message item in the Outlook explorer.
You still can monitor the system clipboard to trigger the process to save the attachments.
private static void SaveSelectedAttachementsToFolder(string destinationFolderPath)
{
var outlookApplication = new Microsoft.Office.Interop.Outlook.Application();
Explorer activeOutlookExplorer = outlookApplication.ActiveExplorer();
AttachmentSelection selectedAttachments = activeOutlookExplorer.AttachmentSelection;
foreach(Attachment attachment in selectedAttachments)
{
attachment.SaveAsFile(Path.Combine(destinationFolderPath, attachment.FileName));
}
}

Access the email attachment

I am trying to access the file attachment in the email message and upload the attachment to a azure table storage as a blob.
using Microsoft.Exchange.WebServices.Data
public void SendEmail(EmailMessage emailMessage)
{Stream stream = null;
foreach (Attachment attachment in emailMessage.Attachments)
{
if (attachment is FileAttachment)
{
FileAttachment file = (FileAttachment)attachment;
file.Load(stream);
emailAttachment.UploadEmailAttachment(attachmentFileName, stream);// This will upload to the table storage
}
}
}
when I load the attachment I am getting a error saying "The request failed schema validation: The required attribute 'Id' is missing.".
Any idea regarding this
As I understand, all you are looking for a way to load the contents of the attachment into a stream which you can further upload as blob.
If that is the case, I would suggest you to write the contents of your file attachment into MemoryStream instead:
var stream = new System.IO.MemoryStream(fileAttachment.Content);
If you want to read the contents as string, you can do that as well:
var reader = new System.IO.StreamReader(stream, UTF8Encoding.UTF8);
var text = reader.ReadToEnd();
Hope this helps!

Save attach of attached mail

I'm using Mail.dll from limilabs to manage an IMAP folder.
There is one mail with an attachment that is an eml file, so a mail.
It has in turn one attached eml file that I need to extract.
So the email structure is as follows:
Email
|- Attachment: file.eml
|- Attachment file2.eml
This is my code:
IMail email = new MailBuilder().CreateFromEml(imap.GetMessageByUID(uid));
Console.WriteLine(email.Subject);
// save all attachments to disk
foreach(MimeData mime in email.Attachments)
{
if (uid == 1376)
{
System.IO.Directory.CreateDirectory(string.Format(#"c:\EMAIL\{0}", uid));
mime.Save(#"c:\EMAIL\" + uid + "\\" + mime.SafeFileName);
MimeData help;
if (mime.ContentType.ToString() == "message/rfc822")
{
//i need to cast this attach in a imail
}
}
}
How can I extract the inner-most eml file (file2.eml in the structure mentioned above)?
From this link, it looks like you should be able to do the following:
if (attachment.ContentType == ContentType.MessageRfc822)
{
string eml = ((MimeText)attachment).Text;
IMail attachedMessage = new MailBuilder().CreateFromEml(eml);
// process further
}
If you only need to extract all attachments from all inner messages, you can use IMail.ExtractAttachmentsFromInnerMessages method:
IMail email = new MailBuilder().CreateFromEml(imap.GetMessageByUID(uid));
ReadOnlyCollection<MimeData> attachments = mail.ExtractAttachmentsFromInnerMessages();
foreach (MimeData mime in attachments)
{
mime.Save(#"c:\" + mime.SafeFileName);
}

vsto + cannot get the filename of the attachment from a RTF email

I am saving the attachments from a mail item. I am checking first the body format so that I only get true attachments. Please ignore the if else statements below with only comments as the next statements. I'll code that once I fix my problem. Now, my problem is I am getting this error when getting the filename of the attachment of a RichText body format mail. All is well in plain and HTML format.
'currentMailItem.Attachments[1].FileName' threw an exception of type 'System.Runtime.InteropServices.COMException'
base {System.Runtime.InteropServices.ExternalException}: {"Outlook cannot perform this action on this type of attachment."}
public static void SaveData(MailItem currentMailItem)
{
if (currentMailItem != null)
{
string PR_ATTACH_METHOD = "http://schemas.microsoft.com/mapi/proptag/0x37050003";
string PR_ATTACH_FLAGS = "http://schemas.microsoft.com/mapi/proptag/0x37140003";
if (currentMailItem.Attachments.Count > 0)
{
for (int i = 1; i <= currentMailItem.Attachments.Count; i++)
{
var attachMethod = currentMailItem.Attachments[i].PropertyAccessor.GetProperty(PR_ATTACH_METHOD);
var attachFlags = currentMailItem.Attachments[i].PropertyAccessor.GetProperty(PR_ATTACH_FLAGS);
if (currentMailItem.BodyFormat == OlBodyFormat.olFormatPlain)
//no attachment is inline
else if (currentMailItem.BodyFormat == OlBodyFormat.olFormatRichText)
{
if (attachMethod == 6)
//attachment is inline
else
//attachment is normal
}
else if (currentMailItem.BodyFormat == OlBodyFormat.olFormatHTML)
{
if (attachFlags == 4)
//attachment is inline
else
//attachment is normal
}
currentMailItem.Attachments[i].SaveAsFile(#"C:\TestFileSave\" + currentMailItem.Attachments[i].FileName);
}
}
}
}
For the RTF format, you have embedded OLE objects used by the RTF body to show the attachment preview and to edit it in-place. You can either filter out such attachment by the attachment type (Attachment.Type == olAttachmentType.olOLE (6)) or extract the attachment data using either
Extended MAPI (C++ or Delphi only) - call IAttach::OpenProperty(PR_ATTACH_DATA_OBJ, IID_IStorage, ...) to open the attachment data as IStorage and then extract file data from one of the IStorage streams. The exact stream name and format depends on the particular application used to create it (Word, Adobe, Paintbrush, Excel, etc.). You can see the data in OutlookSpy (I am its author): select a message with an attachment like that, click IMessage button on the OutlookSpy ribbon, go to the GetAttachmentTable tab, double click on the attachment, select the PR_ATTACH_DATA_OBJ property, right click, select OpenProperty, select IID_IStorage.
Redemption (any language - I am also its author): its RDOAttachment object is smart enough to extract the actual file data for the OLE attachments when you call SaveAsFile or access the FileName property.
This may be old question ...
I have seen similar issue. If image is attached in the body of rich text formatted mailItem then I get similar exception.
However, if the attachment is not image, say a .doc extension then I don't see the error. As far as I understand the issue is with getting attached image's file name. Some how Outlook doesn't know how to get in body attached image's file name from the rich text formatted email item.

Mail attachments not attaching

I am writing a simple email helper class that will be called by a Windows service. When I test though the email attachment is not sending with the rest of the email.
mailAttachmentFilePath is an ArrayList (just for clarification) and mail represents MailMessage class.
if (mailAttachmentFilePath.Count > 0)
{
foreach (string file in mailAttachmentFilePath)
{
Attachment data = new Attachment(file);
mail.Attachments.Add(data);
data.Dispose();
}
}
I am certain I am missing something but, I don't know what it is...
Do the data.Dispose() AFTER you send the email :D.
Remove the data.Dispose(). The attachments are being added by reference so when you call dispose it's actually releasing the attached file. You also don't really need the if statement. Try this:
foreach (string file in mailAttachmentFilePath)
{
Attachment data = new Attachment(file);
mail.Attachments.Add(data);
}

Categories

Resources