Sicos1977 / MSGReader

C# Outlook MSG file reader without the need for Outlook
http://sicos1977.github.io/MSGReader
MIT License
489 stars 168 forks source link

Is it possible to get the Outlook conversation id from a message? #382

Closed AlexPeek01 closed 10 months ago

AlexPeek01 commented 10 months ago

Hi Sicos,

For a project I'm working on I need to obtain the ConversationId from Outlook messages to group them by e-mail chain. Ideally we don't want to use Microsoft Graph or something similar to obtain these since we want users to be able to upload .msg and .eml files even after deleting the original e-mails from their mailbox.

I saw that the ConversationTopic and ConversationIndex were available, but couldn't find the Id. I tried obtaining these through the GetMapiProperty and GetNamedMapiProperty methods, but this kept returning null. The values that I tried for these methods were: 0x0071, 0x0071001E, 0x0070, 0x0070001E, 0x3013001E, PidTagConversationId and PR_CONVERSATION_INDEX. Could you provide any guidance or insights on if this can be done and if so how? If this is not possible do you happen to know of any alternative properties that could provide the same functionality.

Thanks in advance!

Kind regards, Alex

Sicos1977 commented 10 months ago

I added a property to get the conversation id but that is doing almost the same as what you did. So there probably isn't an id inside the msg file to get. If you want you can sent me the file (zip it before sending) so that I can look inside it to see if the id is even in there.

You can sent the file to sicos2002@hotmail.com (ZIP the file before sending !!)

Sicos1977 commented 10 months ago

What you probably need if you want to chain MSG files together is the ThreadIndex --> https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxomsg/9e994fbb-b839-495f-84e3-2c8c02c7dd9b?redirectedfrom=MSDN

This index is normally in the conversation index property or in case of EML files in the ThreadIndex header. In the past I started to program something to do something with it but there was nu use case at that moment so I never finished it.

using System;
using System.Linq;

/// <summary>
///     Decodeert de opgegeven Base64 Outlook thread index
/// </summary>
public sealed class OutlookThreadIndex
{

    #region Properties
    /// <summary>
    ///     Retourneert het <see cref="Guid" /> id van de index
    /// </summary>
    public Guid Id { get; }

    /// <summary>
    ///     Retourneert de datum/tijd van de thread index
    /// </summary>
    public DateTime Date { get; }

    /// <summary>
    ///     Retourneert de ruwe thread index string
    /// </summary>
    public string Raw { get; }

    /// <summary>
    ///     Retourneert <c>true</c> wanneer de thread index valide is
    /// </summary>
    public bool IsValid => Date == default && Id != default;
    #endregion

    #region Constructor
    /// <summary>
    ///     Decodeert de opgegeven Base64 Outlook <paramref name="threadIndex" />
    /// </summary>
    /// <param name="threadIndex"></param>
    public OutlookThreadIndex(string threadIndex)
    {
        Raw = threadIndex;

        var bytes = Convert.FromBase64String(threadIndex);

        // Thread index length should be 22 plus extra 5 bytes per reply
        if (bytes.Length < 22 || (bytes.Length - 22) % 5 != 0)
            return;

        Id = new Guid(bytes.Skip(6).Take(16).ToArray());

        var headerTicks = bytes
                              .Take(6)
                              .Select(b => (long)b)
                              .Aggregate((l1, l2) => (l1 << 8) + l2)
                          << 16;

        Date = DateTime.FromFileTimeUtc(headerTicks);
        //Date = new DateTime(headerTicks, DateTimeKind.Utc).AddYears(1600);

        var childBlockCount = (bytes.Length - 22) / 5;

        for (var i = 0; i < childBlockCount; i++)
        {
            var childTicks = bytes
                                 .Skip(22 + i * 5).Take(4)
                                 .Select(b => (long)b)
                                 .Aggregate((l1, l2) => (l1 << 8) + l2)
                             << 18;

            childTicks &= ~((long)1 << 50);
            Date = Date.AddTicks(childTicks);
        }
    }
    #endregion

    #region ToString
    /// <summary>
    ///     Retourneerd een leesvriendelijke weergave van de thread index
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $"Id: {Id}, Date: {Date.ToLocalTime()}, Valid: {IsValid}";
    }
    #endregion
}
AlexPeek01 commented 10 months ago

First off, thanks for the quick and detailed responses it has helped us a lot. I was under the impression that the Conversation Index was the same as the Conversation Index which Microsoft Graph returns, but these appear to be different. It looks like as you mentioned that we can simply use the ConversationIndex/ThreadIndex for our problem.

Thanks again! Alex

Sicos1977 commented 10 months ago

Prima en succes met de rest :-)

AlexPeek01 commented 10 months ago

Bedankt :D