In my Last Post, I talked about how we get the three mystery hex values necessary to create the “sharing_metadata.xml” file, which is a crucial piece that get’s attached to a sharing invitiation message before it’s sent out to the user we want to share a folder with. Now that we have those values, let’s move on to actually building the XML document for the attachement…
UPDATE 4/22: (Thanks for pointing this out SteveR!)
I forgot to include in this post the method I use to actual GET the mailboxserver value for using in some of the code examples. Its in the user settings configuration. The code I used to obtain it is as follows:
public Dictionary<string, string> GetUserSettings(AutodiscoverService autodiscoverService) { // Get the user settings. // Submit a request and get the settings. The response contains only the // settings that are requested, if they exist. GetUserSettingsResponse userresponse = autodiscoverService.GetUserSettings( txtImpersonatedUser.Text, UserSettingName.UserDisplayName, UserSettingName.InternalMailboxServer, UserSettingName.UserDN ); Dictionary<string, string> myUserSettings = new Dictionary<string, string>(); // Obviously this should be cleaned up with a switch statement // or something, but I was working through the problem hence the // extra effort on the code foreach (KeyValuePair<UserSettingName, Object> usersetting in userresponse.Settings) { if (usersetting.Key.ToString() == "InternalMailboxServer") { string[] arrResult = usersetting.Value.ToString().Split('.'); myUserSettings.Add("InternalMailboxServer", arrResult[0].ToString()); } if (usersetting.Key.ToString() == "UserDisplayName") { string[] arrResult = usersetting.Value.ToString().Split('.'); myUserSettings.Add("UserDisplayName", arrResult[0].ToString()); } if (usersetting.Key.ToString() == "UserDN") { string[] arrResult = usersetting.Value.ToString().Split('.'); myUserSettings.Add("UserDN", arrResult[0].ToString()); } } return myUserSettings; }
Creating the sharing_metadata.xml File
This part is fairly straightforward. Now that we have the 3 magic hex values, let’s look at the elements in the XML document one more time and see what we need in order for this to work:
Elements:
– Hex Id of the folder we want to share (check)
– SmtpAddress of user sharing the folder (check)
– The Hex Id of the Address book object for user sharing the folder (check)
– The Hex Id of the mailbox where the folder resides (check)
– SmtpAddress of the user being offered the invititation (check)
– The datatype of the invitation (in this case “calendar” – check)
Word of Caution
When working with XML documents, case sensitivity is VERY IMPORTANT, so you must be sure you have all your values typed up correctly. I spent a good 3 – 4 hours wasting my time troubleshooting random things when ultimately the reason my invitation was corrupted was because I had the element in the XML file labeled as “EntryID” instead of “EntryId”. So pay close attention when(if) you build XML files statically like I did…
By using the function below, we can generate a file that we will use later to attach to the sharing message object:
public void CreateSharingMessageAttachment(string folderid, string userSharing, string userSharingEntryID, string invitationMailboxID, string userSharedTo, string dataType) { XmlDocument sharedMetadataXML = new XmlDocument(); try { // just logging stuff as well during my debugging using (StreamWriter w = new StreamWriter("SharingMessageMetaData.txt",false,Encoding.ASCII)) { // Create a String that contains our new sharing_metadata.xml file StringBuilder metadataString = new StringBuilder("<?xml version=\"1.0\"?>"); metadataString.Append("<SharingMessage xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "); metadataString.Append("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "); metadataString.Append("xmlns=\"http://schemas.microsoft.com/sharing/2008\">"); metadataString.Append("<DataType>" + dataType + "</DataType>"); metadataString.Append("<Initiator>"); metadataString.Append("<Name>" + GetDisplayName(userSharing) + "</Name>"); metadataString.Append("<SmtpAddress>" + userSharing + "</SmtpAddress><EntryId>" + userSharingEntryID.Trim()); metadataString.Append("</EntryId>"); metadataString.Append("</Initiator>"); metadataString.Append("<Invitation>"); metadataString.Append("<Providers>"); metadataString.Append("<Provider Type=\"ms-exchange-internal\" TargetRecipients=\"" + userSharedTo + "\">"); metadataString.Append("<FolderId xmlns=\"http://schemas.microsoft.com/exchange/sharing/2008\">"); metadataString.Append(folderid); metadataString.Append("</FolderId>"); metadataString.Append("<MailboxId xmlns=\"http://schemas.microsoft.com/exchange/sharing/2008\">"); metadataString.Append(invitationMailboxID); metadataString.Append("</MailboxId>"); metadataString.Append("</Provider>"); metadataString.Append("</Providers>"); metadataString.Append("</Invitation>"); metadataString.Append("</SharingMessage>"); // MessageBox.Show(metadataString.ToString(), "metadataString before loading into soapEnvelope"); sharedMetadataXML.LoadXml(metadataString.ToString()); ExchangeFolderPermissionsManager.form1.Log(metadataString.ToString(), w, "Generate XML"); // MessageBox.Show("returning SOAP envelope now"); w.Close(); } string tmpPath = Application.StartupPath + "\\temp\\"; sharedMetadataXML.Save(tmpPath + "sharing_metadata.xml"); } catch (Exception eg) { MessageBox.Show("Exception:" + eg.Message.ToString(), "Error Try CreateSharedMessageInvitation()"); } }
That’s pretty much all there is to the attachment file, so let’s move on to the actual invitation message itself…
Background on my approach
When you send an email message by way of the Exchange Web Services, API, it is fairly simple. The following code is from the example included in the Microsoft document Getting Started with EWS Managed API:
// Create an email message and identify the Exchange service. EmailMessage message = new EmailMessage(service); // Add properties to the email message. message.Subject = "Interesting"; message.Body = "The merger is finalized."; message.ToRecipients.Add("user1@contoso.com"); // Send the email message and save a copy. message.SendAndSaveCopy();
Not much to it right? Well if we look at the sharing invitation as an email message with just alot of extra properties tagged on, we come to the conclusion that the hard part about this is not actually creating the invitation message, but knowing what properties to set and how to set them properly. The majority of my time during this adventure I found myself pouring through three or four different variants and examples of how to set the properties, so I was getting confused and spending more time validating the information on Microsoft’s web site versus actually having problems with the code itself.
My first issue was because I wasn’t familiar with EWS’s extended properties capabilities, I couldn’t validate my code was running right. Finally, I bit the bullet and posted a question regarding my tactics in the MSDN Forums. Glen Scales, whose posts and blog I ended up returning to frequently for information, was able to run my code successfully so at that point I knew I was working in the right direction, I just needed to set all the properties properly. At this point was when I returned to viewing my code line by line and discovering the “EntryId” attribute on my sharing xml file was actually entered as “EntryID”. Once I changed it, the message sent and I could open it up to share my calendar in OWA or Outlook 2010.
Where I’m getting at is while I don’t believe you actually HAVE to set every one of these extended properties for a message to function properly, I was able to get these values to line up with their counterparts on my “control” invitation message sent through the normal means (ie sent through the client). So tweak/omit these at your own peril, but I got the process working so I wasn’t about to go back and try and figure out which ones were absolutely necessary. According to the Microsoft Protocol documents, these are the extended properties used in a sharing message so by God I was gonna set them!
One interesting item I want to point out was something I was able to confirm with Glen Scales during this conversation regarding the question I had on whether or not me not properly reading some of these properties on my control message was that I generated it in OWA. My question and Glen’s response are below:
(My post)
Thanks for the tips Glen,
Ok so the control reference (PR_Subject) totally came back and when I’m looking at the properties through outlook spy the ones I’m looking for (like x-sharing-capabilities) return a MAPI error so I’m thinking it’s one of two things:
1) Could it be that since I generated the Invitation through OWA instead of the outlook client there are certain properties that don’t get populated when I generate the invite?
or
2) The weird bug thing you mentioned due to my Exchange Version (as you said, SP1 RU5)
…
(Glen’s response)
1) Yep I don’t why but if you do it via OWA none of those extended properties are created and only the sharing_metadata.xml attachment is availble.
…
Ok, so from this I’m able to assume 1) my code works and 2) OWA was the right choice for a control because it sends a more “generic” form of sharing message that should be able to be used as a template, but since all my extra extended properties are working, again, this is why I didn’t try to ferret out the “unnecessary ones”…
So Which Extended Properties Do I Use?
I highly recommend you read the MS Protocol Specifications documents to familiarize yourself with all the properties and how they relate to the workflow of creating a sharing invitation. With that being said, I’m going to assume you either 1) don’t care or 2) are familiar with the protocols, so I am going to move on to the code I used to actually define and then set the extended properties on my sharing invitation.
Let’s start off with the first part, actually defining which properties we are going to be adding to this message:
// Bind to the web services and currently selected folder // Bind to Exchange and impersonate the guy I want to be the "sharer" ExchangeService service = GetExchangeService(); service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, txtImpersonatedUser.Text); // Let's get our values regarding the sharing objects // Notice that this is where the previous functions I // talked about come into play.... string tmpPath = Application.StartupPath + "\\temp\\"; string folderid = GetSharedFolderId("Calendar"); string folderidHex = GetConvertedEWSIDinHex(service,folderid,txtImpersonatedUser.Text); string initiatorEntryID = GetIntiatorEntryID(); string invitationMailboxID = GetInvitationMailboxId(txtImpersonatedUser.Text); string ownerSMTPAddress = txtImpersonatedUser.Text; string ownerDisplayName = GetDisplayName(txtImpersonatedUser.Text)); // This is where I need the binary value of that initiator ID we talked about byte[] binInitiatorEntryId = HexStringToByteArray(initiatorEntryID);
Now that I have all our values ready to be used to set the appropriate properties, let’s define all the extended properties we will be using to attach to this message.
// This is the Guid of the Sharing Provider in Exchange, and it's value does not change Guid binSharingProviderGuid = new Guid("{AEF00600-0000-0000-C000-000000000046}"); // Even though I don't think setting this property is mandatory, // it just seemed like the right thing to do and it works so I \ // ain't messin with it! byte[] byteSharingProviderGuid = binSharingProviderGuid.ToByteArray();
What we did there is first assign the GUID of the SharingProvider to a new Guid variable, because when we define these properties (remember, first we need to define them so our app knows what it means when we say ‘set extended property x to y’, since the API doesn’t have these properties available through all the usual means) we need to not only tell it the Id or Name, and what MapiProperty.Type the property is, we also need to tell it which providor is in charge. When we define extended properties we use the following format:
ExtendedPropertyDefinition SomeProperty = new ExtendedPropertyDefinition(Provider,ID or Name,PropertyType);
So without further adieu, let’s see how we define the Extended Properties for a Sharing Invitation message:
// Sharing Properties (in order of reference according to protocol examples in: [MS-OXSHARE]) // Common Message Object Properties // [MS-OXSHARE] 2.2.1 ExtendedPropertyDefinition PidTagNormalizedSubject = new ExtendedPropertyDefinition(0x0E1D, MapiPropertyType.String); // The PidTagSubjectPrefix is a zero-length string, so I do not set it // ExtendedPropertyDefinition PidTagSubjectPrefix = new ExtendedPropertyDefinition(0x003D, MapiPropertyType.String); // Sharing Object Message Properties // [MS-OXSHARE] 2.2.2.1 ExtendedPropertyDefinition PidLidSharingCapabilities = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A17, MapiPropertyType.Integer); // [MS-OXSHARE] 2.2.2.2 ExtendedPropertyDefinition PidNameXSharingCapabilities = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Capabilities", MapiPropertyType.String); // Sections 2.3 and 2.4 are also zero-length strings, so I won't set those either // [MS-OXSHARE] 2.2.2.3 // ExtendedPropertyDefinition PidLidSharingConfigurationUrl = new //ExtendedPropertyDefinition(PropertySetSharing, 0x8A24, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.4 //ExtendedPropertyDefinition PidNameXSharingConfigUrl = new //ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Config-Url", MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.5 ExtendedPropertyDefinition PidLidSharingFlavor = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A18, MapiPropertyType.Integer); // [MS-OXSHARE] 2.2.2.6 ExtendedPropertyDefinition PidNameXSharingFlavor = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Flavor", MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.7 ExtendedPropertyDefinition PidLidSharingInitiatorEntryId = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A09, MapiPropertyType.Binary); // [MS-OXSHARE] 2.2.2.8 ExtendedPropertyDefinition PidLidSharingInitiatorName = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A07, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.9 ExtendedPropertyDefinition PidLidSharingInitiatorSMTP = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A08, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.10 ExtendedPropertyDefinition PidLidSharingLocalType = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A14, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.11 ExtendedPropertyDefinition PidNameXSharingLocalType = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Local-Type", MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.12 ExtendedPropertyDefinition PidLidSharingProviderGuid = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A01, MapiPropertyType.Binary); // [MS-OXSHARE] 2.2.2.13 ExtendedPropertyDefinition PidNameXSharingProviderGuid = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Provider-Guid", MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.14 ExtendedPropertyDefinition PidLidSharingProviderName = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A02, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.15 ExtendedPropertyDefinition PidNameXSharingProviderName = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Provider-Name", MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.16 ExtendedPropertyDefinition PidLidSharingProviderUrl = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A03, MapiPropertyType.String); // [MS-OXSHARE] 2.2.2.17 ExtendedPropertyDefinition PidNameXSharingProviderUrl = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Provider-URL", MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.1 ExtendedPropertyDefinition PidLidSharingRemoteName = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A05, MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.2 ExtendedPropertyDefinition PidNameXSharingRemoteName = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Remote-Name", MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.3 ExtendedPropertyDefinition PidLidSharingRemoteStoreUid = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A48, MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.4 ExtendedPropertyDefinition PidNameXSharingRemoteStoreUid = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Remote-Store-Uid", MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.5 ExtendedPropertyDefinition PidLidSharingRemoteType = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A1D, MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.6 ExtendedPropertyDefinition PidNameXSharingRemoteType = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Remote-Type", MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.7 ExtendedPropertyDefinition PidLidSharingRemoteUid = new ExtendedPropertyDefinition(PropertySetSharing, 0x8A06, MapiPropertyType.String); // [MS-OXSHARE] 2.2.3.8 ExtendedPropertyDefinition PidNameXSharingRemoteUid = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "X-Sharing-Remote-Uid", MapiPropertyType.String); // Additional Property Constraints // [MS-OXSHARE] 2.2.5.1 ExtendedPropertyDefinition PidNameContentClass = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, "Content-Class", MapiPropertyType.String); // [MS-OXSHARE] 2.2.5.2 ExtendedPropertyDefinition PidTagMessageClass = new ExtendedPropertyDefinition(0x001A, MapiPropertyType.String); // From troubleshooting I noticed I was missing ExtendedPropertyDefinition PidTagPriority = new ExtendedPropertyDefinition(0x0026, MapiPropertyType.Integer); ExtendedPropertyDefinition PidTagSensitivity = new ExtendedPropertyDefinition(0x0036, MapiPropertyType.Integer); ExtendedPropertyDefinition PR_BODY_HTML = new ExtendedPropertyDefinition(0x1013, MapiPropertyType.Binary); //PR_BOD ExtendedPropertyDefinition PR_BODY = new ExtendedPropertyDefinition(0x1000, MapiPropertyType.String); ExtendedPropertyDefinition RecipientReassignmentProhibited = new ExtendedPropertyDefinition(0x002b, MapiPropertyType.Boolean);
One more thing…during my troubleshooting I was thinking that part of the problem was that the message body was not available in regular and html format, and somehow that was causing an issue.
Even though that didn’t turn out to be the case, I still went ahead and defined all instances I found for a message body and html counterpart, and built strings as well as the binary equivelants to set on the message…below is an example of what I did:
string strPRBODYHTML = "<html dir=\"ltr\">\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\r\n<meta name=\"GENERATOR\" content=\"MSHTML 8.00.7601.17514\">\r\n<style id=\"owaParaStyle\">P {\r\n MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px \r\n}\r\n</style>\r\n</head>\r\n<body fPStyle=\"1\" ocsi=\"0\">\r\n<tt>\r\n<pre>SharedByUserDisplayName (SharedByUserSmtpAddress) has invited you to view his or her Microsoft Exchange Calendar.\r\n\r\nFor instructions on how to view shared folders on Exchange, see the following article:\r\n\r\nhttp://go.microsoft.com/fwlink/?LinkId=57561\r\n\r\n*~*~*~*~*~*~*~*~*~*\r\n\r\n</pre>\r\n</tt>\r\n<div>\r\n<div style=\"direction: ltr;font-family: Tahoma;color: #000000;font-size: 10pt;\">this is a test message</div>\r\n</div>\r\n</body>\r\n</html>\r\n"; string strBODY = @" SharedByUserDisplayName (SharedByUserSmtpAddress) has invited you to view his or her Microsoft Exchange Calendar. For instructions on how to view shared folders on Exchange, see the following article: http://go.microsoft.com/fwlink/?LinkId=57561 *~*~*~*~*~*~*~*~*~* test body "; // Convert these to hex and binary equivelants to assign to their relevant // extended properties string hexPRBODYHTML = ConvertStringToHex(strPRBODYHTML); byte[] binPRBODYHTML = HexStringToByteArray(hexPRBODYHTML);
Finally! Creating the Message Itself
Now, lets start building our invitation. First I created the sharing attachment using our CreateSharingMessageAttachment() method from earlier, so we have that in waiting. From there I started creating the invitation:
// Create a new message EmailMessage invitationRequest = new EmailMessage(service); invitationRequest.Subject = "I'd like to share my calendar with you"; invitationRequest.Body = "Sent by Exchange Administrator on behalf of user"; invitationRequest.From = ownerSMTPAddress; invitationRequest.Culture = "en-US"; invitationRequest.Sensitivity = Sensitivity.Normal; invitationRequest.Sender = txtImpersonatedUser.Text; // Set a sharing specific property on the message invitationRequest.ItemClass = "IPM.Sharing"; /* Constant Required Value [MS-ProtocolSpec] */
Ok now let’s start assigning all of these extended properties to the message:
// Section 2.2.1 invitationRequest.SetExtendedProperty(PidTagNormalizedSubject, "I'd like to share my calendar with you"); /* Constant Required Value [MS-OXSHARE] 2.2.1 */ //invitationRequest.SetExtendedProperty(PidTagSubjectPrefix, String.Empty); /* Constant Required Value [MS-OXSHARE] 2.2.1 */ // Section 2.2.2 invitationRequest.SetExtendedProperty(PidLidSharingCapabilities, 0x40290); /* value for Special Folders */ invitationRequest.SetExtendedProperty(PidNameXSharingCapabilities, "40290"); /* Test representation of SharingCapabilities value */ //invitationRequest.SetExtendedProperty(PidLidSharingConfigurationUrl, String.Empty); /* Zero-Length String [MS-OXSHARE] 2.2.2.3 */ //invitationRequest.SetExtendedProperty(PidNameXSharingConfigUrl, String.Empty); /* Zero-Length String [MS-OXSHARE] 2.2.2.4 */ invitationRequest.SetExtendedProperty(PidLidSharingFlavor, 20310); /* Indicates Invitation for a special folder [MS-OXSHARE] 2.2.2.5 */ invitationRequest.SetExtendedProperty(PidNameXSharingFlavor, "20310"); /* Text representation of SharingFlavor value [MS-OXSHARE] 2.2.2.6 */ invitationRequest.SetExtendedProperty(PidLidSharingInitiatorEntryId, binInitiatorEntryId); /* Value from the Initiator/EntryId value in the Sharing Message attachment .xml document */ invitationRequest.SetExtendedProperty(PidLidSharingInitiatorSMTP, txtImpersonatedUser.Text); /* Value from Initiator/Smtp Address in the Sharing message attachment .xml document */ invitationRequest.SetExtendedProperty(PidLidSharingInitiatorName, ownerDisplayName); /* Value from Initiator/Name Address in the Sharing message attachment .xml document */ invitationRequest.SetExtendedProperty(PidLidSharingLocalType, "IPF.Appointment"); /* MUST be set to PidTagContainerClass of folder to be shared */ invitationRequest.SetExtendedProperty(PidNameXSharingLocalType, "IPF.Appointment"); /* MUST be set to same value as PidLidSharingLocalType */ invitationRequest.SetExtendedProperty(PidLidSharingProviderGuid, byteSharingProviderGuid); /* Constant Required Value [MS-OXSHARE] 2.2.2.12 */ invitationRequest.SetExtendedProperty(PidNameXSharingProviderGuid, "AEF0060000000000C000000000000046"); /* Constant Required Value [MS-OXSHARE] 2.2.2.13 */ invitationRequest.SetExtendedProperty(PidLidSharingProviderName, "Microsoft Exchange"); /* Constant Required Value [MS-OXSHARE] 2.2.2.14 */ invitationRequest.SetExtendedProperty(PidNameXSharingProviderName, "Microsoft Exchange"); /* Constant Required Value [MS-OXSHARE] 2.2.2.15] */ invitationRequest.SetExtendedProperty(PidLidSharingProviderUrl, "HTTP://www.microsoft.com/exchange"); /* Constant Required Value [MS-OXSHARE] 2.2.2.16 */ invitationRequest.SetExtendedProperty(PidNameXSharingProviderUrl, "HTTP://www.microsoft.com/exchange"); /* Constant Required Value [MS-OXSHARE] 2.2.2.17 */ // Section 2.2.3 invitationRequest.SetExtendedProperty(PidLidSharingRemoteName, "Calendar"); /* MUST be set to PidTagDisplayName of the folder being shared */ invitationRequest.SetExtendedProperty(PidNameXSharingRemoteName, "Calendar"); /* MUST be set to same value as PidLidSharingRemoteName */ invitationRequest.SetExtendedProperty(PidLidSharingRemoteStoreUid, invitationMailboxID); /* Must be set to PidTagStoreEntryId of the folder being shared */ invitationRequest.SetExtendedProperty(PidNameXSharingRemoteStoreUid, invitationMailboxID); /* MUST be set to same value as PidLidSharingRemoteStoreUid */ invitationRequest.SetExtendedProperty(PidLidSharingRemoteType, "IPF.Appointment"); /* Constant Required Value [MS-OXSHARE] 2.2.3.5 */ invitationRequest.SetExtendedProperty(PidNameXSharingRemoteType, "IPF.Appointment"); /* Constant Required Value [MS-OXSHARE] 2.2.3.6 */ invitationRequest.SetExtendedProperty(PidLidSharingRemoteUid, folderidHex); /* MUST be set to PidTagEntryId of folder being shared */ invitationRequest.SetExtendedProperty(PidNameXSharingRemoteUid, folderidHex); /* Must be set to same value as PidLidSharingRemoteUid */ // Section 2.2.5 invitationRequest.SetExtendedProperty(PidNameContentClass, "Sharing"); /* Constant Required Value [MS-ProtocolSpec] */ invitationRequest.SetExtendedProperty(PidTagMessageClass, "IPM.Sharing"); /* Constant Required Value [MS-ProtocolSpec] */ // ********* ADDITIONAL MAPPED PROPERTIES IM FINDING AS I TROUBLESHOOT ********************** // invitationRequest.SetExtendedProperty(PidTagPriority, 0); /* From troubleshooting I'm just trying to match up values that were missing */ invitationRequest.SetExtendedProperty(PidTagSensitivity, 0); /* From troubleshooting as well */ invitationRequest.SetExtendedProperty(PR_BODY_HTML, binPRBODYHTML); /* From troubleshooting OWA error pointing to serializing HTML failing */ invitationRequest.SetExtendedProperty(PR_BODY, strBODY); invitationRequest.SetExtendedProperty(RecipientReassignmentProhibited, true); /* Because it seemed like a good idea */
So there you have it, the message has now been formatted correctly to be interpreted by Exchange to be an internal sharing invitation between two mailboxes. But we’re not done yet!
Attaching the XML file and Sending the Message
The last piece focuses on properly attaching the xml document and sending it off to the user. Note I said PROPERLY. The only way I was able to get a byte for byte match on the xml file once it had been attached was by using the code below. I’m not saying there’s most likely a better way, but everything else I tried always appended 3 extra bytes to the beginning of the file…
// Add a file attachment by using a stream // We need to do the following in order to prevent 3 extra bytes from being prepended to the attachment string sharMetadata = File.ReadAllText(tmpPath + "sharing_metadata.xml",Encoding.ASCII); byte[] fileContents; UTF8Encoding encoding = new System.Text.UTF8Encoding(); fileContents = encoding.GetBytes(sharMetadata); // fileContents is a Stream object that represents the content of the file to attach. invitationRequest.Attachments.AddFileAttachment("sharing_metadata.xml",fileContents); // This is where we set those "special" headers and other pertinent // information I noted in Part 1 of this series... Attachment thisAttachment = invitationRequest.Attachments[0]; thisAttachment.ContentType = "application/x-sharing-metadata-xml"; thisAttachment.Name = "sharing_metadata.xml"; thisAttachment.IsInline = false;
Now that everythings set to go it’s as simple as addressing the messaging and sending it off:
// Add recipient info and send message invitationRequest.ToRecipients.Add(sharedToUser); invitationRequest.SendAndSaveCopy(); // I always end my methods by returning the EWS // impersonated user to null to clean up service.ImpersonatedUserId = null;
Conclusion
While not necessarily the easiest or most intuitive way to create and send a sharing message invitation through EWS, I definitely feel like this is only the tip of the iceberg and can’t wait till another project comes along that sparks my curiosity and requires me to get my hands dirty with some more Exchange Web Services…
This shows me that Microsoft is opening more and more up to people like me who are not necessarily “in the know” in the world of development, but are willing to do the required research necessary to understand and work through their messaging service, not against it!
In any case this was a great opportunity for me to learn more about EWS and Exchange 2010 administration possibilities. I do most of my work in powershell, but this was a good opportunity as well to jump back into c# for a little bit…
This post was geared towards someone trying to understand what it takes for sharing invitations with EWS (and has a little bit of development experience) out there, because I always found the questions regarding the subject having fairly vague or partial answers. That led me to believe it was possible but required alot of detail and attention. Now all these pieces are put together in one place, so hope it helps someone else!!!
Till next time…
Have you ever considered writing an e-book or
guest authoring on other websites? I have a blog centered on the
same topics you discuss and would really like to have you share some stories/information.
I know my readers would appreciate your work. If you are even remotely interested, feel free to shoot me an email.
Hi
I am trying to use your code to do the following: I have a shared calendar and want to automatically have the calendar published ( as an overlay ) in another user’s calendar. Can you help please?
If I understand your wanting outlook to show a shared calendar in an overlay? Or in OWA? Off the top of my head that seems an altogether different issue, like you’d have to use the local apis available from the Microsoft libraries to do something like that instead of EWS…I could be wrong but I think that’s more the direction you want to go…
Hi
We are looking to have overlay in OWA.
Here is the scenario :
A user has a calendar that he/she wants to share with another user (or group). We want to have this sharing done automatically by clicking a button on a website. The code should be able to share the calendar automatically without the approval of the recipient. [ Out of the box : a user need to go to outlook and send a sharing invitation that the recipient need to accept]…we want to skip that. Instead have the code automatically share the calendar and publish it in his calendar as a shared a calendar.
Thanks
You’ll need a service account running for the web app that has the ability to impersonate in Exchange..one process would create the invitation and maybe pass the message id to the second process that will need to login as the recipient and accept the invitation programmatically. I never got that far (automating the acceptance) so let me do some research and I’ll try to comment back in a day or two to let u know what I’ve found
Hi
Thanks for your help…will your code works with Office 365?
That I’m unsure of…I’ve only done this through an on premise install, nothing federated. In theory it should but I’m unsure of any specific constraints on the office 365 EWS APIs and what u can/can’t do
[…] ← Thanks for visiting!! Understanding Sharing Invitation Requests – EWS Managed API 1.2 : Part 2 → […]
Hi
I created an sample based on your example but i’m getting an error of
“The sharing message is not supported”.
i’m using ews managed api 2.0 and exchange 2013 version.
Any help is appreciated.
Hi
I created an sample based on your example but i’m getting an error of
“internal server occurred.the operation failed”. and i tried without attaching sharing metadata file it was sent successfully but at the receiver end it shows as “the sharing message is not valid. All action buttons have been disabled”.
any help is appreciated.
Naresh…so as I understand it the message sends with the xml attachment but says it’s invalid, and you’ve tried without but run into it also not functioning as intended?
It’s been a while…but I know I never got it to work without the attached xml file. I know a previous commenter stated he got it working but I never revisited that myself. I would focus on the validity of the xml file. By that I mean check to see if the xml file generated is not being encoded properly or has an extra carriage return or is otherwise malformed.
Part of my code shows I had issues with my xml files because I had to put a chunk of code in that removed two extra bytes being “prepended” to my file for whatever reason. Maybe you add/remove that block of code and rerun your tests to see if anything changes?..sorry I’m trying to remember as it’s been a while but if you get any more specific errors let me know.
One more thing I remember is the case sensitivity of the files xml tags. That gave me a huge headache so I would manually build your xml out (copying the exact strings from Ur code) and examine if the tags don’t match up with case or a close tag. (And after rereading my post – it was the attachment of the xml that caused me issues, not the xml itself. I had to force it through a filestream to encode properly)
I have created what I think is a copy of you code and it runs but I am getting errors from the EWS service when I try to send the email or if I fiddle with it I can get it to send but then the email client says it is invalid. I am using Office 365 so I don’t have access to the servers but I was wondering – how did you collect the information about the sharing email to compare with? you mention owa and outlookspy but I have not figured out how to look at a valid sharing email and compare it to what I am creating. Do you any more information on how you captured this information?
Thanks for checking the post out Nick! To be honest its been a while since I dabbled with the Exchange API so ill go back through my notes and hopefully have an answer for you in the next day or two (crammed schedule tomorrow) – but i will definately let you know what I find!
Nick,
This is being pieced together from memory and notes – so I am hoping it puts you in the right direction but I can’t guarantee I didn’t miss something:
To examine a message I sent a sharing invitation to another mailbox I had control over, and from OutlookSpy you can look at the “IMessage” information, and also from the “IMessage” window you can click the “GetNamesFromIDs()” button and see a lot of the properties I talked about in the blog.
Attached a couple images that show where I’m talking about in the UI with OutlookSpy
For OWA all I can think of is that I exported the invite from OWA to a msg file and opened it from OutlookSpy in the client. Not sure if that’s 100% it or if I used the another MAPI Editor/Viewer – but I do see the properties with OutlookSpy.
I hope that points you in the right general direction.
Cheers
Image 1: Select a message, then from Outlook Spy click “IMessage”:
Image 2: Clicking “GetNamesFromIDs()” and viewing extended properties of the message from there:
I got it working – thanks so much for you help. I played around with matching up a lot of properties using outlookspy but in the end the thing that made the difference is that the binSharingProviderGuid that you have did not match what OWA was using. The guid that worked for me is 0006f0ae-0000-0000-c000-000000000046. Once I change that out things started working. I ended up not using the attachment as it was not needed with Office 365 I just had to add all the extended properties.
Thanks – That is what I needed. I found a few differences and I am closer I will let you know how it goes.
Adding this value in the extended property definition,
Guid binSharingProviderGuid = new Guid(“{AEF00600-0000-0000-C000-000000000046}”);
make the outlook2007 to fail.
Otherwise, adapting the code it works Fine.