Entra / Microsoft 365 · Licensing
Assign licenses via CSV
Script to assign licenses for a chosen SKU to a set of users imported from a CSV.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes Directory.ReadWrite.All
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
Function Get-Response ([string]$Prompt,[int]$NumberPossibleAnswers) {# Helper function to prompt a question and get a response$OKtoProceed = $FalseWhile ($OKToProceed -eq $False) {[int]$Answer = Read-Host $PromptIf ($Answer -gt 0 -and $Answer -le $NumberPossibleAnswers) {$OKtoProceed = $TrueReturn ($Answer)} ElseIf ($Answer -eq 0) { #break out of loop$OKtoProceed = $TrueReturn ($Answer)}} #End while}# Connect to the Graph with permission to update the directory (with licenses)Connect-MgGraph -Scopes Directory.ReadWrite.All$InputFile = "c:\temp\Users.csv"$LicensesAvailable = $True# Find the set of SKUs used in the tenant[array]$Skus = (Get-MgSubscribedSku)$SkuList = [System.Collections.Generic.List[Object]]::new()ForEach ($Sku in $Skus) {$SkuAvailable = ($Sku.PrepaidUnits.Enabled - $Sku.ConsumedUnits)$ReportLine = [PSCustomObject]@{SkuId = $Sku.SkuIdSkuPartNumber = $Sku.SkuPartNumberConsumed = $Sku.ConsumedUnitsPaid = $Sku.PrepaidUnits.EnabledAvailable = $SkuAvailable }$SkuList.Add($ReportLine)}# Remove SKUs with no available licenses$SkuList = $SkuList | Where-Object {$_.Available -gt 0}If ($SkuList.count -eq 0) {$LicensesAvailable = $FalseWrite-Host "No SKUs have avaiilable licenses"}If ($LicensesAvailable -eq $True) {[int]$i = 0Write-Host " "Write-host "Product SKUs with available licenses" -foregroundcolor RedWrite-Host "------------------------------------" -foregroundcolor RedWrite-Host ""Write-Host "Select the Microsoft 365 product SKU to assign licenses to users; enter 0 to exit"; [int]$i=0ForEach ($Sku in $SkuList) {$i++$Line = ("{0}: {1} (available units {2})" -f $i, $Sku.SkuPartNumber, $Sku.Available)Write-Host $Line}[Int]$Answer = Get-Response -Prompt "Enter the number of the product SKU to assign" -NumberPossibleAnswers $iIf (($Answer -gt 0) -and ($Answer -le $i)) {$i = ($Answer-1)[string]$SelectedSku = $SkuList[$i].SkuPartNumber[string]$SelectedSkuId = $SkuList[$i].SkuIdWrite-Host "OK. The selected product SKU to assign to user accounts is:" $SelectedSku} Elseif ($Answer -eq 0) {#AbortWrite-Host "Script stopping..." ; break}} # End listing of available SKUsWrite-Host ""Write-Host "Looking for accounts to process..."Write-Host ""# Import user accounts to assign licenses to - all that's important here is to establish an array of# user accounts to process. Instead of reading information from a CSV file, the data could come from# running the Get-MgUser cmdlet with a filter to fetch certain accountsIf ($LicensesAvailable -eq $True) {[array]$Users = Import-CSV $InputFileIf (!($Users)) {Write-Host "Unable to find any users to process - exiting"; break} Else { # Check that there are sufficient licenses available to assign to the number of accounts to processIf ($Users.Count -gt $SkuList[$i].Available) {Write-Host ("{0} users are to receive licenses but there are only {1} licenses available - exiting." -f $Users.Count, $SkuListing[$i].Available)$LicensesAvailable = $False}}}If ($LicensesAvailable -eq $True) {$AssignmentReport = [System.Collections.Generic.List[Object]]::new()# Check each user to see if the account exists and if the SKU is already assignedWrite-Host "Checking user accounts and assigning licenses..."$i = 0ForEach ($User in $Users) {$ErrorMsg = $Null; $i++Write-Host ("Processing account {0} {1}/{2}" -f $User.displayName, $i, $Users.count)$UserData = Get-MgUser -UserId $User.UPN.Trim() -Property id, assignedLicenses -ErrorAction SilentlyContinueIf (!($UserData)) {# Can't find user account, so flag an error$ErrorMsg = ("Error: User account {0} does not exist in Entra ID" -f $User.UPN)Write-Host $ErrorMsg$ReportLine = [PSCustomObject]@{User = $User.UPNStatus = $ErrorMsgTimeStamp = (Get-Date -format "dd-MMM-yyyy hh:mm:ss") }$AssignmentReport.Add($ReportLine)} Else {# Account is available, so check license and assign it if the account doesn't already have it$LicenseData = $UserData | Select-Object -ExpandProperty AssignedLicensesIf ($SelectedSkuId -in $LicenseData.SkuId) {$ErrorMsg = ("Error: License {0} is already assigned to user account {1}" -f $SelectedSku, $User.UPN)Write-Host $ErrorMsg$ReportLine = [PSCustomObject]@{User = $User.UPNStatus = $ErrorMsgTimeStamp = (Get-Date -format "dd-MMM-yyyy hh:mm:ss") }$AssignmentReport.Add($ReportLine)} Else {# Assign the license$License = Set-MgUserLicense -UserId $User.UPN -Addlicenses @{SkuId = $SelectedSkuId} `-RemoveLicenses @() -ErrorAction SilentlyContinueIf ($License) {$StatusMsg = ("Success: Sku {0} successfully assigned to {1}" -f $SelectedSku, $User.UPN)Write-Host $StatusMsg$ReportLine = [PSCustomObject]@{User = $User.UPNStatus = $StatusMsgTimeStamp = (Get-Date -format "dd-MMM-yyyy hh:mm:ss") }$AssignmentReport.Add($ReportLine)} Else {$ErrorMsg = ("Error: Some problem stopped us assigning Sku {0} to {1}" -f $SelectedSku, $User.UPN)Write-Host $ErrorMsg -ForegroundColor Yellow$ReportLine = [PSCustomObject]@{User = $User.UPNStatus = $ErrorMsgTimeStamp = (Get-Date -format "dd-MMM-yyyy hh:mm:ss") }$AssignmentReport.Add($ReportLine)}}}}} # End Processing accounts to assign licenses[array]$Successes = $AssignmentReport | Where-Object {$_.Status -like "*Success:*"}[array]$Failures = $AssignmentReport | Where-Object {$_.Status -like "*Error:*"}$PCentSuccesses = ($Successes.Count/$Users.Count).toString("P")$PcentFailures = ($Failures.count/$Users.Count).toString("P")Write-Host ""Write-Host "All done"Write-Host ("Results of assigning the {0} product SKU" -f $SelectedSku)Write-Host ""Write-Host ("Successful license assignments: {0} {1}" -f $Successes.Count, $PCentSuccesses )Write-Host ("Failures in license assignments: {0} {1}" -f $Failures.Count, $PcentFailures )$AssignmentReport | Out-GridView
Attribution
Author
Office365itpros