InfiniTec - Henning Krauses Blog

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

Exchange 2007 SP1 Item ids

Exchange 2007 introduced a new URL format (Constructing OWA 2007 item ids from WebDAV items) which contained an arbitrary item id, which was based on the EntryId of the item. The format was this:

Length Meaning
1 Length of the structure
sizeof(EntryId) EntryId
1 Item type

This format changed with Exchange Service Pack 1. The layout is now this:

Length Meaning
4 Length of the user's email address
sizeof(EmailAddress) Email address specifying the mailbox which contains the item
4 Size of the EntryId
sizeof(EntryId) The EntryId of the item

This layout also applies to folder ids within a mailbox.

 

   1: <UIRefId="WixUI_Minimal"/>
   2:         <BinaryId="ManagedCustomAction"SourceFile="Include\ManagedCustomAction.dll" />
   3:         <ManagedCustomActionId="test"BinaryKey="ManagedCustomAction"Type="ManagedCustomAction.CustomAction"Execute="immediate"xmlns="http://schemas.infinitec.de/wix/MCAExtension" />
   4:         <ManagedActionSequencexmlns="http://schemas.infinitec.de/wix/MCAExtension">
   5:         <ManagedAction="test"After="CostFinalize"SequenceTable="InstallUISequence" />
   6:     </ManagedActionSequence>
   7: </Product>

Posted by Henning Krause on Thursday, May 15, 2008 6:42 PM, last modified on Thursday, May 15, 2008 8:08 PM
Permalink | Post RSSRSS comment feed

Exchange 2007 SP1 Item ids

Exchange 2007 introduced a new URL format (Constructing OWA 2007 item ids from WebDAV items) which contained an arbitrary item id, which was based on the EntryId of the item. The format was this:

Length Meaning
1 Length of the structure
sizeof(EntryId) EntryId
1 Item type

This format changed with Exchange Service Pack 1. The layout is now this:

Length Meaning
4 Length of the user's email address
sizeof(EmailAddress) Email address specifying the mailbox which contains the item
4 Size of the EntryId
sizeof(EntryId) The EntryId of the item

This layout also applies to folder ids within a mailbox.


Technorati:

Posted by Henning Krause on Thursday, May 15, 2008 2:18 PM, last modified on Thursday, May 15, 2008 8:50 PM
Permalink | Post RSSRSS comment feed

InvalidCastException: Unable to cast object of Type 'X' to 'X'

In a recent project, the above exception was thrown when returning from a web service call. Interestingly, the exception was only thrown if the code was running within a web application. It would run fine when executed in an executable or even the Visual Studio development web server. At first I suspected some sort of permission problem but that couldn't really explain that particular error message. Next suspect was the shadow-copy feature of ASP.NET: The runtime does not actually run the assemblies from the path the IIS virtual directory points. Instead it copies all assemblies to the Temporary ASP.NET Files (C:\Windows\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files on x86 machines or C:\Windows\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files on x64 machines) and executes them from there. So I added two lines of code right before the exception would be thrown:

   1:  Trace.WriteLine(instance.GetType().Assembly.Location)
   2:  Trace.WriteLine(GetType(MyType).Assembly.Location)

Upon execution, the trace output was the following:

   1:  c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\c88143cc\98574940\assembly\dl3\98dad73b\f3cf1948_ddb4c801\MyAssembly.DLL 
   2:  C:\Development\TestApplication\MyAssembly.dll

This was the problem: One assembly was loaded from two different sources. What was the cause? Turns out that the first assembly was loaded because of an assembly reference. This way it was correctly placed in the Temporary ASP.NET Files. But the second instance of the assembly was loaded via reflection with a call to System.Reflection.Assembly.LoadFile. The solution is to use System.Reflection.Assembly.LoadFrom instead of the LoadFile method.

Why is this a solution? The main difference between LoadFrom and LoadFile is that LoadFrom goes through the normal Fusion bind process, thus allowing assembly redirection to happen. LoadFile on the other hand, just loads the assembly from the specified location.

For more information on this, see this blog posting from Suzanne Cooks blog.


Posted by Henning Krause on Tuesday, May 13, 2008 10:23 PM, last modified on Tuesday, November 30, 2010 12:19 AM
Permalink | Post RSSRSS comment feed

New Blog Engine

I've finally switched to a real blog engine with my site - previously I used a homegrown .NET 2.0 solution with an InfoPath form to publish posts. Especially the latter was a little frustrating :-). It took me two days to get the new blog running. I had hoped to do this with less time. Anyway, the blog engine I use now is - nomen est omen - BlogEngine.Net. I can finally write my posts with LiveWriter.

Pros:

  • It's open source and free
  • It's relatively small compared to other solutions (like dasBlog)
  • It seems there are still people working on it
  • Basic installation was done within a few minutes.

Cons:

  • The import tool sucks - basically it doesn't work with either RSS or BlogML. I had to rewrite that tool from scratch.
  • Passwords are stored in clear text - Who does this anymore? Luckily, it wasn't hard to modify the membership provider so it stores only hashes of passwords.
  • The server side import api does not import the last-modified date from the post. To make things worse, the import procedure tries to convert the last modified date to UTC by adding the inverse of the specified time zone from the settings. Guess what happens if you have a positive time zone (CEST +02:00) and subtract this value from DateTime.MinValue....
  • The server side import api does support the concept of a legacy url - yet this feature is not implemented at all. So I had to add that as well....
  • The posts where sorted by creation date, not by the last modification time - seems to be the standard but I'm not comfortable with it.

Was it worth all the work? I think so:

  • The site has now a better comment system
  • Blog posts can be rated again
  • Trackbacks and Pings are supported
  • There are now RSS feeds for categories
  • I've also added search bar (via LiveSearch - I wonder why Google does not offer something like this)
  • Recent posts are listed on the left side.
  • And finally: A list of all articles via the Archive page.

So, have fun with the new site.


Posted by Henning Krause on Tuesday, May 13, 2008 8:50 PM, last modified on Wednesday, November 24, 2010 7:43 PM
Permalink | Post RSSRSS comment feed

Windows Installer Xml 3.0 Extension for managed installers

The Windows Installer technology unfortunately lacks support for managed custom actions or the System.Configuration.Install.Installer class. Rob Mensching posted an article on his blog a while back why Microsoft considers custom action in general and managed custom action in particular a bad idea. While he makes some valid points (some technical and some strategic), I think managed custom installers are not a bad thing:

  • While they add a dependency on the .NET Framework at setup time, for applications using a windows service written in managed code, the Framework must be installed on the target computer at the setup time because Windows Installer will start the service during setup. And for all other managed applications you'll need the framework right after the application has been installed (to run it). Since the Framework cannot be installed using a Merge module, it must be installed before the actual setup of the application.
  • I consider myself a fairly good software developer when it comes to managed code. But my C or C++ knowledge is minimum at best. So I can either write solid managed custom actions (where I have a well-tested BCL at hand) or create spooky and unreliable custom actions in C. I prefer the former option.
  • He mentions a problem with managed custom actions using different versions of the CLR. While this may be a problem, you'll mostly write custom actions using .NET 2.0 these days. And .NET 3.0 and 3.5 both use the same CLR as 2.0.
  • What remains of his technical problems is the fact that Windows Installer on Windows 2003 will try to load the .NET Framework 1.1 into the deferred-custom action server when it tries to register assemblies into the Global Assembly Cache, which will fail if you force the .NET 2.0 runtime into the process with a managed custom action.
  • All those strategic reasons might be ok for the Windows Installer team, but I can't wait a few years until the Windows Installer team bakes all the actions I need into the core of the product. And when they do, you'll need Windows 2015 at the very least…. Not an option.

Apparently, the Visual Studio Team doesn't consider managed custom actions to be harmful - otherwise they wouldn't give you the option to run managed installers in those Visual Studio deployment projects. But these installers do lack a serious feature: The Windows Installer context. It's not that the installer context isn't propagated to the runtime (You may have wondered what the IManagedInstaller interface is meant for :-) ).

Windows Installer Xml also doesn't support managed custom actions out of the box. You have to do this yourself. One option is to decompile a Visual Studio Deployment project and see what Visual Studio does to call a managed custom action. While this will certainly work, you'll end up with a managed installer which has the same limitations as the Visual Studio deployment project: No access to the installer context. Additionally, these installer classes are always called as deferred custom actions. This means that they neither work in the immediate phase of the InstallExecuteSequence nor in the InstallUISequence.

Reinventing the wheel…

To call managed code in-process from the Windows Installer process, an intermediate unmanaged DLL must be called which in turn loads the .NET Framework into the process, spawns an AppDomain and finally runs the managed code inside this AppDomain. This is actually what the Visual Studio Deployment Project does.

The approach I'm using here is based on the article "WiX - Managed Custom Actions" by ForestWalk, which in turn is based on two other aricles: "Wrapping the Windows Installer 2.0 API" by Ian Marino and  “Hosting the CLR within a custom action” by Pablo M. Cibraro (aka Cibrax). The code in the article make it possible to call managed code in every part of the sequence. But the usage is not very intuitive to use, especially managed installer classes.

Since deferred custom actions can only access one property (CustomActionData), all information needed by be managed installer must be placed in this property. And since the CustomActionData is only an unstructured simple string property, some form of serialization is needed to put multiple properties into it.

To support all four methods of the managed installer class, you'll have to create and sequence eight custom actions: For each of the four methods (Install, Commit, Rollback, Uninstall) one action for the parameters (CustomActionData) and one action to run it.

Multiple managed installers will seriously degrade the readability of your Windows Installer XML file. That's why a took the code from the article and put it into a Windows Installer Xml Extension. I also created a small framework to simplify the development of managed installers.

Here is a simple example setup file:

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

    2 <Wixxmlns="http://schemas.microsoft.com/wix/2006/wi"

    3     >

    4   <ProductId="4ed3ff4f-7b33-4915-9801-a0fdd5515647"

    5     UpgradeCode="d4bacea3-a59a-4d44-b95b-1e144edfb88b"

    6     Name="Acme Sample Application"Language="1033"Version="1.0.0.0"

    7     Manufacturer="Acme Software Ltd."

    8   >

    9     <PackageInstallerVersion="300"Compressed="yes"   />  

   10     <MediaId="1"Cabinet="Sample.cab"EmbedCab="yes" />

   11     <DirectoryId="TARGETDIR"Name="SourceDir"FileSource=".\">

   12       <ComponentId="ProductComponent"Guid="865018ca-dc6f-4987-9766-cffe792cb937">

   13         <FileId="f1"Name="ManagedCustomAction.dll"Source="Include\ManagedCustomAction.dll" >

   14           <ManagedInstallerxmlns="http://schemas.infinitec.de/wix/MCAExtension">

   15             <ParameterName="TargetDir">[TARGETDIR]</Parameter>

   16             <ParameterName="AssemblyFile">Assembly is run from [#f1]</Parameter>

   17           </ManagedInstaller>

   18         </File>

   19       </Component>

   20     </Directory>

   21     <FeatureId="ProductFeature"Title="Main Feature"Level="1">

   22       <ComponentRefId="ProductComponent" />

   23     </Feature>

   24     <UIRefId="WixUI_Minimal"/>

   25     <BinaryId="ManagedCustomAction"SourceFile="Include\ManagedCustomAction.dll" />

   26     <ManagedCustomActionId="test"BinaryKey="ManagedCustomAction"Type="ManagedCustomAction.CustomAction"Execute="immediate"xmlns="http://schemas.infinitec.de/wix/MCAExtension" />

   27     <ManagedActionSequencexmlns="http://schemas.infinitec.de/wix/MCAExtension">

   28       <ManagedAction="test"After="CostFinalize"SequenceTable="InstallUISequence" />

   29     </ManagedActionSequence>

   30   </Product>

   31 </Wix>

Managed Installers

The extension makes it very easy to call managed installers or managed custom action.

Just put the tag ManagedInstaller into a File tag and the installer will be called during setup. If you need context information stored in other MSI properties, add a Parameter tag into the ManagedInstaller tag with an appropiate name and the value. From your managed installer, you can use the Parameters dictionary from the InstallContext class. Here is a sample implementation for the Install method of a System.Configuration.Install.Installer class:

    1 publicoverridevoid Install(IDictionary stateSaver)

    2 {

    3     string targetDir = Context.Parameters["TargetDir"];

    4 

    5     for (int i = 3 - 1; i >= 0; i--)

    6     {

    7         InstallerContext.Current.StartAction("ManagedCustomAction", string.Format("Install: Waiting {0} seconds...", i), "");

    8         Thread.Sleep(1000);

    9     }

   10     base.Install(stateSaver);

   11 }

In line 5, the property TargetDir is accessed. This property contains the value as specified in line 15 of the Windows Installer XML file. But far more interesting are the lines 7 and 11: These lines access the Windows Installer process and report details about what the custom action is doing. The two function wrap two flavors of the MsiProcessRecord function. The StartAction method reports the start of a major action (such as "Copying files" or "Creating registry values"). Additionally, a format string for details is specified, in this case "Waiting [1] more seconds"). The ReportDetails now just take the replacement values for the format string, in this case the number of seconds remaining).

Another important method of the InstallerContext class is the LogMessage method which writes directly to the Windows Installer log. Note that you don't have to use this method to log data. You can also use InstallContext.LogMessage or Trace.WriteLine or Console.WriteLine. The output of all those methods is captured and written to the log.

All unhandled exceptions from an Installer class are catched by the framework and cause an error message to be displayed. Unhandled exceptions in the Install, Commit and Rollback methods cause the installation to be aborted. If an exception occurs in the Uninstall method, an error dialog is displayed, but the uninstall will continue.

The four methods are sequence in the InstallExecuteSequence at the following positions:

  • Install, Commit, Rollback: Before InstallServices

  • Uninstall: Before UnpublishComponents

The installer will only be invoked if the component the file is associated with is installed.

Managed custom actions

To run a managed custom action, two things have to be done: Create a ManagedCustomAction tag under the Product tag and fill in the blanks:

  • Id: The name of the custom action
  • BinaryKey: If you want to run the custom action in the immediate sequence or in the InstallUISequence table, add the assembly to the binary table (via the Binary tag) and enter its key here.
  • FileKey: If you want to run this custom in the deferred sequence, add the assembly to the file table (via the File tag) and enter its key here.
  • Type: Name full qualified name of the type you want to run (Namespace + type name)
  • Execute: Either commit, deferred, firstSequence, immediate, oncePerProcess, rollback or secondSequence. These are the same options you have with normal Custom actions (Cutom tag)
  • Impersonate: Yes to run the custom action in the security context of the logged on user. False otherwise. The default is true. Only valid for deferred custom actions.
  • Return: asyncNoWait, asyncWait, check or ignore. These are the same options you have with normal Custom actions (Cutom tag)

Unfortunately Windows Installer XML does not allow extensions in the sequence tables, so I had to create my own: ManagedActionSequence. Add a Managed tag for each custom action you want to schedule. The Managed tag has these attributes:

  • Action: The name of the managed custom action to run.
  • After: The name of the action the managed custom action should be executed after.
  • Before: The name of the action the managed custom action should be execute before.
  • Sequence: The absolute sequence where the managed custom action should run.
  • SequenceTable: The name of the sequence table where the managed custom action should be scheduled: Either InstallUISequence, InstallExecuteSequence, AdminUISequence, AdminExecuteSequence.

The managed custom action must be a class which implements the InfiniTec.Configuration.Install.ICustomAction interface, like in this example:

    1 publicclassCustomAction: ICustomAction

    2     {

    3         publicvoid Execute()

    4         {

    5             string targetDir = InstallerContext.Current.GetProperty("TARGETDIR");

    6 

    7             InstallerContext.Current.StartAction("ManagedCustomAction", "Running custom action...", "Waiting [1] seconds...");

    8             for (int i = 3 - 1; i >= 0; i--)

    9             {

   10                 InstallerContext.Current.ReportDetails(i.ToString());

   11                 Thread.Sleep(1000);

   12             }

   13 

   14 

   15         }

   16     }

This implementation has full access to the MSI properties (see line 5, if scheduled as immediate action) and of course access to the Installer log via InstallerContext.Current.LogMessage.

Other useful classes

Since the custom actions can be executed in the immediate sequence of the install process, it has full access to all properties and tables of the installer. The rows of a view can be accessed via the View class:

    1 using (View view = InstallerContext.Current.OpenView("SELECT * FROM Binary"))

    2 using (RecordCollection records = view.Execute())

    3 {

    4     foreach (Record record in records)

    5     {

    6         string name = record[1].GetString();

    7     }

    8 }

The view returns a RecordCollection which in turn provides access to it's Record instances. Each record consists of one or more fields. Note that if you create a record with the Record.Create(int columnCount) method, the resulting record will have columnCount+1 fields - 0 to the specified value.

Modifications to the original source code

Apart from the newly added code, I made significant changes to the existing code:

To load the .NET runtime into the process I use the CLRHosting project from the article mentioned above. I have replaced all dangerous API calls (strcat, sprintf) with secure ones. But my C and C++ knowledge is VERY limited. I would appreciate it if someone with more knowledge could take a look at the code….

I have also made significant changes to the managed part of the solution. Mainly, I have encapsulated all unmanaged MSI handles in a custom SafeHandle class.

Open issues

  • Deferred custom actions with assemblies in the Binary table are not yet supported
  • Immediate custom actions with assemblies in the File table are not supported (And I don't see how this could work)
  • Managed installers do not have an immediate part
  • Managed custom actions and managed installer classes don't add ticks to the progress bar.
  • A much cleaner approach is to call the custom action in a separate process and provide access to the Windows Installer context via remoting. This approach is dicussed in more detail in the article A New Approach to Managed Custom Actions by Christopher Painter. Unfortunately he didn't release any source code and I'm lacking the necessary C and C++ skills right now.

Installation

Just copy the zip file to a directory and decompress it. In your WIX Project add a reference to the ManagedInstallerWixExtension.dll. In your setup file add the namespace http://schemas.infinitec.de/wix/MCAExtension to the list of namespace definitions.

Requirements

The extension is compiled against WIX 3.0.361 (build from December 21, 2007) using .NET 2.0.

License

The authors of the original articles haven't lost a word about the license, so I assume it's freely available. To keep this stuff freely available I publish this work under the Common Public License, the same license Windows Installer XML is published under.

Downloads

ManagedInstallerWixExtension.zip (637,678 Bytes)
Source files and binaries

Technorati:

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

Working with the Recurrence pattern of Exchange 2007

One of the more difficult tasks with Exchange Web Services is coping with the recurrence pattern of an appointment or task item. Because of the limitations of WSDL and/or the proxy generator of Visual Studio, you end up with a RecurrenceType class which has two properties describing the recurrence of the element with such descriptive names as Item and Item1. To make things worse, the Microsoft did decide not to use enumerations to distinguish between different recurrence pattern types, but uses a unique type for each. Because this can be quite complex, I created this class diagram from the proxy class generated by Visual Studio:


Classdiagram with all the recurrence related types (click to enlarge)

The RecurrenceType type is used both, for appointments and tasks. But on appointments, you can't use the RegeneratingPatternBaseType and its descendants, because those are not supported by Outlook.

Creating a RecurrenceType structure is relatively straigt forward. You just take the appropiate types and fill the properties. Examining an existing structure is different: Since there is no enumeration, you'll have to use a bunch of instanceis type commands, or better yet, use the as operator (TryCast in Visual Basic).

    1 NumberedRecurrenceRangeType numberedRange = recurrence.Item1 asNumberedRecurrenceRangeType;

    2 EndDateRecurrenceRangeType endDateRange = recurrence.Item1 asEndDateRecurrenceRangeType;

    3 NoEndRecurrenceRangeType noEndDate = recurrence.Item1 asNoEndRecurrenceRangeType;

    4 

    5 if (numberedRange != null)

    6 {

    7     // Do something here

    8 }

    9 elseif (endDateRange != null)

   10 {

   11     // Do something here

   12 }

   13 elseif (noEndDate != null)

   14 {

   15     // Do something here                   

   16 }

In this example, the recurrence variable holds an instance of the RecurrenceType class.


Technorati:

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

Private items with WebDAV

Outlook allows an item to be marked as private. For example, if Alice defines an appointment as private in her calendar and Bob, who has read access to Alice's calendar tries to read it, he won't get the details of that appointment. So while he technically has read access to that appointment, Outlook will block out the details. Note that Outlook is the instance which blocks out the instance. Using another protocol like WebDAV allows Bob to see the details.

This private flag can be read and written via WebDAV, but it's somewhat complicated. These are the relevant properties for the so called sensitivity of an item:

  • PR_SENSITIVITY (http://schemas.microsoft.com/mapi/proptag/0x360003)
  • Private Flag (http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8506)
  • The Sensitivity header (urn:schemas:mailheader:sensitivity)

Reading or searching this property is easy: Just use the PR_Sensitivity with a SEARCH or PROPFIND request, and it will be returned as an integer value, which has this meaning:

  • 0: Normal
  • 1: Personal
  • 2: Private
  • 3: Confidential

You can also search for the private flag, which is a boolean value. If it's true, the item is marked private.

As far as I know, Outlook uses only the value 0 and 2.

If you want to set this value, you can just set this property do the desired value. That is, unless you want to clear the flag and set it to 0. This won't work. You have to set the sensitivity mail header to get this working. This is a string property, which must be set to one of these values:

  • Normal
  • Personal
  • Private
  • Company-Confidential

Searching for private items

As I mentioned above, you can search for items with a certain sensitivity. But since the properties are not part of the default item schema, you'll have to cast them to their datatypes. For example, to search for items marked as private using the Private flag, you can use this restriction in the WHERE clause of a SEARCH request:

    1 ("DAV:isfolder" = false) AND Cast("http://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x8506" as "boolean") = true

 


Technorati:

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

Manipulation of extended properties with Exchange 2007 using the ExtendedPropertyType type

To access MAPI properties or custom Outlook properties using the Exchange 2007 Web Services, the ExtendedPropertyType type is used.

Here is a class diagram of the ExtendedPropertyType and its associated properties:


Class diagram of the ExtendedPropertyType type and its associated properties (click to enlarge)

The ExtendedPropertyType has two properties which need to be filled: Item and ExtendedFieldUri. The latter one identfifies the property while the first one contains the value of the property. The Item property is of type object which suggests that it contains the property value in its native representation (e.g. a date stored as System.DateTime, a boolean value as type System.Boolean and so on). Unfortunately, this is not the case. If the property is multi-valued, the Item property contains a array of strings. Otherwise, it contains only one string. To actually do something with the value, it needs to be converted to it's corresponding type.

Here is an example request which performs a FindItem operation on the calendar of a mailbox, requesting the end date of the appointments using an extended property:

    1 privatestatic T[] DeserializeValues<T>(object[] items)

    2 {

    3     if (items == null) returnnew T[0];

    4 

    5     returnArray.ConvertAll<object, T>(items, delegate(object input) { return DeserializeValue<T>((string) input); });

    6 }

    7 

    8 privatestatic T DeserializeValue<T>(string item)

    9 {

   10     TypeConverter converter;

   11     Type type;

   12 

   13     if (string.IsNullOrEmpty(item)) returndefault(T);

   14 

   15     type = Nullable.GetUnderlyingType(typeof (T)) ?? typeof (T);

   16 

   17     if (type == typeof(byte[])) return (T)​(object)Convert.FromBase64String(item);

   18     elseif (type == typeof(DateTime)) return (T) (object) XmlConvert.ToDateTime(item, XmlDateTimeSerializationMode.Utc).ToLocalTime();

   19     elseif (type.IsEnum) return (T)Enum.Parse(type, item);

   20     else

   21     {

   22         converter = TypeDescriptor.GetConverter(type);

   23         return (T)converter.ConvertFromString(item);

   24     }

   25 }

   26 

   27 publicstaticvoid Main(string[] args)

   28 {

   29     ExchangeServiceBinding client = newExchangeServiceBinding();

   30 

   31     client.Url = "https://w2k3x64.contoso.local/ews/exchange.asmx";

   32     client.Credentials = newNetworkCredential("administrator", "password", "contoso");

   33 

   34     DistinguishedFolderIdType folderId = newDistinguishedFolderIdType();

   35     folderId.Id = DistinguishedFolderIdNameType.calendar;

   36 

   37     FindItemType request = newFindItemType();

   38 

   39     PathToExtendedFieldType propertyId = newPathToExtendedFieldType();

   40     propertyId.DistinguishedPropertySetId = DistinguishedPropertySetType.Appointment;

   41     propertyId.PropertyId = 0x820e;

   42     propertyId.PropertyType = MapiPropertyTypeType.SystemTime;

   43     propertyId.PropertyIdSpecified = true;

   44     propertyId.DistinguishedPropertySetIdSpecified = true;

   45 

   46     request.ParentFolderIds = newBaseFolderIdType[] {folderId};

   47     request.ItemShape = newItemResponseShapeType();

   48     request.Traversal = ItemQueryTraversalType.Shallow;

   49     request.ItemShape.BaseShape = DefaultShapeNamesType.Default;

   50     request.ItemShape.AdditionalProperties = newBasePathToElementType[] { propertyId };

   51 

   52     FindItemResponseType result = client.FindItem(request);

   53 

   54     if (result.ResponseMessages.Items[0].ResponseCode != ResponseCodeType.NoError)

   55     {

   56         Console.Out.WriteLine("Error = {0}", result.ResponseMessages.Items[0].ResponseCode);

   57         return;

   58     }

   59 

   60     FindItemResponseMessageType findItemResponse = (FindItemResponseMessageType)result.ResponseMessages.Items[0];

   61     ItemType[] items = ((ArrayOfRealItemsType)findItemResponse.RootFolder.Item).Items;

   62     if (items != null)

   63     {

   64         foreach (ItemType childItem in items)

   65         {

   66             Console.Out.WriteLine("item.Subject = {0}", childItem.Subject);

   67             Console.Out.WriteLine("ParseValue<DateTime?>(childItem.ExtendedProperty[0].Item) = {0}", DeserializeValue<DateTime?>((string)childItem.ExtendedProperty[0].Item));

   68         }

   69     }

   70 

   71     Console.WriteLine("Finished.");

   72     Console.ReadLine();

   73 }

The content of the main function is actually the boring part. More interesting is the DeserializeValue<T> method which takes a string and converts it to the specified value T. In this example, the value is converted to a nullable DateTime. This method in called in line 67.

To update the property, these methods can be used to serialize a typed value to a string:

    1 privatestaticExtendedPropertyType CreateExtendedProperty<T>(PathToExtendedFieldType id, IEnumerable<T> values)

    2 {

    3     ExtendedPropertyType result = newExtendedPropertyType();

    4 

    5     result.ExtendedFieldURI = id;

    6     result.Item = (values != null) ? newList<T>(values).ConvertAll<string>(delegate(T input) { return SerializeValue(input); }).ToArray() : null;

    7 

    8     return result;

    9 }

   10 

   11 privateExtendedPropertyType CreateExtendedProperty<T>(PathToExtendedFieldType id, T value)

   12 {

   13     ExtendedPropertyType result = newExtendedPropertyType();

   14     result.ExtendedFieldURI = id;

   15     result.Item = SerializeValue(value);

   16 

   17     return result;

   18 }

   19 

   20 privatestaticstring SerializeValue<T>(T value)

   21 {

   22     Type type;

   23     TypeConverter converter;

   24     object result;

   25 

   26     result = value;

   27 

   28     if (result == null) returnnull;

   29 

   30     //Unbox nullable types

   31     type = Nullable.GetUnderlyingType(typeof(T));

   32     if (type != null)

   33     {

   34         converter = newNullableConverter(typeof(T));

   35         result = converter.ConvertTo(value, type);

   36     }

   37     else type = typeof(T);

   38 

   39     if (type.IsEnum)

   40     {

   41         result = Convert.ChangeType(result, Enum.GetUnderlyingType(type));

   42         return result.ToString();

   43     }

   44     elseif (type == typeof(DateTime))

   45     {

   46         returnXmlConvert.ToString(((DateTime)result).ToUniversalTime(), XmlDateTimeSerializationMode.Utc);

   47     }

   48     elseif (type == typeof(bool))

   49     {

   50         return ((bool)result) ? "1" : "0";

   51     }

   52     elseif (type == typeof(byte[]))

   53     {

   54         returnConvert.ToBase64String((byte[])result);

   55     }

   56     else

   57     {

   58         returnstring.Format(CultureInfo.InvariantCulture, "{0}", result);

   59     }

   60 }

The SerializeValue<T> method can handle all formats used by the Exchange Web Services and copes with nullable types also.


Technorati:

Posted by Henning Krause on Sunday, October 21, 2007 12:00 AM, last modified on Thursday, November 25, 2010 4:25 AM
Permalink | Post RSSRSS comment feed

A WebDAV PROPPATCH which works for Exchange 2003 breaks with Exchange 2007

If you are using WebDAV to store data in Exchange you may encounter a breaking change when migrating from Exchange 2003 to 2007: A PROPPATCH request which works flawlessly with Exchange 2003 will break with Exchange 2007 returning a 400 Bad Request error.

This may happen if you upload data to the Exchange server containing control characters (chars 0x00 through 0x1f). If this happens inside a HTML-based property (e.g. the HTML body of the email), you can use the following workaround to encode the characters in question. Otherwise you should probably just strip them away.

Workaround

If you are using a .NET based language you can utilize a simply regular expression to convert or strip the invalid values from the data:

    1 Regex _ControlReplacer = newRegex("\\p{Cc}", RegexOptions.Compiled);

    2 

    3 // Use this line if you want to encode the control characters using HTML entities

    4 string result = _ControlReplacer.Replace(data, delegate(Match match) { return"&#x" + Convert.ToInt32(match.Value[0]).ToString("x2") + ";"; });

    5 

    6 // Use this line if you want to strip the control characters from the data

    7 string result = _ControlReplacer.Replace(data, "");


Technorati:

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

Sending mails with WebDAV with recipients in the BCC field

This article on MSDN show how to send an email through a users mailbox using the DAV submission URI. Basically, you perform these steps:

  1. Construct the email following RFC 2822 (and RFC 2045 for more complex mails)
  2. Use the PUT command to upload the mail to the drafts folder of a users mailbox
  3. Use the MOVE command to move the mail to the DAV submission url.

The mail will then be sent to the recipients specified in the header of the mail using the TO: and CC: headers.

If you try to include a BCC: header to send the mail unnoticed of the other recipients, you will get an Unprocessable Entity error on step 3. This is because the BCC information is an envelope field rather than a header field (See RFC 2821 for more infos about SMTP envelopes and mail header).

To cope with this, you'll have to perform two things:

  1. When uploading the mail to the drafts folder using the PUT method, be sure to specify a content type of message/rfc822.
  2. Before moving the mail to the DAV submission uri, issue a PROPPATCH command on the mail in the drafts folder and set the field urn:schemas:httpmail:bcc to the intended BCC recipients. As described in the article, this is a comma delimited list of all recipients in the form "Someone" <someone@example.com>.

Technorati:

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