January 2007 - Posts

Hanselminutes

I've been subscribed to Scott Hanselman's blog for a long time. I love all the tools, tips, etc he posts. Up until this point I have not really gotten into podcasts however due to Scott's awesome blog I figured I would give his podcast, Hanselminutes, a try.

I've listened to about 10 of the shows now and I must say I really enjoy it. It gives me something to listen to during my daily bus trip. If your are into .Net or Technology in general you might want to give Hanselminutes a try, I don't think you will be disappointed. Scott's goal is "Avoid wasting the listener's time." IMHO he is succeeding in that goal. Actually if anything he leaves me wanting more information about certain topics.

 

Thanks Scott and Carl keep up the good work!

 

The only thing I need to figure out is how best to sync up the podcast onto my Sandisk Sansa. Currently I'm just manually putting the shows on. Anyone have any recommendations for tools to do this?

Posted by puzzlehacker | 1 comment(s)
Filed under: , ,

How do I access my Outlook contacts from my web application?

Question

How do I access my Outlook contacts from my web application?

Short Answer

You don't, well at least you shouldn't (It is theoretically possible to access Outlook data from a web application, assuming Outlook is installed and the profile you wish to access data from is setup on the web server).

Long Answer

To address this question we need to realize that Outlook is a client application which is only a front end and local cache for a mail server (typically exchange server). Most web applications are server applications and thus run on servers which usually don't have client applications, such as Outlook, installed on them. Perhaps a better question would be "How do I access my Exchange contacts from my web application?". 

With that in mind lets explore a couple possible solutions for accessing data (not really Outlook data) from the Exchange server.

  • WebDAV - Protocol that Outlook Web Access (OWA) uses to access your Exchange store
  • Exchange Web Services (EWS) - New services added to Exchange 2007 to allow access to most data
  • Active Directory Services Interface (ADSI) - For accessing general Exchange user data information (i.e. access the Global Address List)

These are just three common technologies for accessing data on a Exchange server, for more details see the Exchange Server Development Center or Overview of Programming Features for Exchange Server. To provide a more complete example I've included some code samples and links for each of the three technologies. The code examples demonstrate how to do find contacts using each of the three methods. Keep in mind these samples can be extended to other data types such as mail and appointments, with the exception of ADSI. All the code samples are written in C# and write the data to the console, they can easily be converted to be used by an ASP.Net web application. Also remember to substitute <ExchangeServer>, <username>, <password>, and <domain> with the appropriate values for your Exchange server.

WebDAV

The sample retrieves all contacts from an exchange store which have a first name beginning with 'wes'. Keep in mind before making any Exchange WebDAV requests you must make an forms authorization request first (assuming the Exchange server has forms authorization enabled, which seems to generally be the case). You need to make the authorization request to get the set of authorization cookies to use with any subsequent WebDAV requests.

using System;

using System.IO;

using System.Net;

using System.Text;

using System.Xml;

 

// When Exchange is setup for Forms Authentication you need to do the login separately

public static CookieCollection GetAuthCookies(string server, NetworkCredential credentials)

{

    // URI to OWA authorization dll

    string authURI = string.Format("{0}/exchweb/bin/auth/owaauth.dll",

        server, credentials.UserName);

 

    // Get byte stream of the post request

    byte[] bytes = Encoding.UTF8.GetBytes(string.Format(

        "destination={0}/exchange/{1}&username={2}\\{1}&password={3}",

        server, credentials.UserName, credentials.Domain, credentials.Password));

 

    HttpWebRequest request = WebRequest.Create(authURI) as HttpWebRequest;

    request.Method = "POST";

    request.ContentType = "application/x-www-form-urlencode";

    request.CookieContainer = new CookieContainer();

    request.ContentLength = bytes.Length;

    request.AllowAutoRedirect = false;

 

    using (Stream requestStream = request.GetRequestStream())

        requestStream.Write(bytes, 0, bytes.Length);

 

    // Get response cookies - keep in mind this may throw exceptions

    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)

        return response.Cookies;

}

public static void PrintContactsUsingExchangeWebDAV()

{

    string server = "http<s>://<ExchangeServer>";

    NetworkCredential credentials = new NetworkCredential("<username>", "<password>", "<domain>");

    string uri = string.Format("{0}/exchange/{1}", server, credentials.UserName);

 

    // Create a byte stream of the SQL query to run against the server

    // This query searches for contacts with the givenName the begins with 'wes'

    // Link to Exchange store property names

    byte[] contents = Encoding.UTF8.GetBytes(string.Format(

       @"<?xml version=""1.0""?>

        <g:searchrequest xmlns:g=""DAV:"">

            <g:sql>

                SELECT

                    ""urn:schemas:contacts:sn"", ""urn:schemas:contacts:givenName"",

                    ""urn:schemas:contacts:email1"", ""urn:schemas:contacts:telephoneNumber""

                FROM

                    Scope('SHALLOW TRAVERSAL OF ""{0}/exchange/{1}/contacts""')

                WHERE

                    ""urn:schemas:contacts:givenName"" LIKE 'wes%'

            </g:sql>

        </g:searchrequest>",

      server, credentials.UserName));

 

    HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;

    request.Credentials = credentials;

    request.Method = "SEARCH";

    request.ContentLength = contents.Length;

    request.ContentType = "text/xml";

    request.CookieContainer = new CookieContainer();

    // Keep in mind you may actually want to cache these cookies for other requests

    request.CookieContainer.Add(GetAuthCookies(server, credentials));

 

    using (Stream requestStream = request.GetRequestStream())

        requestStream.Write(contents, 0, contents.Length);

 

    using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)

    using (Stream responseStream = response.GetResponseStream())

    {

        // Process the response as an XML document

        XmlDocument document = new XmlDocument();

        document.Load(responseStream);

 

        foreach (XmlElement element in document.GetElementsByTagName("a:prop"))

        {

            // Do work with data returned for each contact

            Console.WriteLine("Name:  {0} {1}\nEmail: {2}\nPhone: {3}",

                (element["d:givenName"] != null ? element["d:givenName"].InnerText : ""),

                (element["d:sn"] != null ? element["d:sn"].InnerText : ""),

                (element["d:email1"] != null ? element["d:email1"].InnerText : ""),

                (element["d:telephoneNumber"] != null ? element["d:telephoneNumber"].InnerText : ""));

        }

    }

}

Helpful WebDAV Links:

Infinitec Exchange posts - C# and JavaScript forms authentication examples
Marc Charbonneau - Using .NET and WebDAV to access an Exchange server
Hitchhiker guide to enterprise development - Programmatically accessing to OWA through Web Dav
Exchange store property names

Exchange Web Services

This sample code goes through the process of creating a Exchange web service FindItem request. In essentially searches through the contact folder looking for any contact with first name starting with 'wes'.

using System;

using System.Net;

// Generate Exchange Web Services proxy classes by adding a VS web

// reference to http<s>://<ExchangeServer>/EWS/Services.wsdl

// then add a using reference to your proxy classes

 

public static void PrintContactsUsingExchangeWebServices()

{

    ExchangeServiceBinding esb = new ExchangeServiceBinding();

    esb.Credentials = new NetworkCredential("<username>", "<password>", "<domain>");

    esb.Url = @"http<s>://<ExchangServer>/EWS/Exchange.asmx";

 

    // Tell it you want all the item properties

    ItemResponseShapeType itemProperties = new ItemResponseShapeType();

    itemProperties.BaseShape = DefaultShapeNamesType.AllProperties;

 

    // Tell it you only want to look in the contacts folder

    DistinguishedFolderIdType[] folderIDArray = new DistinguishedFolderIdType[1];

    folderIDArray[0] = new DistinguishedFolderIdType();

    folderIDArray[0].Id = DistinguishedFolderIdNameType.contacts;

 

    PathToUnindexedFieldType field = new PathToUnindexedFieldType();

    field.FieldURI = UnindexedFieldURIType.contactsGivenName;

 

    ConstantValueType fieldValue = new ConstantValueType();

    fieldValue.Value = "wes";

 

    // Look for contacts which have a given name that begins with 'wes'

    ContainsExpressionType expr = new ContainsExpressionType();

    expr.ContainmentModeSpecified = true;

    expr.ContainmentMode = ContainmentModeType.Prefixed;

    expr.ContainmentComparisonSpecified = true;

    expr.ContainmentComparison = ContainmentComparisonType.IgnoreCase;

    expr.Constant = fieldValue;

    expr.Item = field;

 

    RestrictionType restriction = new RestrictionType();

    restriction.Item = expr;

 

    // Form the FindItem request

    FindItemType findItemRequest = new FindItemType();

    findItemRequest.Traversal = ItemQueryTraversalType.Shallow;

    findItemRequest.ItemShape = itemProperties;

    findItemRequest.ParentFolderIds = folderIDArray;

    findItemRequest.Restriction = restriction;

 

    // Send the request and get the response

    FindItemResponseType findItemResponse = esb.FindItem(findItemRequest);

 

    if (findItemResponse.ResponseMessages.Items.Length > 0)

    {

        FindItemResponseMessageType responseMessage =

            findItemResponse.ResponseMessages.Items[0] as FindItemResponseMessageType;

        ArrayOfRealItemsType realItems =

            responseMessage.RootFolder.Item as ArrayOfRealItemsType;

 

        foreach (ContactItemType contact in realItems.Items)

        {

            // Do work with data returned for each contact

            Console.WriteLine("Name:  {0} {1}\nEmail: {2}\nPhone: {3}",

                contact.GivenName, contact.Surname,

                (contact.EmailAddresses != null && contact.EmailAddresses.Length > 0 ?

                    contact.EmailAddresses[0].Value : ""),

                (contact.PhoneNumbers != null && contact.PhoneNumbers.Length > 0 ?

                    contact.PhoneNumbers[0].Value : ""));

        }

    }

}

Useful Exchange Web Service Links:

MSDN Exchange Web Services Reference
Stephen GriffinExchange Web Services and MAPI Props
Eric LeeExchange Server 2007 Web Service API for Developers

Active Directory Services Interface

This is a very simplified .Net sample of doing a search in the default active directory for any users that have a first name beginning with 'wes'. Keep in mind that this uses the default active directory on the machine you are running on, see DirectorySearcher.SearchRoot for more details. If you need to specify a particular machine/directory to start your search from then you need to create a DirectoryEntry and pass that to the DirectorySearcher.

using System;

using System.DirectoryServices;

 

// Get user information from your default active directory repository

public static void PrintUsersFromADSI()

{

    // Find any user that has a name that begins with 'wes'

    string filter = "(&(objectCategory=person)(objectClass=user)(givenName=wes*))";

    DirectorySearcher search = new DirectorySearcher(filter);

    foreach(SearchResult result in search.FindAll())

    {

        // Do work with data returned for each address entry

        DirectoryEntry entry = result.GetDirectoryEntry();

        Console.WriteLine("Name:  {0} {1}\nEmail: {2}\nPhone: {3}",

            entry.Properties["givenName"].Value,

            entry.Properties["sn"].Value,

            entry.Properties["mail"].Value,

            entry.Properties["telephonenumber"].Value);

    }

}

Useful ADSI/LDAP Links:

 

InfiniTec - How to get the Global Address List programmatically
Amit Zinman - Creating a list of Users and their e-mail addresses
Steve Schofield - .NET sample LDAP query looking for a specific user name of (smith) and System.DirectoryServices namespace
Mow - PowerShell and Active Directory Part 1, Part 2, Part 3, Part 4, Part 5

 

Disclaimer: The sample code described here is provided on an “as is” basis, without warranty of any kind, to the fullest extent permitted by law. It is for demonstration purposes only and is by no means production quality.

Call for post topics

As some of you may (or may not) know I'm a developer working at Microsoft on the Outlook team. My primary focus is Outlook Programmability (i.e. Outlook COM Object Model). Now that Office 2007 is complete I would like to post some code samples of how to use some of the new Outlook Object Model API's. Two new API's I plan on discussing are the PropertyAccessor and the Table. However, if there are any general questions about Outlook programming that you would like me to discuss or post code samples for please let me know. I've already had a request for sample Powershell scripts that automate Outlook, so I will try to provide a couple examples of that.

Most of my posts are generally technical code samples or tips and tricks. Usually they are solutions to a problem I was faced with and had to spend some time researching to find or develop. I post them to help reduce research time for others (and myself 2 years from now it is a great way to remember things) who are faced with a similar problem.

That said I'm now looking for post ideas that will help give more back to the community that provided me with some many solutions over the years. So if you have a technical question that can best be addressed by a small code sample I'm your man. Off the top of my head some areas I consider myself proficient in are: Outlook Programmability, VBA, Regular Expressions, C# and .NET development.

I'm currently exploring Windows Vista and particularly Vista media center (got my first media center PC setup a couple months ago and just upgraded to Vista), so don't be surprised to see some posts in that area over the coming months.

So if you have a requests please leave a comment and I will see what I can come up with.

Posted by puzzlehacker | 6 comment(s)
Filed under: ,
More Posts