InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Working with the MasterCategoryList Via WebDAV

Before Outlook 2007 categories were plain-text. They had a name, but nothing more. Outlook 2007 enhanced this concept by adding colors to categories:


Each category can be given a color (one of 25) and a shortcut (CTRL+F2 through CTRL+F12). Unlike in former Outlook versions, this list of categories is no longer stored in the registry, but in the default calendar of the mailbox. Outlook 2007 creates a hidden message in that folder with the message class IPM.Configuration.CategoryList. The category configuration is stored in the MAPI property 0x7C08 type bin.base64. To query the master category list via WebDAV, two steps are necessary:

  1. Get the URL of the default calendar of a given mailbox (See Getting Well-Known Mailbox Folder URLs on MSDN).
  2. Search the default calendar for items with a message class of IPM.Configuration.CategoryList.
  3. Parse the binary stream

Getting the default calendar

To accomplish the first step, send a PROPFIND request to the root url of the mailbox:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <propfind xmlns="DAV:">
   3:     <prop>
   4:         <a:calendar xmlns:a="urn:schemas:httpmail:" />
   5:     </prop>
   6: </propfind>

The result might look like this:

   1: <?xml version="1.0"?>
   2: <a:multistatus xmlns:d="urn:schemas:httpmail:" xmlns:a="DAV:">
   3:     <a:response>
   4:         <a:href>http://server/exchange/mailbox/</a:href>
   5:         <a:propstat>
   6:             <a:status>HTTP/1.1 200 OK</a:status>
   7:             <a:prop>
   8:                 <d:calendar>http://server/exchange/mailbox/Calendar</d:calendar>
   9:             </a:prop>
  10:         </a:propstat>
  11:     </a:response>
  12: </a:multistatus>

Searching for the hidden message

Given the correct calendar folder path, a SEARCH query can be constructed to retrieve the property in question from the hidden message:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <searchrequest xmlns="DAV:">
   3:     <sql>
   3:         SELECT 
   4:             "", 
   5:             "" 
   6:             FROM SCOPE ('SHALLOW TRAVERSAL OF "http://server/exchange/mailbox/Calendar"') 
   7:             WHERE 
   8:                 ("DAV:isfolder" = false AND 
   9:                 ("" = 'IPM.Configuration.CategoryList')) 
  11:     </sql>
  12: </searchrequest>


If successful, the server returns something similar like this:

   1: <?xml version="1.0"?>
   2: <a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" 
   3:                xmlns:d="" 
   4:                xmlns:e="" 
   5:                xmlns:a="DAV:">
   6:     <a:contentrange>0-0</a:contentrange>
   7:     <a:response>
   8:         <a:href>http://server/exchange/mailbox/Calendar/IPM.Configuration.CategoryList.EML</a:href>
   9:         <a:propstat>
  10:             <a:status>HTTP/1.1 200 OK</a:status>
  11:             <a:prop>
  12:                 <d:x7c080102 b:dt="bin.base64">Base64 content ommited to improve readability</d:x7c080102>
  13:                 <e:permanenturl>http://server/exchange/mailbox/-FlatUrlSpace-/155ae68de4aeda4987585833042471bc-8a5e2/ac5d26d07c067f4ea4f38a18f64786a3-3a1c8a</e:permanenturl>
  14:             </a:prop>
  15:         </a:propstat>
  16:     </a:response>
  17: </a:multistatus>


Decoding the Property value

The content can now be extracted by loading the result into a System.Xml.XmlDocument or System.Xml.Linq.XDocument and select the appropiate node using an XPath expression. To decode the value, use the System.Convert.FromBase64String and the System.Text.Encoding.UTF8.GetString method. This will yield the XML representation of the Master Category List. Since manipulation of raw XML data is not exactly fun, I've created a sample solution which uses the System.Xml.Serialization.XmlSerializer to construct a strong-typed object from the stream. Below is a class diagram of the available classes:


The MasterCategoryList has a static Load method which takes a System.IO.TextReader instance and reads the the value decoded earlier:

   1: using (var reader = new StringReader(masterCategoryListContent))
   2: {
   3:     var masterCategoryList = MasterCategoryList.Load(reader);
   4:     foreach (var category in masterCategoryList.Categories)
   5:     {
   6:         Console.Out.WriteLine("{0}: Color {1}, Shortcut {2}", category.Name, category.Color, category.KeyboardShortcut);
   7:     }
   8: }

This code assumes that the string representation of the Master Category List is stored in the masterCategoryListContent field.

Once done with manipulating the instance, it can be saved to a System.IO.TextWriter. The Master Category List in the mailbox can then be updated using a PROPPATCH request on the address used above.

Download: Source files


Fellow MVP Glen Scales has an article on his blog about this topic as well: Adding Categories to the Master categories list in Outlook 2007 with a CDO 1.2 script

Posted by Henning Krause on Thursday, May 22, 2008 2:36 PM, last modified on Thursday, May 22, 2008 2:39 PM
Permalink | Post RSSRSS comment feed

Comments (3) -

On 8/12/2008 3:49:27 AM Joon Nan Malaysia wrote:

Joon Nan

Krause, do you have sample codes how to use PROPPATCH to update the master category list?

On 8/8/2014 5:37:14 PM Jakub Kaleta wrote:

Jakub Kaleta

This guy has it figured out (almost)


@&amp;amp;quot;&amp;amp;lt;?xml version=&amp;amp;quot;&amp;amp;quot;1.0&amp;amp;quot;&amp;amp;quot;?&amp;amp;gt;
       &amp;amp;lt;d:propertyupdate xmlns:d=&amp;amp;quot;&amp;amp;quot;DAV:&amp;amp;quot;&amp;amp;quot;
          &amp;amp;lt;o:x7c080102 b:dt=&amp;amp;quot;&amp;amp;quot;bin.base64&amp;amp;quot;&amp;amp;quot;&amp;amp;gt;{0}&amp;amp;lt;/o:x7c080102&amp;amp;gt;

On 12/13/2011 1:04:32 PM Mesut wrote:


Hi THere;

Thanks for this informative article. I have tested your code, it works with my own credentials. However, if I want to access another mailbox&amp;amp;amp;amp;amp;#39;s categories, it throws an error which is
&amp;amp;amp;amp;amp;quot;The specified object was not found in the store&amp;amp;amp;amp;amp;quot; do you have any idea why it is so?

Many thanks


 +Pingbacks and trackbacks (2)