How to Track Licensed Microsoft 365 Administrators In Your Tenant?

As a Microsoft 365 admin, keeping tabs on your licensed administrators isn't just good practice — it's essential for security, compliance, and license optimization. In this post, we'll explain 2 ways using which you can track licensed Microsoft 365 administrators effortlessly — either via Graph PowerShell or a ready-to-use GUI-based WinForms tool.

Who is a Licensed Microsoft 365 Administrator?

A Licensed Microsoft 365 Administrator is:

  • A user assigned one or more administrative roles (e.g., Global Admin, Teams Admin)
  • And who also holds a valid Microsoft 365 license

Examples of such role include: Global Administrator, Exchange Administrator, Teams Adminstrator, SharePoint Administrator etc.,

Not all role holders are automatically licensed — this is why you need to validate.

Why You Should Check for Licensed Microsoft 365 Administrators?

Here are five reasons:

  • Security Enforcement Ensure only valid and licensed users have admin control.
  • Audit Readiness: Meet compliance requirements by tracking role/license correlation
  • License Cost Control Spot unlicensed or over-licensed accounts.
  • Prevent Orphan Admins Avoid roles being assigned to stale/unlicensed users.
  • Operational Clarity Identify active vs. inactive admin accounts.

How To Check for Licensed Microsoft 365 Administrators In Your Tenant?

There are two ways to do this: You can use a Graph PowerShell script or opt for a GUI tool if you want a friendlier experience.

1. Using Graph PowerShell:

If you're comfortable with PowerShell and scripting, this method gives you full control. We’ve published a full article and script walkthrough here: Graph PowerShell Script for Fetching Licensed M365 Admins

This script:

  • Connects to Graph
  • Retrieves all active admin roles
  • Filters only licensed users
  • Displays a clean report (Name, UPN, Roles, License Status)

2. Easier Alternative: Using Our WinForms-Based GUI Script

If you prefer not to work in the command line, we’ve built a simple, click-and-run GUI tool using PowerShell + WinForms. Here's the full script you can save and run as .ps1:

Note: This works only for Windows users.

PowerShell Script: LicensedM365Admins_GUI.ps1

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object System.Windows.Forms.Form
$form.Text = "Licensed Microsoft 365 Admins"
$form.Size = New-Object System.Drawing.Size(800, 500)
$form.StartPosition = "CenterScreen"

$fetchButton = New-Object System.Windows.Forms.Button
$fetchButton.Text = "Fetch Licensed Admins"
$fetchButton.Location = New-Object System.Drawing.Point(20, 20)
$fetchButton.Size = New-Object System.Drawing.Size(160, 30)
$form.Controls.Add($fetchButton)

$exportButton = New-Object System.Windows.Forms.Button
$exportButton.Text = "Export to CSV"
$exportButton.Location = New-Object System.Drawing.Point(200, 20)
$exportButton.Size = New-Object System.Drawing.Size(120, 30)
$exportButton.Enabled = $false
$form.Controls.Add($exportButton)

$dataGrid = New-Object System.Windows.Forms.DataGridView
$dataGrid.Location = New-Object System.Drawing.Point(20, 70)
$dataGrid.Size = New-Object System.Drawing.Size(740, 360)
$dataGrid.ReadOnly = $true
$dataGrid.AllowUserToAddRows = $false
$dataGrid.AutoSizeColumnsMode = "Fill"
$form.Controls.Add($dataGrid)

$script:LicensedAdmins = @()

$fetchButton.Add_Click({
    $fetchButton.Enabled = $false
    $exportButton.Enabled = $false
    $dataGrid.DataSource = $null

    try {
        Connect-MgGraph -Scopes "Directory.Read.All", "User.Read.All" -ErrorAction Stop
        $roles = (Invoke-MgGraphRequest -Method GET -Uri "v1.0/directoryRoles").value
        $licensedAdminsMap = @{}

        foreach ($role in $roles) {
            $roleId = $role.id
            $roleName = $role.displayName
            try {
                $members = (Invoke-MgGraphRequest -Method GET -Uri "v1.0/directoryRoles/$roleId/members").value
            } catch { continue }

            foreach ($member in $members) {
                if (-not $member.userPrincipalName) { continue }
                try {
                    $licenseInfo = Invoke-MgGraphRequest -Method GET -Uri "v1.0/users/$($member.id)/licenseDetails"
                } catch { continue }
                if ($licenseInfo.value.Count -eq 0) { continue }

                $upn = $member.userPrincipalName
                if (-not $licensedAdminsMap.ContainsKey($upn)) {
                    $licensedAdminsMap[$upn] = @{
                        DisplayName       = $member.displayName
                        UserPrincipalName = $upn
                        LicenseStatus     = "Licensed"
                        Roles             = @($roleName)
                        JobTitle          = $member.jobTitle
                    }
                } else {
                    $licensedAdminsMap[$upn].Roles += $roleName
                }
            }
        }

        $script:LicensedAdmins = $licensedAdminsMap.Values | ForEach-Object {
            [PSCustomObject]@{
                "Admin Name"          = $_.DisplayName
                "User Principal Name" = $_.UserPrincipalName
                "License Status"      = $_.LicenseStatus
                "Job Title"           = $_.JobTitle
                "Admin Roles"         = ($_.Roles -join ", ")
            }
        }

        if ($script:LicensedAdmins.Count -gt 0) {
            $dataTable = New-Object System.Data.DataTable
            $script:LicensedAdmins[0].psobject.properties.name | ForEach-Object {
                [void]$dataTable.Columns.Add($_)
            }
            foreach ($obj in $script:LicensedAdmins) {
                $row = $dataTable.NewRow()
                foreach ($col in $dataTable.Columns) {
                    $row[$col.ColumnName] = $obj.($col.ColumnName)
                }
                $dataTable.Rows.Add($row)
            }

            $dataGrid.DataSource = $dataTable
            $exportButton.Enabled = $true
        } else {
            [System.Windows.Forms.MessageBox]::Show("No licensed admins found.", "Info", "OK", "Information")
        }

    } catch {
        [System.Windows.Forms.MessageBox]::Show("Error: $($_.Exception.Message)", "Error", "OK", "Error")
    }

    $fetchButton.Enabled = $true
})

$exportButton.Add_Click({
    $saveDialog = New-Object System.Windows.Forms.SaveFileDialog
    $saveDialog.Filter = "CSV Files (*.csv)|*.csv"
    $saveDialog.Title = "Save Report"
    $saveDialog.FileName = "LicensedM365Admins.csv"

    if ($saveDialog.ShowDialog() -eq "OK") {
        $script:LicensedAdmins | Export-Csv -Path $saveDialog.FileName -NoTypeInformation
        [System.Windows.Forms.MessageBox]::Show("Export completed.", "Export", "OK", "Information")
    }
})

[void]$form.ShowDialog()
  

Click and Play the GIF to see the tool in action

Conclusion

Tracking licensed Microsoft 365 administrators is a smart move for every IT team. Whether you prefer working with Graph PowerShell or a simple GUI interface, you now have two efficient ways to gather and export the data.

Stay proactive. Stay compliant. And most importantly — make administration easier with automation.

Did You Know? Managing Microsoft 365 applications is even easier with automation. Try our Graph PowerShell scripts to automate tasks like generating reports, cleaning up inactive Teams, or assigning licenses efficiently.

Ready to get the most out of Microsoft 365 tools? Explore our free Microsoft 365 administration tools to simplify your administrative tasks and boost productivity.

© Your Site Name. All Rights Reserved. Design by HTML Codex