Creating Outlook 2010 AddIn With C#

We already have a bug tracking app in our company that automatically create bug in TFS when user report a bug. Unfortunately, some user still stubbornly use email to report all their bugs. We have been trying to politely ask them to reply to their emails and ask them to use the app instead but we have been facing a big resistance. So I decided to create an outlook addin to create a bug in TFS based on the content of the email.

Note: This post only contains the HOWTO create the addin itself, not the logic of creating the bug on TFS.

1. Choose the Outlook 2010 Add In in Visual Studio as follow:

blog1

2. IMPORTANT!  To ease debugging, change the embedded interop type to false in all of the Microsoft.Office.Interop files. When it’s left to the default value (true), you won’t be able to evaluate the property values when debugging. If you try to do this, Visual Studio will throw you an “‘Sender Name’ on embedded interop type ‘Microsoft.Office.Interop.Outlook.MailItem’ cannot be evaluated while debugging since it is never referenced in the program. Consider casting the source object to type ‘dynamic’ first or building with the ‘Embed Interop Types’ property set to false when debugging”.

blog2

blog3

3. Add a class to map outlook item object from outlook interop to easy-to-use class.

This step is not really necessary since we can always use static string and reflection to get the value of the property/member of the outlook item just like we’ve done in the GetPropertyValue(string propertyName) method but creating this class makes retrieving member variables much cleaner:

public class OutlookItem
{
private object m_item; // the wrapped Outlook item
private Type m_type; // type for the Outlook item
private object[] m_args; // dummy argument array
private System.Type m_typeOlObjectClass;
#region OutlookItem Constants

private const string OlActions = "Actions";
private const string OlApplication = "Application";
private const string OlAttachments = "Attachments";
private const string OlBillingInformation = "BillingInformation";
private const string OlBody = "Body";
private const string OlCategories = "Categories";
private const string OlClass = "Class";
private const string OlClose = "Close";
private const string OlCompanies = "Companies";
private const string OlConversationIndex = "ConversationIndex";
private const string OlConversationTopic = "ConversationTopic";
private const string OlCopy = "Copy";
private const string OlCreationTime = "CreationTime";
private const string OlDisplay = "Display";
private const string OlDownloadState = "DownloadState";
private const string OlEntryID = "EntryID";
private const string OlFormDescription = "FormDescription";
private const string OlGetInspector = "GetInspector";
private const string OlImportance = "Importance";
private const string OlIsConflict = "IsConflict";
private const string OlItemProperties = "ItemProperties";
private const string OlLastModificationTime = "LastModificationTime";
private const string OlLinks = "Links";
private const string OlMarkForDownload = "MarkForDownload";
private const string OlMessageClass = "MessageClass";
private const string OlMileage = "Mileage";
private const string OlMove = "Move";
private const string OlNoAging = "NoAging";
private const string OlOutlookInternalVersion = "OutlookInternalVersion";
private const string OlOutlookVersion = "OutlookVersion";
private const string OlParent = "Parent";
private const string OlPrintOut = "PrintOut";
private const string OlPropertyAccessor = "PropertyAccessor";
private const string OlSave = "Save";
private const string OlSaveAs = "SaveAs";
private const string OlSaved = "Saved";
private const string OlSensitivity = "Sensitivity";
private const string OlSession = "Session";
private const string OlShowCategoriesDialog = "ShowCategoriesDialog";
private const string OlSize = "Size";
private const string OlSubject = "Subject";
private const string OlUnRead = "UnRead";
private const string OlUserProperties = "UserProperties";
private const string OlSenderName = "SenderName";
#endregion

#region Constructor
public OutlookItem(object item)
{
m_item = item;
m_type = m_item.GetType();
m_args = new Object[] { };
}
#endregion

#region Public Methods and Properties

public string SenderName
{
get
{
return this.GetPropertyValue(OlSenderName).ToString();
}
set
{
SetPropertyValue(OlSenderName, value);
}
}

public Outlook.Actions Actions
{
get
{
return this.GetPropertyValue(OlActions) as Outlook.Actions;
}
}

public Outlook.Application Application
{
get
{
return this.GetPropertyValue(OlApplication) as Outlook.Application;
}
}

public Outlook.Attachments Attachments
{
get
{
return this.GetPropertyValue(OlAttachments) as Outlook.Attachments;
}
}

public string BillingInformation
{
get
{
return this.GetPropertyValue(OlBillingInformation).ToString();
}
set
{
SetPropertyValue(OlBillingInformation, value);
}
}

public string Body
{
get
{
return this.GetPropertyValue(OlBody).ToString();
}
set
{
SetPropertyValue(OlBody, value);
}
}

public string Categories
{
get
{
return this.GetPropertyValue(OlCategories).ToString();
}
set
{
SetPropertyValue(OlCategories, value);
}
}

public void Close(Outlook.OlInspectorClose SaveMode)
{
object[] MyArgs = { SaveMode };
this.CallMethod(OlClose);
}

public string Companies
{
get
{
return this.GetPropertyValue(OlCompanies).ToString();
}
set
{
SetPropertyValue(OlCompanies, value);
}
}

public Outlook.OlObjectClass Class
{
get
{
if (m_typeOlObjectClass == null)
{
// Note: instantiate dummy ObjectClass enumeration to get type.
// type = System.Type.GetType("Outlook.OlObjectClass") doesn't seem to work
Outlook.OlObjectClass objClass = Outlook.OlObjectClass.olAction;
m_typeOlObjectClass = objClass.GetType();
}
return (Outlook.OlObjectClass)System.Enum.ToObject(m_typeOlObjectClass, this.GetPropertyValue(OlClass));
}
}

public string ConversationIndex
{
get
{
return this.GetPropertyValue(OlConversationIndex).ToString();
}
}

public string ConversationTopic
{
get
{
return this.GetPropertyValue(OlConversationTopic).ToString();
}
}

public object Copy()
{
return (this.CallMethod(OlCopy));
}

public System.DateTime CreationTime
{
get
{
return (System.DateTime)this.GetPropertyValue(OlCreationTime);
}
}

public void Display()
{
this.CallMethod(OlDisplay);
}

public Outlook.OlDownloadState DownloadState
{
get
{
return (Outlook.OlDownloadState)this.GetPropertyValue(OlDownloadState);
}
}

public string EntryID
{
get
{
return this.GetPropertyValue(OlEntryID).ToString();
}
}

public Outlook.FormDescription FormDescription
{
get
{
return (Outlook.FormDescription)this.GetPropertyValue(OlFormDescription);
}
}

public Object InnerObject
{
get
{
return this.m_item;
}
}

public Outlook.Inspector GetInspector
{
get
{
return this.GetPropertyValue(OlGetInspector) as Outlook.Inspector;
}
}

public Outlook.OlImportance Importance
{
get
{
return (Outlook.OlImportance)this.GetPropertyValue(OlImportance);
}
set
{
SetPropertyValue(OlImportance, value);
}
}

public bool IsConflict
{
get
{
return (bool)this.GetPropertyValue(OlIsConflict);
}
}

public Outlook.ItemProperties ItemProperties
{
get
{
return (Outlook.ItemProperties)this.GetPropertyValue(OlItemProperties);
}
}

public System.DateTime LastModificationTime
{
get
{
return (System.DateTime)this.GetPropertyValue(OlLastModificationTime);
}
}

public Outlook.Links Links
{
get
{
return this.GetPropertyValue(OlLinks) as Outlook.Links;
}
}

public Outlook.OlRemoteStatus MarkForDownload
{
get
{
return (Outlook.OlRemoteStatus)this.GetPropertyValue(OlMarkForDownload);
}
set
{
SetPropertyValue(OlMarkForDownload, value);
}
}

public string MessageClass
{
get
{
return this.GetPropertyValue(OlMessageClass).ToString();
}
set
{
SetPropertyValue(OlMessageClass, value);
}
}

public string Mileage
{
get
{
return this.GetPropertyValue(OlMileage).ToString();
}
set
{
SetPropertyValue(OlMileage, value);
}
}

public object Move(Outlook.Folder DestinationFolder)
{
object[] myArgs = { DestinationFolder };
return this.CallMethod(OlMove, myArgs);
}

public bool NoAging
{
get
{
return (bool)this.GetPropertyValue(OlNoAging);
}
set
{
SetPropertyValue(OlNoAging, value);
}
}

public long OutlookInternalVersion
{
get
{
return (long)this.GetPropertyValue(OlOutlookInternalVersion);
}
}

public string OutlookVersion
{
get
{
return this.GetPropertyValue(OlOutlookVersion).ToString();
}
}

public Outlook.Folder Parent
{
get
{
return this.GetPropertyValue(OlParent) as Outlook.Folder;
}
}

public Outlook.PropertyAccessor PropertyAccessor
{
get
{
return this.GetPropertyValue(OlPropertyAccessor) as Outlook.PropertyAccessor;
}
}

public void PrintOut()
{
this.CallMethod(OlPrintOut);
}

public void Save()
{
this.CallMethod(OlSave);
}

public void SaveAs(string path, Outlook.OlSaveAsType type)
{
object[] myArgs = { path, type };
this.CallMethod(OlSaveAs, myArgs);
}

public bool Saved
{
get
{
return (bool)this.GetPropertyValue(OlSaved);
}
}

public Outlook.OlSensitivity Sensitivity
{
get
{
return (Outlook.OlSensitivity)this.GetPropertyValue(OlSensitivity);
}
set
{
SetPropertyValue(OlSensitivity, value);
}
}

public Outlook.NameSpace Session
{
get
{
return this.GetPropertyValue(OlSession) as Outlook.NameSpace;
}
}

public void ShowCategoriesDialog()
{
this.CallMethod(OlShowCategoriesDialog);
}

public long Size
{
get
{
return (long)this.GetPropertyValue(OlSize);
}
}

public string Subject
{
get
{
return this.GetPropertyValue(OlSubject).ToString();
}
set
{
SetPropertyValue(OlSubject, value);
}
}

public bool UnRead
{
get
{
return (bool)this.GetPropertyValue(OlUnRead);
}
set
{
SetPropertyValue(OlUnRead, value);
}
}

public Outlook.UserProperties UserProperties
{
get
{
return this.GetPropertyValue(OlUserProperties) as Outlook.UserProperties;
}
}

#endregion

#region Private Helper Functions
private object GetPropertyValue(string propertyName)
{
try
{
// An invalid property name exception is propagated to client
return m_type.InvokeMember(
propertyName,
BindingFlags.Public | BindingFlags.GetField | BindingFlags.GetProperty,
null,
m_item,
m_args);
}
catch (SystemException ex)
{
Debug.WriteLine(
string.Format(
"OutlookItem: GetPropertyValue for {0} Exception: {1} ",
propertyName, ex.Message));
throw;
}
}

private void SetPropertyValue(string propertyName, object propertyValue)
{
try
{
m_type.InvokeMember(
propertyName,
BindingFlags.Public | BindingFlags.SetField | BindingFlags.SetProperty,
null,
m_item,
new object[] { propertyValue });
}
catch (SystemException ex)
{
Debug.WriteLine(
string.Format(
"OutlookItem: SetPropertyValue for {0} Exception: {1} ",
propertyName, ex.Message));
throw;
}
}

private object CallMethod(string methodName)
{
try
{
// An invalid property name exception is propagated to client
return m_type.InvokeMember(
methodName,
BindingFlags.Public | BindingFlags.InvokeMethod,
null,
m_item,
m_args);
}
catch (SystemException ex)
{
Debug.WriteLine(
string.Format(
"OutlookItem: CallMethod for {0} Exception: {1} ",
methodName, ex.Message));
throw;
}
}

private object CallMethod(string methodName, object[] args)
{
try
{
// An invalid property name exception is propagated to client
return m_type.InvokeMember(
methodName,
BindingFlags.Public | BindingFlags.InvokeMethod,
null,
m_item,
args);
}
catch (SystemException ex)
{
Debug.WriteLine(
string.Format(
"OutlookItem: CallMethod for {0} Exception: {1} ",
methodName, ex.Message));
throw;
}
}
#endregion

}

4. Add new IRibbonExtensibilityClass and create a new xml for the UI definition.

I wanted to create two new context menu items whenever I click right on a mail item. To achieve this, I needed to implement the IRibbonExtensibilityClass and create an xml definition as follow:

MyRibbon extensibility class:

[ComVisible(true)]
public class MyRibbon : Office.IRibbonExtensibility
{
private Office.IRibbonUI ribbon;

private Outlook.Application olApplication;

//Override of constructor to pass
// a trusted Outlook.Application object
public MyRibbon(Outlook.Application outlookApplication)
{
olApplication = outlookApplication as Outlook.Application;
}

public string GetCustomUI(string ribbonID)
{
string customUI = string.Empty;
Debug.WriteLine(ribbonID);
//Return the appropriate Ribbon XML for ribbonID
switch (ribbonID)
{
case "Microsoft.Outlook.Explorer":
customUI = GetResourceText(
"CreateAsBug.Explorer.xml");
return customUI;

default:
return string.Empty;
}
}

public void Ribbon_Load(Office.IRibbonUI ribbonUI)
{
ThisAddIn.m_Ribbon = ribbonUI;
}

public bool MyTab_GetVisible(Office.IRibbonControl control)
{
if (control.Context is Outlook.Explorer)
{
Outlook.Explorer explorer =
control.Context as Outlook.Explorer;
Outlook.Selection selection = explorer.Selection;
if (selection.Count == 1)
{
if (selection[1] is Outlook.MailItem)
{
Outlook.MailItem oMail =
selection[1] as Outlook.MailItem;
if (oMail.Sent == true)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return false;
}
}
else if (control.Context is Outlook.Inspector)
{
Outlook.Inspector oInsp =
control.Context as Outlook.Inspector;
if (oInsp.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem oMail =
oInsp.CurrentItem as Outlook.MailItem;
if (oMail.Sent == true)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return true;
}
}

public bool MyTabInspector_GetVisible(Office.IRibbonControl control)
{
if (control.Context is Outlook.Inspector)
{
Outlook.Inspector oInsp =
control.Context as Outlook.Inspector;
if (oInsp.CurrentItem is Outlook.MailItem)
{
Outlook.MailItem oMail =
oInsp.CurrentItem as Outlook.MailItem;
if (oMail.Sent == true)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return true;
}
}

//MyBackstageTab_GetVisible hides the place in an Inspector window
public bool MyBackstageTab_GetVisible(Office.IRibbonControl control)
{
if (control.Context is Outlook.Explorer)
return true;
else
return false;
}

public void OnMyButtonClick(Office.IRibbonControl control)
{
if (control.Context is Outlook.Selection)
{
Outlook.Selection selection =
control.Context as Outlook.Selection;
if (selection.Count == 1)
{
if (control.Id == "CreateReportingBug")
{
CreateReportingBug form = new CreateReportingBug(new OutlookItem(selection[1]));
form.Show();

}
else if (control.Id == "CreateSCDBug")
{
CreateSCDBug form = new CreateSCDBug(new OutlookItem(selection[1]));
form.Show();
}
}
}

}

private static string GetResourceText(string resourceName)
{
Assembly asm = Assembly.GetExecutingAssembly();
string[] resourceNames = asm.GetManifestResourceNames();
for (int i = 0; i < resourceNames.Length; ++i)
{
if (string.Compare(resourceName, resourceNames[i], StringComparison.OrdinalIgnoreCase) == 0)
{
using (StreamReader resourceReader = new StreamReader(asm.GetManifestResourceStream(resourceNames[i])))
{
if (resourceReader != null)
{
return resourceReader.ReadToEnd();
}
}
}
}
return null;
}

}

Explorer.xml:


<!--?xml version="1.0" encoding="utf-8"?-->

      <button id="CreateReportingBug"></button>
      <button id="CreateSCDBug"></button>

I then hooked it up in ThisAddIn.cs as an override as follow:


protected override Microsoft.Office.Core.IRibbonExtensibility CreateRibbonExtensibilityObject()
{
return new MyRibbon(Application);
}

5. Publish the new AddIn.

First of all, don’t forget to change the build profile from debug to release. Then, bring up the properties dialog of the project. Fill in the publishing folder, publish version and click “Publish Now”.

blog4

VOILA!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: