How to capture a Outlook Appointment delete event on custom folder? - c#

I am developing an Outlook AddIn. One part of it that I organize appointments in a specific folder. I want to capture if an element gets deleted (in this case moving out of "my" folder counts as deleted).
I found the article https://stackoverflow.com/questions/10579240/how-to-capture-a-c-sharp-outlook-addin-appointment-delete-event and his/her solution helped a lot, but I have a huge problem: the event only fires in that "session", where my folder was created, not when I get the folder object from outlook.
My code looks like this:
private Outlook.MAPIFolder _CalendarMAPIFolder = null;
private Outlook.MAPIFolderEvents_12_Event _CalendarFolder = null;
private Outlook.Items _CalendarItems = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Outlook.MAPIFolder calendarFolder =
this.Application.GetNamespace("mapi").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
// get my-Folder (if not found, create it)
try
{
_CalendarMAPIFolder = calendarFolder.Folders["my-Folder"];
}
catch
{
_CalendarMAPIFolder = calendarFolder.Folders.Add("my-Folder");
}
_CalendarItems = _CalendarMAPIFolder.Items;
_CalendarFolder = _CalendarMAPIFolder as Outlook.MAPIFolderEvents_12_Event;
if (_CalendarFolder == null)
{
MessageBox.Show("can not cast MAPIFolder to Folder");
}
_CalendarFolder.BeforeItemMove += new Outlook.MAPIFolderEvents_12_BeforeItemMoveEventHandler(Folder_BeforeItemMove);
Debug.Print("events registered");
}
public void Folder_BeforeItemMove(
Object Item,
Outlook.MAPIFolder MoveTo,
ref bool Cancel)
{
Outlook.AppointmentItem aitem = Item as Outlook.AppointmentItem;
string s = "";
if (aitem != null) s = aitem.Subject;
//Cancel = false;
MessageBox.Show("Test! " + s);
}
Does anyone have a solution?
Thank you ;)
Edit: I still have no clue :(((((((

By definition, BeforeItemMove only fires when the user (Outlook client) initiates an Item be moved. It will not fire for synchronization events (i.e. Exchange sync).
If you are connecting Outlook to an Exchange Server, you should look at EWS (Exchange Web Services) if you wish to be notified of folder change events outside the client application (i.e. session). EWS offers push, pull, or streaming notification options. You would attach a notification to "Item deletion" operation.

It was all Microsofts fault! It was a Bug, I just needed to Update Outlook!

Related

Outlook Addin ItemAdd Event handler

I have an outlook addin developed, which has been used by many users. In our addin we have a functionality which will capture any emails getting stored under any specific outlook folder, to capture that i am using ItemAdd event.
User A and User B has same shared mailboxes.
Currently when user A registers a shared folder for capturing emails from addin, only for USER A the ItemAdd event is getting triggered, User B also using the same shared mailbox from our addin, but for him, the event is not triggered. Is it something expected? Do we have any events which triggers if any mails getting added into the specific folders?
Below is the code sample snippet for how the event are registred:
Interop.Folder fldr = this.GetFolder(folder.EntryId);
if (fldr != null)
{
Interop.Items items = fldr.Items;
items.ItemAdd += MappedItems_ItemAdd;
}
public Interop.Folder GetFolder(string entryId)
{
Interop.Folder retVal = null;
try
{
try
{
retVal = m_outlook.Application.Session.GetFolderFromID(entryId) as Interop.Folder;
}
catch { }
if (retVal != null)
{
try
{
string name = retVal.Name;
}
catch (Exception)
{
retVal = null;
}
}
return retVal;
}
The cause of the issue is not related to Outlook, but how the garbage collector works in .net based applications. For example, in the code you declare the source items collection in the limited scope of the if condition:
Interop.Folder fldr = this.GetFolder(folder.EntryId);
if (fldr != null)
{
Interop.Items items = fldr.Items;
items.ItemAdd += MappedItems_ItemAdd;
}
So, when code finishes and goes out of the if condition the items object is marked for swiping form the heap. GC may remove the object at any point of time it runs and you will never receive events from it. To avoid such kind of issues you need to declare the source object at the class level, so it will never goes out of cope and GC will not mark it for collecting:
// declare at the class the source object of events
Interop.Items items = null;
// in the code somewhere you may set it up to handle events
if (fldr != null)
{
items = fldr.Items;
items.ItemAdd += MappedItems_ItemAdd;
}

Outlook Add-in: Am I not aware of some basic, conceptual theories of C# programming?

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.

Suscribing a method to "item-add" event from "Items" Collection With C#

I'm currently migrating a VSTO add in written in VB to C# for outlook.
The general idea, is to log every single email information into a database of my own.
I've searched thoroughly and it seems that NewMail / NewMailEX events(from the application object) are the best options to handle it.
However, both events won't trigger for emails received when outlook client is down.
I'm having problems while trying to process all mails that are downloaded on startup from the exchange server, so i thought that "Item-add" event from items collection, might address this issue.
I know for a fact that this can be addressed within "item-add" event because we are actually handling this issue in the VB code.
However, when a try suscribing to the "item-add" event for each Inbox folder in Outlook, nothing happens!
There is no error nor exception thrown whatsoever.
in our VB code,
we could managed to suscribe to the mentioned event with this code:
outlookNameSpace = Me.Application.GetNamespace("MAPI")
inbox = outlookNameSpace.Stores(account).GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Mailitem = inbox.Items
Private Sub Items_ItemAdd(ByVal item As Object) Handles Mailitem.ItemAdd
Here is my failing C# code:
//looping to fetch all my inboxes
public static void InitialOutlookConfiguration(Outlook.Application myOutlookInstance)
{
Outlook.Accounts myAccounts = myOutlookInstance.GetNamespace("MAPI").Accounts;
foreach(Outlook.Account myAccount in myAccounts)
{
Outlook.MAPIFolder inbox = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
string storeID = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).StoreID;
myInboxes.Add(inbox, storeID);
foreach(Outlook.MAPIFolder inbox in myInboxes.Keys)
{
Outlook.Items myInboxItems = inbox.Items;
myInboxItems.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(OnNewItem);
}
}
}
The object raising the events (myInboxItems) must be alive - otherwise it gets released by the Garbage Collector and no events are raised.
The usual pattern is to introduce your own wrapper class that takes the COM object in question (Items) as a constructor parameter, stores it in a class member and sets up an event handler. You can then then create that wrapper class for each Inbox folder and store each wrapper in a list. That list must be declared on the class level to ensure it (and its items) stay alive when InitialOutlookConfiguration() completes.
public List<Outlook.Items> myInboxMailItems = new List<Outlook.Items>();
public Items InboxMails;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//watch.Start();
//chequear el orden en que solicitamos la ejecucion de la configuracion
//this.Application.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(OnOutlookOutlookStartup);
Outlook.Accounts myAccounts = this.Application.GetNamespace("MAPI").Accounts;
foreach (Outlook.Account myAccount in myAccounts)
{
Outlook.MAPIFolder inbox = myAccount.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
myInboxMailItems.Add(inbox.Items);
foreach (Outlook.Items i in myInboxMailItems)
{
i.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler(test);
}
}
}
public void test(object i)
{
System.Windows.Forms.MessageBox.Show("Eureka!");
}
Just in case anybody sticks with the same issue, thanks Dimitry for your garbage collector insight!

VSTO Outlook Toggle state

I am working on a VSTO Outlook Addin for new mails. User can toggle image when he wants the mail saved.
Currently when the image is clicked i set a global bool to true.
private bool _state;
private void toggleBtn_Click(object sender, RibbonControlEventArgs e)
{
if (_state) {
_state = false;
} else {
_state = true;
}
}
Which works ok until the user opens another new mail window before sending the first one.
How can i store the state per new mail window only?
Thank so much.
Solution
There are two ways to solve this problem.
One : User Properties
Excellent blog on https://www.add-in-express.com/creating-addins-blog/2013/01/30/preserve-outlook-ribbon-controls-state/
Two: Use wrapper to store the state in classes
Read more on https://msdn.microsoft.com/en-us/library/office/ff973716(v=office.14).aspx
Not sure whether you can get the EntryId of the current mail being edited, if so, you can try to cache all the states into a dictionary, something like below
private Dictionary<string, bool> _states = new Dictionary<string, bool>();
private void toggleBtn_Click(object sender, RibbonControlEventArgs e)
{
MailItem ml;
// get current MailItem
// something like: MailItem ml = popupWindow.GetMail
// default false
if (!(_states.Keys.Contains(ml.EntryId))){
_states[ml.EntryId] = false;
}
// toggle the state
_states[ml.EntryId] = !_states[ml.EntryId];
}
however, you might want to handle the popup window closed event to remove the entry from the dictionary when user closes the popup (for example, email sent)
Solution
There are two ways to solve this problem.
One : User Properties Excellent blog on https://www.add-in-express.com/creating-addins-blog/2013/01/30/preserve-outlook-ribbon-controls-state/
Two: Use wrapper to store the state in classes Read more on https://msdn.microsoft.com/en-us/library/office/ff973716(v=office.14).aspx

Outlook Addin shows same dialog in other opened Outlook

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?

Categories

Resources