Export (0) Print
Expand All
Expand Minimize

Creating an Outlook Business Contact Assistant Add-in with Visual Studio Tools for Office

Office 2003

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Naveen Yajaman, Microsoft Corporation

John R. Durant, Microsoft Corporation

November 2005

Applies to: Microsoft Visual Studio 2005 Tools for the Microsoft Office System, Microsoft Office Outlook 2003, Microsoft Visual Studio 2005

Summary:   Learn to use Microsoft Visual Studio 2005 Tools for the Microsoft Office System to create a managed add-in for Microsoft Office Outlook 2003. This add-in creates associations between contacts and Inbox items, and reports on Inbox activity in a user's contacts list. You use the development tools and programmable aspects of Outlook 2003. (18 printed pages)

Download BusinessContactAssistantSolutionSampleSetup.msi.

Contents

Microsoft Office Outlook 2003 is an important communication tool in most businesses. Its capabilities far exceed a typical e-mail client. Although Outlook can create entities that are useful by themselves, such as e-mail messages, contacts, appointments, and meetings, you unleash its real potential when you establish a relationship between these entities. When you do so, the user can view data in unique ways.

This sample, named the Business Contact Assistant, highlights the potential of Outlook using a Microsoft Visual Studio 2005 Tools for Office System add-in to build part of a contact management system. The sample is built around contacts and how Outlook tracks and reports the e-mail activities related to a contact.

The Business Contact Assistant add-in has a top-level, custom menu on the Outlook Standard toolbar. The menu, titled Business Contact Assistant, has five items (Figure 1).

Figure 1. The menu created by the add-in

The menu created by the add-in

The Enable Contact Creation on Send Email menu item allows the user to create a contact when sending an e-mail message. For example, if a user attempts to send an e-mail message to someone not in the Contacts folder, the add-in asks the user whether to create a contact item for the target recipient (Figure 2).

Figure 2. Prompt for creating a contact

Prompt for creating a contact

The add-in creates the contact, if no contact exists with the same e-mail address as the target recipient. This feature makes it easier for users to add entries in their contacts lists.

When the user clicks the View Activity Report menu item in the add-in, it displays a form that contains a scrollable list of contacts and shows how many e-mail messages the user received from and sent to each contact (Figure 3). To do this, the add-in looks through the collection of contacts and inspects two properties added to them by the add-in.

Figure 3. Viewing the activity report in the add-in

Viewing the activity report in the add-in

The add-in tracks and reports only contacts whose category value is set to Business.

After you install the add-in, it tracks e-mail activity in the context of business-related contacts. However, there are existing e-mail messages that the add-in should include in reports. Running the report directly after startup yields a list of contacts with no associated incoming or outgoing e-mail messages. To associate existing e-mail messages with existing contacts, the user selects the Create Email Contact Associations menu item.

Although the values for outgoing and incoming e-mail associated with each business contact grow as the user sends and receives e-mail, he or she might find it useful to reset the values to zero. For example, after a role change, the user might want to know how the activity has changed. The same is true for other big changes, such as an extended leave of absence or changes in the user's team. Also, the user might want to reset the counters after archiving old e-mail messages. Clicking the Reset Email Counters menu item brings up a dialog box for managing the counters (Figure 4).

Figure 4. Resetting e-mail counters

Resetting e-mail counters

In this dialog box, the user can reset the counters for all contacts, or for a smaller list of contacts, by clicking the check box next to each name.

One of the strengths of Outlook is the ease and power of Search Folders. These folders contain pointers to Outlook items contained in other folders. The list of items presented in a Search Folder depends on the search criteria configured as part of the Search Folder definition. The add-in created for this article makes it easy for users to create a Search Folder for each of the Outlook business contacts by clicking the Create Contact Search Folders menu item.

The Business Contact Assistant add-in has three projects (Figure 5). The primary project, BusinessContactAssistant, provides the functionality described so far. The two additional projects control the final deployment of the add-in.

Figure 5. The primary and deployment projects in the add-in

The primary and deployment projects in the add-in

These two projects deploy the add-in on a computer other than the one you use for development. The Microsoft Visual Studio 2005 project system automatically adds the setup project when you create an Outlook add-in from the project template. The other project, which you add, contains classes that make it easier to configure code access security (CAS) permissions on the computer where you run the setup project.

The primary project consists of three forms and several classes (Figure 6).

Figure 6. Files in the main project

Files in the main project

The three forms (Figures 2–4) simplify the tasks of e-mail association, reporting, and counter management for the user. Most of the other files contain classes that handle major operations in the add-in. As with all Outlook add-ins in Visual Studio 2005 Tools for Office, the main file, and the starting point of the entire solution, is the ThisApplication.cs file.

NoteNote

The Business Contact Assistant add-in uses Microsoft Visual C#. Although this article does not explain every line of code, it describes code pertaining to the fundamental architecture and routines that are either difficult or interesting.

When Outlook loads the add-in at startup, it calls the ThisApplication_Startup method. At that time, the add-in subscribes to specified Outlook events and builds a menu on the Standard toolbar. Because the add-in primarily handles contacts, it also requires a reference to a contacts list that the add-in passes throughout the application.

private void ThisApplication_Startup
(object sender, System.EventArgs e)
{
    SubscribeToEvents();
    AddBusinessContactAssistantMenu();
    contactsList = new List<Outlook.ContactItem>();
}
private void SubscribeToEvents()
{
    this.ItemSend += new Outlook.
    ApplicationEvents_11_ItemSendEventHandler(HandleItemSend);
    this.NewMailEx += new Outlook.
    ApplicationEvents_11_NewMailExEventHandler(HandleNewEmails);}
}

The add-in responds to two Outlook events: the ItemSend event that fires whenever Outlook sends an e-mail message and the NewMailEx event that fires whenever an e-mail message arrives in the Inbox. (Outlook added the NewMailEx event.) The code has to capture these events and allow the custom code to execute when the events fire.

The Business Contact Assistant add-in prompts users, asking them whether they want to create a contact entry for the person to whom they are sending the e-mail message (Figure 2). The HandleItemSend function handles this event. After some declarations, the code loops through the list of recipients for the outgoing e-mail message. If the recipient is not the sender, the add-in creates a new instance of the BusinessContactUtility class.

private void HandleItemSend(object outgoingMailItem, 
    ref bool Cancel)
   {
      . . .
         for (int i = 1; 
          i <= outgoingMailItemToProcess.Recipients.Count; i++)
         {
             Outlook.Recipient recipient = 
             outgoingMailItemToProcess.Recipients[i];

     . . .
         BusinessContactUtility contactManager = 
         new BusinessContactUtility();

This class helps isolate the code needed for retrieving a contact item from Outlook or creating a contact. It also has static or shared methods for adding the ReceivedEmail and SentEmail properties when necessary, as well as methods for updating these properties when Outlook sends an e-mail message or receives a message from a specified contact. In the HandleItemSend method, the add-in creates an instance of the BusinessContactUtility class and calls the GetContact method, attempting to acquire the Outlook contact, if it exists. In so doing, it passes the name of the recipient used by the iteration. If the contact does not exist, the code checks to see if the user has enabled creating contacts when sending e-mail messages (Figure 1). If the user has not enabled this, the code exits. If the user has enabled this, and the contact does not already exist, the code checks to see if the address in question was already processed during the current HandleItemSend method call.

             Outlook.ContactItem contactItem = 
             contactManager.GetContact(                                   
             recipient.AddressEntry.Address.TrimStart().
             TrimEnd().ToLower());

             if (contactItem == null)
             {
                 // Check if the Create Contact 
                 //option on sending e-mail message is enabled. 
                 if (!this.createContactsOnSendMail)
                 {
                     return;
                 }

                 bool repeatedEmailAddress = false;
                 for (int j = 0; j < emailAddressesProcessed.Count; j++)
                 {
                     if (0 == BusinessContactUtility.
                     CompareEmailAddress(
                     recipient.AddressEntry.Address, 
                     emailAddressesProcessed[j]))
                     {
                         repeatedEmailAddress = true;
                         break;
                     }
                 }
            if (repeatedEmailAddress)
                  {
                      continue;
                  }

After this simple check, the code attempts to add the new contact and presents the form (Figure 2). To display the text in the dialog box, the add-in uses a custom class to access resources, which are mostly strings. The ResourceAccessor class allows you to localize the application easily by locating captions and other strings in a file that you can alter.

            emailAddressesProcessed.Add(
            recipient.AddressEntry.Address);

            CreateContactQuestionForm form = 
            new CreateContactQuestionForm();
            
            form.Message = String.Format(
            ResourceAccessor.GetString(
            ResourceAccessor.DoYouWantToCreateAContact), 
            recipient.Name);
            
            DialogResult result = form.ShowDialog();

If the user chooses to create a new contact, the add-in calls the CreateNewContact method from the instance of the BusinessContactUtility class.

           if (result.CompareTo(DialogResult.Yes) == 0)
           {
             contactItem = contactManager.
                      CreateNewContact(recipient.AddressEntry.Address);
                      // Hold the lock to ensure that that the list is 
                      // not being changed in the background by a 
                      // different thread.
                      lock (contactListLock)
                      {
                          contactsList.Add(contactItem);
                      }
                      contactItem.Write += 
                      new Outlook.ItemEvents_10_WriteEventHandler(
                      HandleContactItemWrite);
                  }

As mentioned earlier, when the add-in first loads, it creates a generic list of strongly-typed Outlook ContactItem objects.

     contactsList = new List<Outlook.ContactItem>();

The contactsList variable takes advantage of Generics support in the Microsoft .NET Framework 2.0. In so doing, the add-in uses the List object at runtime, but the language compiler checks for type safety issues when it compiles the project. Also, because the List object is type safe, the runtime does not do type checking, which improves performance. After the add-in creates the contact, it adds the ContactItem instance to the contactsList variable list.

The code shown so far in the HandleItemSend function adds a new contact. But, if a specified recipient for the e-mail message already exists as a contact, the add-in runs the following code:

    if (contactItem.Categories != null && 
          contactItem.Categories.Contains("Business"))
          {
              BusinessContactUtility.
              UpdateSentEmailCounter(
              outgoingMailItemToProcess, 
              contactItem);
              
              MailItemProcessor.AddMailProcessedFlag(
              outgoingMailItemToProcess, contactItem);
           }

This code uses a shared method of the BusinessContactUtility class to update the SentEmail property for the contact with an incremented value. Also, it adds a custom user property, EmailProcessedByBusinessContactAssistant, to the sent item, if the property does not already exist.

public static void AddMailProcessedFlag(
Outlook.MailItem mailItem, 
Outlook.ContactItem contactItem)
{
  if (mailItem != null)
  {
    Outlook.UserProperty mailProcessedProperty = null;

    mailProcessedProperty = mailItem.
    UserProperties[emailProcessedByBusinessContactAssistantProperty];
    mailProcessedProperty = mailItem.
            UserProperties.Add(emailProcessedByBusinessContactAssistantProperty,
    Outlook.OlUserPropertyType.olText,
    Type.Missing,
    Type.Missing);
       if (mailProcessedProperty != null)
       {
           string value = mailProcessedProperty.Value as string;
           if (value != null && value.Length > 0)
           {
               value = value + "," + contactItem.EntryID;
               mailProcessedProperty.Value = value;
           }
           else
           {
               mailProcessedProperty.Value = contactItem.EntryID;
           }
        }
    }
}

Then, the code assigns a value to this custom user property. If this is the first time that the add-in populated the field on the mail item, it assigns the value of the EntryID property for the ContactItem object to the field. If the add-in has previously assigned a value to this field, the add-in appends the new EntryID property to the existing value, separated by a comma.

The earlier sample code showed how the add-in handles e-mail messages sent by the user. The following code shows how the HandleNewEmails function handles e-mail messages received by the user. HandleNewEmails receives a list of entry IDs for all the items received in the Inbox since the last time the event fired.

private void HandleNewEmails(string entryIDCollection)
{
    // Get the individual entry ids.
    char[] arr = { ',' };

    string[] entryIds = entryIDCollection.Split(
    arr, StringSplitOptions.RemoveEmptyEntries);

    foreach (string entryId in entryIds)
    {
        // If an item corresponding to the 
        //entry id is not found, then the 
        //item may have been moved, deleted, or archived.
        Outlook.MailItem mailItem = this.Session.GetItemFromID(
        entryId, System.Reflection.Missing.Value) as Outlook.MailItem;
        if (mailItem != null)
        {
            // This is to ensure that 
            //you do not process the mail items sent to the current user.
            if (mailItem.SenderEmailAddress.TrimStart().
            TrimEnd().IndexOf('@') > 0 && 
            mailItem.SenderEmailAddress.TrimStart().
            TrimEnd().Substring(0,
               mailItem.SenderEmailAddress.TrimStart().
               TrimEnd().IndexOf('@')) == 
               this.Session.CurrentUser.Name)
            {
                continue;
            }
            if (mailItem.SenderEmailAddress.
            TrimStart().TrimEnd() == this.Session.CurrentUser.Name)
            {
                continue;
            }
            BusinessContactUtility contactManager = 
            new BusinessContactUtility();
            Outlook.ContactItem contactItem = 
            contactManager.GetContact(
            mailItem.SenderEmailAddress.
            TrimStart().TrimEnd().ToLower());
            if (contactItem != null)
            {
                BusinessContactUtility.
                UpdateReceivedEmailCounter(mailItem, contactItem);
                MailItemProcessor.
                AddMailProcessedFlag(mailItem, contactItem);
                mailItem.Save();
            }
        }
    }
}

Because the add-in knows the EntryID property values for the mail items that were sent, it loops through the collection and attempts to retrieve each mail item by calling the GetItemFromID method.

        Outlook.MailItem mailItem = this.Session.GetItemFromID(
        entryId, System.Reflection.Missing.Value) as Outlook.MailItem;

If the current Outlook user sent the mail item, the routine does nothing and the add-in continues through the list of mail items. If someone else sent the mail item, the add-in uses that sender's e-mail address to locate a ContactItem object. If the add-in finds the contact item, updates the ReceivedEmail property for the contact. Then, the add-in calls the shared AddMailProcessedFlag method to add the contact's EntryID property value to the mail item's EmailProcessedByBusinessContactAssistant property. This links the sender's e-mail address and contact item together.

After you install the Business Contact Assistant add-in, it associates e-mail messages and contacts, for messages sent and received, from that point forward. You can use the Business Contact Assistant menu to associate e-mail messages and contacts that predate the installation of the add-in (Figure 7).

Figure 7. The menu items in the add-in

The menu items in the add-in

When the user selects the Create Email Contact Association menu item, the add-in starts a background, asynchronous process to create these associations.

public void CreateMailItemContactAssociations(
 object sender, DoWorkEventArgs e)
 {
    ReceivedMailItemProcessor receivedMailProcessor = null;
    SentMailItemProcessor sentMailProcessor = null;
    contactRecievedMailItemProcessingCompletedFlag = false;
    receivedMailProcessor = new ReceivedMailItemProcessor();
    Globals.ThisApplication.AdvancedSearchComplete += 
    new Outlook.ApplicationEvents_11_AdvancedSearchCompleteEventHandler(
    receivedMailProcessor.ProcessReceivedMailSearchedUsingAdvancedSearch);
   
    receivedMailProcessor.OnRecievedMailProcessingCompleted += 
    new ReceivedMailProcessingCompletedHandler(
    ContactReceivedMailItemAssociationProcessingCompleted);
    
    receivedMailProcessor.InitiateReceivedMailProcessing();

    Outlook.MAPIFolder sentMailFolder = 
    Globals.ThisApplication.Session.GetDefaultFolder(
    Outlook.OlDefaultFolders.olFolderSentMail) as Outlook.MAPIFolder;
    sentMailProcessor = new SentMailItemProcessor();
    if (sentMailFolder.Items.Count > 0)
    {
    sentMailProcessor.ProcessSentMailItems(sentMailFolder.Items);
    }

    // Because you asynchronously initiated ReceivedMail processing, 
    // wait for that processing to complete.
    while (!contactRecievedMailItemProcessingCompletedFlag)
    {
        System.Threading.Thread.Sleep(1000);
    }

    e.Result = new MailItemContactAssociationResult(
    sentMailProcessor.SentEmailProcessed, 
    sentMailProcessor.ContactsWithSentEmail, 
    receivedMailProcessor.ReceivedEmailProcessed, 
    receivedMailProcessor.ContactsWithReceivedEmail);
 }

This procedure processes the received e-mail messages, and then processes the sent messages. To do this, it creates an instance of the ReceivedMailItemProcessor class and calls its InitiateReceivedMailProcessing method. This method loops through the collection of Outlook contacts and processes those whose value is Business. For those, it uses the Advanced Search capability of Outlook to look for e-mail messages received from that contact.

  string contactIdentifier = 
        processReceivedMailItemAdvancedSearchTagPrefix + 
        Guid.NewGuid().ToString();
        contactReceivedEmailAssociator.Add(
        contactIdentifier, contactItem);
        Outlook.Search search = 
        Globals.ThisApplication.AdvancedSearch(
        scope, filter, searchSubFolders, contactIdentifier);

The most important method call is to the processReceivedMailItemAdvancedSearchTagPrefix method, which creates the association.

     BusinessContactUtility.UpdateReceivedEmailCounter(
          receivedMailItem, contactItem);
          MailItemProcessor.AddMailProcessedFlag(
          receivedMailItem, contactItem);
          receivedMailItem.Save();

After processing the e-mail messages received, the add-in processes the sent messages. To do this, it creates an instance of the SentMailItemProcessor class. It then calls the ProcessSentMailItems method and loops through the sent items. Ultimately, it calls the ProcessSentMailItem method, which looks at the e-mail addresses and attempts to find an existing Outlook contact whose value is set to Business.

     BusinessContactUtility.UpdateSentEmailCounter(
          sentMailItem, contactItem);
          MailItemProcessor.AddMailProcessedFlag(
          sentMailItem, contactItem);
          sentMailItem.Save();

If the contact exists, the add-in updates the appropriate custom property values.

The Business Contact Assistant add-in has two other menu items to consider: View Activity Report and Create Contact Search Folders.

When the user selects the View Activity Report menu item, its event handler creates a new BusinessContactActivityManager instance and calls the BusinessContactActivity method. As you might expect, the method loops through the contacts whose value is Business and attempts to read the custom property values to know how many e-mail messages the user sent to and received from the contact. The method returns a collection of BusinessContact instances to the calling routine. With the collection fully populated, the add-in can create and display a new form from the ViewActivityReportForm method (Figure 8).

Figure 8. The add-in displays an activity report

The add-in displays an activity report

This form has a simple data grid bound to the collection of contacts.

When a user selects the Create Contact Search Folders menu item, the event handler creates a BackgroundWorker class to process the Search Folder so that it is performed asynchronously. The real work, though, is accomplished by an instance of the SearchFolderUtility class. This class has a FindMailItems method that finds contacts with a category value set to Business. As each is found, the add-in creates a custom Search Folder, if one does not exist yet.

    Outlook.Search search = 
    Globals.ThisApplication.AdvancedSearch(
    scope, filter, searchSubFolders, 
    System.Reflection.Missing.Value);
    try
    {
        Outlook.MAPIFolder resultsFolder = 
        search.Save(contactItem.Email1DisplayName);
    }
    catch (System.Runtime.InteropServices.COMException)
    {
        // Do not do anything, 
        //if the folder already exists.
    }

The Outlook object model makes this easy through the Search object. By calling the Save method, Outlook creates a new folder with pointers to the mail items.

The add-in has a feature for resetting the counters for contacts that it tracked. The code for this is straightforward: it displays a form in which users select items whose counters they must reset. The user can then click a button to begin the resetting process. This process resets the custom property values for each contact the user selects.

When you create a Visual Studio 2005 Tools for the Microsoft Office System add-in for Outlook, you use a project template for the new application (Figure 9).

Figure 9. Using the project template for a new add-in

Using the project template for a new add-in

When you use this project template, Visual Studio creates two projects: one for the add-in itself and the other for setup. Because the setup project wraps the output from the main add-in project in an .msi file, you can take that file to another computer to deploy your add-in.

For the add-in to execute and your project to deploy, you must configure the .NET Framework security permissions on the target workstation. Although Visual Studio does not automatically include custom actions for configuring permissions in your setup project, you can add them. The sample add-in described in this article has an additional project that accomplishes this.

In addition to the projects discussed earlier — the add-in itself and the accompanying setup project — the add-in includes a security project. SecurityPolicyCustomAction.exe is a simple executable file that the setup project calls to set the .NET Framework permissions for the add-in.

The entry point for the security application looks like this:

static void Main(string[] args)
{
    string installOrUninstallOption = args[0];
    string targetDirectory = args[1];
    string addinAssemblyName = args[2];
    // The actual code contains checks for valid arguments.
    string frameworkFolder = FrameworkFolderUtility.
    GetRuntimeInstallationDirectory(Environment.Version);

  string allFilesInInstallationLocation = 
  Path.Combine(targetDirectory, "*");
 
  string addinAssemblyPath = 
  Path.Combine(targetDirectory, addinAssemblyName);

  if (installOrUninstallOption.ToLower().Equals("install"))
  {                     
    CaspolSecurityPolicyCreator.
    InstallCASPolicy(frameworkFolder, 
           allFilesInInstallationLocation, 
           addinAssemblyPath, 
           companyNodeName, 
           solutionSampleNodeName, 
           folderNodeName, 
           addinAssemblyNodeName, 
           solutionSampleDescription);
   }
   else if (installOrUninstallOption.ToLower().Equals("uninstall"))
   {
    CaspolSecurityPolicyCreator.
    UnInstallCASPolicy(frameworkFolder, 
           folderNodeName, 
           addinAssemblyNodeName);
   }
}

The critical part of this procedure occurs when the code calls the InstallCASPolicy method or UnInstallCASPolicy method from the CaspolSecurityPolicyCreator class. Together, these methods generate the command-line arguments for the Code Access Security Policy tool, Caspol.exe. They also pass the arguments and the target folder for the .NET Framework to the AddRemoveSecurityPolicyNode procedure.

private static string AddRemoveSecurityPolicyNode(
  string frameworkFolder, string arguments, out int exitCode)
  {
      ProcessStartInfo processStartInfo = 
      new ProcessStartInfo(Path.Combine(frameworkFolder, "caspol.exe"));
      processStartInfo.CreateNoWindow = true;
      processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
      processStartInfo.WorkingDirectory = frameworkFolder;
      processStartInfo.Arguments = arguments;
      processStartInfo.RedirectStandardError = true;
      processStartInfo.RedirectStandardOutput = true;
      processStartInfo.UseShellExecute = false;

      Process process = Process.Start(processStartInfo);
      string outputMessage = process.StandardOutput.ReadToEnd();
      process.WaitForExit();

      exitCode = 0;

      if (process.HasExited)
      {
          exitCode = process.ExitCode;
      }

      return outputMessage;
  }

The code for this procedure creates a new ProcessStartInfo class instance and sets its properties using arguments passed from the call stack. In this way, the routine accomplishes the equivalent of running Caspol.exe with no user intervention. All that remains is to connect this project to the setup project in the add-in.

The setup project in the add-in uses a custom action in the setup project to run the executable file from the SecurityPolicyCustomAction file. When you open the Custom Actions Editor in the setup project, you see custom actions for installing and uninstalling the add-in. (Figure 10).

Figure 10. Custom actions for installing and uninstalling the add-in

Custom actions for installing/uninstalling add-in

Although these custom actions can have any name, give them self-evident names, as shown here. If you look at the properties for the BusinessContactAssistantCASPolicyInstaller action, you can see the name, arguments, and source path for the executable file (Figure 11).

NoteNote

The figure artificially shortens the SourcePath value to show the executable file at the end of the path.

Figure 11. Properties for the custom action

Properties for the custom action

When you add a custom action, Visual Studio prompts you for the output of the executable file or DLL (Figure 12).

Figure 12. Choosing the primary output of the custom installer project

Choosing output of the custom installer project

You choose the primary output of the custom installer project to configure the SourcePath value. You can then set the value for the Arguments property and give it a meaningful name.

Using these custom actions has an advantage: when the setup project in the add-in runs, the security installer project executes, also. Thus, the project setup also configures the .NET Framework security permissions.

Visual Studio 2005 Tools for Office make creating managed code add-ins for Outlook easier than ever. Using these tools, you can focus on coding your business solution rather than troubleshooting your environment. Because the Business Contact Assistant add-in helps users better understand their e-mail use, they can make informed decisions about their communication activities. Such insight is the goal of any customization for information workers: to improve their productivity and contribute to their objectives. This Business Contact Assistant is an example of how to fulfill that goal. Using these tools, you can create world-class add-ins that increase user efficiency and satisfaction in your organization.

For more information about creating an Outlook Business Contact Assistant add-in with Visual Studio Tools for Office, see the following resources:

Show:
© 2014 Microsoft