Delete Unwanted Teams Chat Threads with PowerShell and Azure Automation
In a previous article, I described the basics of using the Teams Delete Chat Graph API to remove, list, and recover chat threads. It’s nice to know that a Graph API exists and understand its basic operation. It’s much better to come up with a working example to help people understand the value of being able to interact with data in a specific way.
Thanking about how tenant administrators might use the ability to delete Teams chat threads, an analogy can be drawn with the occasional need to remove messages from user mailboxes. Perhaps because it is so useful at deleting messages, the Search-Mailbox cmdlet persists despite Microsoft’s best efforts to deprecate it. Administrators use Search-Mailbox in situations such as when some unwanted email arrives in user mailboxes and must be removed.
The same kind of circumstances arise in Teams if someone were to post inappropriate or unwanted content in a chat. For instance, an organization might want to remove a thread because it contains some sensitive information or a school might remove a thread because of some objectionable comments.
Of course, Data Loss Prevention (DLP) policies can stop unwanted content from appearing in chats, but Teams DLP requires higher-level licenses like Office 365 E5. Communication compliance policies can detect inappropriate comments in chats, but that’s another licensed solution. It’s nice to have a way to scan user accounts to find and remove certain chat threads, and that’s what I show how to do in this article.
The code described in this article illustrates the principles behind finding and removing Teams chat threads. It is not a complete solution, but the code does work and can be built on to meet the needs of organizations that need to delete chat threads.
Choosing Azure Automation and Managed Identities
In the previous article, I explained how to use cmdlets from the Microsoft Graph PowerShell SDK to report on the chat threads that a user has access to. This step reveals the chat identifiers, which are needed to remove chat threads. Microsoft PowerShell Graph SDK interactive sessions are limited to delegate permissions, meaning that only the data accessible to the signed-in user is available. Running the same commands after authenticating with an Entra ID registered app allows the use of application permissions and gains access to data across the tenant.
Instead of creating a registered app to run a script, I choose to implement the code in an Azure Automation PowerShell runbook. Scanning every user in a tenant to find target threads will take time, and that’s the kind of task that’s very suitable for Azure Automation. In addition, the Microsoft Graph PowerShell SDK supports managed identities, so I don’t need to worry about managing authentication.
On Demand Migration
Migrate all your workloads and Active Directory with one comprehensive Office 365 tenant-to-tenant migration solution.
Making Azure Automation Able to Delete Teams Chat Threads
Before an Azure Automation runbook can run PowerShell cmdlets to interact with Microsoft 365 data, it must have access to the PowerShell modules that contain the cmdlets used in the runbook and permissions to access the target data. In this case, the key elements needed to delete Teams chat threads using Azure Automation are:
- An Azure Automation account linked to an active Azure subscription.
- The latest version (2.5 or later) of the following SDK modules loaded into the Azure Automation account:
- Microsoft.Graph.Users
- Microsoft.Graph.Users.Actions
- Microsoft.Graph.Authentication
- Microsoft.Graph.Beta.Teams
- Microsoft.Graph.Identity.DirectoryManagement
- Graph roles necessary to access the data needed to find users and chat threads must be assigned to the service principal of the Azure Automation account. The permissions are:
- Users.Read.All or Directory.Read.All (read user information from Entra ID), Chat.Read.All (read chat threads for a user), and Chat.ManageDeletion.All (remove a chat thread)
- Mail.Send (send the results in an email message)
You cannot assign Graph roles to the service principal of an Azure Automation account through the Entra ID admin center. Instead, role assignments must be performed through PowerShell. This example shows how to assign the Chat.Read.All role to the service principal of an Azure Automation account named ManagedIdentitiesAutomation. Details of the role are fetched from the service principal of the Graph enterprise app that’s known to every tenant.
$ManagedIdentity = Get-MgServicePrincipal -Filter "displayName eq 'ManagedIdentitiesAutomation'" $GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" $Role = $GraphApp.AppRoles | Where-Object {$_.Value -eq 'Chat.Read.All'} $AppRoleAssignment = @{ "PrincipalId" = $ManagedIdentity.Id "ResourceId" = $GraphApp.Id "AppRoleId" = $Role.Id } # Assign the Graph permission New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ManagedIdentity.Id -BodyParameter $AppRoleAssignment
Outline of the Code to Find and Delete Chat Threads
The code to find and delete chat threads implements these steps:
- Run the Connect-MgGraph cmdlet to connect to the Graph SDK with the required permissions. Authentication is via a system-managed managed identity associated with the Azure Automation account. Following authentication, the permissions held by the automation account’s service principal become available for the session.
- Use the Get-MgUser cmdlet to find licensed user accounts. Using licenses as a filter is important because it removes any member accounts created by multi-tenant synchronization from the set used for processing.
- Define a set of topics (thread names) to look for. This is the easiest kind of search. Examples of more complex searches are those that look for specific participants in chat threads or chat threads with messages containing specific key phrases.
- Set up a loop through each user account.
- For each account, use the Get-MgBetaUserChat cmdlet to find the chat threads they participate in and filter the threads to look for any that match the defined topics. The script searches for meeting and group chat threads. Most one-to-one chats don’t have topic names.
- If a matching thread is found, capture its details (including chat participants) before running the Remove-MgBetaChat cmdlet to soft-delete the thread. This action removes the thread for all participants, meaning that the thread won’t be found when the script checks other users. The cmdlet can only remove threads that originate in the same tenant, so if the chat is owned by another tenant (someone from this tenant participates in a meeting or group chat started by someone in another tenant), note the fact.
- Pause for a second to accommodate throttling for thread removals.
- Record what happened in a PowerShell list.
- After processing all users, use the Send-MgUserMail cmdlet to email the report (from the list) to a nominated target address from a chosen mailbox.
Figure 1 shows the runbook executing in the test mode of the Azure Automation account. You can see that the script found four chat threads and removed three.
Figure 2 shows an example of the email created and sent by the runbook. Three Graph API-based methods are available to send email. I choose to use the simplest because it’s so easy to take the PowerShell list and convert it to a HTML fragment and use that as the email body.
You can download the complete runbook script from GitHub.
Recovering Deleted Chat Threads
Deleted chat threads can be recovered within seven days by running the Undo-MgBetaTeamWorkDeletedChatDelete cmdlet. After that period lapses, you might still be able to undo the deletion of a thread, but as time progresses, it becomes increasingly likely that Teams internal processing will recognize the deleted status of a thread is longer than seven days. When this happens, Teams deletes the thread permanently and it becomes irrecoverable.
The key information required to recover chat threads is the thread identifier. That’s why the report generated by the script includes the identifier.
The Compliance Issue
It’s worth noting that soft-deletion (or eventual permanent removal) of a Teams chat thread does not make the chat messages unavailable for eDiscovery or compliance purposes. When people post messages to a Teams chat, the Microsoft 365 substrate captures compliance records (cut-down versions of the message content) and stores the compliance records in the Exchange Online mailboxes belonging to chat participants. Deleting chat threads does nothing to the compliance records, which can only be removed by Teams retention policies.
A New Tool for Teams Administrators
Teams administrators might never need to remove chat threads. The fact that Microsoft introduced the Graph APIs indicates that some need exists. If you find yourself in the situation where you do need to get rid of some threads, it’s likely that some pressure will exist to delete the pesky threads to eliminate references to sensitive information. Hopefully, the principles explored here will help – but remember that deletion at the server level doesn’t necessarily lead to immediate removal in clients.
Fantastic article! How can I find chat thread IDs for meeting chats of a particular flavour, for instance meetings that are EE2E enabled? I´ve tried messing around with GET Onlinemeetings but I keep getting 400 Bad Request with app permissions.
The Real Person!
Author Tony Redmond acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
Find a meeting that is EE2E protected and look for a property that identifies this status. Then filter on that property…
Earlier I´ve tried to delete personal 1:1 chats but this wans´t possible at all…Has this changed now?
The Real Person!
Author Tony Redmond acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
If you can find the chat thread id, you can delete it. The difficulty with 1:1 chats is that many of them don’t have a title (subject), so it’s hard to search for them.
I get Permissions issues when I try to run this. This is using a managed identity in Azure automation with the above permissions. Not sure what I’m doing wrong
Remove-MgBetaChat : InsufficientPrivileges Status: 403 (Forbidden) ErrorCode: Forbidden Date: 2023-09-29T08:09:12 Headers: Transfer-Encoding : chunked Vary : Accept-Encoding Strict-Transport-Security : max-age=31536000 request-id : xx-xx-xxxx-xxxx-xxclient-request-id : xxxx-xx-xx-xx-xxx-ms-ags-diagnostic : {“ServerInfo”:{“DataCenter”:”xxxx”,”Slice”:”E”,”Ring”:”4″,”ScaleUnit”:”001″,”RoleInstance”:”ML1PEPF00004AF5″}} Date : Fri, 29 Sep 2023 08:09:12 GMT At line:23 char:1 + Remove-MgBetaChat -ChatId $Chats[0].id + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: ({ ChatId = 19:8…2, IfMatch = }:f__AnonymousType269`2) [Remove-MgBetaChat_Delete], Exception + FullyQualifiedErrorId : Forbidden,Microsoft.Graph.Beta.PowerShell.Cmdlets.RemoveMgBetaChat_Delete
The Real Person!
Author Tony Redmond acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
The service principal for the Azure Automation account that you’re using to execute the runbook doesn’t have the permission to run the Remove-MgBetaChat cmdlet. Make sure that the SP has Chat.ManageDeletion.All and try again.
Thanks it actually seems that it is because the message was sent from an external user in to the organization (as mentioned in your previous article). It was a spam message that was sent to several internal users that I was trying to remove.
Though it seems like there is no way to remove a spam message sent from an external user 🙁
The Real Person!
Author Tony Redmond acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.
The problem here is that the thread is “owned” by another tenant. However, that doesn’t stop the user in your tenant who participates in the thread from leaving it.
True, but we wanted to remove it from all users on their behalf, as it contained an unsafe attachment.