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

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.

Related

Attachments issue when forwarding an email using VSTO

I have a RibbonXML which provide a context menu that takes an action on a received email. That email contains a spreadsheet attachment, and I want to update that spreadsheet (xlsx) and forward it to another recipient...
What is happening is that the recipient see two attachments, one being usually quite small (a few KB), and the other being the correct attachment. This does the same thing with pdf, or text files, so pretty sure it isn't the file type. It shows up in the attachments list in the email inspector, but if you try to do anything with it Outlook says the attachment cannot be found.
I built up the test from scratch and added in components of my solution until the error was found. It seems to be related to removing attachments from the new email item (resulting from the Forward() method).
public void OnTestAttachment(Office.IRibbonControl control)
{
if (control.Context is Selection)
{
Selection selection = control.Context as Selection;
if (selection.Count == 1)
{
object item = selection[1];
if (item is MailItem)
{
MailItem mailItem = item as MailItem;
var newItem = mailItem.Forward();
newItem.Recipients.Add("xxxxx#xxxxx.com");
var newAttachments = newItem.Attachments;
// remove the line below and I don't see the issue
for (int i = newAttachments.Count; i >= 1; i--) { newAttachments.Remove(i); }
{
var body = "Testing.....\r\n";
MSWord.Document document = (MSWord.Document)newItem.GetInspector.WordEditor;
MSWord.Paragraph paragraph = document.Paragraphs.Add(document.Range());
paragraph.Range.Text = body;
}
// do some things with a file..in prod I save the existing file, edit it via code and then save it back down....
// I tested using a byte stream in case that has something to do with the issue (as that's the closest
// match to what is actually going on in prod)
var fileName = #"C:\Users\<me>\Test.xlsx";
var temp = Path.Combine(Path.GetTempPath(), Path.GetFileName(fileName));
byte[] buffer = File.ReadAllBytes(fileName);
using (var stream = new FileStream(temp, FileMode.Create, FileAccess.Write))
{
stream.Write(buffer, 0, buffer.Length);
stream.Close();
}
newAttachments.Add(temp, Microsoft.Office.Interop.Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
// the issues doesn't appear here...it is only once it is received that it appears
newItem.Display();
Marshal.ReleaseComObject(newItem);
Marshal.ReleaseComObject(newAttachments);
Marshal.ReleaseComObject(mailItem);
}
}
Marshal.ReleaseComObject(selection);
}
It shows up in the attachments list in the email inspector, but if you try to do anything with it Outlook says the attachment cannot be found.
The Outlook object model is not related to the issue described. The file can be overwritten with an empty data and then can be attached to the email:
using (var stream = new FileStream(temp, FileMode.Create, FileAccess.Write))
{
stream.Write(buffer, 0, buffer.Length);
stream.Close();
}
The following sentence confirms this:
What is happening is that the recipient see two attachments, one being usually quite small (a few KB), and the other being the correct attachment.
If you need to cope the file content I'd suggest saving the file using the SaveAsFile method of the Attachment class which saves the attachment to the specified path. Thus you can be sure the file content is not empty.

Microsoft Graph - Saving file attachments through 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.

How to paste image from clipboard to Outlook email Body?

Here is my situation.
I've made a Application Windows Form which is editing an Excel sheet depending on what the user is doing (buttons / toggle / text box / etc..).
Once the edtion is complete the new Excel file is generated.
My programm then selects a range of cells, and copies it.
It goes to Clipboard.
I've done multiple tests (if(){}else{} / saving into a .jpg / etc..) and everything is checked true.
I don't want to ATTACH my image.
I don't want to save it, even temporarily, then paste it in the body through the saved .jpg file.
I "just" want to make a Ctrl+V, into my Outlook eMail Body.
here's how i get the image from my clipboard ("MessageBody" is declared at the top as a public string so that i can call it through different regions):
public void ReadData()
{
Excel excel = new Excel(#"E:\c#\Project#XX\Resources\TEST.xlsx", 1);
excel.CopyRange(0, 0, 32, 12);
IDataObject iData = Clipboard.GetDataObject();
if (iData.GetDataPresent(DataFormats.Bitmap))
{
Bitmap MessageBody = (iData.GetData(DataFormats.Bitmap, true) as Bitmap);
//pbx.Image = Image;
//image.Save(#"E:\c#\Project#XX\Resources\bitmap1.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
//MessageBody = image;
}
excel.Close();
excel.Quit();
}
Here's my message building code :
private void flatCustButton013_Click(object sender, EventArgs e)
{
ReadData();
string MessageSubject = $"SomeSubject";
Outlook.MailItem newMail = (Outlook.MailItem)application.CreateItem(Outlook.OlItemType.olMailItem);
newMail.Subject = MessageSubject;
newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
MessageHTMLBody = "<html><body>SomeText.<img src="cid:MessageBody"</img></body></html>";
newMail.HTMLBody = MessageHTMLBody;
//newMail.Body = System.Windows.Input.ApplicationCommands.Paste;
//newMail.Display(false);
//newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML; ;
//newMail.HTMLBody = System.Windows.Input.ApplicationCommands.Paste;
//newMail.Body = MessageBody;
newMail.To = ToAddress;
newMail.SentOnBehalfOfName = FromAddress;
newMail.CC = CcAddress;
newMail.BCC = BccAddress;
//System.Windows.Input.ApplicationCommands.Paste;
//wrdEdit = application._Inspector.WordEditor
newMail.Send();
}
In the previous version, people had to open an Excel file, change the Cells manually, and then click a button (with a macro in VB).
It would open a Outlook eMail item (display:true) with the image pasted already, then just needed to click "Send" and it was ok.
My 2.0 version is meant to automatise this by just click a "Send //Generated Excel Sheet//"Button.
Here's the macro code in VB :
Sub SendMail()
Dim Messager As New Outlook.Application
Dim Mail As Outlook.MailItem
Dim WrdEdit
Range("my_range").CopyPicture
Set Messager = New Outlook.Application
Set Mail = Messager.CreateItem(olMailItem)
With Mail
.To = "some folks"
.CC = "some folks"
'.BCC = ""
.Subject = "SomeSubject"
.Display
.HTMLBody = Mess + .HTMLBody
End With
Set WrdEdit = Messager.ActiveInspector.WordEditor
WrdEdit.Application.Selection.Paste
Set WrdEdit = Nothing
Set Messager = Nothing
Set Mail = Nothing
ActiveWorkbook.Close SaveChanges:=False
End Sub
But i don't get why i would pass through the Inspector (which i have no knowledge about) if i'm succeeding in retrieving the clipboard image.
I found a way by adding an attachment from a local saved file to the mail and displaying it into the body with HTML.
Like so :
newMail.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
Outlook.Attachment attachment = newMail.Attachments.Add(#"path.imageformat", Outlook.OlAttachmentType.olEmbeddeditem, null, $"someTitle");
string imagecid = "whatever";
attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3613041E", imagecid);
newMail.HTMLBody = String.Format("<body><img src=\"cid:{0}\"></body>", imageCid);
the "http://schemas.microsoft.com/mapi/proptag/0x3712001E" code is here : http://www.outlookcode.com/codedetail.aspx?id=1915.
And it works.
However, i really don't like having urls in my code, is there another way ?
And also, it looks like whatever i put into my 'imagecid' string nothing changes.
I'm trying hard to understand what i did with the code, and not just letting it work as it is.
Looks like i need some authorisation from Office to work through an application ? And especially when i want to paste image in a body (which can contain other stuff than just pixel data).
If i did not need this autorization with the VB script, i suppose, it is because i had to "physicly/humanly" press "Send Mail" button in Outlook ?
Are there any other possibilities ? I'm still using a file though to paste in my body, can't i just make a Ctrl+C Ctrl+V programmatically ? :(

How to get and set field format for all the fields in a pdf

I have created EditTextField in PDF using iTextSharp library. I can able to set FieldName to EditTextField. I also want to set DataFormat to it. I will get XML file containing only FieldName and Value. So While merging Value to PDF Template, I want to check the DataFormat and according to that I want to convert the value and set.
I have added a EditTextField. I can able to add name to textfield. I want to add format (Like DateTime format) to TextField. So that next time I can able to fetch all the text fields from PDF and check the format and according to the format, I can able to set the data to it programmatically.
TextField _DOBtext = new TextField(pdfStamper.Writer,
new Rectangle(40, 670, 110, 650), "patient-dob");
_DOBtext.SetFormat("DateFormat", "mm/dd/yyyy"); // we want to set format like this.
pdfStamper.AddAnnotation(_DOBtext.GetTextField(), 1);
pdfStamper.Close();
And while processing the same PDF for filling data, we will first check the AcrofieldName, and logic is written below (its not working code)
var GetField = pdfStamper.Acrofields.Field.where(u=>u.Key == "patient-dob").FirstOrDefault();
var Format = GetField.GetFormat(); // we want like this feature
if (Format != null) {
if (Format.Type = "DateTime") {
value=string.Format(data, Format.FormatString));
stamper.AcroFields.SetField(fieldId, value); //fieldId = "patient-dob"
}
}
Please help me to Set and get the DataFormat
(ediit: add iTextSharp tag)
The reason that iText doesn't support something like SetFormat() and GetFormat() is because the PDF format doesn't support it.
You might notice, however, that Adobe Acrobat allows you to specify a field format. The way that they do this is via a JavaScript file that they ship with all of their products. If other PDF renderers (such as Chrome, Firefox or IE) support this I don't know. This JS file has a bunch of built-in functions that they can use for client-side validation and one of those function is AFDate_FormatEx(). You can see how that lays out in a PDF in this screenshot:
You might have noticed my emphasis above on client-side validation. That's because this is JS which is optional in the spec and it isn't required when programmatically interacting with forms. However, if you are trying to mimic Adobe's product you might want to go down this path. You can programmatically retrieve these settings via something like:
var aditionalActions = reader.AcroFields.Fields["patient-dob"].GetWidget(0).GetAsDict(PdfName.AA);
And you can set it via something like SetAdditionalActions() on your PdfFormField.
This is probably a really fragile path but it might work for you.
However, for me I'd try something different and just hijack another field that I know that I'm not going to use. Looking through the spec I think I'd just pick something like /TM which is just a string used for the field's name when exporting data (I think via FDF or HTTP POST).
var _DOBtext = new TextField(writer, new iTextSharp.text.Rectangle(0, 0, 110, 650), "patient-dob");
var tf = _DOBtext.GetTextField();
tf.MappingName = "patient-dob:date:mm/dd/yyyy";
And to retrieve it:
var mappingName = reader.AcroFields.Fields["patient-dob"].GetWidget(0).GetAsString(PdfName.TM);
You can come up with whatever format you want for this entry, as long as it makes sense to you. I'd recommend keeping the field's original name as part of it and then picking whatever delimiters work for you. The value should travel safely with the PDF regardless of the rendering application.
Sometime ago I wrote a library to manage PDF AcroForm fillup.. this the a sample code from my fill function:
private string fill_form(string output_file)
{
using (PdfReader _pdfReader = new PdfReader(FormPath))
{
using (PdfStamper _pdfStamper = new PdfStamper(_pdfReader, new FileStream(output_file, FileMode.Create)))
{
_pdfStamper.AcroFields.GenerateAppearances = true;
foreach (var _field in _pdfStamper.AcroFields.Fields)
foreach (TemplateField _spField in _lstFields)
{
if (_field.Key.Equals(_spField.Name))
{
switch (_spField.Type )
{
case TemplateFieldType.Text:
_pdfStamper.AcroFields.SetField(_field.Key, _spField.Value);
break;
case TemplateFieldType.Checkbox:
//TODO: check Value field cannot be set != OnValue, Offvalue
if (_spField.Value == _spField.OnValue)
_pdfStamper.AcroFields.SetField(_field.Key, _spField.OnValue);
else
_pdfStamper.AcroFields.SetField(_field.Key, _spField.OffValue);
break;
}
}
}
_pdfStamper.FormFlattening = true;
}
}
return output_file;
}
As you can see in the inner loop I do something similar to your needs.
Hope this helps.
This worked for me (using iText7)
textBox.SetAdditionalAction(PdfName.F, iText.Kernel.Pdf.Action.PdfAction.CreateJavaScript("AFDate_FormatEx(\"yyyy-mm-dd\");"));
textBox.SetAdditionalAction(PdfName.K, iText.Kernel.Pdf.Action.PdfAction.CreateJavaScript("AFDate_KeystrokeEx(\"yyyy-mm-dd\");"));
thanks to the iText RUPS tool.

Read bookmarks in outlook MSG file with C#

My goal is to somehow be able to read bookmarks in an outlook .msg file, then replace them with a different text. I want to do this with C#.
I know how to access the body and change the text, but was wondering if there was a way to access directly the list of all the bookmarks and its location so that i can easily replace them, instead going through the whole body text, splitting it up, etc etc...
edit: this is how a bookmark window looks like from this window one can assign bookmarks, but it should be possible to obtain this list via c#.
Any relevant info is appreciated.
Thanks in advance.
Since Outlook most often uses Word as it's body editor - you need to add a project reference to Microsoft.Office.Interop.Word.dll and then access to the Outlook Inspector's WordEditor during the Inspector.Activate event. Once you have access to the Word.Document - it's trivial to load up the Bookmarks and access/modify their values.
Outlook.Inspector inspector = Globals.ThisAddIn.Application.ActiveInspector();
((Outlook.InspectorEvents_10_Event)inspector).Activate += () =>
{ // validation to ensure we are using Word Editor
if (inspector.EditorType == Outlook.OlEditorType.olEditorWord && inspector.IsWordMail())
{
Word.Document wordDoc = inspector.WordEditor as Word.Document;
if (wordDoc != null)
{
var bookmarks = wordDoc.Bookmarks;
foreach (Word.Bookmark item in bookmarks)
{
string name = item.Name; // bookmark name
Word.Range bookmarkRange = item.Range; // bookmark range
string bookmarkText = bookmarkRange.Text; // bookmark text
item.Select(); // triggers bookmark selection
}
}
}
};

Categories

Resources