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:
- Get the URL of the default calendar of a given mailbox (See Getting Well-Known Mailbox Folder URLs on MSDN).
- Search the default calendar for items with a message class of IPM.Configuration.CategoryList.
- 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: "http://schemas.microsoft.com/mapi/proptag/x7c080102",
5: "http://schemas.microsoft.com/exchange/permanenturl"
6: FROM SCOPE ('SHALLOW TRAVERSAL OF "http://server/exchange/mailbox/Calendar"')
7: WHERE
8: ("DAV:isfolder" = false AND
9: ("http://schemas.microsoft.com/mapi/proptag/x001a001f" = 'IPM.Configuration.CategoryList'))
11: </sql>
12: </searchrequest>
adssdfdsf
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="http://schemas.microsoft.com/mapi/proptag/"
4: xmlns:e="http://schemas.microsoft.com/exchange/"
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>
sdfssdfsdfdsf
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.
Links
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