How to Bulk-Assign Copilot Licenses With PowerShell
🔍 WiseChecker

How to Bulk-Assign Copilot Licenses With PowerShell

Official documentation checked October 14, 2026.

You can assign Microsoft 365 Copilot licenses to an approved user list with Microsoft Graph PowerShell. The important safeguards are choosing the exact purchased product, previewing the users, checking prerequisite licenses and recording errors honestly. A command succeeding is not proof that every Copilot feature has finished provisioning.

The example below performs no assignment unless you explicitly change its final execution switch. It is intended for a small administrator-reviewed batch first, not an unattended tenant-wide rollout.

ADVERTISEMENT

Identify the Product and Required Permissions

SPE_E5 means Microsoft 365 E5, not a Copilot add-on. Check the tenant’s subscribed products and the current Copilot licensing requirements. A base product, an add-on and a bundle can have different service plans; do not select a SKU merely because a web example uses it.

Use an approved Microsoft Graph PowerShell installation and a work account with a supported license-assignment role, such as License Administrator. The assignLicense API documents LicenseAssignment.ReadWrite.All as its least-privileged delegated assignment permission. The example also reads users and subscribed products. Delegated permission consent does not give the signed-in person an Entra administrator role.

Connect-MgGraph -Scopes "LicenseAssignment.ReadWrite.All","User.Read.All","Organization.Read.All"
Get-MgContext | Select-Object TenantId, Account
Get-MgSubscribedSku -All |
    Select-Object SkuPartNumber, SkuId, ConsumedUnits, PrepaidUnits

Confirm the tenant and account before proceeding. Review the exact product, purchased seats, available capacity and intended service-plan configuration in the admin center. Do not buy additional seats or change disabled service plans automatically as an error-recovery step.

Preview a CSV Batch Before Making Changes

Create an approved CSV with one UserPrincipalName column and one sign-in name per row. Use a small pilot list. Update the path and replace the SKU placeholder with the verified GUID from your own tenant.

 $pending = @()
 $targets = @()
 $skuId = $null
 try {
 $csvPath = "C:\Scripts\CopilotUsers.csv"
 $skuId = [guid]"REPLACE-WITH-VERIFIED-SKU-GUID"
 $rows = @(Import-Csv -LiteralPath $csvPath -ErrorAction Stop)
 if ($rows.Count -eq 0 -or
     $rows[0].PSObject.Properties.Name -notcontains 'UserPrincipalName') {
     throw 'The CSV must contain a UserPrincipalName column and at least one user.'
 }
 if (@($rows | Where-Object {
     [string]::IsNullOrWhiteSpace($_.UserPrincipalName)
 }).Count -gt 0) { throw 'The CSV contains an empty user name.' }
 $upns = @($rows | ForEach-Object {
     $_.UserPrincipalName.Trim()
 } | Sort-Object -Unique)
 $sku = @(Get-MgSubscribedSku -All -ErrorAction Stop |
     Where-Object { $_.SkuId -eq $skuId })
 if ($sku.Count -ne 1) { throw 'Selected SKU was not found uniquely in this tenant.' }
 $targets = @(foreach ($upn in $upns) {
     $u = Get-MgUser -UserId $upn -Property Id,UserPrincipalName,UsageLocation,AssignedLicenses -ErrorAction Stop
     if ([string]::IsNullOrWhiteSpace($u.UsageLocation)) {
         throw "UsageLocation is missing for $upn. Review the user's real location."
     }
     [pscustomobject]@{
         Id = $u.Id
         UserPrincipalName = $u.UserPrincipalName
         UsageLocation = $u.UsageLocation
         AlreadyAssigned = (@($u.AssignedLicenses.SkuId) -contains $skuId)
     }
 })
 $targets | Format-Table UserPrincipalName, UsageLocation, AlreadyAssigned
 $pending = @($targets | Where-Object { -not $_.AlreadyAssigned })
 Write-Output "New assignments proposed: $($pending.Count)"
 } catch {
     $targets = @()
     $pending = @()
     throw
 }

Stop if any lookup fails. This preview checks identity and missing usage location, but it does not validate every eligibility rule. Review each user’s qualifying license, correct country/region, account suitability, available seats and any existing group-based assignment. Microsoft’s PowerShell licensing guidance explains usage-location and service-plan requirements. Never set everyone’s location to an arbitrary country to make assignment succeed.

ADVERTISEMENT

Apply the Approved Batch and Verify Every Result

Run the following in the same session only after reviewing the preview. Leave $applyChanges false to inspect the proposed operations. Changing it to true makes real assignments and consumes available licenses. This example enables the selected SKU’s default service plans for new assignments; if your organization needs particular plans disabled, have the licensing owner supply and review those IDs before use.

$applyChanges = $false
$results = @(foreach ($target in $pending) {
    $outcome = 'Preview only'
    $detail = ''
    if ($applyChanges) {
        try {
            $current = Get-MgUser -UserId $target.Id -Property AssignedLicenses -ErrorAction Stop
            if (@($current.AssignedLicenses.SkuId) -contains $skuId) {
                $outcome = 'Skipped: already assigned'
            } else {
                Set-MgUserLicense -UserId $target.Id -AddLicenses @(@{SkuId = $skuId}) -RemoveLicenses @() -ErrorAction Stop | Out-Null
                $outcome = 'Request accepted; verify provisioning'
            }
        } catch {
            $outcome = 'Failed'
            $detail = $_.Exception.Message
        }
    }
    [pscustomobject]@{
        UserPrincipalName = $target.UserPrincipalName
        Result = $outcome
        Detail = $detail
    }
})
$results | Format-Table -Wrap
# Read each user's assigned product and service provisioning status:
foreach ($target in $pending) {
    Write-Output $target.UserPrincipalName
    Get-MgUserLicenseDetail -UserId $target.Id -ErrorAction Stop |
        Where-Object { $_.SkuId -eq $skuId } |
        Select-Object SkuPartNumber, SkuId, ServicePlans
}

The empty removal list leaves other products assigned. Existing instances of this SKU are skipped to avoid replacing their service-plan configuration. The last read-only loop is meaningful after real execution; a preview is not expected to show new licenses.

Save the result table through your approved administrative logging process. It is an operational record, not a substitute for tenant audit logs. Investigate failed rows individually: insufficient permissions, unavailable seats, usage-location restrictions and prerequisite conflicts require different corrections. A batch is not a transaction; successful assignments are not automatically rolled back if a later row fails.

Use Group-Based Licensing for Ongoing Membership Changes

Looping over a group’s current members and directly assigning licenses is a one-time snapshot. It does not create a licensing rule for future joiners or remove a direct license when someone leaves the group.

For an ongoing process, use Microsoft’s group-license management workflow, with reviewed membership, product configuration and assignment errors. Do not combine direct and group assignments without documenting ownership and offboarding behavior.

After the pilot, verify both the saved license state and the actual required Copilot feature for each test user. Expand in controlled batches only after those checks pass. This article’s example is a reviewable starting point, not a performance benchmark or a promise that all services become available immediately.

ADVERTISEMENT