How to change Office 365 licenses in bulk, respecting the License Options.

Suppose your organization has been using Office 365 for some time now, using Office 365 Enterprise E1 licenses, and you company decides to purchase new Office 365 Enterprise E3 licenses. You are now in charge of changing the user license for each licensed user, whilst respecting the former License Options defined. This should be something that you would want to do in bulk.

What I mean by "License Options" is that some users might have an E1 license assigned, but are not licensed for one or more components of the service. Could be that the don't have a Lync Online license assigned, or no SharePoint Online license. When you change the license from E1 to E3, these features could potentially be enabled. This might be an unwanted side-effect of the license change. Please think about the sheer number of permutations that you could encounter; there are at least 4 services that come with an E1 license. Any of these could be either "on" or "off", resulting in 16 permutations (2x2x2x2).

Another challenge might be that some users don't only have an Office 365 license assigned to them, but also an Intune or CRM Online license. This is something we need to take into account when we change the license of a user.

This script might help you accomplish the task at hand.

 #------------------------------------------------------------------------------
#
# Copyright © 2013 Microsoft Corporation.  All rights reserved.
#
# THIS CODE AND ANY ASSOCIATED INFORMATION ARE PROVIDED “AS IS” WITHOUT
# WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS
# FOR A PARTICULAR PURPOSE. THE ENTIRE RISK OF USE, INABILITY TO USE, OR 
# RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
#
#------------------------------------------------------------------------------

Import-Module MSOnline
$cred = Get-Credential
Connect-MsolService -Credential $cred

$oldLicense = "contoso:STANDARDPACK"
$newLicense = "contoso:ENTERPRISEPACK"

$users = Get-MsolUser -MaxResults 5000 | Where-Object { $_.isLicensed -eq "TRUE" }

foreach ($user in $users){
    $upn = $user.UserPrincipalName
    foreach ($license in $user.Licenses) {
        if ($license.AccountSkuId -eq $oldLicense) {
            $disabledPlans = @()
            foreach ($licenseStatus in $license.ServiceStatus) {
                $plan = $licenseStatus.ServicePlan.ServiceName
                $status = $licenseStatus.ProvisioningStatus
                if ($status -eq "Disabled") {
                    if ($plan -eq "EXCHANGE_S_STANDARD") {
                        $disabledPlans += "EXCHANGE_S_ENTERPRISE"
                    } elseif ($plan -eq "SHAREPOINTSTANDARD") {
                        $disabledPlans += "SHAREPOINTWAC"
                        $disabledPlans += "SHAREPOINTENTERPRISE"
                    } else {
                        # Example: MCOSTANDARD
                        $disabledPlans += $plan
                    }                    
                }
            }
            # Always disabled Office 365 Pro Plus.
            $disabledPlans += "OFFICESUBSCRIPTION"
            #$disabledPlans += "RMS_S_ENTERPRISE" 
            #$disabledPlans += "YAMMER_ENTERPRISE" 
            $disabledPlans = $disabledPlans | select -Unique
            if ($disabledPlans.Length -eq 0) {
                Write-Host("User $upn will go from $oldLicense to $newLicense and will have no options disabled.")
                #Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $newLicense -RemoveLicenses $oldLicense 
            } else {
                $options = New-MsolLicenseOptions -AccountSkuId $newLicense -DisabledPlans $disabledPlans
                Write-Host("User $upn will go from $oldLicense to $newLicense and will have these options disabled: $disabledPlans")
                #Set-MsolUserLicense -UserPrincipalName $upn -AddLicenses $newLicense -LicenseOptions $options -RemoveLicenses $oldLicense 
            }
        }
    }
}

So what do we do? How does this work?

First, we connect to Office 365. This should not pose any challenges. Second, we get up to 5,000 licensed users from Windows Azure Active Directory. Again, no challenges there.

Then, for each user, we see if the user has a license for the "old plan", as defined in the $oldLicense variable. When we find such a license, we will detect which services have been enabled. Any service that you did not assign to the user will have the status "Disabled". Whenever we find such a service, we will record this. Yet, some services in an E1 plan might have a different name in the E3 plan. For example Exchange Online; in an E1 plan, this is called "EXCHANGE_S_STANDARD", whereas this same service is called "EXCHANGE_S_ENTERPRISE" in the E3 plan. Hence, we need to translate the names of the disabled services in the old situation to the new names.
Once we did that, we have the opportunity to hard-code some services that we want disabled after the license change. For example Office 365 Pro Plus. This is a service available only in the E3 plan, and not in the old E1 plan. Hence, this service could not have been disabled previously. In my sample script, I added the service for Office 365 Pro Plus, called "OFFICESUBSCRIPTION" to the list of disabled services. This means that any user who's license is changed by the script from E1 to E3 will NOT get the new Office 365 Pro Plus subscription.
After building the list of disabled services, we create a new MsolLicenseOptions object for these, by using the New-MsolLicenseOptions commandlet.

We're now set to actually change the license for the user from E1 to E3, respecting the previously set License Options. The only thing you have to do is to change the proper plan names for $oldLicense and $newLicense (which you can get by running the Get-MsolAccountSku commandlet) and, if you have properly tested everything, remove the "#" in front of the two Set-MsolUserLicense commands in the script.

Please do keep in mind that this is a SAMPLE SCRIPT only! You need to do your own testing before using ANY of this in a production environment.

I hope it helps!