A PowerShell Script to Show How to Use the New API to Remove Chat Threads
Updated 29 May 2024 (cmdlets now use the V1.0 endpoint)
Microsoft 365 notification MC673983 (6 Sept 2023) announces the availability of the Teams Delete Chat Thread Graph API. The API supports the removal of 1:1, meeting, and group chat threads, but not messages in channel conversations. A chat thread is a collection of messages between one or more participants (users or apps).
APIs are also available to retrieve information about deleted chat threads and to restore (undo) deleted chat threads. Deployment of the new APIs has started and should be completed worldwide by the end of September 2023.
Running the Delete Chat APIs
Microsoft says that admins can use the APIs via PowerShell scripts. Out of the box, Teams Graph APIs that interact with user data are limited to delegate permissions. In other words, the only data that’s available is that belonging to the signed-in account. In this article, I focus on using the APIs through an interactive session with the Microsoft Graph PowerShell SDK. I used version 2.5 of the SDK for my testing.
Despite my account’s status as a Teams administrator, the least-permission model used by the Graph and restriction to delegate permissions limits me to chats that my account can access.
The permissions needed to delete chat threads are:
- Chat.ReadWrite: To read chat information.
- Chat.ManageDeletion.All: To delete a chat thread, list deleted chats, or undo (restore) deleted chats.
For an interactive SDK session, make sure to specify these permissions (Scopes) when you run the Connect-MgGraph cmdlet. To access chat data for other users, you’ll need a registered Entra ID app with consent to use these permissions. The app will likely need other permissions, like User.Read.All to read information about user accounts.
Signing in and Getting Chats
Before you can delete chat threads, you need to know some information about the threads. I therefore wrote a script to find all the chat threads accessible to the account that signed into the Graph SDK. The script then builds an array of chat threads of three types by running the Get-MgUserChat cmdlet:
- Group: A chat thread involving more than two participants. The participants can be user accounts (members or guests) or apps.
- OneOnOne: A chat thread between two users in a 1:1 conversation. The users can be in the same or different tenants.
- Meeting: A chat thread associated with a Teams online meeting.
The filter used to fetch chat threads ignores threads of the unknownFutureValue type. Most of these threads appear to belong to future events and aren’t of great interest.
Connect-MgGraph -Scopes Chat.ReadWrite, Chat.ManageDeletion.All -NoWelcomeMessage $Account = (Get-MgContext).Account $UserId = (Get-MgUser -UserId $Account).Id # Get chats for the user [array]$Chats = Get-MgUserChat -Userid $UserId -All -Filter "chattype eq 'group' or chattype eq 'oneonone' or chattype eq 'Meeting'" | Sort-Object LastUpdatedDateTime -Descending
Here’s what I found about chat threads in my account:
$Chats | Group-Object ChatType -NoElement Count Name ----- ---- 59 group 371 meeting 114 oneOnOne
Chat threads can originate in other tenants, most often when people attend meetings organized outside their home tenant. When examining the distribution of chat threads across tenants, it should come as no surprise that the majority of my threads originated in my tenant:
$Chats | Group-Object TenantId -NoElement | Sort-Object Count -Descending | Format-Table Name, Count Name Count ---- ----- b662313f-14fc-43a2-9a7a-d2e27f4f3478 434 72f988bf-86f1-41af-91ab-2d7cd011db47 71 c0a3a43f-9257-4949-be1b-f83cfb83f65f 14 91c369b5-1c9e-439c-989c-1867ec606603 12 1786fc65-d8f0-40d0-b749-2f75d9c349a7 4 22e90715-3da6-4a78-9ec6-b3282389492b 4 2cf6ec6e-2282-4005-9973-344bcba75c54 1
People don’t usually remember tenant identifiers, so my script builds a hash table of identifiers and tenant display names so that the report can output the name of the owning tenant for each chat thread.
To create a report listing information about the chat threads, it’s a matter of looping through each thread and extracting information like the chat participants and the tenant name. Information for some chat participants is missing because their accounts are no longer available. Fetching chat participants takes the longest in terms of how long it takes for the script to run. Figure 1 shows the output of the report.
You can download the complete script from GitHub.
Deleting Chat Threads
Now that we know the details about a user’s chat threads, we can use the delete API to remove threads that we don’t want to keep. The essential requirement is to have the identifier for the chat thread. The report data includes the identifier for each thread, but you can also populate a variable by searching for a chat thread with a specific topic:
$ChatId = $Chats | Where-Object {$_.topic -eq 'External chat with Teams consumer'} | Select-Object -ExpandProperty Id
After finding the chat identifier, we can remove the chat thread using the Remove-MgChat cmdlet and check what we did with the Get-MgTeamWorkDeletedChat cmdlet (which really doesn’t tell us much except confirm the deletion status for a chat thread). Note that the Remove-MgChat cmdlet doesn’t prompt for confirmation:
Remove-MgChat -ChatId $ChatId Get-MgTeamworkDeletedChat -DeletedChatId $ChatId | fl Id : 19:631b8356677448d2af176dea4178f760@thread.v2 AdditionalProperties : {[@odata.context, https://graph.microsoft.com/V1.0/$metadata#teamwork/deletedChats/$entity]}
Deleting a chat thread is a soft-delete action, meaning the deletion can be reversed. The effect of the soft deletion is that Teams hides the thread to make it invisible to all chat participants. A chat thread is a shared object, so deletion only has to occur once to render the thread inaccessible to all participants. Deletion removes all the messages in a thread (you can’t remove a single message). However, deleting a thread only deletes the messages. It does not remove content shared in a thread. This information remains in the Microsoft Teams Chat Files folder in the OneDrive for Business account of the person who shared the file.
The exact delay before a deleted chat thread becomes inaccessible to participants depends on the client that they use and how often clients refresh their cached chats. It’s a bit like the way that message deletions performed by a Teams retention policy can take several days before clients remove the messages from user view.
However, once Remove-MgChat removes a chat thread, all participants lose access to the thread even if they can still see the chat messages in a client. If a chat participant attempts to send a message to a deleted chat thread that’s still visible in their client, the send action fails because the user is no longer a member of the chat.
The Undo-MgTeamWorkDeletedChatDelete cmdlet restores a deleted chat thread. Restores must happen within seven days of deletion. If left any longer, it might not be possible to recover a deleted chat.
Undo-MgTeamworkDeletedChatDelete -DeletedChatId $ChatId
Chat participants should be able to interact with the thread very soon after reversal of the deletion. The client must discover that the chat is no longer deleted. Once that happens, the thread returns to normal.
Delete Chat API Throttling
Microsoft throttles Delete Chat API requests to one request per second within a tenant. This is probably to discourage tenants from viewing the new API as a replacement for retention policies. A retention policy targeted at Teams chat messages can remove chats faster across the entire tenant. However, retention policies are age-based and remove chats older than a specified period (for instance, after seven days). They do not have the flexibility to target chats for removal based on other attributes, such as the topic, a chat participant, or the type of chat.
Throttling means that the Delete Chat API must be considered as a surgical tool to find and remove individual chat threads rather than a broad-brush method to clean up the chats for user accounts.
Can’t Remove Chat Threads from Other Tenant
In the case of federated chats (where a user from one tenant chats with a user in another tenant using Teams external access), chats “belong” to the tenant where the chat originated. In other words, the tenant of the user who started the federated chat.
The same restriction exists for group and meeting chats originating in other tenants. Any attempt to remove a chat that doesn’t belong to a tenant results in a forbidden error:
Remove-MgChat -ChatId $ChatId Remove-MgChat_Delete: InsufficientPrivileges Status: 403 (Forbidden)
Only administrators from the originating tenant can remove chat threads.
Slow But Steady Chat Thread Deletion
There’s nothing particularly complicated about using the Teams Delete Chat API, once you find the identifier for the chat thread to delete. The things to remember are throttling and that you can’t delete a thread originating from another tenant. Apart from that, administrators can (slowly) clean up chat threads with PowerShell as required. I’ll tackle the topic of how best to clean up unwanted chat threads in a future article.
Microsoft Platform Migration Planning and Consolidation
Simplify migration planning, overcome migration challenges, and finish projects faster while minimizing the costs, risks and disruptions to users.
The Real Person!
The Real Person!
Hi Tony, great article! I do have one question if I may, I’ve been tasked to investigate a possible group chat by someone else in our tenant and need to get the conversation log and who joined/invited by who.. but according to this article since user data are limited to delegate permissions, me as an admin can’t really access the conversation right?
Can you elaborate the process of registering Entra ID app and how to use that in order to accomplish the task? Thank you so much!
The process of registering an Entra ID app is well documented online by Microsoft and many others.
According to https://learn.microsoft.com/en-us/graph/api/chat-list-messages?view=graph-rest-1.0&tabs=http, the ChatMessage.Read.Chat permission will list messages in a chat. I think that should be ChatMessage.Read.All permission… But I have not played with the API.
I see there’s now a command
Remove-MgBetaTeamworkDeletedChat
Which I’m assuming performs an immediate hard delete on the soft deleted chat, but I can’t seem to make it work
Status: 405 (MethodNotAllowed)
Know anything about this one?
Nope. I haven’t used it and the documentation https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.beta.teams/remove-mgbetateamworkdeletedchat?view=graph-powershell-beta isn’t great. It seems like the input parameter is the identifier for a deleted chat. What happens thereafter is unknown!
Hey Tony,
We tested this on some accounts and it definitely works to have supervised chat kick in for student-student. One major problem – the chats between a student-teacher were deleted as well and when the student or teacher go to chat each other again, the message gets stuck sending and never goes through. If you refresh the browser you get a “You can’t send messages because you are not a member of the chat” error. Tried deleting local cache on both ends as well as testing from a fresh computer/InPrivate browsing session. I’m thinking this will work itself out after 7 days when the chat goes into hard deletion. Thoughts?
When you delete a chat thread, the deletion action occurs against the Teams message store. Clients need to refresh their cache to respect the deletion. The reason why your users are told that they’re not members of the chat is because the thread is now gone (soft deleted) even if the client thinks otherwise. Things do sort themselves out over time, but as I think this kind of operation should be occasional rather than regular, the client time lag is acceptable.
Thank you for posting this!!!!
As an educational tenant, we have been looking for a way to get supervised chat to kick in for existing chats. Retention policies don’t work for this, they do delete the messages but students are still able to open a new chat. I created an app registration and used the new API to clear out all chat threads for two test students which worked to get supervised chat policy working correctly.