I need to synchronize Outlook contacts to another service. I can subscribe to create, change, and delete events as follows:
Outlook.MAPIFolder folderContacts = this.Application.ActiveExplorer().Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
Outlook.Items contacts = folderContacts.Items;
contacts.ItemAdd += Contacts_ItemAdd;
contact.ItemChange += Contacts_ItemChange;
contacts.ItemRemove += Contacts_ItemRemove;
This works perfectly for create and change as I get the item in the event handler:
private void Contacts_ItemAdd(object Item)
{
Outlook.ContactItem contact = (Outlook.ContactItem)Item;
...
}
However, in case of remove event, I don't get the information of the removed item.
private void Contacts_ItemRemove() {
// how to get deleted item or at least it's EntryID?
}
So how do I get the EntryID of the removed item? I use this ID to identify the item in the other service.
You need to maintain the list of items in each folder. And in the ItemRemove event handler you can compare out the list of existing items with yours. I'd recommend reading the following series of articles which gives an example for the NewMailEx event in Outlook (sometimes it is not fired at all, so developers should looks for possible workarounds like that):
Outlook NewMail event unleashed: the challenge (NewMail, NewMailEx, ItemAdd)
Outlook NewMail event: solution options
Outlook NewMail event and Extended MAPI: C# example
Outlook NewMail unleashed: writing a working solution (C# example)
All MAPI based notifications are raised after the action has already occurred. By the time you get the ItemRemove event, the message is already gone.
On the Extended MAPI level (C++ or Delphi only), when an item is deleted, the store provider raises the following fnevTableModified / TABLE_ROW_DELETED notification (you can see it in OutlookSpy (I am its author) if you click IMAPIFolder button and look at the log at the bottom of the GetContentsTable tab). Only PR_INSTANCE_KEY property is available:
ulEventType: fnevTableModified
tab.ulTableEvent: TABLE_ROW_DELETED
tab.propIndex: (PR_INSTANCE_KEY, cb:4, lpb: 0F 3E D3 A4
tab.propPrior: (PR_NULL, null)
tab.row: (cValues : 0
)
You can only make this work by retrieving PR_INSTANCE_KEY for all items in the folder beforehand so that you can map PR_ENTRYID <-> PR_INSTANCE_KEY.
Outlook Object Model does not expose PR_INSTANCE_KEY in the ItemRemove event. If using Redemption is an option (I am also its author), its RDOItems.ItemRemove event passes the instance key as a parameter. PR_INSTANCE_KEY for all items in a folder can be cached beforehand in a single call using RDOItems.MAPITable.ExecSQL method.
Related
I created an adjoining Form Region for Outlook appointment items (Using Visual Studio 2019 for Office 2019) and I have inputs on that form from which I capture the values into UserProperties.
Everything works fine until I get to the point where I have added some recipients, sent the meeting request, open it again, remove a recipient, and then try to send an update.
The problem is that on the close event of the form region, I retrieve all my input fields' values, store them in UserProperties, and then fire the item's .Save() method, which throws an exception.
When this happens, my UserPropery values are not saved, but the appointments body and the recipient list does indeed save.
When the appointment item is being deleted, the save method will also throw an exception which is understandable. I wrapped it in a try..catch for that.
An example of my code:
private void AdditionalAppointmentInfo_FormRegionClosed(object sender, System.EventArgs e)
{
if (customItem) //In the FormRegionShowing Event, I check for UserProps to see if this is an item created by this add-in
{
try
{
item.UserProperties["MyUserProperty"].Value = MyTextBox.Text;
item.Save(); //Throws exception here when a recipient gets removed.
}
catch (System.Exception ex){}
}
else
{
try
{
item.UserProperties.Add("IsCustomItem", OlUserPropertyType.olText).Value="1"; //set a property to indicate that this item was created using this add-in
item.UserProperties.Add("MyUserProperty", OlUserPropertyType.olText).Value = MyTextBox.Text;
}
catch (System.Exception){}
}
}
I can understand an exception when the appointment item gets deleted, but not one when you remove a recipient.
So while UserProperties works for my intended purposes, is it even the right thing to be using if you want a FormRegion controls' values to persist? Or is there some other way to store persistent user data without having to manually trigger the save method?
You must use the OutlookItem property to get an Outlook item.
How do I execute an event after the Outlook application is fully loaded. I tried to execute some code when the C# VSTO addin startup event fires but I am looking to run a script after the application is finished loading. Any ideas?
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
//Doesn't make sense to add script here because outlook is still not finished loading
}
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 http://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>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
The Startup event of the Application class is fired when Microsoft Outlook is starting, but after all add-ins have been loaded.
For posterity and I use the stack everyday but have never posted.
I maintain a VSTO Add-in for Visio which has common relevance to other Office Add-ins...
I too am trying to run code in the Add-in Startup Event: ThisAddIn_Startup.
this.Application.ActiveDocument.DocumentChanged += MyEventHandler;
I kept getting an exception: Value cannot be null. Parameter name: o
What I found is that, although the add-in is loaded there are no open documents:
this.Application.ActiveDocument is null
I changed my event handler assignment to:
this.Application.DocumentOpened += MyHigherLayerEventHandler;
reference:
https://learn.microsoft.com/en-us/visualstudio/vsto/programming-vsto-add-ins?view=vs-2019#AccessingDocuments
https://learn.microsoft.com/en-us/visualstudio/vsto/events-in-office-projects?view=vs-2019
https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.tools.addinbase.requestcomaddinautomationservice?view=vsto-2017
https://learn.microsoft.com/en-us/visualstudio/vsto/architecture-of-vsto-add-ins?view=vs-2019
THEN -- (this part might need to be in a separate topic, idk, but)
I noticed that the event was firing twice. As a matter of fact both ThisAddIn_Startup --and-- ThisAddIn_Shutdown fire 2 times. I spent about 10 hours this weekend trying to solve this issue. I even added an increment counter with a messagebox : the messagebox popped up 2 times and the counter did NOT increment.
The problem / solution turned out to be registry entries.
If you are anything like me as a developer your machine probably has more than a couple of test projects and maybe some old abandoned ones. I had an older Add-in of the same manifest file name but a different repository location. After a couple of find searches through the registry
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Visio\Addins\TEAM_VSTO <-- i deleted this registry entry
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Visio\Addins\NewAddinName
*presto, magic, event only fires 1 time *facepalm
Anyway, I hope this helps someone.
I'm writing a VSTO for Outlook 2010 (though it needs to work on 2010-2016, and for the most part does). I'm running into a bizarre problem with, as far as I can tell, event handlers never being removed. This means I can't avoid repeatedly invoking event handlers, which is silly and wasteful.
The code in question happens in the event handler for an Explorer's SelectionChange event. The handler checks whether the selection is a MailItem, and if so, it ensures that the Reply, ReplyAll, and Forward events have a handler. Since a given item may be selected more than once, the SelectionChange handler first removes the Reply/ReplyAll/Forward event handlers, in keeping with the pattern shown here (Prevent event handler being hooked twice, when you don't control the event-bearing class implementation).
The problem is, this isn't preventing the Reply (or other response action) event handler from being called once per instance of the SelectionChange event handler firing. This rapidly reaches a silly number of invocations. I thought it might be a synchronization issue, so I wrapped the event handler removal-and-adding in a lock block, to no avail.
private void SelectionChangeHandler()
{
Outlook.Selection sel = Application.ActiveExplorer().Selection;
// First make sure it's a (single) mail item
if (1 != sel.Count)
{ // Ignore multi-select
return;
}
// Indexed from 1, not 0. Stupid VB-ish thing...
Outlook.MailItem mail = sel[1] as Outlook.MailItem;
if (null != mail)
{
Outlook.ItemEvents_10_Event mailE = mail as Outlook.ItemEvents_10_Event;
lock (this)
{ // For each event, remove the handler then add it again
mailE.Forward -= MailItemResponseHandler;
mailE.Forward += MailItemResponseHandler;
mailE.Reply -= MailItemResponseHandler;
mailE.Reply += MailItemResponseHandler;
mailE.ReplyAll -= MailItemResponseHandler;
mailE.ReplyAll += MailItemResponseHandler;
}
ProcessMailitem(mail);
}
}
And the event handler that is being called way too many times:
private void MailItemResponseHandler (object newItem, ref bool Cancel)
{ // We need to get the responded-to item
// NOTE: There really needs to be a better way to do this
Outlook.MailItem old = GetCurrentMail();
if (null == old)
{ // No mail item selected
return;
}
MessageBox.Show(old.Body);
}
This function will eventually do something much more useful than pop a dialog box, but that was a convenient check for "did I find the correct original message?". I shouldn't be getting the same dialog box over and over again, though, and I am.
Am I doing something wrong? Is this a bug in Outlook, or in VSTO? Anybody know how I can avoid getting the duplicate event handler invocations?
Firstly, mailE variable must be declared on the class level, not local, to prevent it from being garbage collected.
Secondly, before setting the event, the old value (mailE) must be released using Marshal.ReleaseComObject.
The event handler is not actually being attached to the MAPI item that Outlook works with. Instead, it's being attached to a .NET object, called a Runtime Callable Wrapper (RCW), which wraps a COM object. Because of the way RCWs work, getting more than one reference to what seems to be the same object - for example, by getting the activeExplorer.Selection()[1] twice - gives multiple RCWs around different COM objects. This meant that the Outlook.MailItem (or Outlook.ItemEvents_10_Event) that I was attempting to remove events from didn't actually have any events on it; it was new-made each time SelectionChangeHandler fired.
Relatedly, because the only reference to an RCW-wrapped COM object is the RCW itself, letting all variables referencing an RCW go out of scope (or otherwise cease to reference that RCW) will result in the RCW being garbage collected (during which the COM object will be released and deleted). This has two relevant impacts on the code in question:
Because the garbage collection is not immediate, old RCWs (and COM objects) with existing event handlers were lingering on, and Outlook could still trigger events on their COM objects. This is why the event handler would be invoked multiple times.
Because the RCW was going out of scope at the bottom of SelectionChangeHandler, it was only a matter of time until garbage collection swept up all of the RCWs (and event handlers) and released all their COM objects. At that point, no events would be attached to that email.
While in practice, my testing happened over a short enough timeframe that I was more likely to get multiple live RCWs than none, selecting a mail item and not interacting with it (or selecting anything else) for long enough to trigger a garbage collection sweep would, in fact, result in the MailItemResponseHandler not being invoked at all when I clicked Reply.
#DmitryStreblechenko gave me the push in the right direction to work this out, but it took some experimentation to figure out. First of all, the relevant MailItem needed to be globally referenced, so its reference to the evented RCW wouldn't go out of scope and, also importantly, so its RCW could still be directly referenced when the SelectionChangeHandler was invoked again. I renamed the variable selectedMail and referenced it at the class level like such:
Outlook.ItemEvents_10_Event selectedMail;
I then modified SelectionChangeHandler so that whenever it is invoked with a single MailItem currently selected, it first removes all the event handlers from selectedMail, and only then points selectedMail to the newly-selected item. The RCW previously reference by selectedMail becomes eligible for garbage collection, but it has no event handlers on it so we don't really care. SelectionChangeHandler then adds the relevant event handlers to the new RCW now referenced by selectedMail.
private void SelectionChangeHandler()
{
Outlook.Selection sel = activeExplorer.Selection;
// First make sure it's a (single) mail item
if (1 != sel.Count)
{ // Ignore multi-select
return;
}
// Indexed from 1, not 0. Stupid VB-ish thing...
Outlook.MailItem mail = sel[1] as Outlook.MailItem;
if (null != mail)
{
if (null != selectedMail)
{ // Remove the old event handlers, if they were set, so there's no repeated events
selectedMail.Forward -= MailItemResponseHandler;
selectedMail.Reply -= MailItemResponseHandler;
selectedMail.ReplyAll -= MailItemResponseHandler;
}
selectedMail = mail as Outlook.ItemEvents_10_Event;
selectedMail.Forward += MailItemResponseHandler;
selectedMail.Reply += MailItemResponseHandler;
selectedMail.ReplyAll += MailItemResponseHandler;
if (DecryptOnSelect)
{ // We've got a live mail item selected. Process it
ProcessMailitem(mail);
}
}
}
Based on Dmitri's answer and comments, I tried calling Marshal.ReleaseComObject(selectedMail) on the old value of selectedMail before I thought to remove the event handlers instead. This helped a little, but either the COM objects are not released immediately or Outlook can still invoke event handlers through them, because events were still firing multiple times if I selected a given email multiple times in a short period before hitting Reply.
There's still one glitch to work out. If I open an inspector and hit Reply in there without changing my selection in the Explorer, it works fine (the MailItemResponseHandler is invoked). However, if I leave the inspector open, switch back to the explorer and select a different email, and then return to the inspector and hit Reply, it doesn't work. If there's an inspector open for the relevant email when that email gets de-selected, I need to avoid removing the event handlers (and then I do need to remove them when the inspector gets closed, unless the email is still selected in the explorer). Messy, but I'll work it out.
My C# application needs to work close to Outlook. I've implemented a method that allows to load Outlook if it is not on yet. After this loading method I need to start working with emails, read Inbox folder etc. . . obviously, all these behaviours must be executed when Outlook is on, in particular if the main Outlook windows is ready.
Looking at the OOM I've found the application Startup event, and I think I could use it to be sure the application is ready...but I don't know how to use it.
To share the idea of what I'm trying to do, here there is the code (simplified):
Main:
OutlookProvider p= new OutlookProvider();
p.Connect();
if(p.AppIsOn) {
// TO DO: start working
}
else
throw new Exception("Error; Unable to connect to Outlook.");
OutlookProvider class:
#region Fields
Outlook.Application oApp;
Outlook.MailItem oMail;
bool AppIsOn;
#endregion
OutlookProvider() { AppIsOn= false; }
Connect() {
try {
// try to connect to the possible running Outlook instance
oApp = (Outlook.Application)Marshal.GetActiveObject("Outlook.Application");
AppIsOn= true;
}
catch(Exception exc) {
// Outlook is not running, so I create my own Outlook instance
// here my app is null so an Exception will be thrown
oApp.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(SetAppIsOn);
oApp= (Outlook.Application)new Outlook.Application();
}
}
void SetAppIsOn() { AppIsOn= true;}
Can I use that event to solve my problem? And if yes how can I implement the Connect() method in order to set my boolean AppIsOn variable?
First of all, you ned to create an instance of the object and only then try to subscribe to the events. You can't set up an event handler when the object is null (not yet initialized). So, the code should look like the following one:
oApp= (Outlook.Application)new Outlook.Application();
oApp.Startup += new Outlook.ApplicationEvents_11_StartupEventHandler(SetAppIsOn);
Anyway, there is no need to handle the Startup event if you automate the host application. Take a look at the C# app automates Outlook (CSAutomateOutlook) sample application which explains how to automate Outlook from C#. The Outlook object model is not asynchronous, so each method or property will take as much time as it needs.
P.S. There is no method to initialize Outlook. The Logon method is only used to log on to a specific profile when Outlook is not already running. If Outlook is not running and you only want to start Outlook with the default profile, do not use the Logon method.
To make sure Outlook is fully initialized, call Namespace.Logon. If Outlook is already running, the call will do nothing.
There is also no reason to call GetActiveObject - Outlook is a singleton, so creating a new object will return the existing object if Outlook is already running.
olApp = new Outlook.Application();
Outlook.Namespace ns = olApp.GetNamespace("MAPI");
ns.Logon();
I'm currently developing an Add-in for MS Outlook 2010 using VS 2013. I am using the NewMailEx event handler to give me the EntryID of new email arriving.
Getting the EntryIDCollection is no issue (and I make sure it's a single EntryID before moving further), but I am unable to use the given ID to find the actual email object (using GetItemFromID()) so that I can access the new email's body. Instead, I just end up with an empty MailItem object after GetItemFromID() is called.
public static Outlook.NameSpace olNameSpace;
...
//EVENT: Item Received (new email arrival)
private static void outLookApp_NewMailEx(string EntryIDCollection)
{
try
{
//THIS part is failing \/ and returning nothing
object item = olNameSpace.GetItemFromID(EntryIDCollection, Type.Missing);
Outlook.MailItem mailItem = item as Outlook.MailItem;
if (mailItem != null)
{
//access email object
}
}
catch (Exception e)
{
MessageBox.Show("Receive mail exception: " + e);
}
}
Do I need to specify the folder for GetItemFromID() even though it's optional?
I'm open to other processes for accessing new email arrivals, but I was just using NewMailEx for the time being to have a simple running example.
Thanks everybody.
Are you sure it is GetItemFromID that returns null? If there is a problem, it will raise an exception rather than return null. Most likely it is the next line (Outlook.MailItem mailItem = item as Outlook.MailItem) that returns null when you cast the returned object to MailItem.
Make sure you are not dealing with ReportItem or MeetingItem objects.
The problem here is the object olNameSpace that is not initialized. Try this:
private void outLookApp_NewMailEx(string EntryIDCollection)
{
olNameSpace = this.Application.GetNamespace("MAPI");
try
{
//THIS part is failing \/ and returning nothing
object item = olNameSpace.GetItemFromID(EntryIDCollection, Type.Missing);
Outlook.MailItem mailItem = item as Outlook.MailItem;
if (mailItem != null)
{
mailItem.Display();
}
}
catch (Exception e)
{
MessageBox.Show("" + e);
}
}
Witty,
What is the actual value passed to the GetItemFromID method?
Do I need to specify the folder for GetItemFromID() even though it's optional?
No, there is no need to specify the second paramter. It is optional.
I'd suggest reading the following series of articles related to handling incoming emails in Outlook:
Handling NewMail, NewMailEx and ItemAdd Outlook events in .NET
Outlook NewMail event unleashed: the challenge (NewMail, NewMailEx, ItemAdd)
Outlook NewMail event: solution options
Outlook NewMail event and Extended MAPI: C# example
Outlook NewMail unleashed: writing a working solution (C# example)
Thanks all for your help.
Ultimately, I couldn't get the GetItemFromID method to work consistently in conjunction with NewMailEx.
However, I do recommend implementing Eugene's recommendation here:
https://www.add-in-express.com/creating-addins-blog/2011/11/10/outlook-newmail-custom-solution/
This consistent scanning of folders works quite well (with 100% dependability in my experimentation). However, if you do experience any latency in using this with Outlook, I recommend increasing the sync delay time and possibly using event handlers such as Folder.Items.ItemAdd and Folders.FolderAdd/FolderChange/FolderRemove to check in between syncs.
My add-in needs to ensure quick processing of new email (for security scanning of links), so it cannot just solely wait in between syncs (in case a link is not scanned before the user reached it), which is why I recommend those other event handlers.
Thanks again,
~B-Witty