Table of Contents
Finding Guest Accounts in Microsoft 365 Groups
Updated: 18-Sep-2024
The article explaining how to report old Entra ID guest accounts and their membership of Microsoft 365 Groups (and teams) in a tenant is very popular and many people use its accompanying script. Taking the idea a little further, this article explores how to find guest accounts above a certain age (365 days – configurable in the script) and report the groups these guests are members of. Any old guest accounts that aren’t in any groups are candidates for removal.
The script uses an old technique featuring the distinguished name of guest accounts to scan for group memberships using the Get-Recipient cmdlet. The approach works, but the variation of values that can exist in distinguished names due to the inclusion of characters like apostrophes and vertical lines means that some special processing is needed to make sure that lookups work. Achieving consistency in distinguished names might be one of the reasons for Microsoft’s plan to make Exchange Online mailbox identification more effective.
In any case, time moves on and code degrades. I wanted to investigate how to use the Microsoft Graph PowerShell SDK to replace Get-Recipient. The script already uses the SDK to find Entra ID guest accounts with the Get-MgUser cmdlet.
The Graph Foundation
Graph APIs provide the foundation for all SDK cmdlets. The first thing to find is an appropriate API to find group membership. I started off with getMemberGroups. The PowerShell example for the API suggests that the Get-MgDirectoryObjectMemberGroup cmdlet is the one to use. For example:
$UserId = (Get-MgUser -UserId Terry.Hegarty@Office365itpros.com).id [array]$Groups = Get-MgDirectoryObjectMemberGroup -DirectoryObjectId $UserId -SecurityEnabledOnly:$False
The cmdlet works and returns a list of group identifiers that can be used to retrieve information about the groups that the user belongs to. For example:
Get-MgGroup -GroupId $Groups[0] | Format-Table DisplayName, Id, GroupTypes DisplayName Id GroupTypes ----------- -- ---------- All Tenant Member User Accounts 05ecf033-b39a-422c-8d30-0605965e29da {DynamicMembership, Unified}
However, because Get-MgDirectoryObjectMemberGroup returns a simple list of group identifiers, the developer must do extra work to call Get-MgGroup for each group to retrieve group properties. Not only is this extra work, calling Get-MgGroup repeatedly becomes very inefficient as the number of guests and their membership in groups increase.
Looking Behind the Scenes with Graph X-Ray
The Entra admin center both list the groups that user accounts (tenant and guests) belong to. Performance is snappy and it seemed unlikely that the code used was making multiple calls to retrieve the properties for each group. Many of the sections in these admin centers use Graph API requests to fetch information, and the Graph X-Ray tool reveals those requests. Looking at the output, it’s interesting to see that the admin center uses the beta Graph endpoint with the groups memberOf API (Figure 1).

We can reuse the call used by the Entra admin center to create the query (containing the object identifier for the user account) and run the query using the SDK Invoke-MgGraphRequest cmdlet. One change made to the command is to include a filter to select only Microsoft 365 groups. If you omit the filter, the Graph returns all the groups a user belongs to, including security groups and distribution lists. The group information is in an array that’s in the Value property returned by the Graph request. For convenience, we put the data into a separate array.
$Uri = ("https://graph.microsoft.com/V1.0/users/{0}/memberOf/microsoft.graph.group?`$filter=groupTypes/any(a:a eq 'unified')&`$top=200&$`orderby=displayName&`$count=true" -f $Guest.Id) [array]$Data = Invoke-MgGraphRequest -Uri $Uri [array]$GuestGroups = $Data.Value
Using a Graph request works and you can continue using it if you like. The latest version of the script uses the Get-MgUserMemberOf SDK cmdlet instead. The cmdlet returns groups and administrative units, so a filter is used to extract the groups from the set retrieved from the Graph. Here’s the current code:
[array]$GuestGroups = Get-MgUserMemberOf -UserId $Guest.id If ($GuestGroups) { # Exclude administrative units $RealGroups = $GuestGroups | Where-Object {$_.additionalproperties.'@odata.type' -eq '#microsoft.graph.group'} $GroupNames = $RealGroups.additionalProperties.displayName -join ", "
A Get-MgUserTransitiveMemberOf cmdlet is also available to return the membership of nested groups. We don’t need to do this because we’re only interested in Microsoft 365 groups, which don’t support nesting. The cmdlet works in much the same way:
[array]$TransitiveData = Get-MgUserTransitiveMemberOf -UserId Kim.Akers@office365itpros.com -All
The Script Based on the SDK
When revising the script, I made some other improvements, including adding a basic assessment of whether a guest account is stale or very stale. The assessment is intended to highlight if I should consider removing these accounts because they’re obviously not being used. Figure 2 shows the output of the report.

You can download a copy of the script from GitHub.
Cleaning up Obsolete Entra ID Guest Accounts
Reporting obsolete guest accounts is nice. Cleaning up old junk from Entra ID is even better. The script generates a PowerShell list with details of all guests over a certain age and the groups they belong to. To generate a list of the very stale guest accounts, filter the list:
[array]$DeleteAccounts = $Report | Where-Object {$_.StaleNess -eq "Very Stale"}
To complete the job and remove the obsolete guest accounts, a simple loop to call Remove-MgUser to process each account:
ForEach ($Account in $DeleteAccounts) { Write-Host ("Removing guest account for {0} with UPN {1}" -f $Account.Name, $Account.UPN) Remove-MgUser -UserId $Account.Id }
Obsolete or stale guest accounts are not harmful, but their presence slows down processing like PowerShell scripts. For that reason, it’s a good idea to clean out unwanted guests periodically.
Learn about mastering the Microsoft Graph PowerShell SDK and the Microsoft 365 PowerShell modules by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.
HI Tony,
As per this script, it will give us the information abou the the last login date of the guest users. But we ran the script and we found that guest users had non-interactive sign ins. How can we get the information with non-interactive sign ins.
This script uses the last sign-in date registered by Azure AD in guest accounts. If you want to look for other dates (what non-interactive sessions do guest accounts use?), you’d have to search the Azure AD sign in log.
Hi Pravin
There are some typos in the array. Change Property to -Property and SignInaActivity to SignInActivity
[Array]$GuestUsers = Get-MgUser -Filter “userType eq ‘Guest'” -All -PageSize 999 -Property id, displayName, userPrincipalName, mail, userType, SignInActivity, createdDateTime, ExternalUserState
I’ve fixed the bugs (no one reported them in GitHub) and upgraded the code so that the script only uses SDK cmdlets. I also checked it out against the current version of the SDK.