Understanding Sharing Invitation Requests – EWS Managed API 1.2

Understanding Sharing Invitation Requests – EWS Managed API 1.2 : Part 1

Before I go into details on exactly how to accomplish programmatically creating a sharing invitation in C# for Exchange 2010 like it’s no big deal, I want to put a disclaimer down right up front:

Please consider the following while reading:

1) I in no way endorse or support any of the code posted here to be used in a production environment. I’m the last guy who needs to be blogging about optimized/secure coding practices and methodologies.
2) I am not responsible if you use this code and subsequently melt down your datacenter…
3) This is a proof of concept approach, so there are probably MANY areas that could be optimized to be less expensive when calling EWS. Again, I just wanted to see if it was possible…and I don’t code for a living…so don’t laugh at my code 😛
4) Finally, the following information on what I did will assume that you have an account setup for impersonation and have the security rights to perform all the relevant operations against EWS in order for this process to work.

Tools and Environment:

Environment Used:

Exchange Version: Exchange 2010 SP1 RU5
EWS Managed API Version: 1.2
My Operating System: Windows 7 x64
Application .NET version: Version 4.0 (NOT the Client Profile)

Development Tools Utilized:
– C# 2010 Express
– OutlookSpy
– EWSEditor
– MFCMAPI
– Outlook 2010

Other Items of note:

Using this method has only been tested for creating sharing folder invitations within the same Exchange Organization. Federated Sharing is a whole other topic that needs to be understood before trying to use this method to send sharing invites to external recipients…

Now that that’s out of the way, down to business!! So I had been playing with the Exchange Web Services Managed API 1.2 and recently built a relatively simple but effective utility that mimics Outlook’s Permissions GUI, except I added the ability to let myself impersonate anyone and used a ListTree of the users’ folders to allow me to manage the permissions on them. Yes, we do a lot of coddling to our users, but also realize we just got done with a huge GroupWise 7.0 -> Exchange 2010 SP1 RU5 migration and so I wanted an easy and effective way of handling the flood of requests coming in without having to perform too many steps to switch between users. The other reason for this app was that there were MANY outdated/irrelevant permissions that came across during the migrations, and some users had upwards of 100 folders and subfolders with tons of outdated permissions on them. Using EWS I was able to recursively loop through all the folders, leaving only the default permissions left for the user to set up once again how they wished. It’s pretty slick!!

In any case, that only whetted my appetite to see what else that the EWS Managed API could do. I decided as a final piece to this little utility, I wanted to be able to call a function that would create a sharing invite on behalf of the impersonated user and send it to the person I had just given reviewer rights on the default calendar folder for. Sounds simple enough…right???

WRONG!!!!! I tell you what, I was not prepared for this adventure….

First things first: Conversion from/to Binary/Hex & other *cough* fun stuff..

Before we even get into the structuring of the objects, let’s run through a couple functions I will be referencing in other code throughout this series.

In order to get the data formatted properly for some of the properties we will be setting later on the message and attachment objects, we need to be able to do two separate types of conversions: 1) Take a string and convert it to its hexadecimal equivalent and 2) Take that hex string and convert it to binary.

This function will take a string (i.e. “Mary a little lamb”) and return the Hexadecimal string value (“4D6172792061206C6974746C65206C616D62”):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public string ConvertStringToHex(string input)
{
    // Take our input and break it into an array
    char[] arrInput = input.ToCharArray();
    String result = String.Empty;
    // For each set of characters
    foreach (char element in arrInput)
    {
        // My lazy way of dealing with whether or not anything had been added yet
        if (String.IsNullOrEmpty(result))
        {
           result = String.Format("{0:X2}", Convert.ToUInt16(element)).ToString();
        }
        else {
            result += String.Format("{0:X2}", Convert.ToUInt16(element)).ToString();
        }
    }
    return result.ToString();
}

The following function will take the Hex value of your data (“4D6172792061206C6974746C65206C616D62”) and convert it to binary (Type byte[]):

Reference: Glen Scales on MSDN TechNet Forums
(His blog is here – If you have ANY curiosity about in-depth EWS development – his blog is the de-facto standard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static byte[] HexStringToByteArray(string input) {
           byte[] Bytes;
           int ByteLength;
           string HexValue = "x0x1x2x3x4x5x6x7x8x9|||||||xAxBxCxDxExF";
           ByteLength = input.Length / 2;
           Bytes = new byte[ByteLength];
           for (int x = 0, i = 0; i < input.Length; i += 2, x += 1)
           {
                      Bytes[x] = (byte)(HexValue[Char.ToUpper(input[i + 0]) - '0'] << 4);
                      Bytes[x] |= (byte)(HexValue[Char.ToUpper(input[i + 1]) - '0']);
           }
           return Bytes;
}

The Mystery that is the Sharing Invitation

My initial research into the subject led me to this post on StackOverflow, which, while not entirely filling in the whole picture, started me on the right track…

Ok, according to the post the “hard part” was building the invitation itself? Well, after a bit of fussing around with code I randomly found I decided to bite the bullet and spend the next three days reading the documentation for the Exchange Messaging Protocol Specifications.

BTW, We are going to be focusing on the default Calendar folder for the remainder of this series. Making this functionality work more dynamically is certainly possible, but way too involved to be worth the effort for me.

Turns out that there are two major components to the Sharing Invitation for a user’s folder.


Figure 1: Sharing Message Object (High Level) – For details, read [MS-OXSHARE] Specification on MSDN

Figure 1: Sharing Message Object (High Level)

The first component, illustrated in Figure 1, is a normal email message that has a bunch of information (in the form of Extended Properties) that tells Exchange that this is a sharing invitation and who it’s for along with a couple other pieces of information. Some of the properties are constants, or rather constant depending on the situation, but we’ll get to that later…


Figure 2: Sharing Message Attachment (High Level)

Figure 2: Sharing Message Attachment (High Level)

As you can see from Figure 2, the second component is a magic “sharing_metadata.xml” file that is attached to the message before sending. It contains information relevant to the sharing of the folder as well. There are only a few XML elements to talk about regarding the sharing_metadata.xml file, so I will conclude this post with an overview of the file, how to obtain the information necessary for the document, and how to properly encode the data so you do not get a corrupted invitation message.

XML Attachment: Contents of the file

Here’s the information inside a sharing_metadata.xml document (the Id’s have been changed to protect the innocent):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0"?>
<SharingMessage xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/sharing/2008">
  <DataType>calendar</DataType>
  <Initiator>
    <Name>SharedByUserDisplayName</Name>
    <SmtpAddress>SharedByUser@contoso.com</SmtpAddress>
<EntryId>00000000DCA740C8C042101AB4B908002B2FE18201000656D2D4B65697A657220507
5626C6963205363686F6F6C732F6F753D6D696E6973747261746976652047726F757020284654
9424F48463233535092F636E3D52656369704757436F6E6E00</EntryId>
  </Initiator>
  <Invitation>
    <Providers>
      <Provider Type="ms-exchange-internal" TargetRecipients="sharedToUser@contoso.com">
        <FolderId xmlns="http://schemas.microsoft.com/exchange/sharing/2008">
00000000EA272C6387A99442B4B371A18905E4DB01007F8000</FolderId>
        <MailboxId xmlns="http://schemas.microsoft.com/exchange/sharing/2008">
00000000EA272C6387A993A547A9A1B0F249B6491B00000000000038A1BB1005E5101AA1BB08002B
2A56C20000454D534D44422E444C4C00000000000000001B55FA20AA6611CD9BC800AA002FC45A0C
000000445443454D585075626C6963205363686F6F6C732F6F753D4578636867652041646D6520
</MailboxId>
      </Provider>
    </Providers>
  </Invitation>
</SharingMessage>

XML Attachment: Elements and Values

That’s it huh? No big deal right? Well, unless you’re a Cylon you’re probably looking at the values in that XML document going WTF is that??? Don’t feel bad…I did too…until I found some interesting documentation in Microsoft Exchange Protocol Specification Library

For starters, it looks like we need 6 values (all strings) to load into the xml at the appropriate elements. Let’s take a look at them: sharing_metadata.xml Elements and relevant information about them

Great, so we have three HEX values we need to figure out, and we want to constrain to the standards as strictly as possible here, because even though I just wanna do it to do it, I also want to see if I can apply this to any user. That’s when I found the MS docs that specify how each of those HEX entries are determined:

XML Attachment: Getting the Initiator EntryId Value

Initiator > EntryId
Also known as: PidTagEntryId of the AddressBook object of the user sharing the folder)

The EntryId is broken down into the following pieces (as documented on MSDN):

Canonical name: PidLidSharingInitiatorEntryId
Description: Contains the value of the PidTagEntryId property (section 2.761) for the Address Book object of the currently logged-on user.
Property set: PSETID_Sharing {00062040-0000-0000-C000-000000000046}
Property long ID (LID): 0x00008A09
Data type:PtypBinary, 0x0102
Area: Sharing
Defining reference: [MS-OXSHARE] section 2.2.2.7
Alternate names: dispidSharingInitiatorEid
Flags (4 bytes): This value MUST be set to 0x00000000. Bits in this field indicate under what circumstances a short-term EntryID is valid. However, in any EntryID stored in a property value, these 4 bytes MUST be zero, indicating a long-term EntryID.
ProviderUID (16 bytes): The identifier for the provider that created the EntryID. This value is used to route EntryIDs to the correct provider and MUST be set to %xDC.A7.40.C8.C0.42.10.1A.B4.B9.08.00.2B.2F.E1.82.
Version (4 bytes): This value MUST be set to %x01.00.00.00.
Type (4 bytes): An integer representing the type of the object. It MUST be one of the values from the following table.

Value (hex bytes) Address book EntryID type
0x00000000%x00.00.00.00 Local mail user (Note there are other values for external recipients, etc but that’s outside the scope of this article)

X500DN (variable): The X500 DN of the Address Book object. The X500DN field is a null-terminated string of 8-bit characters.

So what this told me ultimately was there is a binary property called “PidLidSharingInitiatorEntryId” value I will be needing to assign, and I can use the same value for the EntryId on the sharing_metadata.xml file, but I will need to use the hex representation of that property instead of it’s binary native datatype. (I will need the binary value as well later, but that’s for next post)…

Luckily we can “construct” this value using the following code snippets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
        public String GetMailboxDN()
        {
            string result = "error";
            AutodiscoverService autodiscoverService = new AutodiscoverService();
            // The following RedirectionUrlValidationCallback required for httpsredirection.
            autodiscoverService.RedirectionUrlValidationCallback = RedirectionUrlValidationCallback;
            autodiscoverService.Credentials = new WebCredentials(webCredentialObject);
            // 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.ToString(),
                UserSettingName.UserDisplayName,
                UserSettingName.UserDN,
                );
                foreach (KeyValuePair usersetting in userresponse.Settings)
                {
                    if (usersetting.Key.ToString() == "UserDN")
                    {
                        result = usersetting.Value.ToString();
                    }
                }
                return result;
        }
        public String GetIntiatorEntryID()
        {
            String result = String.Empty;
            // Bind to EWS
            ExchangeService service = GetExchangeService();
service.ImpersonatedUserId =
new ImpersonatedUserId(ConnectingIdType.SmtpAddress, txtImpersonatedUser.Text.ToString());
              // Get LegacyDN Using the function above this one
              string sharedByLegacyDN = GetMailboxDN();
              // A conversion function from earlier
              string legacyDNinHex = ConvertStringToHex(sharedByLegacyDN);
            StringBuilder addBookEntryId= new StringBuilder();
         // Note while I was debugging I logged this to a text file as well
            using (StreamWriter w = new StreamWriter("BuildAddressBookEntryIdResult.txt"))
            {
                addBookEntryId.Append("00000000"); /* Flags */
                addBookEntryId.Append("DCA740C8C042101AB4B908002B2FE182"); /* ProviderUID */
                addBookEntryId.Append("01000000"); /* Version */
                addBookEntryId.Append("00000000"); /* Type - 00 00 00 00  = Local Mail User */
                addBookEntryId.Append(legacyDNinHex); /* Returns the userDN of the impersonated user */
                addBookEntryId.Append("00"); /* terminator bit */
                //Log(addBookEntryId.ToString(), w, "GetAddBookEntryId");
                w.Close();
            }
            result = addBookEntryId.ToString();
            service.ImpersonatedUserId = null;
            return result;
        }

So if you do this you should get a string returned that looks something like the following (one long string):

00000000DCA740C8C042101AB4B908002B2FE18201000000000000002F6F
3D53SDSDSDSDFGA6572205075626C6963205SDDFSD63686F6F63D4578636
8616E67652041646D696E6973747261746976652047726F7570202846594
FDFDFE4FDFFF435350444C54292F636E3D52

Congratulations! One value down…two more to go!!!

XML Attachment: Getting the FolderId Value

Invitation > FolderId
Also known as: EWS FolderId (converted to Hex of course) of the folder being shared
The FolderId that we want to use for the sharing_metadata.xml file can be obtained with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        public String GetConvertedEWSIDinHex(ExchangeService esb, String sID, String strSMTPAdd)
        {
            // Create a request to convert identifiers.
            AlternateId objAltID = new AlternateId();
            objAltID.Format = IdFormat.EwsId;
            objAltID.Mailbox = strSMTPAdd;
            objAltID.UniqueId = sID;
            //Convert  PR_ENTRYID identifier format to an EWS identifier.
            AlternateIdBase objAltIDBase = esb.ConvertId(objAltID, IdFormat.HexEntryId);
            AlternateId objAltIDResp = (AlternateId)objAltIDBase;
            return objAltIDResp.UniqueId.ToString();
        }
// Bind to the folder
Folder folderStoreInfo;
folderStoreInfo = Folder.Bind(service, WellKnownFolderName.Calendar);
string EwsID = folderStoreInfo.Id.UniqueId;
// The value of folderidHex will be what we need to use for the FolderId in the xml file
string folderidHex = GetConvertedEWSIDinHex(service,folderid,txtImpersonatedUser.Text);

Now on to the last mystery value:

XML Attachment: Getting the MailboxId Value

Invitation > MailboxId
Also known as: Structured Value of Sharing Folder’s parent Mailbox

Flags (4 bytes): This value MUST be set to 0x00000000. Bits in this field indicate under what circumstances a short-term EntryID is valid. However, in any EntryID stored in a property value, these 4 bytes MUST be zero, indicating a long-term EntryID.
ProviderUID (16 bytes): The identifier for the provider that created the EntryID. This value is used to route EntryIDs to the correct provider and MUST be set to %x38.A1.BB.10.05.E5.10.1A.A1.BB.08.00.2B.2A.56.C2.
Version (1 byte): This value MUST be set to zero.
Flag (1 byte): This value MUST be set to zero.
DLLFileName (14 bytes): This field MUST be set to the following value, which represents “emsmdb.dll”: %x45.4D.53.4D.44.42.2E.44.4C.4C.00.00.00.00.
WrappedFlags (4 bytes): This value MUST be set to 0x00000000.
WrappedProvider UID (16 bytes): This field MUST be set to one of the values in the following table.

Store object type ProviderUID value
Mailbox Store object %x1B.55.FA.20.AA.66.11.CD.9B.C8.00.AA.00.2F.C4.5A
Public folder Store object %x1C.83.02.10.AA.66.11.CD.9B.C8.00.AA.00.2F.C4.5A

WrappedType (4 bytes): The value of this field is determined by where the folder is located. For a mailbox store this value MUST be set to %x0C.00.00.00. For a public store, this value MUST be set to %x06.00.00.00.
ServerShortname (variable): A string of single-byte characters terminated by a single zero byte, indicating the short name or NetBIOS name of the server.
MailboxDN (variable): A string of single-byte characters terminated by a single zero byte and representing the X500 DN of the mailbox, as specified in [MS-OXOAB]. This field is present only for mailbox database

Using this information we can construct the proper value by using the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public String GetInvitationMailboxId()
{
    ExchangeService service = GetExchangeService();
    service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, txtImpersonatedUser.Text);
    // Generate The Store Entry Id for the impersonated user
    StringBuilder MailboxIDPointer= new StringBuilder();
    // Notice again the logging for debugging the results
    using (StreamWriter w = new StreamWriter("BuildInvitationMailboxIdResult.txt"))
    {
        MailboxIDPointer.Append("00000000"); /* Flags */
        MailboxIDPointer.Append("38A1BB1005E5101AA1BB08002B2A56C2"); /* ProviderUID */
        MailboxIDPointer.Append("00"); /* Version */
        MailboxIDPointer.Append("00"); /* Flag */
        MailboxIDPointer.Append("454D534D44422E444C4C00000000"); /* DLLFileName */
        MailboxIDPointer.Append("00000000"); /* Wrapped Flags */
        MailboxIDPointer.Append("1B55FA20AA6611CD9BC800AA002FC45A"); /* WrappedProvider UID (Mailbox Store Object) */
        MailboxIDPointer.Append("0C000000"); /* Wrapped Type (Mailbox Store) */
        MailboxIDPointer.Append(ConvertStringToHex(GetMailboxServer()).ToString()); /* ServerShortname (FQDN) */
        MailboxIDPointer.Append("00"); /* termination bit */
        MailboxIDPointer.Append(ConvertStringToHex(GetMailboxDN()).ToString()); /* Returns the userDN of the impersonated user */
        MailboxIDPointer.Append("00"); /* terminator bit */
        Log(MailboxIDPointer.ToString(), w, "GetInvitiationEntryID");
        w.Close();
    }
    service.ImpersonatedUserId = null;
    return MailboxIDPointer.ToString();
}

Conclusion

So THAT’s all there is to it….well…to the very first step that is…which was getting the values necessary to build our own
sharing_metadata.xml file.

Next post I will explain how I constructed the attachment, and start explaining how Extended Properties fits in to the puzzle.

Till next time…

Post navigation

10 comments on “Understanding Sharing Invitation Requests – EWS Managed API 1.2 : Part 1”

  1. SteveR says:

    I’ve been figuring out how to send a sharing invitation using EWS and have independently come to a somewhat similar solution. I saw your question on Stack Overflow but only thought to look to see if any answers had been posted the day after I worked out how to do it myself. 🙂

    A few observations:

    1) I construct the various Ids (PidLidSharingInitiatorEntryId etc) as byte arrays and convert them to hex strings later as needed using BitConverter.ToString(id).Replace(“-“, “”)

    private byte[] CombineHeaderAndData(byte[] header, params string[] data)
    {
    var enc = new ASCIIEncoding();
    byte[] ret;

    using (var memStream = new MemoryStream())
    {
    memStream.Write(header, 0, header.Length);

    foreach (string item in data)
    {
    byte[] dnBytes = enc.GetBytes(item);
    memStream.Write(dnBytes, 0, dnBytes.Length);
    memStream.WriteByte(new byte());
    }

    ret = memStream.ToArray();
    }

    return ret;
    }

    private byte[] UserEntryIdFromX500DN(string x500dn)
    {
    var header = new byte[] { 0x00, 0x00, 0x00, 0x00, 0xDC, 0xA7, 0x40, 0xC8, 0xC0, 0x42, 0x10, 0x1A, 0xB4, 0xB9, 0x08, 0x00, 0x2B, 0x2F, 0xE1, 0x82, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

    return CombineHeaderAndData(header, x500dn);
    }

    This seems simpler and perhaps more explicit.The spec says that the various pieces of data such as MailboxDN are “strings of single-byte characters” which seems to imply ASCII.

    2) The GetUserSettingsResponse.Settings is an IDictionary so you can just grab the value(s) you need straight out of it rather than loop through them looking for the one you want i.e. userresponse.Settings[UserSettingName.UserDN] as string

    3) You don’t show getting the ServerShortname needed for the value of PidLidSharingRemoteStoreUid but I’ve found that this can be derived from the UserSettingName.InternalMailboxServer value retrieved in the same way as the other settings. Since the entire process needs to retrieve several of these settings it’s more efficient to retrieve them all together rather than seperately since this results in multiple requests to the server.

    So far I haven’t had to construct and attach the sharing_metadata.xml manually for the sharing invitation to work correctly, although I’ve only tried it in OWA. It was my assumption that the message gets properly constructed on the server using the extended properties that are set on the message, and that includes the necessary metadata being created and attached. I’m not 100% certain about it but it’s working so far.

    Anyway, your S.O. question helped us on the way to figuring this all out, so thanks for that!

    Understanding Sharing Invitation Requests – EWS Managed API 1.2 : Part 2

    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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.

    1
    2
    3
    4
    5
    6
    7
    // 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    string strPRBODYHTML = "<html dir="ltr">rn<head>rn<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">rn<meta name="GENERATOR" content="MSHTML 8.00.7601.17514">rn<style id="owaParaStyle">P {rn   MARGIN-TOP: 0px; MARGIN-BOTTOM: 0px rn}rn</style>rn</head>rn<body fPStyle="1" ocsi="0">rn<tt>rn<pre>SharedByUserDisplayName (SharedByUserSmtpAddress) has invited you to view his or her Microsoft Exchange Calendar.rnrnFor instructions on how to view shared folders on Exchange, see the following article:rnrnhttp://go.microsoft.com/fwlink/?LinkId=57561rnrn*~*~*~*~*~*~*~*~*~*rnrn</pre>rn</tt>rn

    rn

    this is a test message

    rn</div>rn</body>rn</html>rn";

               
    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:
    *~*~*~*~*~*~*~*~*~*
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    // 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…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 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:

    1
    2
    3
    4
    5
    6
    7
    8
    // 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…

Leave a comment