Using the Get-MgUser Cmdlet to Find Unused Guest Accounts

A reader asked if it is possible to find the guest accounts that haven’t signed into a tenant recently and add the accounts to a group. The target could be a distribution list or Microsoft 365 group. Given the widespread use of guest accounts within Microsoft 365 to allow external sharing of resources (Loop being the latest application to adopt Entra ID B2B Collaboration), it’s almost inevitable that some unused guest accounts exist in any Microsoft 365 tenant. Identifying unused guest accounts is a good management practice. If the accounts serve no purpose, why keep them? Let’s explore how to accomplish the goal.

The first step is to create a distribution list or Microsoft 365 group to host the list of inactive guests. You can create the distribution list or group in the Exchange admin center or Microsoft 365 admin center. Alternatively, run the New-DistributionGroup cmdlet (distribution list) or New-UnifiedGroup cmdlet (Microsoft 365 group) from the Exchange Online management module. It doesn’t matter what the target group is called or who manages it because we’ll update the membership with PowerShell. All you need is its name, alias, or other identifier.

Filtering Against Account Type

Next, we need to find guest accounts that haven’t signed in for over a year. The information is easily found with the Get-MgUser cmdlet from the Microsoft Graph PowerShell SDK. Various methods can be used to find the target set of guest accounts, such as the approach taken in the script to report old guest accounts and their group membership, where the set of guest accounts is found before each account is examined to determine its status.

This code finds guest accounts and then filters the accounts based on last sign in date:

$CheckDate = (Get-Date).AddYears(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
# Get guest accounts
[array]$Guests = Get-MgUser -Filter "userType eq 'Guest'" -Property "signInActivity" -All -PageSize 500
# Filter guest users whose last sign-in is over a year ago
[array]$InactiveGuests = $Guests | Where-Object {$_.signInActivity.lastSignInDateTime -lt $CheckDate}

The advantage of this method is that the output array contains both guest accounts that have never signed into the tenant and those that have signed in more than a year ago.

Filtering Against the Last Sign-in Date

Instead of filtering against the user type, you can filter against the last signed in date. However, you can’t create a filter to check both the user type and last signed in date because these properties are of different types. The Graph APIs don’t support filters that combine properties of different types, so like above, we will use a secondary client-side filter to refine the set of accounts found by Get-MgUser. The code is:

$CheckDate = (Get-Date).AddYears(-1)
# Get set of accounts that haven’t signed in for over a year
[array]$InactiveGuests = Get-MgUser -All -PageSize 500 -Filter "signInActivity/lastSignInDateTime le $($Checkdate.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ"))" -Property Id, Mail, displayName, userPrincipalName, signInactivity, UserType
# Filter to find the guest accounts in the set
$InactiveGuests = $InactiveGuests | Where-Object {$_.userType -eq 'Guest'}

The filter used against the last signed in date property only finds accounts that have not signed in for over a year. The filter ignores accounts that have never signed in. In other words, sign-in data must be present in a user account before the filter can find it. If you want to include accounts that have never signed in, use the previous method.

As I ran some tests, I found a potential Graph bug that can include entries for long-gone accounts in the returned set. The bug exists in both the Graph API request and the Get-MgUser cmdlet. While waiting for Microsoft to confirm and fix the bug, I removed the funny records by filtering out any record without an email address:

$InactiveGuests = $InactiveGuests | Where-Object {$null -ne $_.Mail}

After generating the set of unused guest accounts, you can check the results with:

$InactiveGuests | Format-Table DisplayName, Mail, @{Expression={$_.signinActivity.lastSignInDateTime}; label = ‘Last Sign In’}

Adding a Set of Guest Accounts to Groups

The original request asked about adding the set of unused guests to a group. To add the guest accounts to the membership of a distribution group, loop through the array and add the guest with the Add-DistributionGroupMember cmdlet. Setting the error action to silently continue means that the cmdlet won’t protest if an attempt is made to add an account that’s already present in the membership.

ForEach ($Id in $InactiveGuests.Id) {
  Add-DistributionGroupMember -Identity Inactive.Guests.DL -Member $Id -ErrorAction SilentlyContinue
}

If you use a Microsoft 365 group, because the Add-UnifiedGroupLinks cmdlet accepts an array as input for members to be added, a single command is needed. Add-UnifiedGroupLinks ignores accounts that are already in the group membership and only adds new members.

Add-UnifiedGroupLinks -Identity Inactive.Guests.Group -Links $InactiveGuests.Id -LinkType Member

Before adding a batch of inactive guests, it might be wise to disable the greeting message that Microsoft 365 sends to new group members. Otherwise, all the guests will receive a welcome message for the group, which might be a little unexpected and confusing.

Set-UnifiedGroup -Identity Inactive.Guests.Group -UnifiedGroupWelcomeMessageEnabled:$False

The actions taken so far are one-off operations. To maintain the group membership on an ongoing basis, some form of scheduled processing is needed. My preference is to use a scheduled Azure Automation runbook, perhaps on a monthly basis.

Understand the Problem, Then Create the Solution

After all that, I remembered to ask why the reader wanted to populate a group with unused guest accounts. The response was that they wanted to remove the accounts and thought that a group would be a good way to approach the problem. Once again, this proved the truth that IT professionals should understand the full problem before rushing to create a solution. In this case, I would have used filters to find the inactive accounts as described above, and then gone ahead to delete the accounts with a simple loop.

Although it’s possible to recover deleted user accounts for up to 30 days after their deletion (Figure 1), it’s still better to think about each deletion before proceeding.

Where to restore deleted user objects in the Entra admin center

Find unused guest accounts
Figure 1: Where to restore deleted user objects in the Entra admin center

The wise approach is to review the information before rushing to delete any accounts. This step will avoid removing accounts used for important sharing or group memberships. A review is easily done by exporting the information to a CSV file or Excel worksheet. You (or others) can then peruse the output file and remove any guest account that should be kept. After the review, a script can read the information about the guest accounts from the file and then delete the accounts, like this:

[array]$InactiveGuests = Import-CSV "c:\temp\GuestAccountsToRemove.csv"
ForEach ($Account in $InactiveGuests) {
  Write-Host ("Removing guest account for {0} ({1})" -f $Account.DisplayName, $Account.UserPrincipalName)
  Remove-MgUser -UserId $Account.Id
}

The thing to remember is that a tenant might have guest accounts that don’t ever sign in for good reasons. For example, the membership of some of the Microsoft 365 groups hosted in my tenant include guest members that have been in place since 2016. These guests collaborate with other members through email and don’t need to sign in, so they’re tagged as inactive guests. It wouldn’t do to remove those accounts without thinking.

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