How To Assign User Licenses With Microsoft Graph PowerShell

Assigning Microsoft 365 licenses can be a tedious task, even with the bulk action tools in the Microsoft 365 admin center, when you are dealing with hundreds of users, it is not convenient.

Completing these simple but tedious tasks is always better using PowerShell and I definitely recommend it when dealing with hundreds of users, especially when the scripts you need are already available to you!

In this tutorial, we are going to look at how we can use Microsoft Graph PowerShell to assign and remove licenses from users in Microsoft 365.

Pre-requisites

For this tutorial, you must have the Microsoft Graph PowerShell module installed. If you do not have it installed already, check out my guide on How To Install the Microsoft Graph PowerShell Module.

It is also necessary that you have enough available licenses to assign to your users. I have created a hand script here which will export a list of your available and used licenses using Microsoft Graph PowerShell.

Using Set-MgUserLicense

The Set-MgUserLicense cmdlet can be found in the Microsoft.Graph.Users.Actions module, while the minimum level of permissions to use the command is Users.ReadWrite.All, you can also use the Directory.ReadWrite.All permission scope.

This information can be found by using Find-MgGraphCommand, we can also limit the results by selecting to display the ‘Module’ and ‘Permissions’ information.

Find-MgGraphCommand -Command Set-MgUserLicense | Select Module, Permissions

Your output from the above command will look like the following. (you will see 2 results as it is displaying information for the standard (v1.0) profile and the beta profile.

Find-MgGraphCommand with Set-MgUserLicense
Find-MgGraphCommand with Set-MgUserLicense

Find unlicensed users with filters

To begin, you may want to do a review of unlicensed user accounts in your tenant. To find unlicensed users you can apply a filter on the assignedlicenses property while using the Get-MgUser cmdlet. 

For example, to find all users where the license count is equal to 0, using the following command:

Get-MgUser -Filter 'assignedLicenses/$count eq 0' -ConsistencyLevel eventual -CountVariable CountVar -All

Bear in mind, this will find all unlicensed user accounts and if you have any dynamic groups that auto-assign free licenses in your tenant, this will not appear on your result.

Assign a user license with Set-MgUserLicense

First, we need to import the necessary modules into our PowerShell session and connect to Microsoft Graph with the necessary permissions.

Import-Module Microsoft.Graph.Users, Microsoft.Graph.Users.Actions
Connect-MgGraph -Scope User.ReadWrite.All

Now we are connected, we need the ID of the license SKU that we want to assign. We can work this out by running the Get-MgSubscribedSku cmdlet.

Get-MgSubscribedSku | Select SkuPartNumber, SkuId

From the output, you will see a list of all available licenses as well as the SkuId which we will need when we use the Set-MgUserLicense cmdlet.

Get-MgSubscribedSku
Get-MgSubscribedSku

From here, we can store our target license in a variable, below we store the information of our E5 Developer license in the $license variable.

$license = Get-MgSubscribedSku | `
Where-Object {$_.SkuPartNumber -eq "DEVELOPERPACK_E5"}

Another thing we also need to know is the object id of our target user. For this, we use a similar tactic for storing our user information in a variable. Below we are storing the user information of ‘Adele Vance’ in the $user variable.

$user = Get-MgUser | Where-Object {$_.DisplayName -eq "Adele Vance"}

We can now use our variables with the Set-MgUserLicense cmdlet with the -UserId and -AddLicense parameters.

Set-MgUserLicense -UserId $user.Id `
-AddLicenses @{SkuId = ($license.SkuId)} -RemoveLicenses @()

You may notice I have also defined the -RemoveLicenses parameter with an empty value. Without defining the -RemoveLicenses parameter you will get the following error:

Set-MgUserLicense : One or more parameters of the operation ‘assignLicense’ are missing from the request payload. The missing parameters are: removeLicenses.

Below is the full script so you can copy and paste it!

Assign multiple licenses at the same time

Import-Module Microsoft.Graph.Users
Connect-MgGraph -scope User.ReadWrite.All
$license = Get-MgSubscribedSku | Where-Object {$_.SkuPartNumber -eq "DEVELOPERPACK_E5"}
$user = Get-MgUser | Where-Object {$_.DisplayName -eq "Adele Vance"}

Set-MgUserLicense -UserId $user.Id -AddLicenses @{SkuId = ($license.SkuId)} -RemoveLicenses @()

The -AllLicenses parameter also enables you assign multiple licenses at the same time, by added mutiple licenses to a hash table. There are a couple of ways we can acheive this… 

Firstly, we can take all of the licenses assigned to a single user (as a template) and assign these to another user. Start by using the Get-MgUserLicenseDetail cmdlet to store assigned license to a variable.

$lic = Get-MgUserLicenseDetail -UserId $ID

We then need to build an array of hastables which we can include in our Set-MgUserLicense command.

$addlicense = @()
ForEach ($L in $Lic) {
	$addlicense += @{SkuId = ($($L.SkuId))}
}

Let’s look at what we have done, here is the before and after:

Before
Before
After
After

You can see from the above that before we had a list of objects for each license, but after we have an array of hastables containing the key value pairs for each license. This is what we need to include in our command to assign mulitple licenses to a user or users.

Set-MgUserLicense -UserId $id -AddLicenses $addlicense -RemoveLicenses @()

There are different ways you can create your hashtable, in some instances it may be easier to manually create your hashtable array in the event you plan on assigning specific licenses to mulitple users. For example:

$AADSku = Get-MgSubscribedSku -All | Where SkuPartNumber -eq 'AAD_PREMIUM'
$IntSku = Get-MgSubscribedSku -All | Where SkuPartNumber -eq 'INTUNE_A'
$addLicenses = @(
  @{SkuId = $AADSku.SkuId},
  @{SkuId = $IntSku.SkuId}
)

How to assign a license to multiple users with Microsoft Graph PowerShell

To assign a license to multiple users, we first have to store our users and loop through them using the ForEach command.

Your users can be obtained in multiple ways, you can either use the Get-MgUser cmdlet to define your user list, of which you will be able to simply obtain the necessary user attributes for the script. 

Or if you already have a CSV containing your user list, you could import the CSV and then obtain the user id from within the loop.

Here is an example where I store a list of users based on them containing ‘@ourcloudnetwork.com’ in their mail attribute, then I assign each user a DEVELOPERPACK_E5 license.

Import-Module Microsoft.Graph.Users, Microsoft.Graph.Users.Actions
Connect-MgGraph -scope User.ReadWrite.All

$users = Get-MgUser | Where-Object {$_.Mail -match "@ourcloudnetwork.com"}
$license = Get-MgSubscribedSku | Where-Object {$_.SkuPartNumber -eq "DEVELOPERPACK_E5"}

ForEach ($user in $users) {
    Write-host "Assigning license: $($license.SkuPartNumber) to $($user.DisplayName)"
    Set-MgUserLicense -UserId $user.Id -AddLicenses @{SkuId = ($license.SkuId)} -RemoveLicenses @()
}

Or if you want to perform the same task, but this time, use an existing list of users you have saved in a CSV, you can use the below script.

Import-Module Microsoft.Graph.Users, Microsoft.Graph.Users.Actions
Connect-MgGraph -scope User.ReadWrite.All

$users = Import-Csv "C:\temp\userlist.csv"
$license = Get-MgSubscribedSku | Where-Object {$_.SkuPartNumber -eq "DEVELOPERPACK_E5"}

ForEach ($user in $users) {
    Write-host "Assigning license: $($license.SkuPartNumber) to $($user.DisplayName)"
    $userid = $null
    $userid = (Get-MgUser -UserId [email protected]).Id
    Set-MgUserLicense -UserId $user.Id -AddLicenses @{SkuId = ($license.SkuId)} -RemoveLicenses @()
}

Remove a single user license

When removing a single user license, the -RemoveLicenses parameter does not expect a hashtable, but instead just the SkuId string of the license. For example, to remove a single license from a user, we can use the below command:

$lic = Get-MgSubscribedSku -All | Where SkuPartNumber -eq 'INTUNE_A'

Set-MgUserLicense -UserId $ID -AddLicenses @() -RemoveLicenses $lic.SkuId

We can see that this command is succesful while only providing the the SkuId string value in the parameter.

License SkuId
License SkuId

Remove multiple licenses

The concept is similar for removing multiple licenses at once, compared to adding multiple licenses. We can define an array of SkuId values into the command to remove multiple licenses.

Like before, there are multiple way to create your array, you can either define it manually within your script or create it based on the values of an existing user or any available licenses.  

For example, to create an array list of specific SkuId’s, you can use the below:

$AADSku = Get-MgSubscribedSku -All | Where SkuPartNumber -eq 'AAD_PREMIUM'
$IntSku = Get-MgSubscribedSku -All | Where SkuPartNumber -eq 'INTUNE_A'
$Lics = "$($AADSku.SkuID)", "$($IntSku.SkuId)"

The array will look like the following:

Multiple SkuIds
Multiple SkuIds

This array can then be included in your command as follows to remove the specified licenses from a single user. 

Set-MgUserLicense -UserId $ID -AddLicenses @() -RemoveLicenses $lics

You can use the previous example for running this command against mulitple users in your oganisation. 

Daniel Bradley

My name is Daniel Bradley and I work with Microsoft 365 and Azure as an Engineer and Consultant. I enjoy writing technical content for you and engaging with the community. All opinions are my own.

This Post Has 12 Comments

  1. Waheed Gul

    Hi daniel and everyone
    i need an alternate command for bulk license removal and assignment in PowerShell for microsoft365 because below commands is not working after depreciation of Azure PS and MSol PS

    $licencetoremove = “pern:STANDARDWOFFPACK_STUDENT”
    import-csv “D:\student-uoh.csv” | ForEach-Object { Set-MsolUserLicense -UserPrincipalName $_.UserPrincipalName -RemoveLicenses $licencetoremove}

    $licencetoadd = “pern:M365EDU_A3_STUUSEBNFT”
    import-csv “D:\student-uoh.csv” | ForEach-Object { Set-MsolUserLicense -UserPrincipalName $_.UserPrincipalName -AddLicenses $licencetoadd}

    1. Daniel

      Check out the information at the bottom of the post, you can use this for bulk license removal! 🙂

  2. Michael Roman

    Thank you. This is very handy as I try to migrate some of my scripts away from Azure AD and MSOL. I am getting hung up on using an existing CSV of users to assign licenses. Your ForEach loop references a specific account: (Get-MgUser -UserId [email protected]).Id, rather than a variable.

    To work around this I set up a variable $licUser = $users.UserPrincipalName then modified your line to be $userid = (Get-MgUser -UserId $licUser).Id

    This is working great if I only have one user in my CSV but I’m seeing errors:

    Get-MgUser : Cannot process argument transformation on parameter ‘UserId’. Cannot convert value to type System.String.
    At line:5 char:31
    + $userid = (Get-MgUser -UserId $licUser).Id

    Here is my complete snippet of code, not sure if you could offer any suggestions

    $UsageLocation = “US” #sets location to US
    $licUser = $users.UserPrincipalName

    ForEach ($user in $users) {
    Write-host “Assigning licenses to $($user.DisplayName)”
    $userid = $null
    $licUser = $users.UserPrincipalName
    $userid = (Get-MgUser -UserId $licUser).Id
    Update-MgUser -UserId $userid -UsageLocation $UsageLocation
    Set-MgUserLicense -UserId $userid -AddLicenses @{SkuId = ($E3FACSku.SkuId)}, @{SkuId = ($E3EMSSku.SkuId)}, @{SkuId = ($FLOWSku.SkuId)}, @{SkuId = ($PAPPSSku.SkuId)} -RemoveLicenses @()
    }

  3. Michael Roman

    Well, never mind. I tried it a different way and got it working:

    $Users | ForEach-Object {
    Update-MgUser -UserID $_.UserPrincipalName -UsageLocation $UsageLocation
    Set-MgUserLicense -UserID $_.UserPrincipalName -AddLicenses @{SkuId = ($E3FACSku.SkuId)}, @{SkuId = ($E3EMSSku.SkuId)}, @{SkuId = ($FLOWSku.SkuId)}, @{SkuId = ($PAPPSSku.SkuId)} -RemoveLicenses @()
    }

    $users being from the .csv I imported.

    I didn’t think I could use -UserId with $_.UserPrincipalName — I thought they’d be too completely different values.

    #powershellnovice

    1. Daniel

      Good job on figuring it out! I was away from my desk when I saw this comment pop up! There are some scenarios where using the USERNAME with -UserId does not pull complete information and the ID needs to be used instead. But that is just another nuance of Microsoft Graph PowerShell..!

  4. Eman

    Hi Dahieil, awesome guide by the way. Been following through your guide but I can’t seem to get things through.

    I have a csv list of email address with header “email” import it with the comman below

    $users = Import-Csv C:\changelicense\batch01.csv #-Delimiter “;”

    for the loop this is what I’m doing adding yours in between.

    ForEach ($email in $users) {
    Write-host “Assigning license to $($email)”
    $userid = $null
    $userid = (Get-MgUser -UserId $email).Id
    Set-MgUserLicense -UserId $userid -AddLicenses @() -RemoveLicenses @{SkuID =’xxxx’}
    Set-MgUserLicense -UserId $userid -AddLicenses @() -RemoveLicenses @{SkuID =’xxxx’}
    Set-MgUserLicense -UserId $userid -AddLicenses @{SkuID =’xxxxx’} -RemoveLicenses @()
    }

    but I keep getting this error below. Would you happen to understant what I’m missing? please done mind the SkuID I just replaced them with xxxx

    Get-MgUser : Resource ‘@{[email protected]}’ does not exist or one of its queried reference-property objects are not present.
    At line:4 char:5
    + $userid = (Get-MgUser -UserId $email).Id
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: ({ UserId = @{em…ndProperty = }:f__AnonymousType10`3) [Get-MgUser_Get1], RestException`1
    + FullyQualifiedErrorId : Request_ResourceNotFound,Microsoft.Graph.PowerShell.Cmdlets.GetMgUser_Get1
    Set-MgUserLicense : Cannot bind argument to parameter ‘UserId’ because it is an empty string.
    At line:5 char:31
    + Set-MgUserLicense -UserId $userid -AddLicenses @() -RemoveLicense …
    + ~~~~~~~
    + CategoryInfo : InvalidData: (:) [Set-MgUserLicense], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Set-MgUserLicense

    1. Daniel Bradley

      Hi Eman,

      If your CSV has a header of Email, then when you loop through each item in the collection, you would need to user $email.email to extract the value from the property.

      For example: $userid = (Get-MgUser -UserId $email.email).Id

  5. David Son

    This script works for me:

    The input file has this column with 2 entries.
    EmployeeID
    test5
    test6

    $Path = “\\fileservices\IT\Scripts\Email creation scripts\agents.csv”
    $users = Import-Csv -Path $Path

    # Assigns (Microsoft F3) with the almost all services turned off for users in bulk
    $UsageLocation = “MX” #sets location to MX
    $DESKLESSsku = Get-MgSubscribedSku -All | Where SkuPartNumber -eq ‘DESKLESSPACK’
    $disabledPlans = $DESKLESSsku.ServicePlans | `
    Where ServicePlanName -in (“SHAREPOINTWAC”,”YAMMER_ENTERPRISE”,”MICROSOFTBOOKINGS”,”SWAY”,”POWERAPPS_O365_S1″,”PROJECT_O365_F3″,”KAIZALA_O365_P1″,”SHAREPOINTDESKLESS”,”BPOS_S_TODO_FIRSTLINE”,`
    “OFFICEMOBILE_SUBSCRIPTION”,”PROJECTWORKMANAGEMENT”,”VIVA_LEARNING_SEEDED”,”WHITEBOARD_FIRSTLINE1″,”STREAM_O365_K”,”MCOIMP”,”POWER_VIRTUAL_AGENTS_O365_F1″) | `
    Select -ExpandProperty ServicePlanId

    $addLicenses = @(
    @{
    SkuId = $DESKLESSsku.SkuId
    DisabledPlans = $disabledPlans
    }
    )

    $users | ForEach-Object {
    $UserPN = (Get-ADUser -Identity $_.Employeeid).UserPrincipalName
    Update-MgUser -UserId $UserPN -UsageLocation $UsageLocation
    Set-MgUserLicense -UserId $UserPN -AddLicenses $addLicenses -RemoveLicenses @()
    }

    1. Ivan

      Hello David son, I see that you are from Mexico, will it be possible for me to contact you to see if you can help me with some of the script and CSV?

      1. Daniel Bradley

        I am open to direct messages on LinkedIn if you want to message me there.

  6. Faraz

    Hi !
    on using this it asks me to sign in to MS account, I am using the account with License Administrator role but it seems its enough for this: Connect-MgGraph -Scope User.ReadWrite.All

    All I want to do is to assign some license, do you have any idea?

    1. Daniel Bradley

      Hey Faraz, you will be able to do this, but you’ll first need a Global Administrator to consent to the permission.

      Simplest solution is to ask your Global Admin to run Connect-MgGraph -Scope User.ReadWrite.All and click consent. Then you can run Connect-MgGraph 🙂

Leave a Reply