Send multiple attachments with Outlook, success but failiure - c#

The following code is ment to create an e-mail and attach the one or multiple attachemnts. So far the code does just that with success, but it also creates the equal amount of e-mails with the right amount of attachments in each of them. I.e. with three attachments it creates three e-mails all with the three attachments in each of them.
private void SendMail(List<string> paths)
{
DateTime defDt = DateTime.Now.AddMinutes(3);
Microsoft.Office.Interop.Outlook.Application app = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem mailItem = app.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
mailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
mailItem.GetInspector.Activate();
var signature = mailItem.HTMLBody;
mailItem.HTMLBody = signature;
foreach (string u in paths)
{
mailItem.Attachments.Add(u);
}
mailItem.DeferredDeliveryTime = defDt;
mailItem.Display(mailItem);
}
Why does it behave like this? There are no foreach loop besides the one for adding the multiple attachments. When firing with one attachment the error doesn't show (probably since it i creating just one e-mail with one attachment as excpected). Any thoughts from this great community?

The code was called in a foreach loop earlier in the code. The code presented works as expected.

Related

Outlook C# - Processing MailItem and MeetingItem in same function?

I have written some C# code that reads emails from Microsoft Outlook and writes the emails into a MongoDB. Specifically, my code puts an email into a MongoDB-compatible BsonDocument, and then it inserts the BsonDocument into a MongoDB collection.
In the C# interface to Outlook, you can open a Folder and call Folder.Items. This returns an object of type Items. When you iterate over an Items object, you encounter multiple object types, including MailItem and MeetingItem. Most of the fields in MailItem and MeetingItem are the same (e.g. Body, Subject, etc). In my code example below, I have separate functions to process MailItem and MeetingItem. However, this leads to code duplication.
So, my question is: is it there a way to write a function that is agnostic to whether the input is of type MailItem or MeetingItem?
Minimal code example
In my program, I import these things:
using Bson = MongoDB.Bson;
using Driver = MongoDB.Driver;
using Outlook = Microsoft.Office.Interop.Outlook;
And, the next three snippets comprise the body of the program:
namespace ReadEmail{
class Program{
public static Bson.BsonDocument convertMailItemToBson(Outlook.MailItem item){
Bson.BsonDocument retval = new Bson.BsonDocument();
retval.Add("Body", item.Body);
retval.Add("Subject", item.Subject);
return retval;
}
...and the same code a second time for MeetingItem:
public static Bson.BsonDocument convertMeetingItemToBson(Outlook.MeetingItem item){
Bson.BsonDocument retval = new Bson.BsonDocument();
retval.Add("Body", item.Body);
retval.Add("Subject", item.Subject);
return retval;
}
Finally, a main function to connect to Outlook, connect to MongoDB, and feed emails and meetings into the above functions:
static void Main(string[] args){
Outlook.Application outlookApplication = new Outlook.Application();
Outlook.NameSpace outlookNamespace = outlookApplication.GetNamespace("MAPI");
Outlook.MAPIFolder folder = outlookNamespace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
var dbClient = new Driver.MongoClient("mongodb://localhost");
var database = dbClient.GetDatabase("myDb");
var collection = database.GetCollection<Bson.BsonDocument>("myCollection1");
Outlook.Items items = folder.Items;
foreach (var item in items){
if (item is Outlook.MailItem mailItem){
Bson.BsonDocument iBson = convertMailItemToBson(mailItem);
collection.InsertOne(iBson);
}
else if (item is Outlook.MeetingItem meetingItem){
Bson.BsonDocument iBson = convertMeetingItemToBson(meetingItem);
collection.InsertOne(iBson);
}
}
}
}
}
The full program for the above code is in this gist. Note that in the full program, we aren't just reading 2 fields (Subject and Body). Instead, in the full program, we are reading 25 fields.
One thing I tried that hasn't yet worked
For our purposes, the only difference between MailItem and MeetingItem that matters is that MeetingItem doesn't have the To, CC, or BCC fields. So, I thought I could create a templatized function as follows:
public static Bson.BsonDocument convertItemToBson<T>(T item) where T : Outlook.MailItem, Outlook.MeetingItem
{
Bson.BsonDocument retval = new Bson.BsonDocument();
retval.Add("Body", item.Body);
retval.Add("Subject", item.Subject);
return retval;
}
However, I get the following compiler error:
CS0229 Ambiguity between '_MailItem.Body' and '_MeetingItem.Body'.
Note that out of the 25 fields in the full program, all of them have this error. Also note that for my full templatized version, I removed the usage of the To, CC, and BCC fields, because these fields exist in MailItem but not MeetingItem.
Recap
To summarize the question: In the above code, is there a way to combine convertMailItemToBson() and convertMeetingItemToBson() into one function that can accept either a MailItem or a MeetingItem as input?
You can just pass the parameters in method.
public static Bson.BsonDocument convertItemToBson(string body, string subject){
Bson.BsonDocument retval = new Bson.BsonDocument();
retval.Add("Body", body);
retval.Add("Subject", subject);
return retval;
}
and while calling you can call like below.
foreach (var item in items){
if (item is Outlook.MailItem mItem){
Bson.BsonDocument iBson = convertItemToBson(mItem.Body, mItem.Subject);
collection.InsertOne(iBson);
}
else if(item is Outlook.MailItem mItem){
Bson.BsonDocument iBson = convertItemToBson(mItem.Body, mItem.Subject);
collection.InsertOne(iBson);
}
}
There could be many possibilities. I think this would work. I have not tested the code as i don't have all code. Feel free to comment.
Pass the item as a generic object and use reflection to read the property values.

C# VSTO Outlook - select multiple items and send them as attachment

How do I select multiple items and open new mail form and add all selected items as attachments to the new email so I can send them together with the new email?
I guess, this could be broken into:
how to get multiple selected items in outlook (say user selects them all while holding Ctrl key to do multiple selection)?
How to open New Email form in Outlook?
How to attach selected item in 1 above and add them to the new email as attachments?
UPDATE:
So far I have managed to do following (Thanks to Dmitry's comment):
public void SendSelectedMailsAsAttachment()
{
try
{
Selection olSelection = HostAddIn.ActiveExplorer.Selection;
var count = olSelection.Count;
var items = new List<IItem>();
Microsoft.Office.Interop.Outlook.MailItem oMailItem = HostAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
foreach (var sel in olSelection)
{
oMailItem.Attachments.Add(sel);
}
oMailItem.Display(false);
}
catch (Exception ex)
{
//ex message
}
}
but with this code, following happens:
if I select single email and try to send it as attachment, the code above executes just fine and new email form opens but it will show no attachment.
if I select multiple emails and try to send them as attachment, the code above will throw exception when calling oMailItem.Attachments.Add(sel) for the 2nd time with exception "This operation is not supported until the entire message is downloaded. Download the message and try again."
Application.ActiveExplorer.Selection collection
Application.CreateItem / MailItem.Display
Loop through the items in the Application.ActiveExplorer.Selection collection and call MailItem.Attachments.Add for each item.

How to save attached EmailMessage from ItemAttachment object in Exchange EWS?

I've been fighting this problem for some time now, and have failed to find an answer online that works. I am using the Exchange EWS API to do some email processing. One of the things I need to process is an EmailMessage that has attachments on it. One of those attachments happens to be another EmailMessage. I will refer to this as the attached EmailMessage.
I want to convert this EmailMessage to a byte[], however every time I try, I get an Exception. Below is my code:
if (((ItemAttachment)attachment).Item is EmailMessage)
{
EmailMessage msg = ((ItemAttachment)attachment).Item as EmailMessage;
msg.Load(new PropertySet(ItemSchema.MimeContent));
byte[] content = msg.MimeContent.Content;
}
The problem is no matter what I try to load, I get an exception thrown saying
An exception of type 'System.InvalidOperationException' occurred in Microsoft.Exchange.WebServices.dll but was not handled in user code
Additional information: This operation isn't supported on attachments.
If I don't call msg.Load(), I get a different error saying I need to load the content.
I don't understand this. If I do the same operation on an EmailMessage that was not attached to anything, it works just fine. Why does it matter that the EmailMessage was an attachment at one point in time? How can I get the EWS/.NET/Whatever is throwing the Exception to treat the attached EmailMessage as an EmailMessage and not an ItemAttachment?
You need to use the GetAttachments operations on Each on the Embedded Attachments with a propertyset that includes the MimeContent. eg something like (this can made a lot more effienct by grouping the GetAttachment requests if your processing multiple message etc).
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
psPropSet.Add(ItemSchema.MimeContent);
foreach (Attachment attachment in CurrentMessage.Attachments)
{
if (attachment is ItemAttachment)
{
attachment.Load();
if (((ItemAttachment)attachment).Item is EmailMessage)
{
EmailMessage ebMessage = ((ItemAttachment)attachment).Item as EmailMessage;
foreach (Attachment ebAttachment in ebMessage.Attachments)
{
if (ebAttachment is ItemAttachment)
{
Attachment[] LoadAttachments = new Attachment[1];
LoadAttachments[0] = ebAttachment;
ServiceResponseCollection<GetAttachmentResponse> getAttachmentresps = service.GetAttachments(LoadAttachments, BodyType.HTML, psPropSet);
foreach (GetAttachmentResponse grResp in getAttachmentresps)
{
EmailMessage msg = ((ItemAttachment)grResp.Attachment).Item as EmailMessage;
msg.Load(new PropertySet(ItemSchema.MimeContent));
byte[] content = msg.MimeContent.Content;
}
}
}
}
}
}
You have to load the attachment with the flag to load MimeContent:
{
if (attachment is ItemAttachment ia)
{
ia.Load(ItemSchema.MimeContent);
}
}

Use attachments from calendar items - Outlook - C#

I'm trying to use the attachments included in calendar items pulled progmatically.
I have a list of chosen calendar subject lines from a previous dialog box, and while the subject is transferring properly, the body isn't working well (another question altogether) but the attachments aren't working whatsoever.
Here's my foreach loop where the attachments are being placed into an Attachments array for use later:
string[] subjects = new string[dialog.chosen.Count];
string[] bodies = new string[dialog.chosen.Count];
Attachments[] attach = new Attachments[dialog.chosen.Count];
foreach (Outlook.AppointmentItem appt in rangeAppts)
{
foreach (string text in dialog.chosen)
{
if (text == appt.Subject)
{
subjects[i] = appt.Subject;
bodies[i] = Convert.ToString(appt.Body);
attach[i] = appt.Attachments;
i = i + 1;
}
}
}
And then here's where I actually call the method:
sendEmailTemplate(bodies[i], subject, to, "", attachment: attach[i]);
And then the method itself:
public void sendEmailTemplate(string body, string subject, string to, string cc , Attachments attachment = null)
{
Microsoft.Office.Interop.Outlook.Application oApp = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook._MailItem oMailItem = (Microsoft.Office.Interop.Outlook._MailItem)oApp.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.HTMLBody = body;
oMailItem.Subject = subject;
try
{
oMailItem.Attachments.Add(attachment);
}
catch {}
oMailItem.To = to;
oMailItem.CC = cc;
oMailItem.Display(false);
oMailItem.Application.ActiveInspector().WindowState = Microsoft.Office.Interop.Outlook.OlWindowState.olNormalWindow;
}
I've tried several things, however when I actually go to send the e-mail, I end up getting:
Exception: Member not found. HRESULT: 0x80020003
And then I haven't been able to get anything else to work. The try/catch loop on the method is to prevent the above exception as I was getting that exception regardless of whether or not an attachment was present, and now attachments just aren't being added.
I'm using Interop that comes with Office along with C#. Winforms if that makes a difference.
MailItem.Attachments takes either a string (fully qualified file name), or another Outlook item (MailItem, ContactItem, etc.).
You cannot pass Attachments object as an argument. If you need to copy the attachments, loop through all attachments in the Attachments collection, call Attachment.SaveAsFile for each attachment, pass the file name to MailItem.Attachments.Add, delete thee temporary file.

Send all email data to a service from an outlook addin

I'm new to office addins. I'm an MVC programmer but this project has been dumped on me as no one else wants to do it. I need to create an outlook addin that will forward all email data to a service where communications can be tracked by a recruitment system.
I am using
Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(saveEmail);
where I then cast the email into a Outlook.MailItem. The problem is I see no way of getting the from and to email addresses. All it gives me is the name of the people. Am I missing something?
So far the best solution I can think of is to save the msg as a .msg file. Forward that to my service and then user a parser I found to convert it to HTML.
Any suggestions?
To access the recipients, loop through MailItem.Recipients collection and access Recipient.Name and Recipient.Address properties.
Sender related properties are not yet set by the time ItemSend event fires - the earliest you can access sender properties is when Items.ItemAdd event fires on the Sent Items folder (retrieve it using Namespace.GetDefaultFolder).
You can read the MailItem.SendUsingAccount. If it is null, use the first Account from the Namespace.Acounts collection. You can then use Account.Recipient object.
Keep in mind that you should not blindly cast outgoing items to MailItem objects - you can also have MeetingItem and TaskRequestItem objects.
OK using the info given to me by Dmitry Streblechenko and some other info I just looked up here is my solution so far.
In the ItemSend event I first make sure that sent email is moved to the default sent items folder. I'm testing outlook using gmail so normally these will go elsewhere. sentMailItems is made as a class field as apparently it will get garbage collected if its just declared inside the Startup function (Something quite odd to me an MVC programmer :) ).
I' will test this on exchange when I get back to office an hopefully all goes well.
public partial class ThisAddIn
{
public Outlook.Items sentMailItems;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(ItemSend);
sentMailItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items;
sentMailItems.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(Items_ItemAdd);
}
void Items_ItemAdd(object item)
{
MessageBox.Show(((Outlook.MailItem)item).Subject);
var msg = Item as Outlook.MailItem;
string from = msg.SenderEmailAddress;
string allRecip = "";
foreach (Outlook.Recipient recip in msg.Recipients)
{
allRecip += "," + recip.Address;
}
}
private void ItemSend(object Item, ref bool Cancel)
{
if (!(Item is Outlook.MailItem))
return;
var msg = Item as Outlook.MailItem;
msg.DeleteAfterSubmit = false; // force storage to sent items folder (ignore user options)
Outlook.Folder sentFolder = this.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.Folder;
if (sentFolder != null)
msg.SaveSentMessageFolder = sentFolder; // override the default sent items location
msg.Save();
}
//Other auto gen code here....
}

Categories