InfiniTec - Henning Krauses Blog

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

Controlling the "Save in sent items" behavior for email sent via WebDAV

If you send an email with WebDAV using the Exchange Mail Submission URI (see here for an example), the mail is placed in the Sent Items folder after the mail was sent. To prevent this, you can add a SAVEINSENT header to the MOVE request that is used to move the message from the drafts folder to the mail submission url. If you set value of the header to F, the mail will not be placed in the Sent items folder.

There is another header which controls the behavior: The SAVEDESTINATION header. You can set this header to the address of another folder in the same mailbox. The mail will be saved in that folder instead of the default Sent Items folder.


Technorati:

Posted by Henning Krause on Wednesday, August 29, 2007 12:00 AM, last modified on Wednesday, August 29, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Creating hidden items with WebDAV

If you want to create a hidden item in an Exchange folder via WebDAV, you may be tempted to set the DAV:ishidden property to true. But in order to hide the item from Outlook, you must additionally set the property PR_Associated property to true. The PR_Associated property is available via http://schemas.microsoft.com/mapi/proptag/x67aa000b.

Note that you must specify both properties during the creation of an element. Toggling the visibility of an item after it has been created is not possible.


Technorati:

Posted by Henning Krause on Tuesday, August 21, 2007 12:00 AM, last modified on Tuesday, August 21, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Exchange 2007 store event sinks registration woes

In this article, I wrote about how to write managed event sinks for Exchange 2003 and how to register them with the regsvcs tool on the Exchange server. Normally this goes like this:

    1 c:\Windows\Microsoft.NET\Framework\v2.0.50727\RegSvcs.exe -fc ManagedEventSink.dll

This command will register the necessary COM types and create a new COM+ application on the server. If you try this on a 64-bit Exchange 2007, you'll encounter an error like this:

Microsoft (R) .NET Framework Services Installation Utility Version 2.0.50727.42
Copyright (c) Microsoft Corporation.  All rights reserved.

Type library exporter warning: Referenced type is defined in managed component, which is imported from a type library that could not be loaded because it was not registered (type: 'Microsoft.Interop.Exoledb.IExStoreSyncEvents'; component: 'C:\EventSink\Microsoft.Interop.Exoledb.dll').
Type library exporter warning: Referenced type is defined in managed component, which is imported from a type library that could not be loaded because it was not registered (type: 'Microsoft.Interop.Exoledb.ICreateRegistration'; component: 'C:\EventSink\Microsoft.Interop.Exoledb.dll').
Installed Assembly:
        Assembly: C:\EventSink\ManagedEventSink.dll
        Application: Managed EventSink Example
        TypeLib: C:\EventSink\ManagedEventSink.tlb

This error occurs because Exchange 2007 is a 64bit application, and so is the ExOleDb provider which is used for store event sinks. To create the COM+ application, you should call the RegSvcs tool from the 64bit framework directory, which is located under C:\Windows\Microsoft.NET\Framework64\v2.0.50727.

Once you have created the COM+ application, you can proceed normally by configuring it (as described here and here) and register the eventsink in the store.

Storewide event sinks

To register a store-wide event sink, the GUID of the mailbox store is needed for the registration. In Exchange 2000/2003, this info was most easily obtained from Active Directory Users and Computers:

  1. Open AD User & Computers
  2. Enable the “Advanced Features” item In the “View" Menu
  3. Navigate to the hidden folder “Microsoft Exchange System Objects”
  4. Each mailbox store has a SystemMailbox object associated with it in this folder. Select the mailbox from the store you want to install the sink on. The name of the System Mailbox contains the relevant GUID for the registration.
  5. Copy the GUID from the name of the mailbox to the clipboard.

Although Exchange 2007 lacks the AD Users & Computers integration, this still works; albeit the fancy icons are gone, as shown in this example:

image

But with Exchange 2007, there is another option to quickly get the GUID: PowerShell. Just open the Exchange Management Shell and enter this command:

   1: Get-MailboxDatabase | Select-Object -Property StorageGroupName,Guid

This will return a list of all mailbox stores and their corresponding GUIDs.

The event can now be registered on the store of choice:

   1: regevent.cmd add "onsyncsave;onsyncdelete" EventSink.ProgId file://./backofficestorage/ADMIN/%primarysmtpdomain%/MBX/SystemMailbox%mailboxguid%/StoreEvents/GlobalEvents/EventSinkName.evt -m ANY -f "WHERE $DAV:ishidden$ = false AND $DAV:isfolder$ = false"

Replace the EventSink.ProgId with the ProgId of your sink, the %primarysmtpdomain% with your primary smtp domain and the %mailboxguid% with the GUID of the store you want to register the sink on.


Posted by Henning Krause on Monday, August 20, 2007 12:00 AM, last modified on Wednesday, June 11, 2008 7:42 AM
Permalink | Post RSSRSS comment feed

Creating WebDAV PROPFIND/PROPPATCH requests using XmlReader and XmlWriter, Part 1

Every now and then I see questions popping up in the newsgroup where the author posts source code where they create WebDAV requests by string concatenation or using a StringBuilder. While this may work with simple PROPFIND requests, this becomes more a hassle when creating PROPPATCH requests. Apart from that, it is prone to error. But while .NET has some nice XML handling, this can become a problem with Exchange because some properties have local names which start with a zero, like the property for the start date of a task: http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8104.

If you try to use that local name 0x8104 with one of Xml classes you'll get an error stating that this is not a valid local name. Consequently, parsing a WebDAV response containing one of these properties into a XmlDocument or XmlReader will fail with the same error. So a little extra work is needed here. You'll see that working with Exchange gets much easier once you have some helper methods. To create a PROPFIND request, you can use these methods:

    1 publicstaticbyte[] CreatePropFindRequest(paramsstring[] properties)

    2 {

    3     XmlWriterSettings settings;

    4 

    5     settings = newXmlWriterSettings();

    6     settings.Encoding = Encoding.UTF8;

    7 

    8     using (MemoryStream stream = newMemoryStream())

    9     using (XmlWriter writer = XmlWriter.Create(stream, settings))

   10     {

   11         writer.WriteStartElement("propfind", "DAV:");

   12 

   13         writer.WriteStartElement("prop", "DAV:");

   14         foreach (string p in properties)

   15         {

   16             string ns;

   17             string localName;

   18 

   19             ParsePropertyName(p, out ns, out localName);

   20 

   21             writer.WriteElementString("a", XmlConvert.EncodeLocalName(localName), ns, null);

   22         }

   23         writer.WriteEndElement();

   24 

   25         writer.WriteEndElement();

   26         writer.WriteEndDocument();

   27 

   28         writer.Flush();

   29         return stream.ToArray();

   30     }

   31 }

   32 

   33 privatestaticvoid ParsePropertyName(string property, outstring ns, outstring localName)

   34 {

   35     int index;

   36 

   37     if (string.IsNullOrEmpty(property)) thrownewArgumentNullException("property");

   38 

   39     // Custom Outlook properties don't have any namespaces,

   40     // so LastIndexOf maybe -1

   41     index = Math.Max(property.LastIndexOfAny(newchar[] { '/', ':', '#' }) + 1, 0);

   42 

   43     ns = property.Substring(0, index);

   44     localName = property.Substring(index);

   45 }

The handling of those special properties like the one I mentioned above is located in line 21. The XmlConvert.EncodeLocalName() method is used here: The local name 0x8104 is converted to _x0030_x8104, which is now a perfectly valid name.

The other method used here is the ParsePropertyName method. It takes a full qualified property name like DAV:href and splits it into its segments: "DAV:" and "href".

Unfortunately, the response returned by Exchange contains the properties unencoded. Before we can load the response into a XmlDocument or XmlReader, we'll have to encode those property names. A regular expression is suitable for this job:

    1 privatestaticreadonlyRegex _TagNameRegexFinder = newRegex(@"(?<=\</?\w+\:)\w+", RegexOptions.Compiled);

The method to encode local names in the response is this:

    1 staticXmlReader ParsePropfindResponse(string response)

    2 {

    3     if(string.IsNullOrEmpty(response)) thrownewArgumentNullException("response");

    4 

    5     response = _TagNameRegexFinder.Replace(response, delegate(Match match) { returnXmlConvert.EncodeLocalName(match.Value); });

    6 

    7     returnXmlReader.Create(newStringReader(response));

    8 }

The local names of all tags in the response are encoded using the XmlConvert.EncodeLocalName() method.

Now that we have all helper methods in place we can actually perform a request:

    1 privatestaticvoid PropFind()

    2 {

    3     byte[] buffer;

    4     XmlReader reader;

    5     HttpWebRequest request;

    6 

    7     buffer = CreatePropFindRequest("http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8104");

    8 

    9     request = (HttpWebRequest)WebRequest.Create("http://w2k3srv.contoso.local/exchange/administrator/tasks/test.eml");

   10     request.Method = "PROPFIND";

   11     request.ContentType = "text/xml";

   12     request.Credentials = CredentialCache.DefaultCredentials;

   13     request.Headers.Add("Translate", "f");

   14     request.Headers.Add("Depth", "0");

   15     request.SendChunked = true;

   16 

   17     using (Stream stream = request.GetRequestStream())

   18     {

   19         stream.Write(buffer, 0, buffer.Length);

   20     }

   21 

   22     using (WebResponse response = request.GetResponse())

   23     {

   24         string content = newStreamReader(response.GetResponseStream()).ReadToEnd();

   25         reader = ParsePropfindResponse(content);

   26         XmlNamespaceManager nsmgr = newXmlNamespaceManager(reader.NameTable);

   27 

   28         nsmgr.AddNamespace("dav", "DAV:");

   29         XPathDocument doc = newXPathDocument(reader);

   30 

   31         foreach (XPathNavigator element in doc.CreateNavigator().Select("//dav:propstat[dav:status = 'HTTP/1.1 200 OK']/dav:prop/*", nsmgr))

   32         {

   33             Console.WriteLine("{0}{1} ({2}): {3}", element.NamespaceURI, XmlConvert.DecodeName(element.LocalName), element.GetAttribute("dt", DataTypeNamespace), element.Value);

   34         }

   35     }

   36 }

The real interesting parts here are in the lines 7, 25 and 33. In line 7, a buffer created using the CreatePropFindRequest method. Since the parameter is a params array, there is no need to create an array manually. The property names can just be passed as a comma separated list. In line 25, the request from the server is parsed into an XmlReader. In line 31, all properties, which were successfully returned are selected in the reader, and line 33  shows how the various parts of each property can be accessed:

  • The namespace of the property: element.NamespaceURI
  • The local name of the property: XmlConvert.DecodeName(element.LocalName)
  • The data type of the property: element.GetAttribute("dt", DataTypeNamespace)
  • The value of the property: element.Value

Of course, the value is only accessible if it's not a multi valued property. To check this, you can examine the data type of the property: If it starts with the letters "mv", it's a multi valued property and you must parse it accordingly.

The next article will show how to construct PROPPATCH requests using this schema.


Technorati:

Posted by Henning Krause on Monday, July 30, 2007 12:00 AM, last modified on Monday, July 30, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Exchange 2007 Attachment identifiers demystified

In this article I demonstrated how to construct OWA urls from a WebDAV item. If you look at the address of an attachment in a mail of an Exchange 2007 item, you'll notice that it shares a common part. Here is the address of an item in my lab Exchange:

    1 https://w2k3x64/owa/?ae=Item&a=Preview&t=IPM.Note&id=RgAAAADJ6AAdAckcS7lYLU8HPEIRBwBgW74qsZ7RTJzeGdXyv4QaAVQHuk8vAABgW74qsZ7RTJzeGdXyv4QaAVQHulWAAAAJ

The first attachment of this mail has the following address:

    1 https://w2k3x64/owa/attachment.ashx?attach=1&id=RgAAAADJ6AAdAckcS7lYLU8HPEIRBwBgW74qsZ7RTJzeGdXyv4QaAVQHuk8vAABgW74qsZ7RTJzeGdXyv4QaAVQHulWAAAAJ&attid0=EAA2S%2fWIlTJ7S6U0djIHXKRH&attcnt=1

You'll notice that the id parameter is the same for both, the mail and the attachment. As outlined in the above article, this is a modified form of the PR_ENTRYID property. The identifier fro the attachment is specified in the attid0 parameter. This parameter is the url encoded and base64 encoded value of the PR_RECORD_KEY (http://schemas.microsoft.com/mapi/proptag/x0ff90102) property.

Now, if you issue a X-MS-ENUMATTS request on the address of the item, you won't get this property. The trick is to issue a PROPFIND on the address of the attachment which you got from the X-MS-ENUMATTS request. But you should not request all properties from the request, because one of them is the http://schemas.microsoft.com/mapi/proptag/x37010102 property which contains the entire attachment as base64 encoded string. Instead, just request the PR_RECORDKEY. If the item has more than one attachment, you can use the BPROPFIND to get the record keys of all items at once.

Once you  have the record key as base64 string you'll have to perform these steps to derive the attId0 parameter:

  1. Decode the base64 string to a byte array
  2. Create a new byte array with a length of the decoded record key plus 2
  3. Write the length of the byte array to the first two bytes of the new byte array.
  4. Copy the byte array containing the record to the new array starting at offset 2
  5. Perform a base64 encoding on the newly created byte array
  6. Perform a urlencode on that string

The result is a string that you can use for the attId0 parameter. Both, the attach and the attcnt property seem to be always 1.


Technorati:

Posted by Henning Krause on Thursday, July 26, 2007 12:00 AM, last modified on Friday, April 22, 2011 6:48 PM
Permalink | Post RSSRSS comment feed

Where do I come from? - Specifying the sender or author of an Exchange Email with WebDAV

Sometimes you might want to put an email directly in a users mailboox without sending it. Typically you want to set a specific sender on that mail. If you tried this, you'll most probably set the urn:schemas:httpmail:from or urn:schemas:httpmail:sender property to the desired sender name - and got a 403-Forbidden answer from Exchange. Luckily, there are six MAPI properties corresponding to the sender and from address:

  • SenderEmailAddressType (http://schemas.microsoft.com/mapi/proptag/xc1e001f)
  • SenderEmailAddress (http://schemas.microsoft.com/mapi/proptag/xc1f001f)
  • SenderEmailAddressDisplayName (http://schemas.microsoft.com/mapi/proptag/xc1a001f)
  • SentRepresentingEmailAddressType (http://schemas.microsoft.com/mapi/proptag/x64001f)
  • SentRepresentingEmailAddress (http://schemas.microsoft.com/mapi/proptag/x65001f)
  • SentRepresentingEmailAddressDisplayName (http://schemas.microsoft.com/mapi/proptag/x42001f)

The first three properties define the person who actually sent the mail whereas the last three properties define the author or the person on which behalf the mail was sent. If both property sets are set to equal values, Outlook will display just the Name. If they are different, Outlook displays something like "Alice on behalf of Bob". The values you are specifying differ on the type of address you have. This can either be a SMTP address or a Legacy Exchange Distinguished Name. If the email address of Alice is alice@example.com, put these values in the sender properties:

  • SenderEmailAddressType: SMTP
  • SenderEmailAddress: alice@example.com
  • SenderEmailAddressDisplayName: Alice

Outlook will then display the address like this:


Email address properties for an SMTP address (click to enlarge)

If the sender you want to insert is a user on your corporate network you might want to get the Legacy Exchange Distinguished Name from Active Directory. In my lab Exchange, Alice has an Legacy Exchange Distinguished Name of "/o=Contoso Ltd/ou=First Administrative Group/cn=Recipients/cn=alice". This yields the following values for the three properties:

  • SenderEmailAddressType: EX
  • SenderEmailAddress: /o=Contoso Ltd/ou=First Administrative Group/cn=Recipients/cn=alice
  • SenderEmailAddressDisplayName: Alice

Outlook will display this address this way:


Outlook dialog for the details of an Exchange address (click to enlarge)

One additional property you might want to set in this scenario is the MessageFlags (http://schemas.microsoft.com/mapi/proptag/xe070003) property. If you omit this, Outlook will tell you that the message has not been sent. To avoid that message, set the MessageFlags property to 4.


Technorati:

Posted by Henning Krause on Sunday, July 22, 2007 12:00 AM, last modified on Sunday, July 22, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

More on meetings and meeting requests

Updates

  • 2007-12-13 21:20 GMT: Updated the article to include information about Outlook Web Access
  • 2008-01-20 12:00 GMT: Important Bugfix.

In this article, I wrote about the global object Id Outlook uses to correlate meetings and meeting requests. I also wrote that this value is set by Exchange on one of two properties: The global object id (http://schemas.microsoft.com/mapi/id/{6ED8DA90-450B-101B-98DA-00AA003F1305}/0x3). It does, however, not set the other object id, which is used by Outlook in cached mode: http://schemas.microsoft.com/mapi/id/{6ED8DA90-450B-101B-98DA-00AA003F1305}/0x23.

Because of this, I gave the advise to save the meeting to the calendar folder and then reload the meeting along with the autogenerated global object id. Finally, set the second id to the value of the first and commit the changed to the server.

It turns out that this method is not only inefficient but also unnecessary.

You can just set both id values to an arbitrary value. For compatibility reasons, this should be of type binary. Because this id should be unique across all mailboxes on a server, a global unique identifier (GUID) should be used. You can use this snippet to create a base64 encoded string which can be used as a global object id:

    1 Guid id;

    2 string value;

    3 

    4 id = Guid.NewGuid();

    5 value = Convert.ToBase64String(id.ToByteArray());

This solution works fine for all MAPI clients. But it won't work with Outlook Web Access, because OWA uses another property to identify meetings: urn:schemas:calendar:uid. You can set this property to the same value as the global object id - just save it with the datatype string. Here is an example for a meeting created with my library. I have cleaned up the namespaces to improve readability.

[UPDATE 2008-01-20 12:00 GMT]

You have to set the property 0x8218 in the appointment propertyset (http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-C000-000000000046}/0x8218) to 1. This is the ResponseType property and a value of 1 means Meeting Organizer. If you don't set this property, Outlook will remove this appointment after you get a meeting response from one of the attendees.

    1 PROPPATCH /exchange/administrator/Calendar/08986b34-384a-4e39-88b3-2b188fe2f349.eml HTTP/1.1

    2 Depth: 0

    3 Content-Type: text/xml

    4 Accept-Encoding: gzip, deflate

    5 Authorization: Negotiate YIIKlgYGKwYBBQUCoIIKijCCCoagJDAiBgkqhkiC9

    6 Host: w2k3srv.contoso.local

    7 Content-Length: 2822

    8 Expect: 100-continue

    9 

   10 <?xmlversion="1.0"encoding="UTF-8"?>

   11 <propertyupdate

   12     xmlns:dt="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"

   13     xmlns="DAV:"

   14     xmlns:common="http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-c000-000000000046}/"

   15     xmlns:proptag="http://schemas.microsoft.com/mapi/proptag/"

   16     xmlns:cal="http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-c000-000000000046}/"

   17     xmlns:meeting="http://schemas.microsoft.com/mapi/id/{6ed8da90-450b-101b-98da-00aa003f1305}/">

   18     <set>

   19         <prop>

   20             <proptag:x001a001fdt:dt="string">IPM.Appointment</proptag:x001a001f>

   21             <contentclassdt:dt="string">urn:content-classes:appointment</contentclass>

   22             <proptag:x0037001fdt:dt="string">test5</proptag:x0037001f>

   23             <cal:_x0030_x820ddt:dt="dateTime.tz" >2007-12-13T23:57:48Z</cal:_x0030_x820d>

   24             <proptag:x00600040dt:dt="dateTime.tz">2007-12-13T23:57:48Z</proptag:x00600040>

   25             <cal:_x0030_x8516dt:dt="dateTime.tz">2007-12-13T23:57:48Z</cal:_x0030_x8516>

   26             <cal:_x0030_x8215dt:dt="boolean" >0</cal:_x0030_x8215>

   27             <common:_x0030_x8502dt:dt="dateTime.tz">2007-12-13T23:57:48Z</common:_x0030_x8502>

   28             <common:_x0030_x8503dt:dt="boolean">1</common:_x0030_x8503>

   29             <cal:_x0030_x820edt:dt="dateTime.tz" >2007-12-14T01:57:48Z</cal:_x0030_x820e>

   30             <proptag:x00610040dt:dt="dateTime.tz">2007-12-14T01:57:48Z</proptag:x00610040>

   31             <cal:_x0030_x8517dt:dt="dateTime.tz">2007-12-14T01:57:48Z</cal:_x0030_x8517>

   32             <cal:_x0030_x8205dt:dt="int" >2</cal:_x0030_x8205>

   33             <cal:_x0030_x8223dt:dt="boolean" >0</cal:_x0030_x8223>

   34             <cal:_x0030_x8217dt:dt="int" >1</cal:_x0030_x8217>

   35             <cal:_x0030_x8218dt:dt="int" >1</cal:_x0030_x8218>

   36             <meeting:_x0030_x23dt:dt="bin.base64">HKSFpjzfdEK75L/tHWI4rQ==</meeting:_x0030_x23>

   37             <meeting:_x0030_x3dt:dt="bin.base64">HKSFpjzfdEK75L/tHWI4rQ==</meeting:_x0030_x3>

   38             <uiddt:dt="string"xmlns="urn:schemas:calendar:">a685a41c-df3c-4274-bbe4-bfed1d6238ad</uid>

   39             <todt:dt="string"xmlns="urn:schemas:mailheader:">"Alice" &lt;alice@contoso.local&gt;</to>

   40         </prop>

   41     </set>

   42 </propertyupdate>

The corresponding meeting request looks like this:

    1 PROPPATCH /exchange/administrator/Drafts/18e81af8-cfe9-4abb-86bf-814b277cb0bf.eml HTTP/1.1

    2 Depth: 0

    3 Content-Type: text/xml

    4 Accept-Encoding: gzip, deflate

    5 Authorization: Negotiate YIIKlgYGKwYBBQUCoIIKijCCCoagJDAi

    6 Host: w2k3srv.contoso.local

    7 Content-Length: 2766

    8 Expect: 100-continue

    9 

   10 <?xmlversion="1.0"encoding="UTF-8"?>

   11 <propertyupdatexmlns:dt="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/"

   12                 xmlns:proptag="http://schemas.microsoft.com/mapi/proptag/"

   13                 xmlns:meeting="http://schemas.microsoft.com/mapi/id/{6ed8da90-450b-101b-98da-00aa003f1305}/"

   14                 xmlns:appointment="http://schemas.microsoft.com/mapi/id/{00062002-0000-0000-c000-000000000046}/"

   15                 xmlns:common="http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-c000-000000000046}/"

   16                 xmlns="DAV:">

   17     <set>

   18         <prop>

   19             <proptag:x001a001fdt:dt="string" >IPM.Schedule.Meeting.Request</proptag:x001a001f>

   20             <contentclassdt:dt="string">urn:content-classes:calendarmessage</contentclass>

   21             <proptag:x0037001fdt:dt="string" >test5</proptag:x0037001f>

   22             <appointment:_x0030_x820ddt:dt="dateTime.tz" >2007-12-13T23:57:48Z</appointment:_x0030_x820d>

   23             <proptag:x00600040dt:dt="dateTime.tz" >2007-12-13T23:57:48Z</proptag:x00600040>

   24             <common:_x0030_x8516dt:dt="dateTime.tz">2007-12-13T23:57:48Z</common:_x0030_x8516>

   25             <appointment:_x0030_x820edt:dt="dateTime.tz" >2007-12-14T01:57:48Z</appointment:_x0030_x820e>

   26             <proptag:x00610040dt:dt="dateTime.tz" >2007-12-14T01:57:48Z</proptag:x00610040>

   27             <common:_x0030_x8517dt:dt="dateTime.tz">2007-12-14T01:57:48Z</common:_x0030_x8517>

   28             <appointment:_x0030_x8215dt:dt="boolean" >0</appointment:_x0030_x8215>

   29             <appointment:_x0030_x8223dt:dt="boolean" >0</appointment:_x0030_x8223>

   30             <meeting:_x0030_x3dt:dt="bin.base64">HKSFpjzfdEK75L/tHWI4rQ==</meeting:_x0030_x3>

   31             <meeting:_x0030_x23dt:dt="bin.base64" >HKSFpjzfdEK75L/tHWI4rQ==</meeting:_x0030_x23>

   32             <uiddt:dt="string"xmlns="urn:schemas:calendar:">a685a41c-df3c-4274-bbe4-bfed1d6238ad</uid>

   33             <appointment:_x0030_x8217dt:dt="int" >3</appointment:_x0030_x8217>

   34             <appointment:_x0030_x8205dt:dt="int" >2</appointment:_x0030_x8205>

   35             <appointment:_x0030_x8224dt:dt="int" >2</appointment:_x0030_x8224>

   36             <proptag:x0063000bdt:dt="boolean" >1</proptag:x0063000b>

   37             <todt:dt="string"xmlns="urn:schemas:mailheader:">"Alice" &lt;alice@contoso.local&gt;</to>

   38         </prop>

   39     </set>

   40 </propertyupdate>

 


Technorati:

Posted by Henning Krause on Friday, July 13, 2007 12:00 AM, last modified on Saturday, July 2, 2011 3:51 PM
Permalink | Post RSSRSS comment feed

Getting the original values of an item in an OnSyncSave event sink

If you are developing an OnSyncEvent sink (see here for an article on how to implement them with managed code), you may need the value of the item being modified before the event took place. Unfortunately, the Eventsink infrastructure only exposes the modified item.

The OnSyncSave event sink is called twice: Before and after the changes have been committed to to the store. In the first call, you can get the original values simply by opening the modified item again.

Let's assume you got the ADO Record instance from the supplied pEventInfo parameter with this code:

    1 IExStoreDispEventInfo eventItem;

    2 eventItem = (IExStoreDispEventInfo) pEventInfo;

    3 Record record;

    4 record = (Record) eventItem.EventRecord;

Once you have this record, you can get the permanent url of the item, create a new ADO Record instance and load the original item into that record:

    1 try

    2 {

    3     string url;

    4 

    5     url = (string)record.Fields["http://schemas.microsoft.com/exchange/permanenturl"].Value;

    6 

    7     _TraceSource.TraceEvent(TraceEventType.Verbose, 0, "Opening original item at " + url);

    8 

    9     original = newRecord();

   10     original.Open(url, eventItem.EventConnection, ConnectModeEnum.adModeRead,

   11                   RecordCreateOptionsEnum.adFailIfNotExists, RecordOpenOptionsEnum.adOpenSource, null,

   12                   null);

   13 }

   14 catch (COMException ex)

   15 {

   16     if ((uint) ex.ErrorCode == 0x80040e19)

   17     {

   18         _TraceSource.TraceEvent(TraceEventType.Verbose, 0, Resources.CannotOpenOriginalItem);

   19         return;

   20     }

   21     else

   22     {

   23         throw;

   24     }

   25 }

You can now access the original item and access its properties.


Technorati:

Posted by Henning Krause on Thursday, July 5, 2007 12:00 AM, last modified on Thursday, July 5, 2007 12:00 PM
Permalink | Post RSSRSS comment feed

Creating meeting requests with WebDAV and Outlook in Cached mode

Under the support article KB308373, Microsoft had an example on how to create a meeting request for a meeting with WebDAV. Unfortunately, they have removed this article. Bascially, the process is like this:

  1. Create a meeting item in the calendar folder
  2. Create a messge item with the Outlook Message class of IPM.Schedule.Meeting.Request and a DAV:contentclass of urn:content-classes:calendarmessage. Then, copy all appointment related properties from the appointment to the meeting request item.
  3. Save the request message to the drafts folder of your mailbox.
  4. Move the message from the drafts folder to the ##MailSubmissionUri## folder.

This works in principle - unless you are using Outlook in cached mode. In this case, Outlook won't find the appointment which corresponds to the meeting request. Depending whether Outlook is in online or cached mode, it uses two different properties to find the appointment connected to a request: The global object id (http://schemas.microsoft.com/mapi/id/{6ED8DA90-450B-101B-98DA-00AA003F1305}/0x3) and a cached version of the global object id (http://schemas.microsoft.com/mapi/id/{6ED8DA90-450B-101B-98DA-00AA003F1305}/0x23). More information on the global object id can be found here. The global object id is set by Exchange. The other object id seems to be created by Outlook only.

So, if you create a meeting request via WebDAV and don't set the second object id, Outlook in cached mode will not find your appointment entry.

One solution to this problem is to reload the appointment after step 1 and load the global object id. Then, set the second global object id to the value of the first. And add the global object id to the properties of the meeting request.


Technorati:

Posted by Henning Krause on Monday, July 2, 2007 12:00 AM, last modified on Monday, February 28, 2011 10:21 AM
Permalink | Post RSSRSS comment feed

Constructing OWA 2007 item ids from WebDAV items

Prior Exchange 2007, it was easy for an application to get an url for any Exchange item which could be used to open the element in a browser: Simply use the DAV:href property which could be retrieved from any item.

When you try this with Exchange 2007, you won't get the expected result. Outlook Web Access 2007 (OWA) uses a different schema to open files. Instead of

    1 http://server/exchange/alias/contacts/test.eml

OWA 2007 uses this format:

    1 https://server/owa/?ae=Item&a=Open&t=IPM.Contact&s=Draft&id=RgAAAADns5U5Pdo%2bSamzP%2fK78yQOBwBgW74qsZ7RTJzeGdXyv4QaAAhgHichAABgW74qsZ7RTJzeGdXyv4QaAAhgHjBxAAAR

But if you query the DAV:href property via WebDAV in Exchange 2007, you still get the url as seen in the first example. The tricky part of the second url is clearly the id parameter, as everything else is either static (depending on the operation one wants to perform) or can be directly derived from the item (the message class for example).

It turns out that the id parameter is a modified version of the entry id of the element. To derive the id from the entry id, you must follow these steps:

  1. Load the entry id via WebDAV (PROPFIND or SEARCH on the item, get property http://schemas.microsoft.com/mapi/proptag/xfff0102).
  2. Decode the BASE64 encoded entry id to a byte array (in .NET, use the Convert.FromBase64String() method)
  3. Create a new byte array with the length of the entryid byte array plus 2
  4. Write the length of the entry id in the first byte of the byte array
  5. Copy the entry id to the new byte array, starting at offset 1
  6. Write the item type identifier into the last byte of the element
  7. Convert the byte array to a BAS64 encoded string (using Convert.ToBase64String() method)
  8. Urlencode the string

Here is a C# snippet which does the conversion:

    1 publicstaticstring CreateOwaUrl(string entryId, ItemType itemTypeId)

    2 {

    3     byte[] binaryEntryId;

    4     byte[] owaId;

    5     string result;

    6 

    7     binaryEntryId = Convert.FromBase64String(entryId);

    8     owaId = newbyte[binaryEntryId.Length+2];

    9     owaId[0] = (byte) binaryEntryId.Length;

   10     Array.Copy(binaryEntryId, 0, owaId, 1, binaryEntryId.Length);

   11     owaId[owaId.Length - 1] = (byte) itemTypeId;

   12 

   13     result = Convert.ToBase64String(owaId);

   14     result = System.Web.HttpUtility.UrlEncode(result);

   15 

   16     return result;

   17 }

I've discovered most of the item identifiers, but there are certainly more...

    1 publicenumItemType: byte

    2 {

    3     None = 0,

    4     Folder = 0x1,

    5     CalendarFolder = 0x2,

    6     ContactsFolder = 0x3,

    7     TasksFolder = 0x4,

    8     NotesFolder = 0x5,

    9     JournalFolder = 0x6,

   10     Message = 0x9,

   11     MeetingMessage = 0xa,

   12     CalendarItem = 0xf,

   13     CalendarItemOccurrence = 0x10,

   14     Contact = 0x11,

   15     DistributionList = 0x12,

   16     Task = 0x13,

   17     TaskRequest = 0x14,

   18     Note = 0x15,

   19     Post = 0x16,

   20 }

 


Technorati:

Posted by Henning Krause on Saturday, March 24, 2007 12:00 AM, last modified on Sunday, November 28, 2010 9:56 PM
Permalink | Post RSSRSS comment feed