Finding Stale Azure AD B2B Guest Accounts based on lastSignInDateTime

Collaboration between Azure Active Directory tenants typically involves Azure AD Guest accounts. After a few years, the proliferation of ‘Guest’ accounts usually becomes a focus, especially for larger tenants. As Azure AD has matured the meta data associated with accounts, along with Microsoft Graph improvements is making it easier to define and locate stale Azure AD B2B Guest Accounts. In this post I investigate Azure AD with Microsoft Graph API’s to find stale Azure AD B2B Guest Accounts.

Options to determine Stale Azure AD B2B Guest Account Status

  • accountEnabled
    • Is the account currently enabled? If not can it simply be deleted?
  • createdDateTime
    • When was the B2B Guest Account created? When used in conjunction with other attributes listed here can we determine if the B2B Guest account is still required?
  • externalUserState and externalUserStateChangeDateTime
  • lastSignInDateTime
  • signIn Session Token
    • signInSessionsValidFromDateTime is the new name for the previous refreshTokensValidFromDateTime attribute.
      • the default token refresh lifetime in Azure AD is 90 days and in some examples looking to identify stale accounts is used to determine account currency.
        • Note: Not all Microsoft 365 products appear to update this attribute.
          • In my testing I’ve seen where the timestamp on this attribute is from mid-2019 but the actual last sign-in timestamp is from December 2020. So whilst there are several solutions for identifying stale Azure AD B2B Guest Accounts using this attribute, I won’t be using it in my solution.

Searching Azure AD Sign-Ins Report (and why it’s not a valid option)

Retention of logs/reports for Azure AD Sign-Ins is dependent on your licensing level. The Azure AD Sign-Ins Report (which you can get through the Azure AD Admin Portal and via Microsoft Graph) is limited to at most, 30 days. If you are exporting your Azure AD Logs to Azure Monitor you can keep a longer history of this data. However, for our purpose this isn’t an option, and is only shown here for completeness.

Stale Azure AD B2B Guest Accounts

Stale Azure AD B2B Guest Accounts Logic

With the summary above of what attributes we have to work with, let’s summarise a plan on how to accurately identify stale accounts.

  • If the Azure AD account is a B2B Guest Account
    • AND accountEnabled equals False OR
    • externalUserState is Pending AND the invitation date and time is older than <stale period> OR
    • lastSignInDateTime is older than the accepted stale time period
    • THEN identify the B2B Guest Account as ‘stale‘.

PowerShell Script Prerequisites

The example script in this post utilizes an additional PowerShell Module to simplify the process. You will require:

  • MSAL.PS to simplify authentication to Microsoft Graph.
    • Install the module from the PS Gallery using PowerShell 5.1+ using command
      • Install-Module MSAL.PS -Force -AcceptLicense

You will need to register an Azure AD Application with Application Permissions for the AuditLog.Read.All and User.Read.All scopes. The script example below uses both these scopes. The registered application will need to be authorized (Admin consent) for the tenant. You will need to record the registered Application (client) ID and Directory (tenant) ID from the Overview page of the registered application for use in the script. Finally, generate a secret from the Certificates & secrets tab and record the secret also for use in the script.

Stale Azure AD Guest Accounts Script

The screenshot below shows the output from the PowerShell script. After identifying the stale accounts based on the criteria logic listed above, each categorization is a collection containing the associated accounts. A series of Write-Host statements in the script give a summary of what was identified in the analysis as detailed above. The output below is based on inactivity greater than 90 days.

Stale Azure AD B2B Guest Accounts based on lastSignInDateTime summary
  • The $guests collection contains all inactive Guest accounts
  • The $disabledGuests collection contains all inactive Guest accounts that are flagged as disabled
  • The $guests and $disabledGuests collections include updated Azure AD User Objects with the lastSignInDateTime attribute. See line 193 in the script below.
  • The $pendingGuests collection contains all B2B Guest Invitations that are ‘pendingAcceptance
  • The $stalePendingInvites collection contains all the B2B Guest Invitations that are ‘pendingAcceptanceand where the invite was sent prior to the specified inactive period
  • Finally, the $staleGuests collection contains all inactive guests and stale pending invites for the specified inactive period

Script Functions

The PowerShell script contains four functions. One to perform authentication to Microsoft Graph using the Tenant ID and Application (client) ID and Client Secret of the AAD Registered Application that contains AuditLog.Read.All and User.Read.All Application permissions. A second to obtain Azure AD Users’ that have not signed in for a specified period. A third to get a user’s last sign-in date and time and a fourth to get all ‘pending’ Guest Invitations.

As mentioned above there is a function that leverages the MSAL.PS PowerShell Module to simplify authentication to Microsoft Graph. The AuthN function takes the -credential and -tenantID parameters. The credential parameter is the ClientID and ClientSecret from the AAD registered application. The tenantID parameter is the objectID of the Azure AD Tenant where the application is registered.

The GetAADSignIns function is used to get Azure AD accounts whose last sign in is older than the specified period.

The GetAADUserSignInActivity function will return the date and time of the last sign-in for a user. It takes a single parameter (-ID) which is the objectID of an Azure AD User object. The BETA Microsoft Graph Users API is used, so by default returns all attributes on the Azure AD User Object.

The GetAADPendingGuests function will return all B2B Guest invitations that are ‘PendingAcceptance‘.

The script is parameterized. Make the following updates with your configuration information;

  • 161 for the Tenant ID that you will be retrieving the report from
  • 163 for the ClientID and ClientSecret of the registered Azure AD Application
  • 176 for the number of days over which you consider an account that has not signed in or a B2B invitation not to be accepted to be stale (e.g. 90 Days).
    • Note: Azure AD stores the dates and time in UTC and the script subtracts the specified number of days from your current local date


Using Microsoft Graph API’s we can determine the status of Azure AD Guest Accounts. The example above could be easily adapted to perform similar analysis on Member Accounts. Keep in mind, if you want to retrieve the actual last sign-in date and time for many member accounts it will take time to retrieve it as it requires an API call per account.

The collections could also be output directly into an Excel spreadsheet as I showed in my recent post for Getting Microsoft 365 Individual User Usage Reports with PowerShell.