Detecting Inactive Guest Accounts Based on Sign-in Activity is Easy
Many PowerShell scripts to report inactive guest accounts are available on the internet (here’s an example that I wrote six years ago). All of the scripts that I have seen to date base the concept of inactivity on account sign-in activity. In other words, if an account hasn’t signed in for a set period, it must be inactive. As explained here, the code to detect inactive guest accounts based on sign-in activity can be boiled down to a few lines of PowerShell. Everything else that’s in a script handles the generation of the report and similar housekeeping:
$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} Write-Host ("Looks like there are {0} guest accounts that haven't signed in for over a year..." -f $InactiveGuests.count)
There’s no magic here. Just basic PowerShell interacting with the Microsoft Graph. The only complication is that Microsoft requires Entra P1 licenses for access to sign-in activity information, which is just silly.
Entra ID access reviews for guest accounts are a little more sophisticated and boasts some extra tricks to detect inactivity but essentially take the same path to figuring out if inactive guest accounts are present in a group or groups.
AI Doesn’t Extend the State of the Art for Inactive Guest Detection
When I wrote about the agentic future Microsoft outlined at a TEC 2025 keynote, I discussed the access review agent and expressed my disappointment that the AI-powered agent doesn’t apply any extra intelligence to how it determines whether guests are inactive. The Teams chat interaction is cute and possibly better at elucidating responses from group owners and administrators who are supposed to finalize the results of access reviews.
My major bugbear is that the agent mimics what happens today with manual access reviews and doesn’t attempt to use any of the other sources of information available within Microsoft 365 to construct a more holistic view of guest accounts to decide whether accounts are inactive. For instance, I pointed out that the unified audit log tracks actions taken by guests, so it should be easy to figure out if a guest does something useful instead of twiddling their thumbs and.
Working Demonstration of Information-Enriched Inactive Guest Assessment
It’s unfair to criticize unless you’re absolutely sure of your facts, so I sat down to create the definitive inactive guests report to illustrate how the integration of extra information can enrich the decision-making process about inactive guests. To be fair to Microsoft, checking for inactive guests is only one reason to use access reviews as part of a tenant’s identity governance framework. However, the need to manage guest accounts is a scenario that affects many Microsoft 365 tenants, so I think it’s worth exploring.
The code is implemented as a PowerShell script. The advantage of PowerShell is that it’s easy to automate many aspects of guest management. The downside is that access to some of the information that’s needed, specifically data from the unified audit log, is slow to obtain asynchronously. Given its resource-intensive processing, the script is the kind of job that is a good candidate for Azure Automation. You’ll see why as I describe how the script works.
Creating the Inactive Guests Report
The first thing the script does is to run the Connect-MgGraph cmdlet to connect to the Microsoft Graph PowerShell SDK followed by Connect-ExchangeOnline to connect to Exchange Online (for audit log access). Some housekeeping is done to establish if the code is running interactively or in Azure Automation. If interactive, the script checks that it has the right permissions.
Next, the script searches the unified audit log for events logged when a guest account is added to an organization’s directory in three different ways: SharePoint sharing; being added to a Microsoft 365 group or team by a group owner (or administrator; and being added by an administrator in the Entra admin center (Figure 1). The logic here is that it’s good to know who added a guest account and how the guest was added.

The script then retrieves all guest accounts in the tenant, including details like account sign-in activity and sponsors before beginning its major loop to:
- Find if any audit records exist for “important” actions such as uploading or modifying a SharePoint Online file or posting a message to a Teams conversation. To speed things up, the script goes back 30 days to look for actions. You could go back 180 (Purview Audit standard) or 365 (Purview Audit Premium) days but increasing the search horizon will make it much longer to process individual accounts.
- Check when the guest was added to the tenant. Recent guests might be given some leeway in terms of activity where a guest account that’s been around for six years and no trace exists of activity is an immediate candidate for removal.
- Find if the guest account has a photo. Like all user accounts, tenants can add photos to guest accounts through the Entra admin center or by running the Set-MgUserPhotoContent cmdlet. If a guest account has a photo, it’s an indication that some importance might be attached to the account (of course, if all guest accounts in the tenant have photos, then the indication is void).
- Check how many groups the guest account belongs to and the names of those groups. If a guest is present in multiple groups, the account might be important for some reason. On the other hand, if they’re not a member of any groups or only belong to one group, the account’s claim to be retained in the tenant is weaker.
- Check if the guest account is disabled.
The script then reports the status of each guest account, noting normal details like display name, user principal name, email address, and:
- Names of the account sponsors. Although sponsors are a relatively recent addition to account properties, they can be useful in determining if a guest should be kept. For example, the account for an external legal advisor who’s a member of an email-based Microsoft 365 group (aka Outlook group) and only interacts with the group via email might be terrifically important, and that importance could be indicated by sponsorship by the CEO or CIO.
- Creation date and number of days since the creation of the guest account.
- Date of the last detected audit event.
- Type of the last detected audit event. For example, “FileModified” means that the guest modified a file in SharePoint Online or OneDrive for Business.
- Number of audit activities found for the last 30 days. The higher the number, the more active the guest is.
- Top 3 audit activities.
- Last administrator action. The last action taken by an administrator that involved the guest, such as adding the user to a group. The name of the administrator is also captured.
- Last sign in by and last successful sign in by the guest account (and yes, there is a difference). The number of days since the last successful sign-in is also noted.
- The email domain the guest comes from (to summarize where the guests in the tenant come from).
- The number and names of groups that the guest is a member of.
- A guest status (inactive or active) that’s based on audit events.
At the end of its processing, the script generates a HTML report and a CSV file or Excel worksheet. The HTML report contains the most important information (Figure 2) while the other file has all the data. Both are emailed to an address designated in the script.

None of this is rocket science and I’m sure some improvements could be made. For example, the audit records for actions that help to understand when a guest was added or last modified could be stored in a hash table rather than an array for speedier performance. You could decide that checking for account photos or group membership is immaterial and remove those sections of the code to reduce the time required to process an account (about 7 seconds in my testing).
For access reviews of tenant accounts, the use of the unified audit log to check guest activity could be replaced with the usage report data that’s available for several Microsoft 365 workloads. Based activity assessments for accounts on usage reports is faster than using audit log data. However, usage data is not gathered for guest accounts, and the data is always at least two days behind real time. Using slightly outdated data should not be an issue for an access review because an inactive account is unlikely to suddenly become active. Here’s an example of accessing the usage report APIs to construct a picture of user activity across multiple workloads.
To play with the inactive guest assessment script and create your version, copy it from the Office 365 for IT Pros GitHub repository and go to work.
Better Data Drives Better Decisions
Imperfect as the script might be, the code proves the point that determination of guest account inactivity based solely on a sign-in date is a poor and short-sighted assessment. Just because the community has been judging guest accounts on sign-in activity for years is no excuse not to explore other methods.
AI is at its best when it surfaces unexpected but important points to inform better decision making. Leveraging and analyzing all available information is what I hoped the Entra ID agents would do, and that’s what I would like to see the agents do in the future.