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);
}
}
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.
Win 10 - VS 2019 - Office365 - Exchange Online
My first post - woo hoo!!! (Although I have been lurking forever.)
I am writing my first Outlook Add-in with my first experience with C#. I have been hacking on computers since the 1980s and have had experience with about 10 different languages but am very new to this C# and .NET environment.
What I have will build without complaint and works as intended - most of the time.
namespace RoomReservations
{
public partial class ThisAddIn
{
private Outlook.Inspectors inspectors = null;
private Outlook.UserProperty objUserPropertyEventId = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
}
private void ThisAddIn_Shutdown(object sender, System.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
}
void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
{
#pragma warning disable IDE0019 // Use pattern matching
Outlook.AppointmentItem appt = Inspector.CurrentItem as Outlook.AppointmentItem;
#pragma warning restore IDE0019 // Use pattern matching
// is it an AppointmentItem
if (appt != null)
{
// is it a meeting request
if (appt.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
{
appt.Save();
// save EventId as UserProperty
Outlook.AppointmentItem mtg = null;
mtg = (Outlook.AppointmentItem)Inspector.CurrentItem;
mtg.MeetingStatus = Outlook.OlMeetingStatus.olMeeting;
if (mtg != null)
{
if (mtg is Outlook.AppointmentItem)
{
string strEntryId = mtg.EntryID;
Outlook.UserProperties objUserProperties = mtg.UserProperties;
objUserPropertyEventId = objUserProperties.Add("MeetingEntryId", Outlook.OlUserPropertyType.olText, true, 1);
objUserPropertyEventId.Value = strEntryId;
}
}
if (mtg != null)
Marshal.ReleaseComObject(mtg);
}
if (appt != null)
Marshal.ReleaseComObject(appt);
}
// define event handler for ItemSend
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
public void Application_ItemSend(object Item, ref bool Cancel)
{
// use EventId to identify current meeting request
var app = new Microsoft.Office.Interop.Outlook.Application();
var ns = app.Session;
Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);
// is ItemSend a request or a cancel
if (meeting.MeetingStatus != Outlook.OlMeetingStatus.olMeetingCanceled)
{
// ItemSend is a request
MessageBox.Show("Not Cancelled.");
if (meeting is Outlook.AppointmentItem)
{
if (meeting.MeetingStatus == Outlook.OlMeetingStatus.olMeeting)
{
// if a location was provided
if (meeting.Location != null)
{
Cancel = true;
bool blnTorF = false;
Outlook.Recipient recipConf;
// has the user included "JHP - Room Reservations" for placement
// on the shared calendar for all conference room availability
foreach (Outlook.Recipient objUser in meeting.Recipients)
{
if (objUser.Name == "JHP - Room Reservations")
blnTorF = true;
}
// add "JHP - Room Reservations" if not already included
if (blnTorF == false)
{
recipConf = meeting.Recipients.Add("JHP - Room Reservations");
recipConf.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
if (recipConf != null)
Marshal.ReleaseComObject(recipConf);
}
// add reservarion to specific conference room calendar
String strLocation = meeting.Location;
Outlook.Recipient recipRoomUser;
if (strLocation.Contains("142"))
{
recipRoomUser = meeting.Recipients.Add("conference142#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("150"))
{
recipRoomUser = meeting.Recipients.Add("conference150#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("242"))
{
recipRoomUser = meeting.Recipients.Add("conference242#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
if (strLocation.Contains("248"))
{
recipRoomUser = meeting.Recipients.Add("conference248#jhparch.com");
recipRoomUser.Type = (int)Outlook.OlMeetingRecipientType.olRequired;
}
// send request
meeting.Recipients.ResolveAll();
meeting.Send();
}
}
}
}
else
{
// ItemSend is a cancel
MessageBox.Show("Meeting is cancelled.");
}
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
When working correctly, a meeting request will put 4 separate entries on 4 separate calendars. A meeting cancel will remove 4 separate entries from 4 separate calendars. The erratic behavior that occurs for a request includes placing the meeting event on only 2 calendars. Unusual behavior that occurs for a cancel includes deleting the meeting event from only 2 calendars. Or the meeting cancel has to be sent out 3 or 4 times before the meeting event is removed from all calendars. But then, I send another meeting request without changing any code and it is placed on all 4 calendars, as expected. And a cancel of that request will remove it from all 4 calendars on the first try. This odd behavior is common within our test group of three users, each running the identical add-in.
I feel like I understand how the native functions [if, foreach, etc.] work. But I don't know what I don't know. I have added some housekeeping [Marshal.ReleaseComObject()]. I'm not sure if it is everywhere it needs to be. I'm not sure if there are other means of housekeeping I am not aware of that could be causing this behavior. I am having a difficult time understanding why this will work, then not work, then work - all without changing any code.
Thanks for any leads that point in the direction of discovery,
Chris
The best way to understand how the code works is to run it under the debugger. Just set some breakpoints and run the solution hitting F5 in Visual Studio. When your breakpoints are hit you can go line-by-line and see intermediate results.
First of all, there is no need to subscribe to the ItemSend event in the NewInspector event handler. That is an Application-wide event, so you need to subscribe only once at startup.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
inspectors = this.Application.Inspectors;
inspectors.NewInspector += new Microsoft.Office.Interop.Outlook.InspectorsEvents_NewInspectorEventHandler(Inspectors_NewInspector);
// define event handler for ItemSend
Application.ItemSend += new Outlook.ApplicationEvents_11_ItemSendEventHandler(Application_ItemSend);
}
Second, the item which is being sent is passed as a parameter to the ItemSend event handler, so you can use it instead of getting a separate one. So, the following code doesn't make sense:
public void Application_ItemSend(object Item, ref bool Cancel)
{
// use EventId to identify current meeting request
var app = new Microsoft.Office.Interop.Outlook.Application();
var ns = app.Session;
Outlook.AppointmentItem meeting = ns.GetItemFromID(objUserPropertyEventId.Value);
Instead, use the parameter:
public void Application_ItemSend(object Item, ref bool Cancel)
{
Outlook.AppointmentItem meeting = Item as Outlook.AppointmentItem;
Third, there is no need to create a new Outlook Application instance in the ItemSend event handler, use the Application property of your add-in class instead
Fourth, if you need to change the outgoing item there is no need to call the Send method in the ItemSend event handler.
meeting.Send();
Use the second parameter if you want to allow or cancel any further processing of that item. For example, if the event procedure sets this argument to true, the send action is not completed and the inspector is left open.
Fifth, I'd recommend using the try/catch blocks to log exceptions and release underlying COM objects in the finally, so you could be sure that everything went well and all objects were released. For example:
private void CreateSendItem(Outlook._Application OutlookApp)
{
Outlook.MailItem mail = null;
Outlook.Recipients mailRecipients = null;
Outlook.Recipient mailRecipient = null;
try
{
mail = OutlookApp.CreateItem(Outlook.OlItemType.olMailItem)
as Outlook.MailItem;
mail.Subject = "A programatically generated e-mail";
mailRecipients = mail.Recipients;
mailRecipient = mailRecipients.Add("Eugene Astafiev");
mailRecipient.Resolve();
if (mailRecipient.Resolved)
{
mail.Send();
}
else
{
System.Windows.Forms.MessageBox.Show(
"There is no such record in your address book.");
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message,
"An exception is occured in the code of add-in.");
}
finally
{
if (mailRecipient != null) Marshal.ReleaseComObject(mailRecipient);
if (mailRecipients != null) Marshal.ReleaseComObject(mailRecipients);
if (mail != null) Marshal.ReleaseComObject(mail);
}
}
You may find the How To: Create and send an Outlook message programmatically article helpful.
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 developing an outlook plugin in C#.
I want to be able to detect when outlook has finished the "send/receive" option after startup, so that I can then run operations on the received mail items.
What I have tried so far:
Manually calling Application.Session.SendAndReceive() on startup.
This runs fine, but the method returns before the send/receive is complete, which is the opposite of what I want
Overriding Application.NewMail and Application.NewMailEx - neither of these trigger as one might hope at startup (NewMailEx doesn't fire at all, NewMail is unreliable)
Calling NameSpace.SyncObjects.AppFolders.Start(); and registering the SyncObjects.AppFolders.SyncEnd event - this event fires before outlook has finished downloading mail
Iterating through NameSpace.SyncObjects, calling Start(), and registering SyncEnd - this method doesn't fire at all.
What is a solution here which will work depenably?
It seems that there is a hack to detect when syncing has finished; namely to override Application.Reminders.BeforeReminderShow as DWE suggests in this SO answer here
This event (in my testing) always fires after outlook syncing has finished.
Then, in order to make sure the reminder window fires, you add a new reminder at startup, and then hide the reminder again within Reminders_BeforeReminderShow
The code then being something like:
public partial class ThisAddIn
{
private ReminderCollectionEvents_Event reminders; //don't delete, or else the GC will break things
AppointmentItem hiddenReminder;
private void ThisAddIn_Startup(object sender, EventArgs e)
{
//other stuff
hiddenReminder = (AppointmentItem)Application.CreateItem(OlItemType.olAppointmentItem); //add a new silent reminder to trigger Reminders_BeforeReminderShow.This method will be triggered after send/receive is finished
hiddenReminder.Start = DateTime.Now;
hiddenReminder.Subject = "Autogenerated Outlook Plugin Reminder";
hiddenReminder.Save();
reminders = Application.Reminders;
reminders.BeforeReminderShow += Reminders_BeforeReminderShow;
}
private void Reminders_BeforeReminderShow(ref bool Cancel)
{
if (hiddenReminder == null) return;
bool anyVisibleReminders = false;
for (int i = Application.Reminders.Count; i >= 1; i--)
{
if (Application.Reminders[i].Caption == "Autogenerated Outlook Plugin Reminder") //|| Application.Reminders[i].Item == privateReminder
{
Application.Reminders[i].Dismiss();
}
else
{
if (Application.Reminders[i].IsVisible)
{
anyVisibleReminders = true;
}
}
}
Cancel = !anyVisibleReminders;
hiddenReminder?.Delete();
hiddenReminder = null;
//your custom code here
}
}
Yup, this is very kludgy, but that's the nature of working with outlook, and I haven't seen any free alternative which can actually claim to work reliably, whereas this works in all of the use cases I've tried it in. So that's a hit I'm willing to take to get a working solution.
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);
}