Analyze External Meeting Participants for Teams Meetings to Know What External Domains a Tenant Connects with

In a previous article, I explained how to use the Microsoft Graph PowerShell SDK to analyze Teams chats to determine the set of external domains with users that communicate with people in a tenant. The idea is that if you know the external domains that are in active contact with your tenant, you can update the Teams external access configuration to add those domains to the allow list (manually or with PowerShell).

A reader asked if it is possible to extract information about external meeting participants for Teams online meetings. The desire was to identify all external users that tenant users had communicated with using Teams (chat, meetings, and calls). My response is that the calendar data is available through the Graph Events API and I pointed the reader to the article about analyzing room mailbox usage and an article covering how to use the Events API.

It’s lovely to be able to answer a question and walk away without a care in the world. Unhappily, although I was reasonably sure that my response was accurate, the only way to solve the nagging doubt in my mind was to write some code. Fortunately, the task wasn’t too difficult because much of the ground is covered in previous articles and scripts.

The Need for Application Permissions

Any script that processes user mailbox data needs to use Graph application permissions. If not, delegated permissions will allow access to the signed-in user’s mailbox and no more. To use application permissions, the script uses an Entra ID app to authenticate. Providing the app is assigned consent for the correct application permissions, it can retrieve information from any mailbox in the tenant. Sensitive mailboxes can be protected from this kind of exercise using RBAC for applications.

Defining Target Accounts for the Script

Thinking about how the script should run, I decided to include a facility to define the set of mailboxes to run against using an Entra ID group. In large tenants, you don’t want to run tests against every mailbox, so it makes sense to set up a group to define the target mailboxes used for testing. In production, dynamic groups can be used to target mailboxes. For instance, to analyze meetings for all users in the United States, you could use a dynamic group with a membership rule to find accounts with “United States” in the country property.

The script also needs to identify meeting participants from the host tenant. This means that the code needs to read the set of domains defined for the tenant. All of which means that the full set of Graph application permissions needed for the code to run is:

  • User.Read.All: Read user account information, including licenses.
  • Group.Read.All: Check whether the test group exists.
  • GroupMember.Read.All: Read the membership of the test group.
  • Calendars.Read: Read meeting events from user calendars.
  • Domain.Read.All: Read accepted domains defined for the tenant.

Charting The Script to Analyze External Meeting Participants

With the permissions resolved and an idea about how to define the set of target users in place, let’s chart out the flow of the script.

First, run the Connect-MgGraph cmdlet to connect the app to the Graph. I used an X.509 certificate for authentication.

Next, check if the test group is available. If it is, retrieve the identifiers for the group members and store the data in an array. If not, retrieve all user accounts licensed with a Teams service plan so that events in their calendars can be analyzed. Here’s the code to find the target users:

# Define the GUID for the Teams service plan
$TeamsServicePlanId = '57ff2da0-773e-42df-b2af-ffb7a2317929'
# Define the name of an Entra ID group whose members are used to test the script (or run the script for a subset of users)
$GroupName = "Test Users for Graph Scripts"
# See if the test group exists containing the user accounts for testing. 
# If not, find all user accounts licensed with a Teams service plan
Try {
    $Group = Get-MgGroup -Filter "displayName eq '$GroupName'" -ErrorAction Stop
    $GroupMembers = Get-MgGroupMember -Group $Group.Id
    [array]$Users = $GroupMembers | Select-Object -ExpandProperty Id
} Catch {
    [array]$Users = Get-MgUser -Filter "assignedPlans/any(c:c/servicePlanId eq $TeamsServicePlanId and capabilityStatus eq 'Enabled')" `
      -ConsistencyLevel eventual -CountVariable Test -All -PageSize 500 | Select-Object -ExpandProperty Id
}

In either case, the result is an array of user account object identifiers.

The script now runs the Get-MgDomain cmdlet to find the set of domains defined for the Microsoft 365 tenant. Any meeting participant from any of these domains is excluded from the analysis.

# Get the domains defined for the tenant
[array]$Domains = Get-MgDomain -All | Select-Object -ExpandProperty Id | Sort-Object

After defining the time window to check for (365 days, but easily changed), the code moves to loop through the set of accounts. Events for the time window are fetched and then filtered to find online meetings that have not been cancelled. A client-side filter is used here because the Events API doesn’t support a server-side filter to find online events.

[array]$CalendarData = Get-MgUserCalendarView -UserId $User -StartDateTime $StartDate -EndDateTime $EndDate -All -PageSize 250 

# Drop cancelled events and filter to get online meetings - if you want to exclude private events from the set, use Where-Object {$_.sensitivity -ne "private"}
$CalendarData = $CalendarData | Where-Object {$_.isCancelled -eq $False -and $_.isOnlineMeeting -eq $True}

Each event is analyzed to make sure that the organizer is from the tenant. For this analysis we don’t care about situations when users from our tenant attend meetings organized in other tenants. It’s easy to remove this check if you prefer, but the tenant has zero control over who people in other tenants invite to meetings, so it’s best to exclude this data.

For the other meetings, the code extracts the meeting participants and finds the participants from external domains. The domain information is noted.

ForEach ($CalendarEvent in $CalendarData) {

# Check if the meeting organizer is from our tenant. If not, the event is organized elsewhere and we ignore it 
$Organizer = $CalendarEvent.organizer.emailaddress
   If ($Organizer.address.Split("@")[1] -notin $Domains) {
      Continue
   }
# Get the set of attendees and extract the domains for attendees not in our tenant
   [array]$EventAttendees = $CalendarEvent.attendees.emailaddress
     If ($EventAttendees) {
       ForEach ($Attendee in $EventAttendees) {
          $AttendeeDomain = $Attendee.Address.Split('@')[1]
          If ($AttendeeDomain -notin $Domains) {
             $UserDomainsFound += $AttendeeDomain
           }
        }
    }
}

The remaining processing adds the set of external domains found for events in the mailbox to an array and captures details about the number of meetings processed for the mailbox.

$DomainsFound = $DomainsFound + $UserDomainsFound

$ReportLine = [PSCustomObject]@{
    User            = $User
    MeetingCount    = $CalendarData.Count
    ExternalDomains = ($UserDomainsFound | Sort-Object -Unique) -join "; "
}
$CalendarInfo.Add($ReportLine)

After processing all mailboxes, the script finds the set of unique domains encountered across all mailboxes and reports the outcome (Figure 1).

The script finds some domains by analyzing external meeting participants.
Figure 1: The script finds some domains by analyzing external meeting participants

The complete script can be downloaded from GitHub.

What Next?

Now that you know what external domains participants for Teams meetings organized by your tenant come from, the next question is what to do with the information? I guess you could add the tenants to the allow list in the Teams external access configuration. Or maybe your curiosity is assuaged by the analysis, and it’s time to move to another challenge.

Joking apart, this exercise is a good example of how code from existing scripts can be repurposed – if you know where to look (like Practical365.com!).

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