Cmdlet/Switch-MSOLUserLicense.ps1
Function Switch-MSOLUserLicense { <# .SYNOPSIS Switches a user from one SKU to another SKU .DESCRIPTION Replaces one SKU with another SKU on a collection of users. * Prompts for SKUs if none are provided * Allows for Disabling or Enabling Plans on the new SKU * -AttemptToMaintainPlans will try to keep the current plan setting for the user on the new SKU .PARAMETER Users Single UserPrincipalName, Comma seperated List, or Array of objects with UserPrincipalName property. .PARAMETER SKU SKU that should be added. .PARAMETER SKUToReplace SKU that will be removed. .PARAMETER Location If provided will set the location of the user. .PARAMETER PlansToDisable Comma seperated list of SKU plans to Disable. .PARAMETER PlansToEnable Comma seperated list of SKU plans to Enable. .PARAMETER AttemptToMaintainPlans Tries to keep the same plan states on the new SKU that is being assigned. For plans that are unique to the new SKU it will default to enabling them. .PARAMETER LogFile File to log all actions taken by the function. .OUTPUTS Log file showing all actions taken by the function. .EXAMPLE Switch-MSOLUserLicense -Users $NewUsers -Logfile C:\temp\switch_license.log -SKU company:ENTERPRISEPACK -SKUToReplace company:STANDARDPACK Replaces the STANDARDPACK with the ENTERPRISEPACK and enables all plans. .EXAMPLE Switch-MSOLUserLicense -Users $NewUsers -Logfile C:\temp\switch_license.log -SKU company:ENTERPRISEPACK -SKUTOReplace company:STANDARDPACK -PlanstoDisable Deskless,Sway,Teams1 Replaces the STANDARDPACK with the ENTERPRISEPACK and disables Deskless, Sway, and Teams. #> Param ( [Parameter(Mandatory = $true)] [array]$Users, [Parameter(Mandatory = $true)] [string]$LogFile, [string]$SKU, [string]$SKUToReplace, [string[]]$PlansToDisable, [string[]]$PlansToEnable, [switch]$AttemptToMaintainPlans ) # Takes in a list of plans and creates a new list based on currently enabled plans + list Function Get-PlansToMaintain { param ( [Parameter(Mandatory = $true)] [string]$SKU, [Parameter(Mandatory = $true)] [string]$SKUToReplace, [Parameter(Mandatory = $true)] $MSOLUser ) Write-Log "Determining Plans to Maintain" # Null out our values [string[]]$CurrentDisabledPlansNames = $null [array]$CurrentDisabledPlans = $null # Get the Plan names for our new SKU [string[]]$NewPlans = ((Get-MsolAccountSku | Where-Object { $_.accountskuid -eq $SKU }).servicestatus.serviceplan.servicename).toupper() # Get the License object $license = $MSOLUser.licenses | Where-Object { $_.accountskuid -eq $SKUToReplace } # Get currently disabled plans [array]$CurrentDisabledPlans = ($license.servicestatus | Where-Object { $_.provisioningstatus -eq "disabled" }) # If there are no currently disabled plans then return null since there are no plans to update if ($null -eq $CurrentDisabledPlans) { Write-Log "No Currently Disabled Plans; All Plans will be enabled." $Output = "Enabled" Return $Output } # Otherwise we need to show what is currently disabled and then calculate the new list to disable else { # Pull out the plan names foreach ($plan in $CurrentDisabledPlans) { [string[]]$CurrentDisabledPlansNames = $CurrentDisabledPlansNames + ($plan.serviceplan.servicename).toupper() } Write-Log ("Plans disabled in current SKU: " + $CurrentDisabledPlansNames) # Go thru each plan that needs to be disabled and if it is there remove it from the list of plans in the new SKU foreach ($Plan in $CurrentDisabledPlansNames) { # Builds a list of plans to enable from the new SKU $NewPlans = ($NewPlans | Where-Object { $_ -ne $Plan }) } # If we didn't get back any plans to enable then something went wrong and we need to error out if ($null -eq $NewPlans) { Write-Log "[ERROR] - All plans are on the new SKU are set to be disabled" Write-Log "[ERROR] - Enabling Plans instead." Write-Error "All plans will be Enabled." $Output = "Enabled" Return $Output } # Take back our list of plans that need to be enabled and get back a list of plans to disable else { [string[]]$Output = Set-EnabledPlan -SKU $SKU -PlansToEnable $NewPlans Return $Output } } } ### Main #### # Make sure we have a valid log file path Test-LogPath -LogFile $LogFile # Make sure we have the connection to MSOL Test-MSOLServiceConnection # Make sure we didn't get both enable and disable if (!([string]::IsNullOrEmpty($PlansToDisable)) -and !([string]::IsNullOrEmpty($PlansToEnable))) { Write-Log "[ERROR] - Cannot use both -PlansToDisable and -PlansToEnable at the same time" Write-Error "Cannot use both -PlansToDisable and -PlansToEnable at the same time" -ErrorAction Stop } elseif ((!([string]::IsNullOrEmpty($PlansToDisable)) -or !([string]::IsNullOrEmpty($PlansToEnable))) -and $AttemptToMaintainPlans){ Write-Log "[ERROR] - Cannot use -AttemptToMaintainPlans with -PlansToDisable or -PlansToEnable" Write-Error "Cannot use -AttemptToMaintainPlans with -PlansToDisable or -PlansToEnable" -ErrorAction Stop } # Make user our Users object is valid [array]$Users = Test-UserObject -ToTest $Users # If no value of SKU was passed in then call Select-Sku to allow one to be picked if ([string]::IsNullOrEmpty($SKU)) { $SKU = Select-SKU -Message "Select New SKU For Users:" } # If a value has been passed in verify it else { $SKU = Select-SKU -SKUToCheck $SKU } # If no value of SKUToReplace was passed in then call Select-Sku to allow one to be picked if ([string]::IsNullOrEmpty($SKUToReplace)) { $SKUToReplace = Select-SKU -Message "Select SKU to be replaced" } # If a value has been passed in verify it else { $SKUToReplace = Select-SKU -SKUToCheck $SKUToReplace } ## Make sure skutoreplace and sku don't match if ($SKUToReplace.toupper() -eq $SKU.ToUpper()) { Write-Log "[ERROR] - `$SKU and `$SKUToReplace match. Unable to replace license with itself." Write-Log "[ERROR] - Please use Update-MSOLUserLicensePlan or Set-MSOLUserLicensePlan to modify license plans." Write-Error -Message "-SKU and -SKUToReplace can not have the same value." -ErrorAction Stop } # Testing the plan inputs to make sure they are valid if (!([string]::IsNullOrEmpty($PlansToDisable))) { Test-Plan -Sku $SKU -Plan $PlansToDisable # Get the license options $LicenseOption = Set-LicenseOption -DisabledPlansArray $PlansToDisable -SKU $SKU } # If plans to enable has a value then we test them elseif (!([string]::IsNullOrEmpty($PlansToEnable))) { Test-Plan -Sku $SKU -Plan $PlansToEnable # Get the disabled plans and License options [string[]]$CalculatedPlansToDisable = Set-EnabledPlan -SKU $SKU -Plan $PlansToEnable $LicenseOption = Set-LicenseOption -DisabledPlansArray $CalculatedPlansToDisable -SKU $SKU } elseif ($AttemptToMaintainPlans) { Write-Log "Will attempt to maintain existing user plans when moving to new License" $ExistingPlans = (Get-MsolAccountSku | Where-Object { $_.accountskuid -eq $SKUToReplace }).servicestatus.serviceplan.servicename $NewPlans = (Get-MsolAccountSku | Where-Object { $_.accountskuid -eq $SKU }).servicestatus.serviceplan.servicename foreach ($Plan in $NewPlans) { # If the new plan name isn't in the existing list of plans then we will end up enabling it if ($ExistingPlans -contains $Plan) { } else { # Add to our list of plans we will be enabling by default [string[]]$PlansEnabledByDefault = $PlansEnabledByDefault + $Plan } } # If we generated at least one plan that didn't match between the two SKUs then inform the user and get consent to continue if ($PlansEnabledByDefault.Count -gt 0) { Write-Log "Cannot match the following plans between the two SKUs so they will be enabled by default:" Write-Log $PlansEnabledByDefault # Prompt the user to upgrade or not $title = "Agree to Enable" $message = "When the SKU switch is complete the plans listed above will be enabled by default. `nIs this OK?" $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Continues Switching SKUs enabling the listed plans." $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Stops the function." $options = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No) $result = $host.ui.PromptForChoice($title, $message, $options, 0) # Check to see what the user choose switch ($result) { 0 { Write-Log "Agreed to enabled listed plans by default." } 1 { Write-Log "[ERROR] - Did not accept default plan enablement." Write-Error "User terminated function. Unable to enable default plans." -ErrorAction Stop } } } } # If none are provided then we will enable all plans else { Write-Log "Enabling all plans" $LicenseOption = Set-LicenseOption -SKU $SKU } # "Zero" out the user counter [int]$i = 1 [int]$ErrorCount = 0 foreach ($Account in $Users) { Write-Log ("==== Processing User " + $account.UserPrincipalName + " ====") # Null out our reused variables $CalculatedPlansToDisable = $null # Get the current user object so we can pass it in once and not ask for it multiple times $MSOLUser = Get-MsolUser -UserPrincipalName $Account.UserPrincipalName # Check to see if the sku we are trying to assign is already on the user is so break out of the foreach if (!($null -eq (($MSOLUser).licenses | Where-Object { $_.accountskuid -eq $SKU }))) { Write-Log ("[WARNING] - " + $SKU + " is already assigned to the user.") Write-Warning "User already has $SKU assigned." } # Make sure we have the SKU we are replacing assigned elseif ($null -eq (($MSOLUser).licenses | Where-Object { $_.accountskuid -eq $SKUToReplace })) { Write-Log ("[WARNING] - " + $SKUToReplace + " is not assigned to the user.") Write-Warning "User doesn't have $SKUToReplace Assigned." } else { # Since attempt to maintain is per user we need to calculate per user license options if ($AttemptToMaintainPlans) { # Get the disabled plans and License options [string[]]$CalculatedPlansToDisable = Get-PlansToMaintain -SKU $SKU -SKUToReplace $SKUToReplace -MSOLUser $MSOLUser # If we found no disabled plans then turn on everything if ($CalculatedPlansToDisable[0] -eq "Enabled") { Write-Log ("Turning on all Plans for " + $Sku + " on user " + $Account.UserPrincipalName) } # Turn on only the plans we found that need to be disable in the new SKU else { $LicenseOption = Set-LicenseOption -DisabledPlansArray $CalculatedPlansToDisable -SKU $SKU } } # If we are not trying to maintain plans we can use the license options that were calculated before the foreach else { } [string]$Command = ("Set-MsolUserLicense -UserPrincipalName `"" + $Account.UserPrincipalName + "`" -AddLicenses " + $SKU + " -RemoveLicenses " + $SKUToReplace + " -LicenseOptions `$LicenseOption -ErrorAction Stop -ErrorVariable CatchError") Write-Log ("Running: " + $Command) # Try our command try { Invoke-Expression $Command } # If we have any error write out and stop # Doing this so I can customize the error later ## TODO: Update error with resume information! catch { Write-Log ("[ERROR] - " + $CatchError.ErrorRecord) Write-Error ("Failed to successfully switch licenses for user " + $account.UserPrincipalName) $ErrorCount++ } } # Update the progress bar and increment our counter Update-Progress -CurrentCount $i -MaxCount $Users.Count -Message "Switching Licenses" # Update our user counter $i++ } Write-Log ("Finished Swapping SKU " + $SKUToReplace + " to " + $SKU + " for " + $Users.count + " Users.") If ($ErrorCount -gt 0) { Write-Log ($ErrorCount.ToString() + " ERRORS DURING PROCESSING PLEASE REVIEW ENTRIES WITH '[ERROR]' FOR MORE INFORMATION") } } |