Create an Inactive M365 User Report with Microsoft Graph PowerShell

With many users in your Microsoft 365 tenant, it is difficult to simply glance at your user list to determine if it is accurate or not. As well as this, you need to ensure you are not being overbilled and that you are not susceptible to additional risk of compromise by having dormant and available accounts still in your tenant. 

In this tutorial, I am going to show you how to use Microsoft Graph PowerShell to export a list of active and inactive users in your tenant. You can then use this information to clear up and inactive user accounts. 

Pre-requisites

To generate the Inactive Microsoft 365 user report in this tutorial you must ensure you have the Microsoft Graph PowerShell Beta modules installed. Check out my tutorial here: How To Install the Microsoft Graph PowerShell Module which details how to install and upgrade the Microsoft Graph PowerShell modules.

As well as this, to grant consent to Microsoft Graph API permissions in Microsoft Entra you will need to sign in interactively at the start of the script with a global admin account. Once that is done, you can then lower the permissions on the account used to sign-in.

The permissions you will grant to the Microsoft Graph Command Line Tools application are:

  • User.Read.All
  • AuditLog.Read.All

Inactive M365 User Report Script

To use the script, I recommend hovering your cursor over the script below and using the copy function at the top right. Then paste the script into your IDE, PowerShell ISE or Notepad and ensure you understand what it is doing. When you are happy, run it from PowerShell ISE or paste it into your PowerShell session.

Important: Always thoroughly understand what a script is doing before you run it against your environment. Even if it does not make any changes, it is often beneficial to alert your security team to advise of any potential alerts that running a script will generate.

The script will first gather all the users in your tenant with the Get-MgBetaUser cmdlet. It will then loop through each user to get their basic information, including the Sign-in Activity and Licensing assignments, then generate a new PowerShell object with this information and add it to the report. The report will then be exported as a CSV file to C:\temp\ where you can filter and sort the LastSignIn Activity columns to achieve your result.

<#
AUTHOR: Daniel Bradley
LINKEDIN: https://www.linkedin.com/in/danielbradley2/
TWITTER: https://twitter.com/DanielatOCN
WEBSITE: https://ourcloudnetwork.com/
Info: This script was written by Daniel Bradley for the ourcloudnetwork.com blog
#>

#Connect to Microsoft Graph
Connect-MgGraph -scope User.Read.All, AuditLog.read.All

#Gather all users in tenant
$AllUsers = Get-MgBetaUser -All -Property signinactivity

#Create a new empty array list object
$Report = [System.Collections.Generic.List[Object]]::new()

Foreach ($user in $AllUsers)
{
    #Null variables
    $SignInActivity = $null
    $Licenses = $null

    #Display progress output
    Write-host "Gathering sign-in information for $($user.DisplayName)" -ForegroundColor Cyan

    #Get current user information
    $licenses = (Get-MgBetaUserLicenseDetail -UserId $User.id).SkuPartNumber -join ", "

    #Create informational object to add to report
    $obj = [pscustomobject][ordered]@{
            DisplayName                = $user.DisplayName
            UserPrincipalName          = $user.UserPrincipalName
            Licenses                   = $licenses
            LastInteractiveSignIn      = $User.SignInActivity.LastSignInDateTime
            LastNonInteractiveSignin   = $User.SignInActivity.LastNonInteractiveSignInDateTime
            LastSuccessfullSignInDate  = $User.SignInActivity.LastSuccessfulSignInDateTime
        }
    
    #Add current user info to report
    $report.Add($obj)
}

$report | Export-CSV -path C:\temp\Microsoft365_User_Activity-Report.csv -NoTypeInformation

Summary

Although with an efficient onboarding and offboarding process, this take may not be necessary. However, as time goes on, accounts can be left behind and you may be surprised at what you find.

Inactive and licensed users are costly, multiple users with expensive licenses can then easily rack up hundreds of pounds a month in wasted spend. Microsoft is not going to do this due diligence for you, so you must do it yourself!

As well as the cost, inactive user accounts are just another vector for compromise or abuse to get access to your tenant. Many times in the past have I seen poorly configured tenants, only to notice a breach due to the impact of a user’s ability to work, which often only happens when the attacker has exhausted what they can steal from the organisation and has decided to provoke havoc. If an inactive user is compromised, it could go undetected for a longer period, compared to an active user.

Daniel Bradley

My name is Daniel Bradley and I work with Microsoft 365 and Azure as an Engineer and Consultant. I enjoy writing technical content for you and engaging with the community. All opinions are my own.

Leave a Reply