AdminDroid e859700d21 Find M365 User License Assignment Path
Enhancements and special case handled
2025-08-21 10:16:47 +05:30

249 lines
7.8 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<#
=============================================================================================
Name: Find & Export Microsoft 365 User License Assignment Paths using PowerShell
Version: 2.0
website: o365reports.com
~~~~~~~~~~~~~~~~~~
Script Highlights:
~~~~~~~~~~~~~~~~~~
1. The script uses MS Graph PowerShell and installs MS Graph PowerShell SDK (if not installed already) upon your confirmation.
2. The script can be executed with MFA enabled account too.
3. Exports directly assigned licenses alone.
4. Exports group-based license assignments alone.
5. Helps to identify users with license assignment errors.
6. Converts SKU name into user-friendly name.
7. Produces a list of disabled service plans for the assigned license.
8. Exports report results as a CSV file.
9. The script is scheduler friendly.
10. It can be executed with certificate-based authentication (CBA) too.
For detailed Script execution: https://o365reports.com/2024/05/14/find-export-microsoft-365-user-license-assignment-paths-using-powershell/
~~~~~~~~~~~
Change Log:
~~~~~~~~~~~
V1.0 (May 17, 2024) File created.
V2.0 (Aug 21, 2025) Upgraded from the Graph beta module to the Graph module, implemented error handling for retrieving expired trial subscriptions, and upgraded to use new LicenseFriendlyName.csv for license mapping.
============================================================================================
#>
Param
(
[switch]$ShowDirectlyAssignedLicenses,
[switch]$ShowGrpBasedLicenses,
[switch]$DisabledUsersOnly,
[Switch]$FindUsersWithLicenseAssignmentErrors,
[switch]$CreateSession,
[string]$TenantId,
[string]$ClientId,
[string]$CertificateThumbprint
)
Function Connect_MgGraph
{
#Check for module installation
$Module=Get-Module -Name microsoft.graph -ListAvailable
if($Module.count -eq 0)
{
Write-Host Microsoft Graph PowerShell SDK is not available -ForegroundColor yellow
$Confirm= Read-Host Are you sure you want to install module? [Y] Yes [N] No
if($Confirm -match "[yY]")
{
Write-host "Installing Microsoft Graph PowerShell module..."
Install-Module Microsoft.Graph -Repository PSGallery -Scope CurrentUser -AllowClobber -Force
}
else
{
Write-Host "Microsoft Graph PowerShell module is required to run this script. Please install module using 'Install-Module Microsoft.Graph' cmdlet."
Exit
}
}
#Disconnect Existing MgGraph session
if($CreateSession.IsPresent)
{
Disconnect-MgGraph
}
Write-Host Connecting to Microsoft Graph...
if(($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne ""))
{
Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome
}
else
{
Connect-MgGraph -Scopes "User.Read.All","AuditLog.read.All" -NoWelcome
}
}
Function Convert-FrndlyName {
param(
[Parameter(Mandatory=$true)]
[Array]$InputIds
)
$EasyName = $FriendlyNameHash[$InputIds]
if(!($EasyName))
{$NamePrint = $InputIds}
else
{$NamePrint = $EasyName}
return $NamePrint
}
Connect_MgGraph
$Location=Get-Location
$ExportCSV="$Location\M365Users_LicenseAssignmentPath_Report_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).csv"
$ExportResult=""
$ExportResults=@()
#Get license in the organization and saving it as hash table
$SKUHashtable=@{}
Get-MgSubscribedSku All | foreach{
$SKUHashtable[$_.skuid]=$_.Skupartnumber
}
#Get Licenses
$FriendlyNameHash = @{}
Import-Csv -Path .\LicenseFriendlyName.csv -ErrorAction Stop | ForEach-Object{
$FriendlyNameHash[$_.String_Id] = $_.Product_Display_Name
}
#Get friendly name of Service plan from external file
$ServicePlanHash=@{}
Import-Csv -Path .\ServicePlansFrndlyName.csv | ForEach-Object {
$ServicePlanHash[$_.ServicePlanId] = $_.ServicePlanFriendlyNames
}
$GroupNameHash=@{}
#Process users
$RequiredProperties=@('UserPrincipalName','DisplayName','EmployeeId','CreatedDateTime','AccountEnabled','Department','JobTitle','LicenseAssignmentStates','AssignedLicenses','SigninActivity')
$Count=0
Get-MgUser -All -Property $RequiredProperties | ForEach-Object {
$Count++
$Print=1
$DirectlyAssignedLicense=@()
$GroupBasedLicense=@()
$DirectlyAssignedLicense_FrndlyName=@()
$GroupBasedLicense_FrndlyName=@()
$UPN=$_.UserPrincipalName
Write-Progress -Activity "`n Processing user: $Count - $UPN"
$DisplayName=$_.DisplayName
$AccountEnabled=$_.AccountEnabled
$Department=$_.Department
$JobTitle=$_.JobTitle
$LastSignIn=$_.SignInActivity.LastSignInDateTime
if($LastSignIn -eq $null)
{
$LastSignIn = "Never Logged In"
$InactiveDays = "-"
}
else
{
$InactiveDays = (New-TimeSpan -Start $LastSignIn).Days
}
$LicenseAssignmentStates=$_.LicenseAssignmentStates
if($AccountEnabled -eq $true)
{
$AccountStatus='Enabled'
}
else
{
$AccountStatus='Disabled'
}
foreach($License in $licenseAssignmentStates)
{
$SkuName=$SkuHashtable[$License.SkuId] #When activated trial ends, it will not be available in the Get-MgBetaSubscribedSKU. In that case, respective SKU name will be empty.
if($SkuName -ne $null)
{
$FriendlyName=Convert-FrndlyName -InputIds $SkuName
}
else
{
$FriendlyName=$License.SkuId
}
$DisabledPlans=$License.DisabledPlans
$ServicePlanNames=@()
if($DisabledPlans.count -ne 0 )
{
foreach($DisabledPlan in $DisabledPlans)
{
$ServicePlanName = $ServicePlanHash[$DisabledPlan]
if(!($ServicePlanName))
{$NamePrint = $DisabledPlan}
else
{$NamePrint = $ServicePlanName}
$ServicePlanNames += $NamePrint
}
}
$DisabledPlans=$ServicePlanNames -join ","
$State=$License.State
$LicenseError=$License.Error
#Filter for users with license assignment errors
if($FindUsersWithLicenseAssignmentErrors.IsPresent -and ($State -eq "Active"))
{
$Print=0
}
if($License.AssignedByGroup -eq $null)
{
$LicenseAssignmentPath="Directly assigned"
$GroupName="NA"
#Filter for group based license assignment
if($ShowGrpBasedLicenses.IsPresent)
{
$Print=0
}
}
else
{
$LicenseAssignmentPath="Inherited from group"
#Filter for directly assigned licenses
if($ShowDirectlyAssignedLicenses.IsPresent)
{
$Print=0
}
$AssignedByGroup=$License.AssignedByGroup
# Check Id-Name pair already exist in hash table
if($GroupNameHash.ContainsKey($AssignedByGroup))
{
$GroupName=$GroupNameHash[$AssignedByGroup]
}
else
{
$GroupName=(Get-MgGroup -GroupId $AssignedByGroup).DisplayName
$GroupNameHash[$AssignedByGroup]=$GroupName
}
}
if($Print -eq 1)
{
$ExportResult=[PSCustomObject]@{'Display Name'=$DisplayName;'UPN'=$UPN;'License Assignment Path'=$LicenseAssignmentPath;'Sku Name'=$SkuName;'Sku_FriendlyName'=$FriendlyName;'Disabled Plans'=$DisabledPlans;'Assigned via(group name)'=$GroupName;'State'=$State;'Error'=$LicenseError;'Last Signin Time'=$LastSignIn;'Inactive Days'=$InactiveDays;'Account Status'=$AccountStatus;'Department'=$Department;'Job Title'=$JobTitle}
$ExportResult | Export-Csv -Path $ExportCSV -Notype -Append
}
}
}
Disconnect-MgGraph | Out-Null
#Open output file after execution
Write-Host `nScript executed successfully.
if((Test-Path -Path $ExportCSV) -eq "True")
{
Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to access 3,000+ reports and 450+ management actions across your Microsoft 365 environment. ~~" -ForegroundColor Green `n`n
$Prompt = New-Object -ComObject wscript.shell
$UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4)
if ($UserInput -eq 6)
{
Invoke-Item "$ExportCSV"
}
Write-Host `nDetailed report available in: -NoNewline -ForegroundColor Yellow
Write-Host $ExportCSV
}
else
{
Write-Host "No user found" -ForegroundColor Red
}