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 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.
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:
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.
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:
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.
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}
Check out the information at the bottom of the post, you can use this for bulk license removal! 🙂
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 @()
}
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
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..!
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
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
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 @()
}
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?
I am open to direct messages on LinkedIn if you want to message me there.
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?
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 🙂