Export (0) Print
Expand All
Expand Minimize

Retrieving Lync-Archived Conversation History with Exchange Web Services

Summary:   This article discusses how to retrieve conversation history that is archived by Microsoft Lync 2010. You learn how to use Microsoft Exchange Web Services (EWS) to obtain archived conversations that are stored in the Microsoft Outlook Conversation History folder. You also learn how to query the conversation history by using a specific date, topic, and participant.

Applies to:   Microsoft Lync 2010 | Microsoft Lync Server 2010 | Microsoft Exchange Server 2010 SP1 | Microsoft Exchange Server 2007 SP1 | Microsoft Exchange Web Services Managed API 1.1 SDK

Published:   May 2011 | Provided by:   Kurt De Ding, Microsoft | About the Author

Contents

You can use Microsoft Exchange Web Services (EWS) to retrieve and query Lync-archived instant messaging conversation history.

  • Retrieve all Lync-archived conversations.

  • Retrieve the history of conversations with specified participants.

  • Retrieve saved conversations that mention specified words or expressions.

  • Retrieve past conversations on or after a certain date and time.

The following sections explain how to use EWS to retrieve and query Lync-archived conversation history. After you retrieve the conversation history, and can send the retrieved conversations in a contextual data channel for the specified conversation. For information about how to set up a contextual data channel in a contextual collaboration, see Using UCMA 3.0 and Lync 2010 for Contextual Communication: Scenario Overview (Part 1 of 6).

Setup Requirements

  • Set up a user account in a Microsoft Lync Server 2010 deployment that is integrated with Microsoft Exchange Server 2010 SP1 or Microsoft Exchange Server 2007 SP1.

  • Ensure that Microsoft Lync 2010 conversations are archived in Microsoft Outlook. To archive conversation in Lync 2010:

    1. In the Lync Options dialog box, click Personal.

    2. Select the Save instant messaging conversations in my email Conversation History folder option.

  • Install Microsoft Visual Studio 2010 development system on the development computer.

  • Install Microsoft Exchange Web Services Managed API 1.1 on the development computer.

NoteNote

For information about how to install Exchange Web Services Managed API 1.1, see Additional Resources. By default, the required Microsoft.Exchange.WebServices.DLL assembly is installed in the %programfiles%\Microsoft\Exchange\WebServices\1.1 directory.

This section explains how set up a Microsoft Visual Studio development system project to use Exchange Web Services (EWS) to retrieve Lync-archived instant messaging conversations.

To set up the Visual Studio project

  1. Start Microsoft Visual Studio 2010.

  2. Create a new Console project in C#. In the project’s Property settings, ensure that .NET Framework 3.5 or .NET Framework 4.0 is selected under Target framework.

  3. In the Visual Studio project, add a reference to the Microsoft.Exchange.WebServices.dll assembly. The default directory for the assembly is %programfiles%\Microsoft\Exchange\WebServices\1.1.

  4. Import the relevant EWS namespace into the Visual Studio project by adding the following using directive to the code file.

    using Microsoft.Exchange.WebServices.Data;
    

    You must import the System.Collections.Generic namespace into the project, if the namespace is not already imported.

    The EWS assembly contains two other namespaces: Microsoft.Exchange.WebServices.Dns and Microsoft.Exchange.WebServices.AutoDiscoverer. In this article, the Microsoft.Exchange.WebServices.Data namespace is used.

  5. Declare the following class-level instance variables:

    
            ExchangeService _exchangeService = null;
            int _pageSize = 50;
            Folder _imHistoryFolder = null;
            List<EmailMessage> _imHistory = null;
    
    
    

    The (_exchangeService) variable will be used to hold the to-be-instantiated ExchangeService instance. The _imHistoryFolder variable will hold the Conversation History folder that contains the conversation history items. The _pageSize variable is used to specify that the query results are returned 50 items at a time. The _imHistory variable will be used to hold the retrieved conversation items that are returned from a given query.

The code file appears in the next example.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Exchange.WebServices.Data;

namespace LyncImHistoryConsole
{
    public class LyncConversationHistory
    {
        ExchangeService _exchangeService = null;
        int _pageSize = 50;
        Folder _imHistoryFolder = null;
        List<EmailMessage> _imHistory = null;

        static void Main(string[] args)
        {
        }
    }
}


The following sections discuss how to instantiate EWS, obtain the Conversation History folder, and retrieve Lync-archived instant messaging conversation items. The first step is to initialize and connect to Exchange Web Services.

To initialize and connect to Exchange Web Services (EWS), you create an instance of the ExchangeService type and then bind it to the EWS URL that services the email account as specified by a given email address. A good position to complete this step is in the constructor of your application class. In the following example, the application class is LyncConversationHistory.


        /// <summary>
        /// Class constructor
        /// </summary>
        /// <param name="emailAddress"></param>
        /// <param name="exchangeVersion"></param>
        public LyncConversationHistory(string emailAddress)
        {
            _exchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
            _exchangeService.UseDefaultCredentials = true;
            _exchangeService.AutodiscoverUrl(emailAddress);

            _imHistory = new List<EmailMessage>();
        }

The AutodiscoverUrl call that appears in the previous code example automatically resolves the optimal EWS URL for the specified email address.

Lync saves instant messaging conversations in the Conversation History folder in the Exchange account of each participant. In the following example, the Conversation History folder is obtained by enumerating the well-known folders in the user’s email account. In Exchange Web Services (EWS), this process is achieved by calling the FindFolders method on the ExchangeService instance.


        private Folder FindImHistoryFolder()
        {
            FolderView folderView = new FolderView(_pageSize, 0);
            folderView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
            folderView.PropertySet.Add(FolderSchema.DisplayName);
            folderView.PropertySet.Add(FolderSchema.ChildFolderCount);

            folderView.Traversal = FolderTraversal.Shallow;
            Folder imHistoryFolder = null;

            FindFoldersResults findFolderResults;
            bool foundImHistoryFolder = false;
            do
            {
                findFolderResults = _exchangeService.FindFolders(WellKnownFolderName.MsgFolderRoot, folderView);
                foreach (Folder folder in findFolderResults)
                {
                    if (folder.DisplayName.ToLower() == "conversation history")
                    {
                        imHistoryFolder = folder;
                        foundImHistoryFolder = true;
                    }
                }
                folderView.Offset += _pageSize;
            } while (findFolderResults.MoreAvailable && !foundImHistoryFolder);

            return imHistoryFolder;
        }


In the following example, all of the archived conversation items are retrieved from the Conversation History folder by calling the Exchange Web Services (EWS) FindItems methods.


        /// <summary>
        /// Retrieve all instant messaging conversation history items 
        /// </summary>
        /// <returns> A collection of Item instances.</returns>
        public IEnumerable<Item> RetrieveImHistory()
        {
            // Get the "Conversation History" folder, if not already found.
            if (_imHistoryFolder == null)
            {
                _imHistoryFolder = this.FindImHistoryFolder();
                if (_imHistoryFolder == null)
                {
                    return null;
                }
            }

            // Get Conversation History items.
            List<Item> imHistoryItems = new List<Item>();
            FindItemsResults<Item> findResults;

            ItemView itemView = new ItemView(_pageSize);
            itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly);
            SearchFilter.SearchFilterCollection searchFilterCollection = null;

            do
            {
                findResults = _exchangeService.FindItems(_imHistoryFolder.Id, searchFilterCollection, itemView);
                imHistoryItems.AddRange(findResults);
                itemView.Offset += _pageSize;
            } while (findResults.MoreAvailable);

            return imHistoryItems;
        }

In the previous example, the resultant Item instances are not fully loaded. Some properties are available on Item objects. For example, DisplayTo and DateTimeCreated. Other properties become available only after Item.Load() is called. Examples of these properties include Subject and Body. The Body property contains all the texts of the conversation. An attempt to read the Subject or Body property on an unloaded Item object throws an exception.

Calling Item.Load() with the Body text can affect performance. For example, in a collection of approximately 150 saved conversation items, it takes more than 26 times longer to obtain all 150 items with the Body text than without it. Because it blocks the UI thread, the reduced performance can be problematic when a large set of conversation history items are queried and loaded. The problem can be solved by using an asynchronous query pattern or by delaying an Item.Load() method call until it is absolutely needed.

You can use EWS to search for archived conversations that included specific participants or topics and, perhaps, on or after a certain date and time. To search for conversations with a specified participant, you can add a search filter to match the DisplayTo property of conversations. To query conversations about a specific topic, you can create a search filter to match the specified topic keywords or phrases against the texts in the Body of a conversation. To get the conversations that occurred on or after a specified date and time, you use a search filter to specify that the DateTimeCreation property value of a conversation must be greater than or equal to a specified DateTime value. To match all three search criteria, with Exchange Web Services (EWS), you can set the LogicalOperator.And on a search filter collection.


        /// <summary>
        /// Query saved conversations with specified participants  
        /// about specified topics and on or after the specified time
        /// </summary>
        /// <param name="participantNames">Names of conversation participants.</param>
        /// <param name="queryTexts">Texts discussed in the conversation</param>
        /// <param name="createTime">Time when the conversation was initially started</param>
        public void QueryImHistory(string[] participantNames, string[] queryTexts, DateTime createTime)
        {
            // Get the "Conversation History" folder, if not already found.
            if (_imHistoryFolder == null)
            {
                _imHistoryFolder = this.FindImHistoryFolder();
                if (_imHistoryFolder == null)
                {
                    return;
                }
            }

            // Get Conversation History items.
            _imHistory.Clear();
            FindItemsResults<Item> findResults;

            ItemView itemView = new ItemView(_pageSize);
            itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly);

            // Create a search filter collection, with a logical AND operator, to add query predicates.
            SearchFilter.SearchFilterCollection searchFilterCollection 
                       = new SearchFilter.SearchFilterCollection(LogicalOperator.And);

            // Add query predicates for conversation topic texts and/or participant names.
            SearchFilter searchFilter = null;

            if (queryTexts != null)
            {
                foreach (string queryText in queryTexts)
                {
                    searchFilter = new SearchFilter.ContainsSubstring(
                        ItemSchema.Body, queryText, ContainmentMode.Substring, ComparisonMode.IgnoreCase);
                    searchFilterCollection.Add(searchFilter);
                }
            }

            if (participantNames != null)
            {
                itemView.PropertySet.Add(ItemSchema.DisplayTo);
                foreach (string pName in participantNames)
                {
                    searchFilter = new SearchFilter.ContainsSubstring(ItemSchema.DisplayTo, pName);
                    searchFilterCollection.Add(searchFilter);
                }
            }


            // Add the query predicate for the start time of conversations.
            if (createTime != null)
            {
                itemView.PropertySet.Add(ItemSchema.DateTimeCreated);
                searchFilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeCreated, createTime);
                searchFilterCollection.Add(searchFilter);
            }

            do
            {
                findResults = _exchangeService.FindItems(_imHistoryFolder.Id, searchFilterCollection, itemView);
                foreach (Item item in findResults.Items)
                {
                    EmailMessage msg = item as EmailMessage;
                    msg.Load(new PropertySet(BasePropertySet.FirstClassProperties));
                    _imHistory.Add(msg);
                }
                itemView.Offset += _pageSize;
            } while (findResults.MoreAvailable);

        }


The previous code example adds filters to the DisplayTo field, the Body text, and the DateTimeCreated property of the archived conversations. The search filter for the body text is of the SearchFilter.ContainsSubstring type and it contains the specified topic keywords or phrases. The type of the query expression is ItemSchema.Body and the value of the expression is queryTexts. Similarly, a conversation matches the specified participant names if the DisplayTo property contains the specified participant names (participantNames). The search filter for selecting conversations that started on or after a specified time is of the SearchFilter.IsGreaterThanOrEqualTo type. The type of the query expression is specified by ItemSchema.DateTimeCreated, and the value of the expression is createTime. The logical AND operator ensures that all predicates are satisfied.

To test code example, add the following main method of the class.


        static void Main(string[] args)
        {
            string emailAddress = "user@contoso.com";

            Console.WriteLine("Press a key to continue");
            Console.ReadKey();

            LyncConversationHistory lyncConvHistory = new LyncConversationHistory(emailAddress);

            Console.WriteLine("Query Lync-archived conversations by user@contoso.com”);
            Console.WriteLine(“ containing keyword of SDK that occurred on or after 1/1/2010:");

            lyncConvHistory.QueryImHistory(
                new string[] { "John" },
                new string[] { "SDK" },
                new DateTime(2010, 1, 1)
                );

            foreach (EmailMessage msg in lyncConvHistory._imHistory)
            {
                Console.WriteLine("Conversation with " + msg.DisplayTo + " on " + msg.Subject + " at " + msg.DateTimeCreated.ToString());
                Console.WriteLine(msg.Body.Text);
                Console.WriteLine("");
            }

            Console.WriteLine("Retrieve Lync conversation history:");
            IEnumerable<Item> items = lyncConvHistory.RetrieveImHistory();
            if (items != null)
            {
                foreach (Item item in items)
                {
                    item.Load();
                    Console.WriteLine(item.DisplayTo + ", " + item.Subject + " [" + item.DateTimeCreated + "]");
                }
            }


            Console.WriteLine("Press a key to exit");
            Console.ReadKey();
        }



The previous code example queries the conversations with a specified participant (“user@contoso.com”) and specifies the “SDK” keyword on or after January 1, 2010. It also enumerates all the conversation history items. In Visual Studio, press F5 to build and run the application in the debug mode.

This article explained how Exchange Web Services (EWS) can be used in a unified communications application to retrieve Lync-archived instant messaging conversation history. Specifically, it discussed the following tasks:

  1. Set up a Visual Studio application project and then add reference to the EWS assembly to the project.

  2. Create an ExchangeService object against Exchange Server 2010 SP1 and then connect it to a EWS URL for a given email address.

  3. Enumerate the well-known folders in the user’s email account to obtain the Conversation History folder, which was used by Lync to save instant messaging conversations.

  4. Retrieve all items in the Conversation History folder.

  5. Query saved conversations about certain topics with specified participants on or after a given starting time.

Kurt De Ding is a Senior Programming Writer in the Microsoft Office Content Publishing (UA) group.

Show:
© 2015 Microsoft