Creating a TFS Work Item from an Outlook mail message using VSTO

Many times new work-items are the by-product of an email thread. Sometimes it a question that turns into a feature request or a bug report from an external customer. When this happens I need to move the email message to one screen and open VS in another. Browse out to the Work Item menu and create a new work item. Go back to Outlook and CTRL+A, CTRL+C to get the message, go back to VS and paste it … not really a big deal but why not streamline it a bit more?

So I spent a few minutes looking at the VSTO wizard that comes with Visual Studio 2005 (see more here: https://msdn.microsoft.com/office/understanding/vsto/training/labs05/default.aspx) and whipped up a quick Outlook add-in that lets me right-click on an Email and create a new workitem from it.  You can see an example of the context menu created in Outlook when your right-click on an email message.  Notice the "Create Workitem" menu item.

The email subject becomes the bug title and the email body the description. It would be just a tad more work to save the email as a temp file and make it an attachment instead.

To get started you will want to create an Outlook Add-In project (this means you need to have VSTO installed!).  This will create a lot of the plumbing code you need to make this work.  I've only included the code I modified.  The link I gave earlier includes walk-throughs that will teach you how to do this.

The code is fairly well documented so I won't spend a lot of time on it.  This is a quick hack - about 30 minutes of work (which was mostly figuring out how to write an Outlook add-in) and it is pretty rigid.  A better solution would be to let the user configure the project and server name.  Also support for mandatory fields or custom fields would need to be added - but I think this shows how easy it is to extend Outlook in a time-saving way.

using System;

using System.Windows.Forms;

using Microsoft.VisualStudio.Tools.Applications.Runtime;

using Outlook = Microsoft.Office.Interop.Outlook;

using Office = Microsoft.Office.Core;

using System.Collections.Generic;

using Microsoft.Office.Interop.Outlook;

using Microsoft.TeamFoundation.Client;

using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace EmailToWorkitem

{

    public partial class ThisApplication

    {

        // the prompt and action name

        const string createNewPrompt = "Create Workitem";

        Outlook.Explorer _explorer = null;

        private void ThisApplication_Startup(object sender, System.EventArgs e)

        {

            // cache the explorer object

            _explorer = this.Explorers.Application.ActiveExplorer();

            // when an email selection changes this event will fire

            _explorer.SelectionChange += new ExplorerEvents_10_SelectionChangeEventHandler(_explorer_SelectionChange);

        }

        // event fired when any selection changes.

        void _explorer_SelectionChange()

        {

            foreach (object selectedItem in _explorer.Selection)

            {

                // we only want to deal with selected mail items

                MailItem item = selectedItem as MailItem;

                if (item != null)

                {

                    // see if the action already exists on mail item

                    Action newAction = item.Actions[createNewPrompt];

                    // and create it if it does not

                    if(newAction == null)

                    {

                        newAction = item.Actions.Add();

                        newAction.Name = createNewPrompt;

                        newAction.ShowOn = OlActionShowOn.olMenu;

                        newAction.Enabled = true;

                        item.Save();

                    }

                    // add the event handler for our action

                    item.CustomAction += new ItemEvents_10_CustomActionEventHandler(item_CustomAction);

                }

            }

        }

        void item_CustomAction(object Action, object Response, ref bool Cancel)

        {

            try

            {

                Action mailAction = (Action)Action;

                switch (mailAction.Name)

                {

                    // only process the action we know about

                    case createNewPrompt:

                        try

                        {

                            MailItem mailItem = _explorer.Selection[1] as MailItem;

                            if (mailItem != null)

                            {

                                // TODO: modify the server details to point you your server

                                TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer("https://yourserver:8080");

                                WorkItemStore store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));

                                // TODO: modify the project name to your project

                                WorkItemTypeCollection workItemTypes = store.Projects["YOURPROJECT"].WorkItemTypes;

                                // Enter the work item as a bug

                                WorkItemType wit = workItemTypes["bug"];

                                WorkItem workItem = new WorkItem(wit);

                             workItem.Title = mailItem.Subject;

                                workItem.Description = mailItem.Body;

                                workItem.Save();

                                MessageBox.Show(string.Format("Created bug {0}", workItem.Id));

  }

                            else

                            {

                                MessageBox.Show("Unable to convert selected item to a mail item");

                            }

                        }

          finally

                        {

                            Cancel = true;

                        }

                        break;

                       

                }

            }

            catch (System.Exception e)

            {

                MessageBox.Show(e.ToString());

            }

        }

        private void ThisApplication_Shutdown(object sender, System.EventArgs e)

        {

        }

        #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(ThisApplication_Startup);

            this.Shutdown += new System.EventHandler(ThisApplication_Shutdown);

        }

        #endregion

    }

}