Use Graph SDK to Retrieve Compliance Records from Mailboxes

In a previous article, I explored how to use the Microsoft Graph PowerShell SDK to find mailbox items sent from specific addresses. The idea was to clean up mailboxes by removing notification emails sent by different Microsoft 365 services. This time, I explore finding messages addressed to specific recipients to discover insights about Copilot interactions.

The context is very different because we’re looking through items stored in a non-IPM folder. The non-IPM folders are system folders that are not exposed to regular email clients like Outlook. However, the folders store a lot of interesting information, including compliance records generated for use by eDiscovery and other Purview compliance solutions.

Compliance Records for Copilot Interactions

Compliance records captured for Copilot interactions are the target for our search. Each time someone prompts Copilot for Microsoft 365 for information, Microsoft 365 captures audit records to record the interaction. It’s possible to extract and analyze the audit records to gain an overview of how people are using Copilot for Microsoft 365. At the same time, because interactions take the form of a chat between Copilot and the user, Microsoft 365 captures compliance records in the hidden TeamsMessagesData folder in the user’s mailbox. Compliance records for messages posted to channel conversations are in the TeamsMessagesData folder in the group mailbox used by the host team.

Audit records capture a certain type of information to record that an action occurred and who performed the action. Because the function of compliance records is to capture information that can be used for eDiscovery and other compliance solutions, the data is different. In fact, compliance records are a modified and incomplete version of message items with properties like the sent date, created date, sender, recipient, and message body. Don’t let anyone tell you that Teams stores messages in Exchange Online. It doesn’t. The items being referred to are compliance records.

Teams and Copilot are not the only workloads that the Microsoft 365 substrate generates compliance records for. Similar arrangements exist for Viva Engage (Yammer) messages and Planner, albeit with those compliance records being kept in different folders.

You can access the TeamsMessagesData folder and view the Teams and Copilot compliance records stored there with the MFCMAPI utility. The PR_HTML property in a compliance record holds details of the interaction between Copilot and the user. For instance, Figure 1 shows details of a compliance record logged when the user opened a Word document, and Copilot automatically created a bulleted summary of the document content.

Copilot compliance record viewed through the MFCMAPI utility
Figure 1: Copilot compliance record viewed through the MFCMAPI utility

Finding the Identifier for the TeamsMessagesData Folder

Before a script can fetch compliance records from a folder, it must know how to find the items. In Graph terms, this means knowing the folder identifier. The problem is how to find the folder identifier when the Graph APIs don’t list non-IPM folders (Recoverable Items is a notable exception because it’s a well-known folder).

One solution is to run the Get-ExoMailboxFolderStatistics cmdlet to retrieve details of the TeamsMessagesData folder.

[array]$Folders = Get-ExoMailboxFolderStatistics -Identity $User.Id -FolderScope NonIPMRoot | Select-Object Name, FolderId 
$TeamsMessagesData = $Folders | Where-Object {$_.Name -eq "TeamsMessagesData"}

Name              FolderId
----              --------
TeamsMessagesData LgAAAAB+7ILpFNx8TrktaK8VYWerAQA3tTkMTDKYRI6zB9VW59QNAAMBRPnuAAAB

The folder identifier output by Exchange Online uses a format called StoreId. We need to convert the StoreId into the EntryId format before finally calling the translateExchangeIds API (using the Invoke-MgTranslateUserExchangeId cmdlet) to convert the EntryId value into the format used by Graph API requests, which is called a RestId. Too many types of identifier features in that description, but the point is that we convert the value output by Get-ExoMailboxFolderStatistics to make it usable with the Graph. The gory details of dealing with folder identifiers can be found in this article.

After all that processing, I’ll disclose a little secret. TeamsMessagesData is an undocumented member of the well-known folder set. The work done to find the folder identifier is unnecessary because the requests can simply reference “TeamsMessagesData” like “Inbox” or any other of the well-known folders. However, the technique described above will work for any mailbox folder and is therefore valuable to know.

Finding Copilot Interactions

Once a folder identifier is available, finding the compliance records for chat messages sent by Copilot is straightforward. There’s only one sender on a message and Graph filters work quite nicely against the sender. The filter is similar to the one used in the previous article with the difference that we’re checking against the sender’s display name rather than the email address:

[array]$Items = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId $RestId -All -PageSize 500 -Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate) and (sender/emailAddress/name eq '$CP0' or sender/emailAddress/name eq '$CP1' or sender/emailAddress/name eq '$CP2' or sender/emailAddress/name eq '$CP3' or sender/emailAddress/name eq '$CP4' or sender/emailAddress/name eq '$CP5') "
 -Property Sender, SentDateTime, Body Preview, ToRecipients

Copilot has neither a mailbox nor an account within the tenant, so it doesn’t have an SMTP address. The emailaddress property only holds display names, like “Copilot in PowerPoint”, and “Microsoft 365 chat.” The filter therefore operates against the display names captured in the compliance records.

Finding Messages Received by Copilot

Things are different when attempting to find messages received by Copilot. These are the prompts entered by users, so the information might be quite interesting. To find messages received by a certain address, we must search against the recipient data captured in the compliance records. A major technical difference is that a message can have many recipients of different types. The Graph forms a collection of to, cc, and bcc recipients for a message, but the question is then how to find compliance records for messages sent to Copilot, especially when Copilot records only store a display name.

The first problem is that the Graph doesn’t support filtering messages by a recipient (SMTP address or display name). You can’t filter against the toRecipients, ccRecipients, or bccRecipients properties.

Not being able to filter for a recipient is disappointing. Microsoft’s advice is to run a Graph search using the List messages in folder API using a searchable property for the messages collection.

Copilot interactions are not between users, so the compliance records don’t include an SMTP address. The records do include a display name, like “Copilot in Word” in the toRecipients property. According to Microsoft’s documentation, the searchable elements for addresses include SMTP address, alias, and display name, so this seems to match the need. To test the theory, we’ll look for messages addressed to Copilot and use Invoke-MgGraphRequest cmdlet to run the request. This example uses the participants property, which checks to, cc, and bcc recipients:

$Uri = ('https://graph.microsoft.com/V1.0/users/{0}/mailfolders/{1}/messages?$search="participants:copilot"' -f $user.id, $restid)
[array]$Data = Invoke-MgGraphRequest -Method Get -Uri $Uri

Unhappily, the search returns nothing. We know the method works because it is successful for IPM folders like Sent Items when the request runs through the Graph Explorer (Figure 2).

 A Graph request finds a message sent from an address
Figure 2: A Graph request finds a message sent from an address

The inability to search for recipients might be for many reasons. For instance, Microsoft might have blocked access to the message store index for the TeamsMessagesData folder because the intended search access is via eDiscovery. For whatever reason, we need a way to find the compliance records for prompts received by Copilot.

MVP Glen Scales, who knows more about interrogating mailboxes than most, pointed me to using the Outlook MAPI API to search message recipients. This method checks the single-value extended property that contains the display names of recipients for an exact match. We can therefore run a command like this to find compliance records for prompts sent to Copilot in Word:

[array]$Items = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId $RestId -All -PageSize 500 -Filter "singleValueExtendedProperties/any(ep:ep/id eq 'String 0x0E04' and ep/value eq 'Copilot in Word')"

Even better, because we need to find compliance records for interactions sent to Copilot in many different applications, a substring (“contains”) query can find the necessary items. Here’s the filter I used with the Get-MgUserMailFolderMessage cmdlet:

[array]$ItemsReceived = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId 'TeamsMessagesData' -All -PageSize 500 -Property Sender, SentDateTime, BodyPreview, ToRecipients -Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate) AND (singleValueExtendedProperties/any(ep:ep/id eq 'String 0x0E04' and contains(ep/value,'Copilot in')))" 

The only thing left to do is find the messages received by Microsoft 365 business chat, or put more correctly, Microsoft 365 Graph-grounded chat. The substring query clearly won’t find these records, so I ended up running a separate query:

[array]$ItemsChat = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId 'TeamsMessagesData' -All -PageSize 500 -Property Sender, SentDateTime, BodyPreview, ToRecipients -Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate) AND (singleValueExtendedProperties/any(ep:ep/id eq 'String 0x0E04' and ep/value eq 'Microsoft 365 Chat'))"

After running the three queries (find items sent by Copilot, items received by Copilot in apps, and items received by Microsoft 365 business chat), all that’s needed is to stitch the three arrays together into one and report what we found. Figure 3 shows an extract of the compliance records retrieved by the script.

 Reporting Copilot interactions
Figure 3: Reporting Copilot interactions

Figure 4 shows the summary of the Copilot interactions sent from the different apps. I removed messages sent by the user from this analysis because I wanted to highlight the most popular apps. The high number reported for Copilot in Word is due to the way that Copilot generates automatic summaries when it opens documents. The number reported for Copilot in SharePoint captures details of Copilot agents, both the default agents for sites and the custom agents created to reason over specific sets of content.

Practical Graph: Analyzing Microsoft 365 Copilot Interactions Using Compliance Records
Figure 4: Summarizing the Copilot Interactions for a user

You can download the script from GitHub. Please remember that this code is intended to demonstrate what’s possible rather than being a fully developed solution.

The Brute Force Approach

While grappling with the problem of finding compliance records received by compliance, I considered using a brute force approach. This involves the retrieval of all the compliance records matching the search period and applying a client-side filter to find the targeted messages. I say brute force because the TeamsMessagesData folder also holds compliance records for Teams chats. Given the popularity of Teams, a user could generate tens of thousands of compliance records annually. Fetching a large amount of data to extract the set relating to Copilot is not an elegant way of doing the job.

But sometimes technology forces you to do odd things. With that in mind, here’s some code to fetch the compliance records and extract the target set. The code minimizes the amount of data fetched by imposing a date range (I used one year) and requesting a minimal set of properties for each item. After fetching the information from the Graph, a loop through the records extracts the properties we need into a PowerShell list that is then filtered to extract the records for interactions sent by Copilot:

[array]$Items = Get-MgUserMailFolderMessage -UserId $User.Id -MailFolderId $RestId -All -PageSize 250 -Filter "(ReceivedDateTime ge $StartDate and ReceivedDateTime le $EndDate)" -Property Sender, SentDateTime, BodyPreview, ToRecipients

$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($Item in $Items) {
   
    $ReportLine = [PSCustomObject][Ordered]@{ 
        Sender  = $Item.Sender.emailaddress.Name
        To      = $Item.Torecipients.emailaddress.name -join ","
        Sent    = $Item.SentDateTime
        Body    = $Item.BodyPreview
    }
    $Report.Add($ReportLine)
}
[array]$CopilotRecords = $Report | Where-Object {($_.Sender -like "Copilot*") -or ($_.To -like "Copilot*") -or ($_.To -eq "Microsoft 365 Chat"}

The output of the $CopilotRecords array are extracts of the compliance records for Copilot interactions between Copilot and the user and vice versa. It’s what we want, even if it took some effort to get there. The bad news is that fetching lots of data from the Graph and applying a client-side filter is much slower, usually by a factor of 8 to 10 times.

Even without filtering, being able to extract and analyze Teams compliance records might be useful in an eDiscovery investigation scenario. Purview eDiscovery Premium can recreate Teams chat threads, which is something that can be easily done if you have all the compliance records for a chat to hand.

Lessons Learned

The lesson from this exercise is simple. It’s easy to use the Graph to find messages based on sender but not so easy to find messages based on a to, cc, or bcc recipient. However, even when the Graph APIs seem to not have an answer, some of the older APIs can help, which is what happened in this case.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Leave a Reply