InfiniTec - Henning Krauses Blog

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

Setting the Meeting organizer with WebDAV

If you plan a meeting with Outlook and invite attendees, Outlook will send meeting requests to all attendees. If they accept the request, a meeting will be created in the attendees calendar. While you can send meeting requests via WebDAV, you might want to create the meetings directly in the attendees mailbox. In this case, you are either creating the meeting with the account of the organizer (in this case that account needs access to the mailboxes of the attendees) are a server account which has those permissions. In either case you might want to control the meeting organizer.

The meeting organizer is stored in three properties:

  • PR_SENT_DISPLAYNAME (http://schemas.microsoft.com/mapi/proptag/x0042001f)
  • PR_SENT_ADDRESSTYPE (http://schemas.microsoft.com/mapi/proptag/x0064001f)
  • PR_SENT_EMAILADDRESS (http://schemas.microsoft.com/mapi/proptag/x0065001f)

The format of the these properties are documented here.


Technorati:

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

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

Comments welcome…

I've finally added a real comments feature to this website. Comments will appear instantly, but I will remove spam or otherwise inappropriate comments.

So feel free to add comments to every article on this site.


Technorati:

Posted by Henning Krause on Wednesday, August 22, 2007 12:00 AM, last modified on Wednesday, August 22, 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

Visual Studio 2008 WCF proxy generation tool and the ObservableCollection

Visual Studio 2008 has a nice WSDL generator for WCF services. Among other things, one nice feature is it's ability to generate strongly-typed lists (List<T> in C#) where the Visual Studio 2005 service proxy generator would have generated arrays. But there's more: The configuration program let's you actually choose the type of list to implement:


The WCF service configuration editor (click to enlarge)

Since I'm using WPF here I badly missed one list in the available dropdown: The ObservableCollection<T>.

But it turns out that you can force the configuration editor to generate a service proxy using the ObservableCollection<T> for arrays: Just hit the "Show all files" button in the solution explorer and navigate to your WCF reference, expand the node and open the file called Reference.svcmap. It's an XML file containing the settings for the proxy generation tool. And this file contains a tag called CollectionMapping. If you have previously changed the collection mapping with the editor, you'll find your settings there. For the above selection of the standard generic List, you'll find an entry like this:

    1 <CollectionMappings>

    2   <CollectionMappingTypeName="System.Collections.Generic.List`1"Category="List" />

    3 </CollectionMappings>

If you change the TypeName attribute from System.Collections.Generic.List`1 to System.Collections.ObjectModel.ObservableCollection`1 the service proxy generation tool will use ObservableCollections in the next update. And the configuration editor will just show a (Custom) in the drop-down, so you can still use it to modify the other settings.


Technorati:

Posted by Henning Krause on Monday, August 13, 2007 12:00 AM, last modified on Tuesday, August 14, 2007 12:00 PM
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

Building SQL Server 2000 compatible scripts with the SQL Server Management Studio

A very nice feature was added to the SQL Management Studio with SQL Server 2005 Service Pack 2: The ability to generate scripts which are compatible with SQL Server 2000!

To enable this option, open the options dialog (Tools --> Options) and select the Scripting node on the left. Click on Script for server version and select either SQL Server 2000 or 2005.


Options dialog from the SQL Server Management Studio (click to enlarge)

 


Technorati:

Posted by Henning Krause on Monday, July 23, 2007 12:00 AM, last modified on Monday, July 23, 2007 12:00 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