248 lines
8.6 KiB
PowerShell
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: Remove Direct Licenses for Group-Licensed Users Using PowerShell
Version: 1.0
website: o365reports.com
Script Highlights:
~~~~~~~~~~~~~~~~~
1. This script generates reports on users with overlapping direct & group-based license assignments. 
2. The script removes direct assigned license(s) if the same license inherited via groups too.  
3. This script installs MS Graph PowerShell SDK (if not installed already) upon your confirmation. 
4. The script can be executed with an MFA-enabled account too. 
5. The script is schedular-friendly. 
6. It can be executed with certificate-based authentication (CBA) too. 
For detailed Script execution: https://o365reports.com/2024/08/27/remove-direct-licenses-for-group-licensed-users-using-powershell/
============================================================================================
#>
Param
(
[switch]$GenerateReportOnly,
[switch]$CreateSession,
[switch]$Force,
[string]$TenantId,
[string]$ClientId,
[string]$CertificateThumbprint
)
Function Connect_MgGraph
{
#Check for module installation
$Module=Get-Module -Name microsoft.graph.beta -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.beta -Repository PSGallery -Scope CurrentUser -AllowClobber -Force
}
else
{
Write-Host "Microsoft Graph Beta 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[$SkuName]
if(!($EasyName))
{$NamePrint = $SkuName}
else
{$NamePrint = $EasyName}
return $NamePrint
}
Connect_MgGraph
$Location=Get-Location
$ExportCSV = "$Location\M365Users_Overlapping_License_Report_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).csv"
$LogFile="$Location\LicenseRemoval_LogFile_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm-ss` tt).ToString()).csv"
$ExportResult=""
$ExportResults=@()
$PrintedUser=0
$Count=0
#Get license in the organization and saving it as hash table
$SKUHashtable=@{}
Get-MgBetaSubscribedSku All | foreach{
$SKUHashtable[$_.skuid]=$_.Skupartnumber}
#Get friendly name of license plan from external file
$FriendlyNameHash=Get-Content -Raw -Path "$Location\LicenseFriendlyName.txt" -ErrorAction Stop | ConvertFrom-StringData
#Confiramtion for removing duplicate license assignment
$LicenseRemovalConfirmation=$false
if($Force.IsPresent)
{
$LicenseRemovalConfirmation=$true
}
elseif(!($GenerateReportOnly.IsPresent))
{
Write-Host "Do you want to remove the direct license(s) that inherited via groups? [Y] Yes [N] No " -ForegroundColor Yellow
$Confirm= Read-Host
if($Confirm -match "[Y]")
{
$LicenseRemovalConfirmation=$true
}
else
{
Write-Host "Proceeding report generation without removing license(s) from users" -ForegroundColor Cyan
}
}
#Process users
$RequiredProperties=@('UserPrincipalName','DisplayName','EmployeeId','CreatedDateTime','AccountEnabled','Department','JobTitle','LicenseAssignmentStates')
Get-MgBetaUser -Filter "assignedLicenses/`$count ne 0" -All -Property $RequiredProperties -ConsistencyLevel eventual -CountVariable Records | select $RequiredProperties | ForEach-Object {
$Count++
$Print=1
$DirectlyAssignedLicense=@()
$GroupBasedLicense=@()
$DirectlyAssignedLicense_FrndlyName=@()
$GroupBasedLicense_FrndlyName=@()
$DirectlyAssignedSKUs=@()
$InheritedSKUs=@()
$DuplicateAssignment=@()
$UPN=$_.UserPrincipalName
Write-Progress -Activity "`n Processed user: $Count - $UPN"
$DisplayName=$_.DisplayName
$AccountEnabled=$_.AccountEnabled
$Department=$_.Department
$JobTitle=$_.JobTitle
$LicenseAssignmentStates=$_.LicenseAssignmentStates
if($AccountEnabled -eq $true)
{
$AccountStatus='Enabled'
}
else
{
$AccountStatus='Disabled'
}
foreach($License in $LicenseAssignmentStates)
{
$SKU=$License.SkuId
$SkuName=$SkuHashtable[$SKU]
$FriendlyName=Convert-FrndlyName -InputIds $SkuName
if($License.AssignedByGroup -eq $null)
{
$DirectlyAssignedLicense += $SkuHashtable[$SKU]
$DirectlyAssignedLicense_FrndlyName += $FriendlyName
$DirectlyAssignedSKUs +=$SKU
}
elseif($License.AssignedByGroup -ne $null -and $License.State -eq "Active")
{
$GroupBasedLicense += $SkuHashtable[$SKU]
$GroupBasedLicense_FrndlyName +=$FriendlyName
$InheritedSKUs +=$SKU
}
}
#Check for duplicate license assignment
if($DirectlyAssignedLicense.Count -ne 0 -and $GroupBasedLicense.Count -ne 0)
{
$IsDuplicateLicenseFound = Compare-Object -ReferenceObject $DirectlyAssignedSKUs -DifferenceObject $InheritedSKUs -IncludeEqual -ExcludeDifferent
if($IsDuplicateLicenseFound.inputObject -ne $null)
{
$DuplicateLicenses=$IsDuplicateLicenseFound.inputObject
$DuplicateLicenseCount=$DuplicateLicenses.count
foreach ($DuplicateLicense in $DuplicateLicenses)
{
$DuplicateAssignment += $SkuHashtable[$DuplicateLicense]
}
$Print=0
}
}
if($Print -ne 0)
{
return
}
$DirectlyAssignedLicense=$DirectlyAssignedLicense -join ","
$GroupBasedLicense=$GroupBasedLicense -join ","
$DirectlyAssignedLicense_FrndlyName=$DirectlyAssignedLicense_FrndlyName -join ","
$GroupBasedLicense_FrndlyName=$GroupBasedLicense_FrndlyName -join ","
$DuplicateAssignment=$DuplicateAssignment -join ","
#Export output to CSV file
$PrintedUser++
$ExportResult=[PSCustomObject]@{'Display Name'=$DisplayName;'UPN'=$UPN;'Directly Assigned Licenses'=$DirectlyAssignedLicense;'Group Based Licenses'=$GroupBasedLicense;'Duplicate License(s)'=$DuplicateAssignment;'Duplicate License Count'=$DuplicateLicenseCount;'Directly Assigned Licenses(Frndly_Name)'=$DirectlyAssignedLicense_FrndlyName;'Group based Licenses(Frndly_Name)'=$GroupBasedLicense_FrndlyName;'Account Status'=$AccountStatus;'Department'=$Department;'Job Title'=$JobTitle}
$ExportResult | Export-Csv -Path $ExportCSV -Notype -Append
#Remove Direct License Assignement if the same license assigned via group too
if(($LicenseRemovalConfirmation -eq $true))
{
foreach($DupLicense in $DuplicateLicenses)
{
$FriendlyName=Convert-FrndlyName -InputIds $DupLicense
Write-Progress "Removing $FriendlyName license from the user $UPN"
Set-MgBetaUserLicense UserId $UPN -RemoveLicenses @($DupLicense) -AddLicenses @() | Out-Null
If($?)
{
"Removing $FriendlyName license from the user $UPN successful" | Out-File $LogFile -Append
}
Else
{
"Removing $FriendlyName license from the user $UPN failed" | Out-File $LogFile -Append
}
}
}
}
#Open output file after execution
Write-Host `nScript execution completed
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 get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n
Write-Host "Exported report has $PrintedUser user(s)"
Write-Host "Detailed report available in:" -NoNewline -ForegroundColor Yellow; Write-Host $ExportCSV
if($LicenseRemovalConfirmation -eq $true)
{
Write-Host "License removal log file available in:" -NoNewline -ForegroundColor Yellow; Write-Host $LogFile
}
$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"
}
}
else
{
Write-Host "No user found" -ForegroundColor Red
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 get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n
}