Share via


Office 365: How to Manage User License Lifecycle with PowerShell

Managing license subscriptions in Office 365 can be a challenge. There is a host of different subscriptions and each one of these can contain multiple service plans such as Exchange Online, Sharepoint Online, Skype for Business and so on. To assign these licenses to a large number of users in the Office 365 Admin Portal is not really feasible and so to do bulk operations you'll need to leverage PowerShell. The official documentation for performing license assignments in PowerShell can be found here. This is very useful in scripting the service entitlements for new users, but not all users are static and in many cases, you’ll need to manage licenses as users move between positions and licensing needs change. This isn’t as easy as it sounds (or should be), and has a couple of obstacles:

Problem 1 - DisabledPlans

The biggest drawback to configuring user licenses via PowerShell lies in the design of the New-MsolLicenseOptions cmdlet. The problem is that the -DisabledPlans parameter is inherently the wrong approach to license automation. For example, let’s say we’ve set up a script that licenses users for the EnterprisePack and you’ve added Sharepoint to the disabled plans. In its original state, this would have enabled Exchange, Skype for Business, and Yammer. However, last year Microsoft added a new service plan to the license - Sway. This means that as soon as Sway became available as an assignable license in your tenant, Sway would have been assigned to your users because it hasn’t been explicitly added to the list of disabled plans.

So what we need to solve this problem is a method of setting enabled plans rather than disabled ones. That way no services will be provisioned for users unless you have explicitly added it in your script.

Problem 2 - AddLicenses

When you license a user who is currently unlicensed you use the -AddLicenses parameter of Set-MsolUserLicense to provision the license for the first time. However, if a user is already licensed and you need to modify the provisioned service plans, you need to omit the -AddLicenses parameter. If you don’t you’ll receive an error - “The license is invalid.” This isn’t a very descriptive error, and you’ll get the same error if you assign an appropriate license but with invalid options. This can be a big roadblock to an automated solution since we won’t know how to react to the error appropriately.

Solution

To solve these problems here's a PowerShell function called Set-O365UserLicense. This function accepts license templates in the form of one or more hash tables and can set this license template for multiple users. And to fully round out its functionality, it will also remove any licenses for a user that are not contained in the license template. This provides a declarative style of licensing for Office 365 users that is much more usable than the functionality native to the MSOnline module.

Using Set-O365UserLicense

Here’s an example of how you might use the function to automate licensing for the ENTERPRISEPACK SKU:

# Sales users will get Exchange and Skype
# Engineers will get Exchange and Sharepoint
 
$Sales = Get-MsolUser -Department Sales -All
$Engineering = Get-MsolUser -Department Engineering -All
 
$SalesTemplate = @{
    AccountSkuId = "contoso:ENTERPRISEPACK"
    EnabledPlans = "EXCHANGE_S_STANDARD","MCOSTANDARD"   
}
 
$EngineeringTemplate = @(

    @{
      AccountSkuId = "contoso:ENTERPRISEPACK"
      EnabledPlans = "EXCHANGE_S_STANDARD"
    },

    @{
        AccountSkuId = "contoso:PROJECTONLINE_PLAN_1"
        EnabledPlans = "SHAREPOINT_PROJECT_EDU","SHAREPOINTENTERPRISE_EDU"
    }
 )


$Sales | Set-O365UserLicense -LicenseTemplate $SalesTemplate
$Engineering | Set-O365UserLicense -LicenseTemplate $EngineeringTemplate

Later, if the licensing needs change for a group, you can update the template and re-apply it and the user's license assignments will be corrected to match. If a user moves between departments, the run this script again and the user will be updated to the matching template.

The Complete Script

Below is the entire Set-O365UserLicense function. Copy and paste this into your profile (or download here) or add it to a licensing script to take advantage of its capabilities.

function Set-O365UserLicense
{
    <#
            .SYNOPSIS
            Sets licenses for  Office 365 users.
             
            .PARAMETER MsolUser
            Specifies an Azure Active Directory user to set  license entitlements for. Should be an object  of type [Microsoft.Online.Administration.User] which is returned by the Get-MsolUser cmdlet found in the Azure Active Directory (MSOnline) module.
             
            .PARAMETER LicenseTemplate
            Specifies a licensing template to apply to the user. The license template should be a collection of one or more hashtables with two keys in  each: "AccountSkuId"  and "EnabledPlans."  The AccountSkuId value should be the complete name of a license subscription including the tenant name, and the EnabledPlans value should be the names of any of the service plans that belong to that license subscription that you'd like enabled for  the user(s). Any plans not included in the EnabledPlans value will be disabled. This means that you must specify at least one service plan for  each AccountSkuId that you would like to provision.
            As an example, this  license template contains values to provide a user with Exchange, Sharepoint, Office Web Apps, Skype For Business, and Yammer.
            $LicenseTemplate = @(
                @{
                    AccountSkuId = 'whitehouse:ENTERPRISEPACK'
                    EnabledPlans = 'EXCHANGE_S_ENTERPRISE','SHAREPOINTENTERPRISE','SHAREPOINTWAC','MCOSTANDARD','YAMMER_ENTERPRISE'
                },
                @{
                    AccountSkuId = 'whitehouse:PROJECTONLINE_PLAN_1'
                    EnabledPlans = 'OFFICESUBSCRIPTION'
                }
            )
             
            .EXAMPLE
            $Template = @{AccountSkuId = 'whitehouse:ENTERPRISEPACK'; EnabledPlans = 'EXCHANGE_S_ENTERPRISE'}
             
            $User = Get-MsolUser -UserPrincipalName abe.lincoln@whitehouse.gov
            Set-O365UserLicense -MsolUser $User -LicenseTemplate $Template
             
            .INPUTS
            [Microsoft.Online.Administration.User]
             
            .NOTES
            Author: Matt McNabb
            Date: 3/17/2016
            Prerequisites
            Azure Active Directory Module (MSOnline)
            PowerShell v2.0+
            Office 365 global admin account
            Connection to Azure Active Directory
    #>
 
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.Online.Administration.User]
        $MsolUser,
         
        [Parameter(Mandatory = $true)]
        [ValidateScript(
            {
                foreach ($Item in $_)
                {
                    ($Item.ContainsKey('AccountSkuId')) -and
                    ($Item.ContainsKey('EnabledPlans')) -and
                    ($Item.AccountSkuId) -and
                    ($Item.EnabledPlans)
                }   
            }
        )]
        [hashtable[]]
        $LicenseTemplate
    )
 
    begin
    {
        # get skus and available service plans
        $AccountSkuIds = Get-MsolAccountSku
         
        # convert enabled service plans to disabled
        # generate license options collection
        $LicenseOptions = foreach  ($Item in  $LicenseTemplate)
        {
            $AllPlans = ($AccountSkuIds | Where-Object { $_.AccountSkuId -eq $Item.AccountSkuId }).ServiceStatus
            $DisabledPlans = ($AllPlans | Where-Object { $_.ServicePlan.ServiceName -notin $Item.EnabledPlans }).ServicePlan.ServiceName
            New-MsolLicenseOptions -AccountSkuId $Item.AccountSkuId -DisabledPlans $DisabledPlans
        }
    }
 
    process
    {
        # add license with options for each sku
        # if error, try to just set the license options
         
        $UserPrincipalName = $MsolUser.UserPrincipalName
        $CurrentSkus = $MsolUser.Licenses.AccountSkuId
         
        if ($PSCmdlet.ShouldProcess($UserPrincipalName))
        {
            $Splat = @{
                UserPrincipalName = $UserPrincipalName
            }
             
            # set licenses and options from template
            foreach ($LicenseOption in $LicenseOptions)
            {
                $Sku = "$($LicenseOption.AccountSkuId.AccountName):$($LicenseOption.AccountSkuId.SkuPartNumber)"
                $Splat.AddLicenses = $Sku
                $Splat.LicenseOptions = $LicenseOption
                 
                try
                {
                    Set-MsolUserLicense @Splat -ErrorAction Stop
                }
                catch [Microsoft.Online.Administration.Automation.MicrosoftOnlineException]
                {
                    switch ($_)
                    {
                        { $_.Exception -match '.+UsageLocation$'  } { throw  $_; break  }
         
                        default
                        {
                            $Splat.Remove('AddLicenses')
                            Set-MsolUserLicense @Splat -ErrorAction Stop
                        }
                    }       
                }
            }
             
            # remove any licenses that the user currently owns that aren't included in the template
            foreach ($License in $CurrentSkus)
            {
                if ($License -notin $LicenseTemplate.AccountSkuId )
                {
                    Set-MsolUserLicense -UserPrincipalName $UserPrincipalName -RemoveLicenses $License -ErrorAction Stop
                }
            }
        }
    }
}

Requirements

To use this function you’ll need to have the following:

If any of this is unfamiliar to you, please visit this blog and read through the entire four-part series on managing Office 365 user licenses.

Thanks for reading!