Exchange 2003 meeting request submission, update and cancellation from server-side applications


I recently had a project where part of the functionality was to make meeting room booking functionality against Exchange Server 2003 (I re-invented OWA 🙂 - no, it was just part of the application, integrated with other functionalities), and also to cancel or update the request and do free/busy lookup. It was an ASP.net application, so the application was running in an IIS worker process, meaning that I had to use server-side APIs to do the job. Otherwise, a popup message could block the server process, making my web application unresponsible. The APIs available for server-side applications are



  1. Extended MAPI (note: it's not supported from .net, so it's not a real choice)

  2. CDOEX

  3. CDOSYS

  4. WebDav

  5. ExOleDb

As you might now, I was working as a support engineer at the Exchange Development team at Microsoft GTSC-PSS, so if I’m not going to use an official documented API to do the stuff, who does it then? So I *HAVE TO* do it in a right way.


Creating, cancelling and updating a meeting request is not just a raw operation (like changing a property or copying an item), so the APIs above were shrinked down to the ones containing the business logic for these operations. This is a shorter list:



  1. CDOEX

  2. WebDav

… partially: CDOEX is OK, it has all the business logic I need, but the problem is that it can be used only on the Exchange Server, locally (otherwise, part of its functionality doesn't work). Of course, my app is running on a different box. As an alternative choice, I could have designed my application to publish the CDOEX functionality on the Exchange Server as a set of webservice methods, but I couldn't do that, due to (obvious) security and performance reasons at the Customer.


So, WebDav remains. Hmmm … not a broad list of choices! 🙂  Let's check the documentation around WebDav: the kb article at http://support.microsoft.com/?id=308373 tells you how to send a meeting request with WebDav, while http://support.microsoft.com/?id=920134 tells that nothing else is supported than a few operations listed there (of course, the operations needed for me are not on the list). So, WebDav is also not a good choice, as even if it *COULD* be used to create the meeting request, then later, after you sent the request, you are alone in the dark again, as it’s *SURELY* not supported to cancel or update it. The reason could be that just part of the fields needed for these operations are documented/created in the WebDav calendar schema. So, I’m in a big trouble now: there’s no way to deliver my funcionality with documented APIs. Of course, Exchange 2007 brings the nice and shiny webservices out of the box, but I can’t wait for it, or even if I could, my Customer is not going to upgrade for another 1-2 years. So, I’m in a real trouble.


Then, OWA came into my mind. Every functionality that OWA has, can be called by HTTP requests (that's the way Http works :). Of course, the way OWA handles meeting requests is OK. The only question is that if it’s supported (will it change after the next SP?) or not. Unfortunately, it's also not supported, but it's partially documented. So, this seems to be the best (and only) choice remained. You can find the OWA customization docu at http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=6532E454-073E-4974-A800-1490A7CB358F, it's a nice documentation, worth a look! So, I started reverse engineering my scenarios by using NetMon 2.0 and wrote some nice helper functions. Pasted here the simplified copies FYI. Here they are:


Cancelling a meeting request with an OWA command:


This sample shows you how to cancel a meeting that you organized with multiple attendees. It's just a sample, it's not complete, for example, the folder "Calendar" is hard coded while it could be more smart by reading up the Calendar folder's name from the mailbox root. Anyways, here we go:

/// <summary>
/// Cancels a meeting request
/// </summary>
/// <param name="mainMeetingItemName">Meeting item name in the user's calendar</param>
public static void CancelMeetingRequest(
    string mainMeetingItemName, string exchSvrName, string mailboxName)
{
    NetworkCredential networkCredential = null;
   
    // Set the OWA server's name, the mailbox name and the user's OWA calendar Url
    string owaCalendarUrl = string.Format("http://{0}/exchange/{1}/calendar",
        exchSvrName, mailboxName);


    // Make sure the slash at the end
    owaCalendarUrl = MakeSureSlashAtTheEnd(owaCalendarUrl);


    // Create the request string
    string webRequest = string.Format("{0}{1}?Cmd=getcancel",
        owaCalendarUrl, mainMeetingItemName);


    // Create the request object
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(webRequest);


    // Set the credentials based on the configuration settings
    networkCredential = CredentialCache.DefaultNetworkCredentials;
    request.Credentials = networkCredential;


    // Set up the headers
    request.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, " +

        application/msword, */*";
    request.Headers.Add("Accept-Language", "hu");
    request.Headers.Add("Accept-Encoding", "gzip, deflate");
    request.UserAgent =
        "Mozilla/4 0 (compatible; MSIE 6 0; Windows NT 5 1; SV1; " +

        "NET CLR 1 0 3705;  NET CLR 1 1 4322;  NET CLR 2 0 50727)";


    // Send the request and get the response stream
    HttpWebResponse owaResponse =

        (HttpWebResponse)request.GetResponse();


    // Retrieve and form the location of the moved Cancel message.
    //  This message is created by OWA for sending it to the meeting attandees
    string cancelMsgLocation = "http://" + exchSvrName +

        owaResponse.ResponseUri.AbsolutePath;


    // Build the mail submission URI of the current user for sending

    // the message
    string submissionUri = "http://" + exchSvrName + "/exchange/" +
        mailboxName + "/##DavMailSubmissionURI##/";
   
    // Move the draft message to the submission Uri
    MoveItemWithWebDav(cancelMsgLocation, submissionUri, networkCredential);


    // Delete the meeting from the calendar
    DeleteItemWithWebDav(owaCalendarUrl + mainMeetingItemName,

        networkCredential);
}



The point is to do a ?Cmd=GetCancel call on the meeting item, retrieve the Url OWA gives back to you in the webresponse (that will be the cancellation item's Url that OWA pre-created for you) and send that item out.


Updating a meeting request with OWA commands


This one is a piece of cake! You just have to do the neccessary changes on the meeting item in the organizer's calendar - probably by updating the recipients (to: field -> required attendees, cc: field -> optional attendees, bcc: field -> resources), update the start and end date/time, location of the appointment item if neccessary - as when you create a meeting request with WebDav, then run the following function to ask OWA to send an update to all recipients. Even if you empty the attendees fields (to:, cc:, bcc:), OWA will manage the attendees receive az update request containing the changes:

/// <summary>
/// Sends an update to the attendees of a meeting request
/// </summary>
/// <param name="booking">A booking instance that has changed</param>
/// <param name="owaMailboxUrl">Url of the user's mailbox</param>
/// <param name="calendarFolderName">The user's calendar folder name</param>
public static void UpdateMeetingRecipients(
    Data.Booking booking, string owaMailboxUrl, string calendarFolderName)
{
    NetworkCredential networkCredential = null;


    // Make sure the slash at the end
    owaMailboxUrl = MakeSureSlashAtTheEnd(owaCalendarUrl);
    calendarFolderName = MakeSureSlashAtTheEnd(calendarFolderName);
   
    // Set up the calendar Url
    owaCalendarUrl = owaMailboxUrl + calendarFolderName


    // Collect the meeting item's Url
    string meetingItemUrl = owaCalendarUrl + booking.AppointmentItemName;


    // Create the request object
    HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(
        meetingItemUrl);


    // Set the method and content type
    request.Method = "POST";
    request.ContentType = "application/x-www-UTF8-encoded";


    // Set the network credentials
    networkCredential = CredentialCache.DefaultNetworkCredentials;
    request.Credentials = networkCredential;


    // Set up the headers
    request.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, " +

        "application/msword, */*";
    request.Headers.Add("Accept-Language", "hu");
    request.Headers.Add("Accept-Encoding", "gzip, deflate");
    request.UserAgent = "Mozilla/4 0 (compatible; MSIE 6 0; Windows NT 5 1; " +
        SV1; NET CLR 1 0 3705;  NET CLR 1 1 4322;  NET CLR 2 0 50727)";


    // Write to the request stream
    Stream requestStream = request.GetRequestStream();
    StreamWriter requestBodyWriter = new StreamWriter(requestStream);


    requestBodyWriter.Write("Cmd=sendappt\n");
    requestBodyWriter.Write("Required=\n");
    requestBodyWriter.Write("Optional=\n");
    requestBodyWriter.Write("Resource=\n");
    requestBodyWriter.Write("FormType=appointment\n");
    requestBodyWriter.Write(string.Format("MsgID=/{0}{1}\n",

        calendarFolderName, booking.AppointmentItemName));
    requestBodyWriter.Write(

        string.Format("urn:schemas:httpmail:subject={0}\n",

        "Changed: " + booking.Subject));
    requestBodyWriter.Write(

        string.Format("urn:schemas:httpmail:htmldescription={0}\n",
        "This is an updated meeting request));
    requestBodyWriter.Write(

        string.Format("urn:schemas:calendar:dtstart={0}\n",

        ConvertDateToOwaFormat(booking.Start)));
    requestBodyWriter.Write(

        string.Format("urn:schemas:calendar:dtend={0}\n",

        ConvertDateToOwaFormat(booking.End)));
    requestBodyWriter.Write(

        string.Format("urn:schemas:calendar:location={0}\n",

        booking.MeetingRoom.Name));
    requestBodyWriter.Write(

        string.Format("CALENDAR:UID=/{0}{1}\n",

        calendarFolderName, booking.AppointmentItemName));
    requestBodyWriter.Write(

        "urn:schemas:calendar:responserequested=1\n");
    requestBodyWriter.Write(

        string.Format(

        "http://schemas.microsoft.com/exchange/subject-utf8=%7B0%7D/n",

        booking.Subject));


    // Flush the stream
    requestBodyWriter.Flush();
    requestBodyWriter.Close();


    // Send the request
    HttpWebResponse owaResponse = (HttpWebResponse)request.GetResponse();
}


This is nice, isn't it? 🙂 

Creating the meeting request 


For creating a meeting request, I used the documented way with WebDav, which seems to be also not supported: http://support.microsoft.com/?id=308373


Summary 


Don't forget that the examples shown above are unsupported and they may won't work with the next service pack of Exchange Server 2003. If you have a chance, use CDOEX on the Exchange Server by exposing it as a set of webservices. Otherwise, you could use this unsupported thing that I mentioned above, but if there's anything going wrong, you can't go to Microsoft Product Support for help. Feel free to post any questions here, if there's a problem with it.

Comments (17)
  1. Mladen says:

    Hi.

    I have a strange problem:

    I can send the meeting request cancelation and the client gets the cancelation mail with

    "remove from calendar" option but the appointment doesn’t get removed.

    i tried your method but it doesn’t work.

    The cancelMsgLocation is the same as the original appointment URI i’m trying to cancel.

    is that normal?

    because the mail i send is an appointment and not a cancelation.

    I can’t believe that cancelation and updating can’t be done with WebDAV.

    It’s ridicolous…

  2. Mladen says:

    BTW.

    this whole OWA procedure is completly unnecessary.

    you have to send the meeting request like i described here:

    http://weblogs.sqlteam.com/mladenp/articles/9560.aspx

    in

    Life of an item in WebDAV generation process and problems that arise – 3. Meeting Requests

    and if you set the

    urn:schemas:calendar:uid to some value

    then the meeting request cancelatation is simple.

    1. take you appointment to which you’ve already set the UID.

    2. set http://schemas.microsoft.com/exchange/outlookmessageclass = "IPM.Schedule.Meeting.Canceled"

    urn:schemas:mailheader:content-class = "urn:content-classes:calendarmessage"

    urn:schemas:calendar:method = "Cancel"

    3. proppatch that

    4. move the appointment to ##DavSubmissionURI##

    update is a maater of updating the appointment and sending another meeting request.

  3. dszabo says:

    Hello,

    Sorry for the late reply.

    No, it’s not normal if the cancelMsgLocation is the same Url as the original appointment, as it should be a meeting cancellation item pre-created by OWA for you to send it out, in your Drafts folder. This is typically the cancel message that you can edit before sending out the cancellation, this is why it takes 2 step to do it.

    Are you sure that you are reading out the correct URL from OWA’s response? If you do the cancellation with OWA on the UI, does it work? Maybe, the creation of the meeting request went wrong? If cancelling a meeting request you created with you application works with OWA from the UI, trace the network activity while doing it, then look up the cancellation packets and you’ll find what’s wrong with your code.

    The reason I used OWA was that (1) you don’t have to reverse engineer how meeting cancellations work inside, OWA does the job for you, you just have to specify the input parameters and the business logic works fine, and (2) because WebDav meeting cancellation is also not supported (its schema is not fully documented) and the Exchange dev team more suggests the OWA way to do it if needed.

  4. Manju says:

    Hi,

    I have two different exchange organizations in different forest and active directory. Let’s call it A and B. I use outlook or OWA to create meeting requests from A Org to users in B Org. These organizations depend on internet or smart host to communicate.

    I want to how these meeting are carried between each organization ?

    I am obeserving different times when they are scheduling meetings:(

    How do I track, these meeting sent from A to B or B to A over internet ?

    any inputs appreciated

  5. dszabo says:

    Hello Manju,

    * The meeting requests are traveling in an ICAL standard format if you do them using the OWA APIs or WebDav.

    * The free/busy part is another question (e.g. – how you look up the free/busy) – you have to solve this by looking up different free/busy URLs.

    * The different start times could be caused by different timezone settings in OWA. You can check it on the OWA control panel for your mailboxes.

    David

  6. Mauricio says:

    Hi David !

    You´re article is really nice !

    Is there a way my ASP.NET application can create meetings for different users, using only one credential for authentication againts exchange ?

  7. dszabo says:

    Hello Mauricio,

    Create a Credential object by specifying your system account’s name and share this object among web sessions (Session object) – not sure if the Credential object is thread safe, but I expect it is. You could also try sharing the HttpWebRequest object.

    David

  8. Joseph Sheinis says:

    Thanks a lot David, very useful.

    Your examples show to send requests from the meeting ORGANIZER’s mailbox.

    Can you dive an idea on how to submit accept / decline / propose new time requests from an ATTENDEE mailbox?

  9. dszabo says:

    Hi Joseph,

    Take a look at the following page: http://msdn2.microsoft.com/en-us/library/aa204771(office.11).aspx.

    This will help you in searching for the appropriate message classes – I’m sure you will find a few samples.

  10. jsheinis@nexjsystems.com says:

    Thanks a lot again David,

    Using message class means using Mladen’s approach with PROPPATCH, right? Is there an OWA way?

    When I’ve tried Mladen’s approach, I got the cancellation email delivered to all the attendees, including the organizer – what could I be doing wrong?

    And I did not find a message class for the "propose new time" request 🙁

  11. dszabo says:

    Hello again Joseph,

    You are right, there are not too many examples around on these things. I suggest doing what I did – run a network monitor on your client box and use OWA to cancel the meeting request or to run the Propose New Time command. You’ll be able to reverse-engineer the HTTP requests then and you’ll be 100% compliant with OWA. Be aware that most of these methods don’t work with Exchange 2007. If you are going to deliver something for an Exchange 2007 Customer, use the new web services instead! Cheers, David

  12. Khan says:

    I have built a complete API on Sending/Cancelling/Updating meeting requests with WebDAV. It supports freebusy lookup too. EMail me at khanrocks@gmail.com to know more about it.

  13. khan says:

    I can’t provide you the code as it is confidential but I can certainly help on this topic. So If you have any questions please email me.

  14. Mehrdad Zandi says:

    Hi everybody,

    I want to implement an api to access the Exchange Server’s Conference room booking without using outlook.

    The implemented API should be worked in a device with Window CE ( a thin OS).

    The API should get, uppdate, delete the Conference booking rooms.

    If some body is created a new booking by outlook, then

    the new booking should be displayed on the devise display.

    Best regards

    /Mehrdad

  15. Tim says:

    I’ve tried this several times (the udpate) and it’s not working.  I receive a 200 response the meeting update never gets sent.  I’ve traced it using fiddler and the request is identical to the one in OWA.

    Here’s my code:

           Dim meetingItemUrl As String

           Dim request As Net.HttpWebRequest

           Dim MyCredentialCache As System.Net.CredentialCache

           Dim requestStream As IO.Stream

           Dim owaResponse As Net.HttpWebResponse

           Dim s As New Text.StringBuilder

           Dim bytes() As Byte

           meetingItemUrl = strCalendarUri + owaAppointmentItemName & ".eml"

           MyCredentialCache = New System.Net.CredentialCache

           MyCredentialCache.Add(New System.Uri(strCalendarUri), "BASIC", New System.Net.NetworkCredential(My.Settings.mail_owa_username, My.Settings.mail_owa_password))

           request = Net.HttpWebRequest.Create(meetingItemUrl)

           request.Method = "POST"

           request.ContentType = "application/x-www-UTF8-encoded"

           request.Credentials = MyCredentialCache

           request.Accept = "*/*"

           request.Headers.Add("Accept-Language", "en-us")

           request.Headers.Add("Accept-Encoding", "gzip, deflate")

           request.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)"

           requestStream = request.GetRequestStream()

           s.AppendLine("Cmd=sendappt")

           s.AppendLine("Required=")

           s.AppendLine("Optional=")

           s.AppendLine("Resource=")

           s.AppendLine("FormType=appointment")

           s.AppendLine(String.Format("MsgID=/Calendar/{0}", owaAppointmentItemName & ".eml"))

           s.AppendLine(String.Format("urn:schemas:httpmail:subject={0}", v_subject))

           s.AppendLine(String.Format("urn:schemas:calendar:location={0}", "Changed location"))

           s.AppendLine(String.Format("CALENDAR:UID=/Calendar/{0}", owaAppointmentItemName & ".eml"))

           s.AppendLine("urn:schemas:calendar:responserequested=1")

           s.AppendLine(String.Format("http://schemas.microsoft.com/exchange/subject-utf8={0}", v_subject))

           s.AppendLine(String.Format("urn:schemas:httpmail:textdescription={0}", "This is an updated meeting request"))

           bytes = System.Text.Encoding.UTF8.GetBytes(s.ToString)

           requestStream.Write(bytes, 0, bytes.Length)

           requestStream.Flush()

           requestStream.Close()

           owaResponse = request.GetResponse

           owaResponse.Close()

  16. Ronnie Peretz says:

    First great article.

    I don’t know if you can help me but do you know a link that has more help on WebDav? I manage to create a an Appointment (WebDAV) but if you setup anther appointment at the same time it overwrite my first appointment. I rather have both appointment. do you have the same problem?

    I used that code:

    http://msdn.microsoft.com/en-us/library/ms877306(EXCHG.65).aspx

  17. ram says:

    Thanks for your post.

    I am not able to use your code in exchange server 2003 currently.

    is it still valid?

    i am able to create appointment using webdav but not able to send invites to the attendees.

    Any suggestions are welcome.

    My issue is as explained below.

    social.msdn.microsoft.com/…/88ccc151-3525-4c87-850d-fafb13f4de73

Comments are closed.

Skip to main content