Table of Contents
removeAllAccessForUser Graph API Delivers Limited Suppression of External Chat Messages
It’s amazing what you can find when looking for something else. When writing the article about how to remove Teams chat threads, I stumbled upon a page in the security, compliance, and privacy section of the Teams administration documentation called Remove an external chat from a user’s view. The page describes how Teams administrators can remove the content of chats involving external users where some objectionable or harmful material is posted. Sounds good and worth looking into.
The feature is based on the removeAllAccessForUser Graph API, which describes itself as being able to remove access to a chat for a user. To me, this sounds like the API should eject a user from a chat and bar them from ever rejoining the chat, but that’s not what the API does.
Engaging in External Chat
Take the example of where a user in another Microsoft 365 tenant starts a chat with someone in your tenant. Teams goes into defensive mode and cautions the user about what might happen if they join the chat (Figure 1). The good thing here is that the user can preview the messages posted in the chat to get an idea of whether they should join.
Let’s assume that the user thinks that the invitation to chat is legitimate and they join the chat. At this point we have two consenting adults in a one-to-one Teams chat. If some of the messages become objectionable, our user might protest and complain to the Teams administrators. The administrators spring into action to remove the external chat from the user’s view, as promised by the documentation.
Identifying the External Chat
Three pieces of information are needed by the API:
- The chat identifier.
- The user identifier.
- The tenant identifier.
The last two items are easily found by running the Get-MgUser and Get-MgOrganization cmdlets. You can find the chat identifier by running the Get-MgUserChat cmdlet in app-only mode. External chats are identified by filtering to find chats that didn’t originate in the home tenant:
$tenantId = (Get-MgOrganization).Id $Chats = Get-MgUserChat -UserId $UserId -all -Filter "tenantid ne '$tenantId'" $Chats | format-table id, createddateTime Id CreatedDateTime -- --------------- 19:110ef7a6-82ac-41c0-b0d1-86d88fc566b6_59e09287-ac1b-4ff7-80a3-08d0d1eed939@unq.gbl.spaces 29/04/2026 16:15:21 19:59e09287-ac1b-4ff7-80a3-08d0d1eed939_eff4cd58-1bb8-4899-94de-795f656b4a18@unq.gbl.spaces 16/04/2024 21:19:08 19:59e09287-ac1b-4ff7-80a3-08d0d1eed939_b7afdc46-9934-42cc-88fe-0e4bb9c57835@unq.gbl.spaces 10/01/2025 16:32:49 19:59e09287-ac1b-4ff7-80a3-08d0d1eed939_55a07ff9-ec5f-45e0-b704-733f617fe669@unq.gbl.spaces 16/11/2023 21:54:01
Microsoft recommends running an audit search to find events related to the creation of external chats. It’s a somewhat longwinded method to find external chats but has the benefit that you don’t need to use an app-only session with the Chat.Read.All application permission that’s required to read chat information for a user.
Here’s the code I used to run an audit search to find events of interest and then filter the events to find those related to external chats:
$StartDate = (Get-Date).AddDays(-60)
$EndDate = Get-Date
[array]$Domains = Get-AcceptedDomain | Select-Object -ExpandProperty DomainName
$Operations = "MemberAdded"
$RecordType = "MicrosoftTeams"
Write-Output "Searching audit log for Microsoft Teams MemberAdded events between $StartDate and $EndDate..."
[array]$Records = Search-UnifiedAuditLog -StartDate $StartDate -EndDate $EndDate -Formatted -SessionCommand ReturnLargeSet -ResultSize 5000 -Operations $Operations -RecordType $RecordType
# Remove duplicates
$Records = $Records | Sort-Object Identity -Unique
If ($Records) {
Write-Host ("{0} audit records found for {1} operations between {2} and {3}" -f $Records.Count, $Operations, $StartDate, $EndDate)
} Else {
Write-Host ("No audit records found for {0} operations between {1} and {2}" -f $Operations, $StartDate, $EndDate)
Break
}
$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($Rec in $Records) {
$AuditData = $Rec.AuditData | ConvertFrom-Json
If ($AuditData.CommunicationType -eq "OneOnOne") {
$ForeignDomain = $AuditData.ParticipantInfo.ParticipatingDomains | Where-Object {$_ -notin $Domains}
$ForeignTenantId = $AuditData.ParticipantInfo.ParticipatingTenantIds | Where-Object {$_ -ne $AuditData.OrganizationId}
$ReportLine = [PSCustomObject][Ordered]@{
TimeStamp = Get-Date ($AuditData.CreationTime) -format 'dd-MMM-yyyy HH:mm'
User = $AuditData.UserId
Operation = $AuditData.Operation
UserId = $AuditData.UserKey
ChatThreadId = $AuditData.ChatThreadId
MembersAdded = $AuditData.Members.UPN -join ", "
HasForeignTenantUsers = $AuditData.ParticipantInfo.HasForeignTenantUsers
ForeignDomain = $ForeignDomain
ForeignTenantId = $ForeignTenantId
}
$Report.Add($ReportLine)
}
}
$Report = $Report | Sort-Object {$_.TimeStamp -as [datetime]} -Descending
The contents of the $Report array contain information about all external chats originating from other tenants. It should be easy to find the chat of interest from the list. All you need is the chat identifier.
Removing User Access to Chat Messages
To prepare to remove the messages for the user, build a hash table containing details of the user and tenant:
$UserInfo = @{}
$UserData = @{}
$UserData.Add("id", $UserId)
$UserData.Add("tenantId", $TenantId)
$UserInfo.Add("user", $UserData)
The easiest way to remove the messages is to run the Remove-MgChatAccessForUser cmdlet using the hash table as input. Alternatively, use the Invoke-MgGraphRequest cmdlet to run the Graph API request:
Remove-MgChatAccessForUser -ChatId $ChatId -BodyParameter $UserInfo -ErrorAction Stop
or:
$Uri = ("https://graph.microsoft.com/V1.0/chats/{0}/removeAllAccessForUser" -f $ChatId)
Invoke-MgGraphRequest -Method POST -Uri $Uri -OutputType PSobject -Body $UserInfo -ErrorAction Stop
Using the API requires the Chat.ReadWrite.All permission. Delegated access works if the signed-in account is a global or Teams administrator.
Surprising Effect of Removing Access to an External Chat
Running the cmdlet suppresses any messages posted to the chat to that point. However, the chat remains visible and new messages can continue to be exchanged between the two participants (Figure 2). This might come as a surprise because you might assume that the targeted chat has been rendered non-functional.
The external user sees all messages in the chat (Figure 3). Running the command has no effect because the scope of the command is limited to the tenant in which it is run. Comparing the two sets of messages reveals that messages posted before 7:27PM are missing in the tenant user’s view but are present in the external user’s view.
Another oddity is that running the Remove-MgChatAccessForUser cmdlet to remove subsequent messages from the chat doesn’t seem to do anything. The cmdlet completes without a problem but none of the messages are suppressed.
External Chats Cannot be Removed by a Tenant
With the right permissions, administrators can delete a chat thread. However, this approach doesn’t work with external chats because the chat didn’t originate in your tenant:
Remove-MgChat -ChatId $ChatId "AclCheckFailed-The initiator 28:app:b662313f-14fc-43a2-9a7a-d2e27f4f3478_dbdb9d9f-f6b1-4bac-8490-1a7a994b5eb2 does not have permission to access thread 19:110ef7a6-82ac-41c0-b0d1-86d88fc566b6_59e09287-ac1b-4ff7-80a3-08d0d1eed939@unq.gbl.spaces. Tenant Id mismatch.",
An Unsatisfactory and Misleading API
Microsoft’s documentation is misleading. The removeAllAccessForUser API can remove some messages from the view of a user, but that’s not the same as removing all access to a chat for a user. Moreover, the API fails to do anything when subsequently run against an external chat. That sounds like something badly wrong is happening internally. Put in a nicer way, maybe today’s implementation of the API is incomplete.
This way that the API works makes it unsuitable for incident response scenarios where administrators need to stop harmful interaction. It removes historical visibility but does nothing to prevent further communication. In practice, “remove access” means “hide existing messages in an external chat thread for one user in one tenant.” There’s not much else an administrator can do given that they have no control over chats hosted in another tenant.
As I have often said, it’s better to operate an allow list for external Teams collaboration. That way you’ll know what tenants can create external chats rather than leaving your tenant open to potential phishing or other chat-enabled attacks.
We published the original edition of the Office 365 for IT Pros eBook in May 2015. At that point, the book was 500 pages long, focused mostly on Exchange Online, and cost $49.95, or the equivalent of 25 cups of black coffee. Now the 12th version is 1280 pages, covers the core components of Microsoft 365, and includes the 380-page Automating Microsoft 365 with PowerShell eBook, and costs $49.95, or less than 10 cups of black coffee. Your favorite beverage has gotten way more expensive since 2015 while Office 365 for IT Pros continues to add value at a great price. Who doesn’t like a book that is updated monthly to make sure that its text keeps pace with everything that happens inside Microsoft 365? https://gum.co/O365IT/

