Monday, 25 December 2017

Assign or Remove O365 Licenses

Hi Folks,

Here is a script for managing O365 licenses. Get it from the TechNet Gallery.

If you work with O365 then sooner or later you''ll be asked to assign some licences - or remove them to that matter - to more than just one user. Using the portal will take forever.

A PowerShell script is the natural option, but it may be daunting to get the hang of the licensing object structures and to get your head around building a DisabledOptions list of services of what NOT should be enabled as opposed to just say "this is enabled, that is not".

I've put together a script which will do it all: it will enable or disable a particular service within a plan.

The script implements an interactive, user-driven flow of actions, which resembles the way NTDSUTIL or DISKPART work.

Prerequisites: You'll need the Microsoft Azure Active Directory Module for Windows PowerShell.

How it works:

1. Create a text file with all the UPNs for all the users who will have a license assigned or removed. No header line, just a list of UPNs:


In this example we have 3x valid users, 1x non-existent user and 1x incorrectly formatted user - will see its significance later in this post.
2. Run the script:

Manage-O365Licenses.ps1 -InputFile "Your_Text_File_With_UPNs.txt" -Location CountryCode

where the country code is the ISO 3166 Alpha-2 two-letter code of the country that the users will be assigned to. For the full list of country codes see https://www.iso.org/obp/ui/#search/code/.

For example, if your users' UPNs are listed in Users.txt and they are all in Australia, you would use the following command ...:

Manage-O365Licenses.ps1 -InputFile Users.txt -Location AU

... or simply launch the script with no parameters at all and it will prompt for the file and location:

Manage-O365Licenses.ps1


NOTE: If your users are distributed across different countries then you'll have to run the script separately for each country. It doesn't allow for mixing locations in one run.

If you happen to be already connected to O365 (you ran Connect-MSOLService previously and authenticated successfully) then the script will move on to the next step. Otherwise it will prompt for credentials, then it will connect to O365.

3. Select what you want to do: Disable or Enable licenses:


Select 0 or 1 and press Enter, or X to exit.
Your selection will be confirmed in green (see next screenshot).

4. Select the plan. Same process: enter a number, or X to exit:




Some plans are exposed in PowerShell, however they are hidden in he GUI. I couldn't identify a reason. If it isn't in the GUI then probably it shouldn't be touched. These show up as "--- not available for selection in the Portal ---".

Also, the list only contains the plans that I have come across, and it is probably incomplete, or Microsoft will come up with new ones. If you come across such a plan then its cryptic name will be displayed with "(no friendly name found)" appended, similar to the following:


For the record, the screenshot is a simulation of what you may encounter. The SPE_3 plan is the equivalent of "Microsoft 365 E3", and I removed the definition for the sake of illustration. It is back in the version that's available for download in the TechNet Gallery.
5. Select the service. Same process: enter a number, or X to exit. For illustration, I typed a couple of invalid selections to demo how it is handled:


6. Then a last chance of change of heart: review your selections and select Y or N:


The script runs and displays the statistics:



What do the stats mean? Let's open the license.log file:


There is an entry for each user:


  • SUCCESS: The operation has completed successfully for the user.
  • NOCHANGE: The license is already at the desired state, no change needed.
  • FAIL: The user does not exist in the organization (e.g. fakeuser@yourorg.com.au) or it failed format validation (e.g. gibberish is not in the correct format).
  • NOAVAILABLELICENSE: Ran out of licenses (not illustrated here).


WARNING: The script overwrites the log file! If you want to preserve the logs between different runs then move or rename the file before you run the script again.

The scripts will also catch some errors. In case an error is caught, a debug.log file is created. It is only created if a cmdlet generates one of those red errors.

Just like the license.log file, debug.log is wiped every time the script runs. However it is only created if a cmdlet generates an error. It looks like this ...:


... and its associated entry in the license.log file:



Some error handling features:

  • Validation of input file. Exits if it doesn't exist.
  • Validation of country code. Exits if it is invalid.
  • Graceful exit if credential input is cancelled.
  • Graceful exit if wrong credentials are provided.
  • Graceful handling of invalid selection input (out of range or invalid characters).
  • Option to exit at any point.
  • UPN format validation.
  • Existing user validation.
  • Licence count validation.
  • Catches some error conditions with error details in debug.log. No error, no log.
  • Detailed record of changes and other stats in license.log (not an error but good to have).

Licence Agreement

The scripts are licenced under the NMF (Not My Fault) agreement.
Edit, change and use the scripts at your own risk.
Give credit where due.

Download: Manage-O365Licenses.ps1

Enjoy :-)


5 comments:

  1. Hi Zoltan, Thank you so much for detailed script. i am new to PS scripting and trying my best to learn and understand.

    # My scenario is i have total of 9000 users which have 7000 E4 and 2000 E5.
    * Goal is to migrate 7000 users to E3 from E4 and remaining 2000 with K1 license.
    Users are spread all across geographic locations. I was testing few simple scripts and i was able to add license with usage location (eg:US or SG)

    ====================================
    Simple Script

    Connect-MSOLService
    $users = import-csv "C:\Max\Run\users11.csv" -delimiter ","
    foreach ($user in $users)
    {
    $upn=$user.UPN
    $usagelocation=$user.usagelocation
    $SKU=$user.SKU
    Set-MsolUser -UserPrincipalName $upn -UsageLocation $usagelocation
    Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $SKU
    }

    ===================================================

    My Question is if i use your script and do not add user location, will that work ?

    Please do advice me . I really appreciate your time and effort.

    ReplyDelete
  2. I would consider using the AzureAD License issuing functionality. This is a vast improvement to scripting (which I also started making before the features were available in AAD).

    Handling massive amounts of licensed users is easy as pie, if you create AD or AAD groups that contains the users you want to issue a specific license to.
    As for the service plans, these too can be assigned on an individual basis but still on a group of users, so you have a more manageable way to change it.

    ReplyDelete
  3. Yes, thank you but as of now i need to figure out powershell script for my knowledge as this script is great, i need help to modify this

    ReplyDelete
  4. Manoj, my script requires a usage location, however it only sets it if a user is unlicensed. If a user is already licensed then it doesn't touch the UsageLocation property, and obviously the Location parameter will not be used.

    My script probably doesn't suit well your needs, although it can be used to achieve the same, in a couple of passes.

    You can modify it, how easily though it depends on your understanding of PowerShell and the object structure that's stored in he users' Licenses property. Usually it is easier to write a script from scratch than to alter one.

    If you only need to replace one plan with another (e.g. E4 with E3) and you are happy to enable all services within the assigned SKU, then you can easily do it as follows:

    1. (Optional) Re-assign the user to another geographical location if needed:

    Set-MsolUser -UserPrincipalName "davidchew@contoso.com" -UsageLocation SG

    2. Replace the license:

    Set-MsolUserLicense -UserPrincipalName "davidchew@contoso.com" -AddLicenses "contoso:DESKLESS" -RemoveLicenses "contoso:ENTERPRISEPACK"

    Obviously you can build an input file with relative ease with the many thousands of users that fall within the same category (e.g. they all have E4 and it will be changed to E3 for everyone in the list), and feed its content to the script.

    The above example is taken from the Set-MsolUserLicense TechNet documentation at https://docs.microsoft.com/en-us/powershell/module/msonline/set-msoluserlicense?view=azureadps-1.0.

    With the assumptions outlined above, it can probably be done in a couple of lines. CAVEAT: one mistake, and you'll break thousands of users - the power of PowerShell :-p

    Run the Get-MsolAccountSku command to find the correct SKU that you need to supply in the Set-MsolUserLicense command.

    Again, with this method you will enable all services within the assigned SKU. Then you can use my script to selectively unlicense individual services.

    Hope this helps.


    For details

    ReplyDelete
  5. Thank you for reply. I tested this script and this is great if I want to disable/Enable Microsoft Teams or yammer with input of users

    I guess my script is like

    1. Add license
    2. Remove license

    I will add E3 license, it doesn’t hurt if there is double license. Then I will use same script and change (Set-MsolUserLicense -UserPrincipalName $upn -removeLicenses $SKU) to remove and input file will be changed to E4 license name ENTERPRISEWITHSCAl



    Connect-MSOLService
    $users = import-csv "C:\Max\Run\users11.csv" -delimiter ","
    foreach ($user in $users)
    {
    $upn=$user.UPN
    $usagelocation=$user.usagelocation
    $SKU=$user.SKU
    Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $SKU
    }



    My input file usv looks like


    UPN SKU
    users1@contoso.com
    contoso:ENTERPRISEPACK
    user2@contoso.com
    contoso:ENTERPRISEPACK





    ReplyDelete