5

Before you close this as a duplicate please look at the other similarly titled question, there is no answer to the problem he just marked it as answered and left.

I am getting this lovely and descriptive error from the EWS manged API whenever I attempt to edit the RequiredAttendees property on an appointment.

Set action is invalid for property.

Looking at the exception details shows me that it is indeed the RequiredAttendees property that is causing problems but I have no idea why.

The credentials I use to connect to the service are those of the meeting organizer, I have even tried impersonating the user with no luck. Scratching my head trying to figure out what went wrong here.

Here are the relevant parts of the update routine that are causing problems.

PropertySet props = new PropertySet(
        AppointmentSchema.Start,
        AppointmentSchema.End,
        AppointmentSchema.Id,
        AppointmentSchema.Organizer,
        AppointmentSchema.Subject,
        AppointmentSchema.Body,
        AppointmentSchema.RequiredAttendees);
props.RequestedBodyType = BodyType.Text;

Appointment appointment = Appointment.Bind(_service, new ItemId(appointmentId), props);

if (IsResource(appointment.Organizer.Address) && appointment.Organizer.Address != resourceId)
{
    /*
    * removed for brevity, no attendee manipulation here
    */
}
else 
{
    List<Attendee> remove = new List<Attendee>();
    foreach (var attendee in appointment.RequiredAttendees)
    {
        if (IsResource(attendee.Address) && attendee.Address != resourceId)
        {
            remove.Add(attendee);
        }
    }
    remove.ForEach(a => appointment.RequiredAttendees.Remove(a));
    if (!appointment.RequiredAttendees.Any(a => a.Address == resourceId))
    {
        appointment.RequiredAttendees.Add(resourceId);
    }
}

/*
* removed for brevity, no attendee manipulation here
*/

if (IsAvailable(resourceId, startTime, endTime, appointmentId))
    appointment.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToAllAndSaveCopy);
else
    throw new RoomUnavailableException();

Request Trace:

<Trace Tag = "EwsRequest" Tid="14" Time="2017-09-25 20:20:24Z" Version="15.00.0847.030">
  <?xml version = "1.0" encoding="utf-8"?>
  <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Header>
      <t:RequestServerVersion Version = "Exchange2013" />
    </ soap:Header>
    <soap:Body>
      <m:UpdateItem ConflictResolution = "AlwaysOverwrite" SendMeetingInvitationsOrCancellations="SendToAllAndSaveCopy">
        <m:ItemChanges>
          <t:ItemChange>
            <t:ItemId Id = "AAMkAGEwYWRjZjA3LWNlZjAtNDI2Ny05ZjQwLWUzYWZjOThhMjkzNwBGAAAAAABWdX+yf6THTpO/1LYpoG6xBwD6lEwS6u8XQbDhIlTh/X/UAAAAAAENAAD6lEwS6u8XQbDhIlTh/X/UAAAi3oSdAAA=" ChangeKey="DwAAABYAAAD6lEwS6u8XQbDhIlTh/X/UAAAi3ocU" />
            <t:Updates>
              <t:SetItemField>
                <t:FieldURI FieldURI = "calendar:RequiredAttendees" />
                < t:CalendarItem>
                  <t:RequiredAttendees>
                    <t:Attendee>
                      <t:Mailbox>
                        <t:Name>Exchange Test</t:Name>
                        <t:EmailAddress>etest @supertester.com</t:EmailAddress>
                        <t:RoutingType>SMTP</t:RoutingType>
                        <t:MailboxType>Mailbox</t:MailboxType>
                      </t:Mailbox>
                    </t:Attendee>
                    <t:Attendee>
                      <t:Mailbox>
                        <t:EmailAddress>redroom @supertester.com</t:EmailAddress>
                      </t:Mailbox>
                    </t:Attendee>
                  </t:RequiredAttendees>
                </t:CalendarItem>
              </t:SetItemField>
            </t:Updates>
          </t:ItemChange>
        </m:ItemChanges>
      </m:UpdateItem>
    </soap:Body>
  </soap:Envelope>
</Trace>

Response Trace:

<Trace Tag = "EwsResponse" Tid="14" Time="2017-09-25 20:20:24Z" Version="15.00.0847.030">
  <?xml version = "1.0" encoding="utf-8"?>
  <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Header>
      <h:ServerVersionInfo MajorVersion = "15" MinorVersion="1" MajorBuildNumber="225" MinorBuildNumber="41" Version="V2_48" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <m:UpdateItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
        <m:ResponseMessages>
          <m:UpdateItemResponseMessage ResponseClass = "Error" >
            <m:MessageText>Set action is invalid for property.</m:MessageText>
            <m:ResponseCode>ErrorInvalidPropertySet</m:ResponseCode>
            <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey>
            <m:MessageXml>
              <t:FieldURI FieldURI = "calendar:RequiredAttendees" />
            </m:MessageXml>
            <m:Items />
          </m:UpdateItemResponseMessage>
        </m:ResponseMessages>
      </m:UpdateItemResponse>
    </s:Body>
  </s:Envelope>
</Trace>
FlyingStreudel
  • 4,434
  • 4
  • 33
  • 55
  • I would suggest you enable tracing https://msdn.microsoft.com/en-us/library/office/dd633676(v=exchg.80).aspx and post the trace response which may tell you more about why the error happens. – Glen Scales Sep 17 '17 at 23:58
  • @GlenScales I added the traces. Still not sure why this isn't working. – FlyingStreudel Sep 25 '17 at 20:25
  • 1
    Are you trying to modify the original appointment or a copy ? eg its looks like your trying to modify the instance of an appointment located in a Meeting Room calendar which wouldn't work even if you impersonated the Organizer. The only copy that is valid for modification is the original item stored in the Organizer calendar. – Glen Scales Sep 26 '17 at 05:04
  • @GlenScales Ah ok that might be it, I'm pretty new to exchange I didn't realize there was a difference between the two. Thanks a bunch, I'll have to see how to find the original meeting. – FlyingStreudel Sep 26 '17 at 17:13
  • @GlenScales That was it, thanks a bunch! – FlyingStreudel Oct 03 '17 at 16:43

2 Answers2

3

Big thanks to Glen Scales for pointing me in the right direction with this one.

When I was retrieving appointments I was using the following code:

CalendarFolder calendar = CalendarFolder.Bind(_service, new FolderId(WellKnownFolderName.Calendar, resourceId), PropertySet.IdOnly);
CalendarView cView = new CalendarView(startDate, endDate, _maxAppointments);
cView.PropertySet = new PropertySet(PropertySet.IdOnly);
FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);

Where resourceId was the address of the room mailbox, not the organizer of the meeting.

Editing the attendees on an appointment that is not the original is not allowed so that is what was giving me the error. In order to update the attendees I have to retrieve the organizers appointment with the following code (borrowed heavily from this post):

appointment = FindOrganizerAppointment(appointment);

/// <summary>
/// Finds the related Appointment.
/// </summary>
/// <param name="appointment">The appointment whose original is to be found.</param>
/// <returns></returns>
private Appointment FindOrganizerAppointment(Appointment appointment)
{
    try
    {
        Impersonate(appointment.Organizer.Address);

        var filter = new SearchFilter.IsEqualTo
        {
            PropertyDefinition = new ExtendedPropertyDefinition
                (DefaultExtendedPropertySet.Meeting, 0x03, MapiPropertyType.Binary),
            Value = GetObjectIdStringFromUid(appointment.ICalUid)
        };

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

        return _service.FindItems(WellKnownFolderName.Calendar, filter, view).Items[0] as Appointment;
    }
    catch (Exception e)
    {
         throw e;
    }
    finally
    {
        DisableImpersonation();
    }
}

/// <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);
}
FlyingStreudel
  • 4,434
  • 4
  • 33
  • 55
1

Please do note that "RequiredAttendees" object will be filled only if it a meeting.

In your else statement add this check

if(appointment.IsMeeting)
{
  List<Attendee> remove = new List<Attendee>();
 foreach (var attendee in appointment.RequiredAttendees)
 {
    if (IsResource(attendee.Address) && attendee.Address != resourceId)
    {
        remove.Add(attendee);
    }
 }
 remove.ForEach(a => appointment.RequiredAttendees.Remove(a));
 if (!appointment.RequiredAttendees.Any(a => a.Address == resourceId))
 {
    appointment.RequiredAttendees.Add(resourceId);
 }
}

info about appointment meeting property MSDN link