Get the organizer's calendar appointment using EWS for Exchange 2010

ahlun picture ahlun · Sep 30, 2010 · Viewed 10.1k times · Source

I have an synchronization application with sync appointments with Exchange 2010, and i have some questions.

  1. UserA creates an appointment and add UserB as attendee, this means UserA is the organizer of the appointment and UserB outlook calendar will have an appointment entry created.
  2. UserA and UserB calendar appointment will have their own IDs (UniqueID).
  3. If for example i am only given the ID (UniqueID) of UserB's calendar appointment, is that a method to retrieve UserA's calendar appointment? Because i think they should be some link between the organizer and the attendees appointment, i just don't know how.

Answer

Dan picture Dan · Mar 25, 2011

For anyone coming after me - here is the details on how this works. I'll also post reference to my blog with source files.

In short - appointments are tied together using the UID property. This property is also termed as the CleanUniqueIdentifier. While this example code could be adjusted based on a "bug" fix referenced in the blog post below, this source code is done because the requirements are to work with => 2007 SP1.

This assumes you have some prior knowledge of what the EWS is and how to use it (EWS API). This also is building off of blog post "EWS: UID not always the same for orphaned instances of the same meeting" and post "Searching a meeting with a specific UID using Exchange Web Services 2007"

Required setup for this to work:

  1. User account that can be a "Delegate" or has "Impersonation" privileges for respective accounts.

Problem: Each "appointment" in exchange has a unique id (Appointment.Id) that is the exact instance identifier. Having this id, how can one find all related instances (recurring or attendee requests) in a calendar?

The code below outlines how this can be accomplished.

[TestFixture]
public class BookAndFindRelatedAppoitnmentTest
{
  public const string ExchangeWebServiceUrl = "https://contoso.com/ews/Exchange.asmx";

  [Test]
  public void TestThatAppointmentsAreRelated()
  {
    ExchangeService service = GetExchangeService();

    //Impersonate the user who is creating the Appointment request
    service.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.PrincipalName, "Test1" );
    Appointment apptRequest = CreateAppointmentRequest( service, new Attendee( "[email protected]" ) );

    //After the appointment is created, we must rebind the data for the appointment in order to set the Unique Id
    apptRequest = Appointment.Bind( service, apptRequest.Id );

    //Impersonate the Attendee and locate the appointment on their calendar
    service.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.PrincipalName, "Test2" );
    //Sleep for a second to let the meeting request propogate YMMV so you may need to increase the sleep for this test
    System.Threading.Thread.Sleep( 1000 );
    Appointment relatedAppt = FindRelatedAppointment( service, apptRequest );

    Assert.AreNotEqual( apptRequest.Id, relatedAppt.Id );
    Assert.AreEqual( apptRequest.ICalUid, relatedAppt.ICalUid );
  }

  private static Appointment CreateAppointmentRequest( ExchangeService service, params Attendee[] attendees )
  {
    // Create the appointment.
    Appointment appointment = new Appointment( service )
    {
      // Set properties on the appointment.
      Subject = "Test Appointment",
      Body = "Testing Exchange Services and Appointment relationships.",
      Start = DateTime.Now,
      End = DateTime.Now.AddHours( 1 ),
      Location = "Your moms house",
    };

    //Add the attendess
    Array.ForEach( attendees, a => appointment.RequiredAttendees.Add( a ) );

    // Save the appointment and send out invites
    appointment.Save( SendInvitationsMode.SendToAllAndSaveCopy );

    return appointment;
  }

  /// <summary>
  /// Finds the related Appointment.
  /// </summary>
  /// <param name="service">The service.</param>
  /// <param name="apptRequest">The appt request.</param>
  /// <returns></returns>
  private static Appointment FindRelatedAppointment( ExchangeService service, Appointment apptRequest )
  {
    var filter = new SearchFilter.IsEqualTo
    {
      PropertyDefinition = new ExtendedPropertyDefinition
        ( DefaultExtendedPropertySet.Meeting, 0x03, MapiPropertyType.Binary ),
      Value = GetObjectIdStringFromUid( apptRequest.ICalUid ) //Hex value converted to byte and base64 encoded
    };

    var view = new ItemView( 1 ) { PropertySet = new PropertySet( BasePropertySet.FirstClassProperties ) };

    return service.FindItems( WellKnownFolderName.Calendar, filter, view ).Items[ 0 ] as Appointment;
  }

  /// <summary>
  /// Gets the exchange service.
  /// </summary>
  /// <returns></returns>
  private static ExchangeService GetExchangeService()
  {
    //You can use AutoDiscovery also but in my scenario, I have it turned off      
    return new ExchangeService( ExchangeVersion.Exchange2007_SP1 )
    {
      Credentials = new System.Net.NetworkCredential( "dan.test", "Password1" ),
      Url = new Uri( ExchangeWebServiceUrl )
    };
  }

  /// <summary>
  /// Gets the object id string from uid.
  /// <remarks>The UID is formatted as a hex-string and the GlobalObjectId is displayed as a Base64 string.</remarks>
  /// </summary>
  /// <param name="id">The uid.</param>
  /// <returns></returns>
  private static string GetObjectIdStringFromUid( string id )
  {
    var buffer = new byte[ id.Length / 2 ];
    for ( int i = 0; i < id.Length / 2; i++ )
    {
      var hexValue = byte.Parse( id.Substring( i * 2, 2 ), System.Globalization.NumberStyles.AllowHexSpecifier );
      buffer[ i ] = hexValue;
    }
    return Convert.ToBase64String( buffer );
  }
}