Office 365 License Assignment

There is perpetually a lot of angst around licensing users for Office 365 workloads.  Most of my customers over the years have wanted to ease into deployment, only enabling certain services at a time.  Of course, as an evergreen service, we are always adding features, leading to new service plans to disable as you discover them.

Oh, what is a service plan?  Glad you asked.

Anatomy of an Office 365 License

Licenses are broken into two parts--SKUs and their constituent components, Service Plans.  A service plan can be viewed as an individual service or option (think Exchange Online, SharePoint Online, Skype for Business, Exchange Online Archiving, Yammer, Mobile Device Management, etc).  A SKU is made up of one or more service plans.

When it comes to discovering and configuring licenses and options, you have a few cmdlets at your disposal:

  • Get-MsolAccountSku - List all of the licenses available in your tenant
  • New-MsolLicenseOptions - Create a set of service plan assignments
  • Set-MsolUserLicense - Assign a license to a user principal object

Diving into them:

  • Get-MsolAccountSku [-TenantId <Guid>] [<CommonParameters>]
  • New-MsolLicenseOptions -AccountSkuId <string> [-DisabledPlans <string[]>] [<CommonParameters>]
  • Set-MsolUserLicense -UserPrincipalName <string> [-AddLicenses <string[]>] [-LicenseOptions <LicenseOption[]>] [-RemoveLicenses <string[]>] [-TenantId <Guid>] [<CommonParameters>]

After connecting to Office 365, let's go ahead and see what my tenant has:

 PS C:\> Get-MsolAccountSku
 AccountSkuId                    ActiveUnits     WarningUnits    ConsumedUnits
------------                    -----------     ------------    -------------
contoso:ENTERPRISEPACK_GOV       500             0               4

So, it looks like we have an Enterprise 3 (E3) government plan.  But what's available inside?  We unfortunately don't give you a great way to see the inside of a SKU.  If you thought you could run a Get-MsolAccountSku | FL, you'd be wrong.  The licenses are stored in an array, so you need to enumerate the array.

 PS C:\> Get-MsolAccountSku | fl
ExtensionData   : System.Runtime.Serialization.ExtensionDataObject
AccountName     : contoso
AccountSkuId    : contoso:ENTERPRISEPACK_GOV
ActiveUnits     : 500
ConsumedUnits   : 4
LockedOutUnits  : 0
ServiceStatus   : {Microsoft.Online.Administration.ServiceStatus,
                  Microsoft.Online.Administration.ServiceStatus,
                  Microsoft.Online.Administration.ServiceStatus,
                  Microsoft.Online.Administration.ServiceStatus...}
SkuPartNumber   : ENTERPRISEPACK_GOV
SuspendedUnits  : 0
TargetClass     : User
WarningUnits    : 0

The information that we're looking for is stored inside the ServiceStatus property, so let's dig a little deeper:

 PS C:\> (Get-MsolAccountSku | ? { $_.AccountSkuId -like "*ENTERPRISEPACK*" }).SeviceStatus
 ServicePlan                             ProvisioningStatus
-----------                             ------------------
INTUNE_O365                             Success
RMS_S_ENTERPRISE_GOV                    Success
OFFICESUBSCRIPTION_GOV                  Success
MCOSTANDARD_GOV                         Success
SHAREPOINTWAC_GOV                       Success
SHAREPOINTENTERPRISE_GOV                Success
EXCHANGE_S_ENTERPRISE_GOV               Success

These are the objects that make up the ENTERPRISEPACK_GOV SKU.  Now that I've satisfied my curiousity about what is in the SKU, how do I assign it?

So, for example, let's say you have a tenant named contoso.onmicrosoft.com, and you have purchased Enterprise Plan 3 (E3) licenses.  Your command would look like something this:

 Set-MsolUserLicense -UserPrincipalName user@contoso.com -AddLicenses contoso:ENTERPRISEPACK

That's great. Simple and easy--as long as you're satisfied with every user getting every service plan or option enabled.  A lot of my customers, however, want to phase it in.  And that's where it gets tricky.

Set-MsolUserLicense has a -LicenseOptions parameter that you can use to specify why service plans you want to disable.  By licensing options, we really mean "what service plans do we want to enable or disable?"  When we build a licensing option setting, we're adding objects to a "DisabledOptions" property.  Negative assignment isn't my favorite, but someone who is smarter than me came up with idea, so we'll go with it.

For example, let's say you want to disable SharePoint and SharePointWeb Apps for the ENTERPRISEPACK SKU.  It would look like this:

 PS C:\> $E3Options = New-MsolLicenseOptions -AccountSkuId contoso:ENTERPRISEPACK -DisabledPlans @('SHAREPOINTENTERPRISE_GOV','SHAREPOINTWAC_GOV')

Then, when you assign it to a user, you use this syntax:

 PS C:\> Set-MsolUserLicense -UserPrincipalName user@contoso.com -AddLicenses 'contoso:ENTERPRISEPACK' -LicenseOptions $E3Options

A challenge that this negative assignment paradigm presents is, "What if I only want to assign Exchange Online out of SKU, regardless of what other options exist?"  Part of the evergreen service model is we always are adding new things, and sometimes this things come in the form of new service plans to existing SKUs.

Since the license option object is built by adding items you DON'T want (instead of including things you do), one way to achieve this outcome is to dynamically create a filter to build the license option for you.  Here's the code that I've come up with to do it:

 # E3 Exchange and Skype only
[array]$DisabledPlans = @()
$Sku = (Get-MsolAccountSku | ? { $_.AccountSkuId -like “*ENTERPRISEPACK*”}).AccountSkuId
[array]$SkuServicePlans = (Get-MsolAccountSku | ? { $_.AccountSkuId -eq $sku }).ServiceStatus
[array]$EnabledPlans = @('EXCHANGE_S_ENTERPRISE_GOV','MCOSTANDARD_GOV')
[regex]$EnabledPlansRegex = ‘(?i)^(‘ + (($EnabledPlans |foreach {[regex]::escape($_)}) –join “|”) + ‘)$’
Foreach ($Plan in $SkuServicePlans)
     {
     $item = $Plan.ServicePlan.ServiceName
     If ($item -notmatch $EnabledPlansRegEx)
          {
          Write-Host “Adding $($Plan.ServicePlan.ServiceName) to DisabledPlans”
          $DisabledPlans += $Plan.ServicePlan.ServiceName
          }
     }
$E3Options = New-MsolLicenseOptions -AccountSkuId $Sku -DisabledPlans $DisabledPlans

What this does is create a dynamic RegEx filter to add service plans not matching service plan names in the $EnabledPlans array object.

Happy scripting!