Assigning Licenses to a Bunch of Users

A reader comment for the article covering how to assign licenses to user accounts with the Microsoft Graph PowerShell SDK asked for an example showing how to read accounts from a CSV and assign licenses to those accounts. In other words, how to use PowerShell for bulk assignment of licenses to target user accounts. It’s the kind of thing people do when introducing new products to a tenant.

When Microsoft eventually releases Microsoft 365 Copilot for public consumption, I imagine that organizations willing to stump up the $30/user/month price will look for some sort of bulk assignment mechanism after they decide who gets Copilot licenses. Given that Copilot requires tenants to have Microsoft 365 enterprise licenses, they can use group-based licensing. That’s the best approach and the script discussed here is very much a do-it-yourself assignment mechanism for those who can’t use group-based licensing. Then again, it’s always nice to understand how things work so that you can create your own automation if necessary.

Finding User Accounts to Process

Conceptually, the processing steps are simple. The first step is to import details of target user accounts from a CSV file. Alternatively, you can use another mechanism to establish the set of target accounts. Suitable mechanisms include:

  • Membership of a Microsoft 365 group (including dynamic groups). You need to filter the group membership to remove any guest accounts.
  • Membership of a distribution list (including dynamic distribution lists). Distribution lists can include objects that can’t be targeted for license assignment, like public folders or mail users, so some filtering is necessary.
  • Membership of an Entra ID administrative unit.
  • User accounts are found by applying a filter to the Get-MgUser cmdlet.

For example, you could find a set of target accounts by looking for all the accounts located in a certain country:

[array]$Users = Get-MgUser -All -Filter "country eq 'United States'"

Of course, using filters to find user accounts only works if account properties are populated with accurate information.

Creating a Target Users Array for Bulk License Assignment

The point is that it doesn’t matter how you generate a set of target accounts. All that’s important is that your script provides a set of identifiers that can be used for license assignment. Those identifiers can be account object identifiers (GUIDs) or user principal names.

For this example, because so many people use CSV files to point to target accounts, that’s what I do in the example script:

$InputFile = "c:\temp\Users.csv"
[array]$Users = Import-CSV $InputFile

The data contained in the $Users array holds the user principal name and display name of the accounts we want to assign licenses to:

UPN                                DisplayName
---                                -----------
Lotte.Vetler@office365itpros.com   Lotte Vetler
Hans.Flick@office365itpros.com     Otto Flick
James.Ryan@office365itpros.com     James Ryan
Hans.Geering@office365itpros.com   Hans Geering
Joe.Sop@contoso.com                Joe Sop
Ben.James@office365itpros.com      Ben James
Brian.Weakliam@office365itpros.com Brian Weakliam

Assigning Licenses to Each User Account

The job’s half done when the set of target user accounts is available. After all, the only thing that’s left to do is to run the Set-MgUserLicense cmdlet for each account. Well, that’s certainly true if you want to perform a one-off operation, but it’s best to build some checks and balances in any script code that might be reused.

To illustrate what I mean, examine the processing displayed in Figure 1.

Running the License assignment script

Bulk license assignment Microsoft 365
Figure 1: Running the License assignment script

The script:

  • Finds the subscribed products (SKUs) known to the tenant and selects the SKUs that still have some available licenses.
  • Presents the list of SKUs to the user to allow them to choose which license to assign to the target users.
  • Checks that sufficient available licenses exist to assign to all the target users.
  • Checks that each target user does not already have the license in their assigned set.
  • If the license is not present for a user, attempt to assign the license to the account (note: if your tenant uses restricted administrative units, only accounts holding administrator roles for the administrative units can assign licenses to member accounts).
  • Captures details of the success or failure of the license assignment and records the information in a list object (Figure 2).
  • Reports details of the processing, including how many successful and failed license assignments occurred.
Results of the license assignment script
Figure 2: Results of the license assignment script

The Microsoft 365 Kill Chain and Attack Path Management

An effective cybersecurity strategy requires a clear and comprehensive understanding of how attacks unfold. Read this whitepaper to get the expert insight you need to defend your organization!

More Improvements Possible

I spent a happy afternoon playing with the script to anticipate some of the situations that you might encounter during license assignment operations, but I think I barely scratched the surface. There is much more that could be done to expand the script to handle different conditions, such as:

  • Assigning multiple product SKUs at one time.
  • Disabling service plans that the organization doesn’t want people to use from product SKUs. For instance, the organization might decide that Viva Engage (Yammer) isn’t required and therefore disables the Viva Engage Core service plan in SKUs like Office 365 E3 and E5. That’s possible, but if you disable Viva Engage, you lose some Teams functionality, like the Q&A app in meetings.
  • Share the results of the license assignment processing with other people via email or Teams.

Please feel free to amend the script I wrote (available for download from GitHub) to add your ideas. I might disagree with your suggestions, but at least we can have a discussion.

Bulk License Assignment Automated Your Way

PowerShell makes it possible to automate operations the way you want things done rather than the way Microsoft thinks things should be done. That’s its big advantage and it’s the core reason why every Microsoft 365 administrator should be passingly fluent in PowerShell.

If you attend The Experts Conference in Atlanta (Sept 19-20, 2023), make sure to come to the Great PowerShell Script-Off to have some fun and support the contestants as they struggle with the challenges we’ll set for them. All the challenges are common Microsoft 365 tenant automation tasks, so you might even learn something too!

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Francisco Tarela

    Hi thanks for the info, can you help me?, I’m keep receiving this error: Error: User account does not exist in Entra ID . (Line 96 of the code). I create 3 csv files, also export one from microsoft it self just to test it but still saying the same error.

  2. Rocky

    Hi Tony, your script is great, but if i need to disable some function like sway or kaizala?

  3. Irvens Antoine

    Also missed this part from the error…

    Users\IrvensAntoine\My Documents\WindowsPowerShell\Assign-LicensesViaCSV.PS1:76 char:35

  4. Irvens Antoine

    Many thanks for creating a script to do what Microsoft doesn’t want us having an easy accessible way to perform actions like this.

    Not entirely sure what I’m doing wrong, but I continue to receive this error when I try to execute the script.

    + If ($LicensesAvailable -eq $True) {
    + ~
    Missing closing ‘}’ in statement block or type definition.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingEndCurlyBrace

    I didn’t think anything in the script needed to be changed, as side from Input File location. Any tips on what I need to do to get past this?

    Many thanks in advance.

  5. Roman

    Greetings.
    Thank you for your work, a very necessary thing.

    Tell me if the list does not show all the SKUs present in the subscription. It turns out that not everything is shown when choosing a destination. Are there any restrictions?
    Thank you in advance!

  6. Muhamed Rafeeq

    Hi Tony,
    I work in a school enviornment and the students are assigned with one the below license. I want to disable the Teams license from the students account listed in a csv file. I have to enable the Teams license later. Students UserPrincipalName are saved in this location: C:\Data\StudentsToDisable.CSV

    Please help me with the powershell script

    ‘M365EDU_A5_STUUSEBNFT’
    ‘ENTERPRISEPACKPLUS_STUDENT’
    ‘STANDARDWOFFPACK_STUDENT’
    ‘M365EDU_A3_STUUSEBNFT’
    ‘STANDARDWOFFPACK_IW_STUDENT’

    1. Avatar photo

      There are many script examples that are available on the internet that you can use as a basis. You don’t say how you know which license to assign to an account. Assuming that this information is in the CSV file, I would:

      • Connect to the Graph with Connect-MgGraph. Make sure that the scope set is User.ReadWrite.All.
      • Import the user data from the CSV file with Import-CSV.
      • For each user, run Set-MgUserLicense to assign the license and disable the Teams service plan (57ff2da0-773e-42df-b2af-ffb7a2317929). Make sure that you know the GUID for each product license you plan to use, like 31d57bc7-3a05-4867-ab53-97a17835a411 for M365EDU_A5_STUUSEBNFT.

      Product GUIDs are available from https://learn.microsoft.com/en-us/entra/identity/users/licensing-service-plan-reference.

  7. Steve

    I keep getting this error. I ran it with just changing the filename to mine & even tried the snippet above. Same error

    WARNING: One or more headers were not specified. Default names starting with “H” have been used in place of any missing headers.

    1. Steve

      Cannot index into a null array.
      At line:76 char:5
      + Write-Host (“{0} users are to receive licenses but there are only …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : InvalidOperation: (:) [], RuntimeException
      + FullyQualifiedErrorId : NullArray

  8. Damien Hartmann

    Hi Tony, thank you for the very intersting script. I’m trying to use only a part of it as my task is a one shot group add of licences, but I am stuck with an error.
    Here is the script I adapted:
    $InputFile = “c:\Users\damien.hartmann\Documents\comptes.csv”
    [array]$Users = Import-CSV $InputFile
    ForEach ($User in $Users) { $License = Set-MgUserLicense -UserId $User.UPN -Addlicenses @{SkuId = ‘$18181a46-0d4e-45cd-891e-60aabd171b4e’}
    }
    Would you by chance see where I made a mistake?

      1. Damien Hartmann

        Thanks, but I still get the same error message:
        Set-MgUserLicense : Impossible de lier l’argument au paramètre « UserId », car il s’agit d’une chaîne vide.
        Au caractère C:\Users\damien.hartmann\Documents\WindowsPowerShell\ajouter_licences_v3.ps1:3 : 66
        + … r in $Users) { $License = Set-MgUserLicense -UserId $User.UPN -Addlic …
        + ~~~~~~~~~
        + CategoryInfo : InvalidData : (:) [Set-MgUserLicense], ParameterBindingValidationException
        + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Set-MgUserLicense
        (sorry for the French)

        1. Damien Hartmann

          Finally I managed to make it work, thanks again!

          1. Pranav P

            Hey can you send me the code you used

  9. Paul Maina

    Hi Tony, I found the script, whoch is working fine for me. Thank you

  10. Paul Maina

    Hi Tony, The arcticle is elaborate an dvery helpfull, please share the Script that does the output shown in powershell above

Leave a Reply