none
create appointment with different owner (bis) RRS feed

  • Question

  • Hi,

    we are creating an appointment using ews and we're trying to set the properties to show the appointment as created by someone else.

    we have manipulated several mapi attributes ( see for example https://social.msdn.microsoft.com/Forums/office/en-US/1c898db1-83ac-43f7-8fbb-3b85cfbedca3/create-appointment-with-different-owner?forum=exchangesvrdevelopment )

    It's now working fine with one exception:

    In outlook the block accept/Tentative/Decline/Propose New Time/Respond is not showing.

    We tried to compare an appointment created programmatically and a received invitation with mfcmapi but we didn't manage to find the difference that would trigger the displaying of this block.

    The real problem is that if we cancel the appointment, it does not propose to send an update to the organizer (but when we go to scheduling assistant, the correct owner is displayed).

    Any idea what is missing?


    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Thursday, January 28, 2016 12:03 AM

Answers

  • IMO this doesn't sound like a good idea going through the acceptance method will keep the meetings to spec what your trying to do sounds like it may produce an unsupported result which might lead so unwanted side affects for anybody who uses it. (eg it sounds like you owner hijacking which is what the bug in IOS activesync used to do which was a bad thing)

    It doesn't look like you understand what the difference is between tagged properties and Named properties this may help https://msdn.microsoft.com/en-us/library/office/cc979184.aspx

    PR_SENDER_ENTRYID is a tagged property so all you need is

    var extendedPropertyDefinition3 = new ExtendedPropertyDefinition(0x0C19, MapiPropertyType.Binary);
    Cheers
    Glen

    Wednesday, February 3, 2016 4:59 AM

All replies

  • Have you looked at the recipients collection ? it looks like you where only changing the Sender properties in your previous thread however the recipients collection is what represents the attendee/organizer of a Meeting. The other question is have you actually created a Meeting ? or just an appointment ?

    Cheers
    Glen

    Thursday, January 28, 2016 5:16 AM
  • Your're probably right when you point the meeting vs appointment.

    I thought that an appointment with attendees was a meeting but it's not the case.

    It actually created an appointment.

    With mfcmapi, I can see that the property PidLidMeetingType is missing.

    Spent a couple of hours trying to create a meeting but the attempts failed.

    CalendarItemType appointment = new CalendarItemType();
    appointment.ItemClass = "IPM.Appointment";
       

    If I set isMeeting = True and IsMeetingSpecified = true, it generates an error saying that the property is invalid (probably a readonly field).

    If I try to set it through MAPI, I'm stuck trying to send a Long ( I find it painfull to block on such simple details ).

     // MeetingRequest
    ExtendedPropertyType pr_MeetingRequest = new ExtendedPropertyType();
    PathToExtendedFieldType epExpr_MeetingRequest = new PathToExtendedFieldType();
    epExpr_MeetingRequest.PropertyType = MapiPropertyTypeType.Long;
    epExpr_MeetingRequest.PropertyTag = "0x80d5";
    pr_MeetingRequest.ExtendedFieldURI = epExpr_MeetingRequest;
    long lval = 1;
    pr_MeetingRequest.Item = lval;

    It raises an exception: The type System.Int64 may not be used in this context.


    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Thursday, January 28, 2016 11:08 PM
  • I would suggest you post your whole create request as I think you doing a number of things incorrectly.

    Any property in 0x805 range is going to be a named property and also the property type should be Integer not long (PT_Long == Integer in EWS see https://msdn.microsoft.com/en-us/library/office/cc839705.aspx) so your property definition should be

                PathToExtendedFieldType Prop = new PathToExtendedFieldType();
                Prop.PropertyId = 0x80d5;
                Prop.PropertyIdSpecified = true;
                Prop.DistinguishedPropertySetId = DistinguishedPropertySetType.Meeting;
                Prop.PropertyType = MapiPropertyTypeType.Integer;

    But you shouldn't really have to set that property manually

    Cheers
    Glen

    Friday, January 29, 2016 3:53 AM
  • public void CreateCalendarEntry(string userName) { CreateItemType createItemType = new CreateItemType(); createItemType.SavedItemFolderId = new TargetFolderIdType(); DistinguishedFolderIdType calendarFolder = new DistinguishedFolderIdType(); calendarFolder.Id = DistinguishedFolderIdNameType.calendar; createItemType.SavedItemFolderId.Item = calendarFolder; createItemType.Items = new NonEmptyArrayOfAllItemsType(); createItemType.Items.Items = new ItemType[1]; CalendarItemType calItem = CreateCalendar(); createItemType.Items.Items[0] = calItem; createItemType.SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType.SendToNone; createItemType.SendMeetingInvitationsSpecified = true; CreateItemResponseType createItemResponse = _EwsBinding.CreateItem(createItemType); ArrayOfResponseMessagesType responses = createItemResponse.ResponseMessages; TestReadDumpedMB.ExchangeWebServices.ResponseMessageType[] responseMessages = responses.Items; // Access the response messages. foreach (TestReadDumpedMB.ExchangeWebServices.ResponseMessageType respMsg in responseMessages) { if ((respMsg.ResponseClass == ResponseClassType.Error) || (respMsg.ResponseClass == ResponseClassType.Warning)) { Console.WriteLine("Error saving calendar : " + respMsg.MessageText); } else { Console.WriteLine("No Error saving calendar"); } } } private CalendarItemType CreateCalendar() CalendarItemType appointment = new CalendarItemType(); appointment.ItemClass = "IPM.Appointment"; // appointment.ItemClass = "IPM.Schedule.Meeting.Request"; appointment.Location = "Paris"; appointment.Subject = "test attendee"; appointment.Body = new TestReadDumpedMB.ExchangeWebServices.BodyType(); appointment.Body.BodyType1 = BodyTypeType.HTML; appointment.Body.Value = "message.Description"; TimeZoneDefinitionType tz = new TimeZoneDefinitionType(); tz.Id = "Romance Standard Time"; appointment.StartTimeZone = tz; appointment.EndTimeZone = tz; DateTime source = new DateTime(2016, 04, 24,10,15,00); appointment.Start = source; appointment.StartSpecified = true; DateTime target = new DateTime(2016, 04, 24, 11, 15, 00); appointment.End = target; appointment.EndSpecified = true; appointment.IsAllDayEvent = false; appointment.IsAllDayEventSpecified = false; appointment.RequiredAttendees = new AttendeeType[1]; appointment.RequiredAttendees[0] = new AttendeeType(); appointment.RequiredAttendees[0].Mailbox = new EmailAddressType(); appointment.RequiredAttendees[0].Mailbox.EmailAddress = "test@domain.com"; PathToExtendedFieldType prop = new PathToExtendedFieldType(); prop.PropertyId = 0x80d5; prop.PropertyIdSpecified = true; prop.DistinguishedPropertySetId = DistinguishedPropertySetType.Meeting; prop.PropertyType = MapiPropertyTypeType.Integer; //ExtendedPropertyType pr_MeetingRequest = new ExtendedPropertyType(); //pr_MeetingRequest.Item = prop; //appointment.ExtendedProperty = new ExtendedPropertyType[1]; //appointment.ExtendedProperty[0] = pr_MeetingRequest; return appointment; }

    In fact, if I set  createItemType.SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType.SendToNone, it creates an appointment.

    If I set  createItemType.SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType.SendOnlyToAll, it creates a meeting.

    What I have to achieve is create a meeting with send to none.

    If I try the piece of code you posted, I'm hitting the error:

    the type Test.ExchangeWebServices.PathToExtendedFieldType was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

    ( Using PathToExtendedFieldType is ok with other properties).

    If I try  appointment.ItemClass = "IPM.Schedule.Meeting.Request", I'm getting the error " Operation would change object type, which is not permitted."

    We must find a way to create a meeting without sending any mails. (then we relink the entries together using the CleanuGlobalObjectID).


    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Friday, January 29, 2016 12:34 PM
  • You shouldn't really be doing it this way in an Attendee mailbox because your not creating a Meeting in a supported way, what you should always be doing is create the Meeting in the Organizer mailbox add the attendees and then use the Accept operations for the attendees. In the Organizer Mailbox if you want to create a Meeting and not send invitation its generally a better idea to add the Organizer first and use the SendAndSave with no other attendees then added the Attendees in using the Updateitems this should set all the correct properties. eg

                EWSProxy.CalendarItemType NewMeeting = new EWSProxy.CalendarItemType();
                NewMeeting.Subject = "Test Meeting";
                NewMeeting.RequiredAttendees = new EWSProxy.AttendeeType[1];
                NewMeeting.RequiredAttendees[0] = new EWSProxy.AttendeeType();
                NewMeeting.RequiredAttendees[0].Mailbox = new EWSProxy.EmailAddressType();
                NewMeeting.RequiredAttendees[0].ResponseType = EWSProxy.ResponseTypeType.Organizer;
                NewMeeting.RequiredAttendees[0].ResponseTypeSpecified = true;
    
    
                NewMeeting.RequiredAttendees[0].Mailbox.EmailAddress = "gscales@datarumble.com";
    
                NewMeeting.Start = DateTime.Now;
                NewMeeting.StartSpecified = true;
                NewMeeting.End = DateTime.Now.AddHours(1);
                NewMeeting.EndSpecified = true;
                EWSProxy.TimeZoneDefinitionType tz = new EWSProxy.TimeZoneDefinitionType();
                tz.Id = "Romance Standard Time";
                NewMeeting.StartTimeZone = tz;
                NewMeeting.EndTimeZone = tz;
    
    
                EWSProxy.CreateItemType createItemRequest = new EWSProxy.CreateItemType();
                createItemRequest.MessageDisposition = EWSProxy.MessageDispositionType.SaveOnly;
                createItemRequest.MessageDispositionSpecified = true;
                createItemRequest.SendMeetingInvitationsSpecified = true;
                createItemRequest.SendMeetingInvitations = EWSProxy.CalendarItemCreateOrDeleteOperationType.SendToAllAndSaveCopy;
                createItemRequest.Items = new EWSProxy.NonEmptyArrayOfAllItemsType();
                createItemRequest.Items.Items = new EWSProxy.ItemType[1];
                createItemRequest.Items.Items[0] = NewMeeting;
    
    
                createItemRequest.SavedItemFolderId = new EWSProxy.TargetFolderIdType();
                EWSProxy.DistinguishedFolderIdType dfDraftsFolder = new
                EWSProxy.DistinguishedFolderIdType();
                dfDraftsFolder.Id = EWSProxy.DistinguishedFolderIdNameType.calendar;
                createItemRequest.SavedItemFolderId.Item = dfDraftsFolder;
    
                EWSProxy.ExchangeServiceBinding esb = new EWSProxy.ExchangeServiceBinding();
                esb.RequestServerVersionValue = new EWSProxy.RequestServerVersion();
                esb.RequestServerVersionValue.Version = EWSProxy.ExchangeVersionType.Exchange2013_SP1;
                esb.Url = service.Url.ToString();
                esb.Credentials = new NetworkCredential("user", "pass"); 
                esb.CookieContainer = new CookieContainer();
                EWSProxy.CreateItemResponseType createItemResponse = esb.CreateItem(createItemRequest);
    
                EWSProxy.ItemInfoResponseMessageType responseMessage = createItemResponse.ResponseMessages.Items[0] as EWSProxy.ItemInfoResponseMessageType;
                EWSProxy.CalendarItemType nmResp = responseMessage.Items.Items[0] as EWSProxy.CalendarItemType;
    
    
                EWSProxy.UpdateItemType uiUpdate = new EWSProxy.UpdateItemType();
    
                EWSProxy.CalendarItemType updateMeeting = new EWSProxy.CalendarItemType();
                updateMeeting.RequiredAttendees = new EWSProxy.AttendeeType[1];
                updateMeeting.RequiredAttendees[0] = new EWSProxy.AttendeeType();
                updateMeeting.RequiredAttendees[0].Mailbox = new EWSProxy.EmailAddressType();
                updateMeeting.RequiredAttendees[0].ResponseType = EWSProxy.ResponseTypeType.Accept;
                updateMeeting.RequiredAttendees[0].ResponseTypeSpecified = true;
                
                updateMeeting.RequiredAttendees[0].Mailbox.EmailAddress = "jcool@datarumble.com";
    
                EWSProxy.SetItemFieldType sit = new EWSProxy.SetItemFieldType();
                EWSProxy.PathToUnindexedFieldType att = new EWSProxy.PathToUnindexedFieldType();
                att.FieldURI = EWSProxy.UnindexedFieldURIType.calendarRequiredAttendees;
    
                EWSProxy.SetItemFieldType set = new EWSProxy.SetItemFieldType();
                set.Item = att;
                set.Item1 = updateMeeting;
                uiUpdate.SendMeetingInvitationsOrCancellationsSpecified = true;
                uiUpdate.SendMeetingInvitationsOrCancellations = EWSProxy.CalendarItemUpdateOperationType.SendToNone;
                uiUpdate.ItemChanges = new EWSProxy.ItemChangeType[1] { new EWSProxy.ItemChangeType() };
                uiUpdate.ItemChanges[0].Item = nmResp.ItemId;
                uiUpdate.ItemChanges[0].Updates = new EWSProxy.ItemChangeDescriptionType[1];
                uiUpdate.ItemChanges[0].Updates[0] = set;
    
                uiUpdate.ConflictResolution = EWSProxy.ConflictResolutionType.AutoResolve;
    
                EWSProxy.UpdateItemResponseType uiItemResponse = esb.UpdateItem(uiUpdate);
                responseMessage = uiItemResponse.ResponseMessages.Items[0] as EWSProxy.ItemInfoResponseMessageType;
                EWSProxy.CalendarItemType uiResp = responseMessage.Items.Items[0] as EWSProxy.CalendarItemType;
    Cheers
    Glen

    Monday, February 1, 2016 5:15 AM
  • Hi Glenn,

    we're making progress, we're close to a solution.

    In your sample, you create a meeting in gscales' mailbox.

    gscales is the owner, and the jcool is added as attendee.

    Good at this point.Then we do the same in jcool's mailbox

    We create the same meeting. jcool is the owner and gscales is added as attendee.

    Using our magic, we "link" them using the cleanglobabalobjectid attribute.

    Both are now organizer and both can cancel the meeting. (we're back to the point where we raised the question and we don't want jcool to be allowed to cancel it).

    It would be interesting if we could modify the PR_SENDER_ENTRYID after creation.

    We manage to forge it at creation time, but we do not get the syntax for modification.

    During creation we would do:

    // Representing PR_SENDER_ENTRYID 
                ExtendedPropertyType pr_senderENTRYID = new ExtendedPropertyType();
                PathToExtendedFieldType epExpr_senderENTRYID = new PathToExtendedFieldType();
                epExpr_senderENTRYID.PropertyType = MapiPropertyTypeType.Binary;
                epExpr_senderENTRYID.PropertyTag = "0x0C19";
                pr_senderENTRYID.ExtendedFieldURI = epExpr_senderENTRYID;
                pr_senderENTRYID.Item = BuildSenderID(name, email);

    For modification, we try :

               var extendedPropertyDefinition3 = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Appointment, 0x0C19, MapiPropertyType.Binary);
                appointment = Appointment.Bind(_Service, new ItemId(itemID), new PropertySet(extendedPropertyDefinition3));
                appointment.SetExtendedProperty(extendedPropertyDefinition3, val3);
                appointment.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone);
    

    But it creates a named prop name and does not modify the PR_SENDER_ENTRYID.

    ------------------------

    Note: Why are we trying to do it this way?

    We do migrate millions of appointments between systems. We do use a mechanism that sends invitation and programmatically we perform a kind of autoaccept (opent the target mailboxes, find the invitations and accept them) and we want to improve the mechanism and avoid to send millions of invitations



    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Tuesday, February 2, 2016 11:18 PM
  • IMO this doesn't sound like a good idea going through the acceptance method will keep the meetings to spec what your trying to do sounds like it may produce an unsupported result which might lead so unwanted side affects for anybody who uses it. (eg it sounds like you owner hijacking which is what the bug in IOS activesync used to do which was a bad thing)

    It doesn't look like you understand what the difference is between tagged properties and Named properties this may help https://msdn.microsoft.com/en-us/library/office/cc979184.aspx

    PR_SENDER_ENTRYID is a tagged property so all you need is

    var extendedPropertyDefinition3 = new ExtendedPropertyDefinition(0x0C19, MapiPropertyType.Binary);
    Cheers
    Glen

    Wednesday, February 3, 2016 4:59 AM
  • Thanks Glenn for your assistance on this, it really helps.

    "IMO this doesn't sound like a good idea..."

    What would be the alternative?

    - Microsoft API does not allow to create an entry with an alternate organizer

    - Microsoft API does not provide a way to send a meeting with an auto accept mechanism.

    Let's say you have 1000 users to migrate, 500 calendar entries in each and 2 attendees in each meeting (including external users).

    The "official way" would be to create the meeting with the flag sendtoall, then open the target mailboxes and accept the invitations. Result:

    - millions of invitations sent.

    - During migration by batches, target attendees might not exist yet.

    - Performance issues due to high level of calls, lots of impersonations, etc...

    By the way, this is what we've done over the past 2 years and the calendar migration is getting the bottleneck and the cause of all throttling we're getting ( despite sleep loops everywhere :-) ).


    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Wednesday, February 3, 2016 8:28 AM
  • Everything is working fine now.

    We managed to create the meeting without sending invitations everywhere and to modify the organizer after the creation of the meeting.

    The last point we are missing is to modify PidLidAppointmentStateFlags.

    I'll open a separate thread for this.

    Thank you very much again for your help.


    -- Emmanuel Dreux <a href="http://www.cloudiway.com" title="IAM and migration solutions for the Cloud"> http://www.cloudiway.com</a>

    Wednesday, February 3, 2016 11:51 PM