VSTO Outlook Add-ins – Getting Contacts from Distribution Lists

A while ago I was tasked with creating an Outlook Add-in that enables a user to opt-in their contacts for synchronization with a KMBS product named Page Scope Enterprise Suite (PSES). Part of this add-in is to offer a panel at the bottom of every contact so they can be opt-ed in for synchronization like so:

Outlook Contact Form Region

As you can see this isn’t very fancy – two check-boxes to control if you want the email and/or fax addresses sent along to PSES. Naturally, there is also an option to opt-in distribution lists:

Outlook Distribution Form Region

This is of course where the fun began. In my personal opinion distribution lists were not properly implemented, or rather updated from earlier versions of Outlook. For instance, take a close look at the image above. Notice how a “fax” address is listed under the “E-mail” column? Do you also notice how it has the @ symbol? What in the world is going on here?

Another issue is how the ContactItem relates to the Recipient – or rather how you can obtain the actual contact from the item in the distribution list. Based on documentation we should be able to use the GetContact() method found a little ways down on the Recipient class.

Outlook.Recipient.AddressEntry.GetContact()

Sadly however, this doesn’t work (for me at-least) no matter what I did. So after a decent amount of trial and error I was able to determine that a Recipient’s EntryID actually contains the the Contact’s EntryID.

For example:

Recipient.EntryID = "abcd-1234";
Contact.EntryID = "1234";

This is simplified of course as real ID’s are quite long. So to actually get the contact we should do this:

Outlook.Recipient r = DistListItem.GetMember(1);
string rid = r.EntryID;
rid = rid.Substring(rid.Length - 48);

Outlook.ContactItem c = Application.Session.GetItemFromID(rid, null) as Outlook.ContactItem;

Hopefully someone else will find this mess useful as I couldn’t find anything a year ago about these oddities and issues.

log4net PatternString

I’ve been using log4net since it was first available for Mono and barely know anything about it. To a certain extent you’ve got to love it because of that – it just works! The project is great, but development appears somewhat stagnant. Either way, I’ve never needed to know much more than basic configuration and how to get it monitored even though I’ve used it for daemons, client applications, Add-ins, etc.

Today however, I learned something! A while ago I needed to have the RollingFileAppender log to a varying location depending on Windows version (XP vs Vista/7). At the time I found posts showing me how to use variables within the “file” option like so:

<file value="${APPDATA}\Company\Product\logs\data.log" />

Not having looked at the log4net code this is either explicitly converted to a user’s roaming folder, or it is replaced as an environment variable. This works great for applications that should log per user, but what if I needed the “All Users” roaming folder on Windows? Neither ${COMMONAPPDATA} or ${ALLUSERAPPDATA} worked for me so I needed another option that would cut it.

Enter log4net.Util.PatternConverter

By extending this very simple class and implementing one method we can now do variable substitution. The syntax leaves a lot to be desired, but hey – it works!

<file type="log4net.Util.PatternString">
  <converter>
    <name value="sub" />
    <type value="SpecialFolderPathConverter, Assembly" />
  </converter>
  <conversionPattern value="%sub{ApplicationData}\Company\Product\logs\addin.log" />
</file>

With this setup now all I have to do is create the Assembly.SpecialFolderPathConverter class:

using System;
using System.IO;

namespace Assembly
{
    public class SpecialFolderPathConverter : log4net.Util.PatternConverter
    {
        protected override void Convert(TextWriter writer, object state)
        {
            // Option gets set to contents of {}
            if (String.IsNullOrEmpty(this.Option))
                return;

            try {
                Environment.SpecialFolder sf = (Environment.SpecialFolder)
                    Enum.Parse(typeof(Environment.SpecialFolder), this.Option, true);

                writer.Write(Environment.GetFolderPath(sf));
            } catch (Exception) {
            }
        }
    }
}

As you can probably see this class takes any string within the {} brackets following %sub and replaces it using Environment.GetFolderPath which is quite simple, and helpful.

Thank you log4net!