I'm making an Outlook Add-in (Visual Studio 2010, .NET 4.0, C#), and I would like to automatically archive a user's email after they send it. What I have so far is the following:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//Create an event handler for when items are sent
Application.ItemSend += new ApplicationEvents_11_ItemSendEventHandler(saveEmail);
}
private void saveEmail(object Item, ref bool Cancel)
{
}
What I've found through debugging is that my saveEmail method fires off right before the email actually sends. This is OK, ideally I would like it to be fired off immediately after the email is sent successfully, so if there's a way to do that I'd appreciate some pointers.
In any case, I can get inside that method and what I'd like to do is access that email as an Outlook.MailItem object and use the .SaveAs method with whatever parameters I choose. How would I go about grabbing the currently-opened-and-about-to-be-sent-email as a MailItem object?
you can try with this code
private void saveEmail(object Item, ref bool Cancel)
{
var msg = Item as Outlook.MailItem;
msg.SaveAs(yourPath, Outlook.OlSaveAsType.olMSG);
}
Related
I'm trying to write an Outlook Add-in that deletes an email after the email has been read completely.
The problem is that the read-flag turns true the second we click on it and it doesn't give us much time to read the email.
I tried to delete the email after closing it:
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
}
}
void MailItem_Close(ref bool Cancel)
{
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
Outlook.Items inboxReadItems = inbox.Items.Restrict("[Unread]=false");
foreach (Outlook.MailItem mail in inboxReadItems)
{
mail.Delete();
}
}
This was the idea i came up with, sometimes it works but most of the times it ends with an error in the mail.Delete():
The Error: System.Runtime.InteropServices.COMException: 'The item’s properties and methods cannot be used inside this event procedure.'
First of all, your event handler may not be fired because the source object is declared in the methods and the scope of that object is limited by the method/function. After execution of the method/function ends, the garbage collector may swipe the heap (unpredictable) with your source object, so you may never get the event fired.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
}
}
Instead, you need to declare the sourceobject at the class level if you want to keep getting events fired, in your case maintain a list of objects (which is also inefficient when dealing with COM objects):
List<Outlook.MailItem> items = new List<Outlook.MailItem>();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
Outlook.MAPIFolder inbox = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
foreach (Outlook.MailItem mail in inbox.Items)
{
((Outlook.ItemEvents_10_Event)mail).Close += new Outlook.ItemEvents_10_CloseEventHandler(MailItem_Close);
list.Add(mail);
}
}
But in that case you may reach the limit of currently held and opened objects in Outlook with Exchange. So, the better solution is to handle the SelectionChange event of the Explorer class and deal with the currently selected items only. Every time the selection is changed, your source objects are renowned (old are released and new ones are subscribed).
But a better yet solution is to implement an item wrapper for Outlook items, so you could subscribe to the Close event or any other events in a convenient fashion. See Implement a wrapper for inspectors and track item-level events in each inspector for more information.
This is as inefficient as it gets - you set up event handlers on (potentially) thousands of emails in the Inbox folder. Do not do that.
Track the Explorer.SelectionChange event and set up event handlers only on those emails that are selected (and remove event handlers from the previous selections). If the item is unread, trap its read state change, and put its entry id in a list. When selection changes, delete the previously unread email(s) from that list.
I'm writing Outlook VSTO add-in and I want to do size check before attachment add and if the file is too big I want to upload it to the cloud, so the code looks like:
public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.Inspectors.NewInspector += InspectorsOnNewInspector;
}
private void InspectorsOnNewInspector(Inspector inspector)
{
if (inspector.CurrentItem is MailItem mailItem)
{
mailItem.BeforeAttachmentAdd += MailItemOnBeforeAttachmentAdd;
}
}
private void MailItemOnBeforeAttachmentAdd(Attachment attachment, ref bool cancel)
{
// check and upload
cancel = true;
}
private void ThisAddIn_Shutdown(object sender, EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[SuppressMessage("ReSharper", "ArrangeThisQualifier")]
[SuppressMessage("ReSharper", "RedundantDelegateCreation")]
[SuppressMessage("ReSharper", "RedundantNameQualifier")]
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
The problem is that all works fine until the file exceeds the size limit configured in MS Exchange. When it happens I get a notification message and after clicking "OK" mailItem.BeforeAttachmentAdd event doesn't fire. How can I deal with it?
None of your event handlers will work for more than a few seconds - you are setting the event handler either on a temporary variable (created by the compiler) in case of Application.Inspectors.NewInspector or on a local variable (when you set mailItem.BeforeAttachmentAdd event handler).
The object raising the events must be alive - store these objects on the global (class) level to make sure they are not collected by the Garbage Collector.
Also, there is no particular / guaranteed order of events, but I would imagine Outlook would always get the first pick. Worst case, you can patch IDropTarget implementation of the Outlook window and provide your own implementation. Not much you can do if an attachment is being inserted from the Ribbon...
I am using VSTO to create an event when an email is sent. The goal is change Attachments.
I already have other addins that run in the ItemSend event, but the problem is, I want my addin to run first. As I've read, there is no execution order for the Outlook addins sent event, but there must be some order even if only by name or guid...
I tried this solution (the problem is, if I have 2 mail windows open, the first window doesn´t run the event ... :( there is some overwrite event problem)
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
//This run in the end off all ItemSend Events.... :(
//this.Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(MyFunction2);
}
private void Custom_Inspector(Inspector Inspector)
{
if (Inspector != null && Inspector.CurrentItem != null && Inspector.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem mailItem = Inspector.CurrentItem as Outlook.MailItem;
if (mailItem.EntryID == null)
{
((ItemEvents_10_Event)mailItem).Send += new ItemEvents_10_SendEventHandler(MyFunction);
}
}
}
void MyFunction(ref bool Cancel)
{
MailItem mailItemContext = ((Inspector)this.Application.ActiveWindow()).CurrentItem as MailItem;
if (mailItemContext != null)
{
//my custom code here
}
}
this.Application.Inspectors.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
To get the NewInspector event of the Inspectors class fired you need to keep the source object alive, i.e. prevent it from swiping by the garbage collector. So, I'd recommend declaring the an instance of the Inspectors class at the global scope - at the class level.
The Outlook object model doesn't provide anything for changing the order of events. From my experience add-ins are loaded based on the ProgID value (sorted in the alphabetical order) and events are fired in the reverse order, i.e. a LIFO queue.
Eugene 100000 thanks! in reality Outlook Order Plugin Events by alphabetical reverse.
But by the way, how set NewInspector in top class? i need to define inside class ThisAddIn a prop call:
public partial class ThisAddIn
{
public Microsoft.Office.Interop.Outlook.Inspectors _inspector;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
_inspector = this.Application.Inspectors;
_inspector.NewInspector += new InspectorsEvents_NewInspectorEventHandler(Custom_Inspector);
}
}
I have a Outlook AddIn that shows a dialog every time a user sends a message.
I am opening the dialog and getting the message information on onItemSend event. This is working as expected: Once the message is sent, it opens a dialog, with some information such as recipients, subject, etc.
The problem is when the user keeps Outlook opened and send a message from another source, such as phone or tablet. After the user sends the message, the opened Outlook will display the dialog, even if it was not sent from that instance.
For instance, there was a user that has the addin installed at his home and office. He kept his home Outlook opened and after a busy day at work, sending several messages and displaying the dialogs, he checks his outlook at home. It will be several opened dialogs, regarding the messages he sent during the day.
Is there a way to restrict the dialogs? Kind of open the dialog where the user is sending the message from? This way the user will not have any dialog in any other opened outlook...
Thanks
EDIT
Just checked my code and there was a misunderstand (of my part off course). I removed some code from the methods, just to be easier to read...
There are two event handlers in the code:
Outlook.Folder sentbox;
Outlook.Items myOlItems = null;
this.itemsendhndler = newicrosoft.Office.Interop.Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
this.Application.ItemSend += itemsendhndler;
this.hndler = new Outlook.ItemsEvents_ItemAddEventHandler(Application_ItemAdd);
sentbox = this.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.Folder;
myOlItems = sentbox.Items;
myOlItems.ItemAdd += this.hndler;
Then, in Application_ItemAdd there is the onItemSend method, that I thought it was the Application_ItemSend...
void Application_ItemAdd(object Item)
{
this.onItemSend(Item);
}
private void Application_ItemSend(object item, ref bool Cancel)
{
Outlook.MailItem mail = item as MailItem;
if (mail == null)
return;
//Make sure this is a E-mail message
if (string.Compare(OutlookItemHelper.GetMessageClass(mail), "IPM.Note") != 0)
return;
mail.DeleteAfterSubmit = false;
Outlook.Folder sentFolder = this.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.Folder;
if (sentFolder != null)
mail.SaveSentMessageFolder = sentFolder; // override the default sent items location
Cancel = false;
}
private void onItemSend(object Item)
{
Outlook.MailItem itm = null;
itm = (Outlook.MailItem)Item;
using (ExampleDialog dlg = new ExampleDialog())
{
//code after actions...
}
}
Anyway, the dialog is not being opened in Application_ItemSend event. My bad guys...I learnt something new today!
Thanks guys and I apologize for the newbie question...
Are you sure? Application.ItemSend will only fire on the instance of Outlook that actually sends the message. Items.ItemAdd event on the Sent Items folder on the other hand will fire in each instance of Outlook. Is that what your code is doing?
Because I'm in the ribbon's class, there is no pointer to the Outlook.Application object. So, I cannot use
Application.ItemSend += new ApplicationEvents_11_ItemSendEventHandler(MyItemSendEventHandler)
this event handler.
How can I reach Outlook.Application object in ribbon class or are there another way to catch send event?
public void SendEnMail(Office.IRibbonControl control) //OnAction Function
{
Outlook.Application oApp = new Outlook.Application();
Outlook._MailItem myMail = (Outlook._MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem);
myMail.Display(true);
Outlook.Application application = Globals.ThisAddIn.Application;
application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
void Application_ItemSend(object Item, ref bool Cancel)
{
string a = ((Microsoft.Office.Interop.Outlook.MailItem)Item).Body;
System.Windows.Forms.MessageBox.Show(a);
Cancel = true;
}
I cannot catch the ItemSend event like that. but if I write the eventhandler to ThisAddIn class
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
void Application_ItemSend(object Item, ref bool Cancel)
{
string a = ((Microsoft.Office.Interop.Outlook.MailItem)Item).Body;
System.Windows.Forms.MessageBox.Show(a);
Cancel = true;
}
like this, it is working.
I usually do this:
Outlook.Application application = Outlook.Application.GetActiveInstance();
But this should work the same:
Outlook.Application application = Marshal.GetActiveObject("Outlook.Application") as Outlook.Application;
Or if youre making a COMAddIn you can catch the application object in the OnConnection event.
Hope it helps
Edit:
It makes sence that binding to the event is done when the addin starts. As when you bind to the event on the onAction you will (from that point on) keep catching the ItemSend for every Item the amount of times you clicked the button (as it adds a new handler). You could also try to catch the mail.SendEvent
private Outlook.MailItem myMail;
public void SendEnMail(Office.IRibbonControl control) //OnAction Function
{
myMail = (Outlook.MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem);
myMail.Send += mail_Send;
}
void mail_Send(ref bool Cancel)
{
string a = myMail.Body;
System.Windows.Forms.MessageBox.Show(a);
Cancel = true;
}
Edit2:
Excuse me as i'm using a different library.
According to MSDN it should have been the Send event instead of SendEvent event. I've changed the example accordingly.
Hope it works this time.
I think i see the issue is youre using the _MailItem, which is an interface of the MailItem interface. use the MailItem interface instead