Public/GraphUserLicenseServicPlan/Set-GraphUserLicenseServicePlan.ps1
# Module: Orbit.Users # Function: VoiceConfig/Licensing # Author: David Eberhardt # Updated: 10-JAN-2021 # Status: Live #TODO Function calls to Set-GraphUserLicenseServicePlan need to be changed to Set-GraphUserLicenseServicePlan and alias removed function Set-GraphUserLicenseServicePlan { <# .SYNOPSIS Changes one or more Service Plans for Licenses assigned to an Graph User Object .DESCRIPTION Enables or disables a ServicePlan from all assigned Licenses to an Graph User Object Supports all Service Plans listed in Get-Microsoft365LicenseServicePlan .PARAMETER UserPrincipalName The UserPrincipalName, ObjectId or Identity of the Object. .PARAMETER Enable Optional. Service Plans to be enabled (main function) Accepted Values are available with Intellisense and can be retrieved with Get-Microsoft365LicenseServicePlan (Column ServicePlanName) No action is taken for any Licenses not containing this Service Plan .PARAMETER Disable Optional. Service Plans to be disabled (alternative function) Accepted Values are available with Intellisense and can be retrieved with Get-Microsoft365LicenseServicePlan (Column ServicePlanName) No action is taken for any Licenses not containing this Service Plan .PARAMETER PassThru Optional. Displays User License Object after action. .EXAMPLE Set-GraphUserLicenseServicePlan [-UserPrincipalName] Name@domain.com -Enable MCOEV Enables the Service Plan Phone System (MCOEV) on all Licenses assigned to Name@domain.com .EXAMPLE Set-GraphUserLicenseServicePlan -UserPrincipalName Name@domain.com -Disable MCOEV,TEAMS1 Disables the Service Plans Phone System (MCOEV) and Teams (TEAMS1) on all Licenses assigned to Name@domain.com .EXAMPLE Set-GraphUserLicenseServicePlan -UserPrincipalName Name@domain.com -Enable MCOEV,TEAMS1 -PassThru Enables the Service Plans Phone System (MCOEV) and Teams (TEAMS1) on all Licenses assigned to Name@domain.com Displays User License Object after application .INPUTS System.String .OUTPUTS System.Void - Default Behavior System.Object - With Switch PassThru .NOTES Data in Get-Microsoft365LicenseServicePlan as per Microsoft Docs Article: Published Service Plan IDs for Licensing https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/licensing-service-plan-reference#service-plans-that-cannot-be-assigned-at-the-same-time .COMPONENT Licensing .FUNCTIONALITY Changes the License Object provided by enabling or disabling Service Plans on each License assigned (if present) to an Graph User Object .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Users/Set-GraphUserLicenseServicePlan.md .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_Licensing.md .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_UserManagement.md .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/ #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [Alias('Set-ServicePlan', 'Set-GraphUserLicenseServicePlan')] [OutputType([Void])] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('ObjectId', 'Identity')] [ValidateScript( { If ($script:OrbitRegexUPN.isMatch($_) -or $script:OrbitRegexGuid.isMatch($_)) { $True } else { throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid UPN or ObjectId' } })] [string[]]$UserPrincipalName, [Parameter(HelpMessage = 'Service Plan(s) to be enabled on this Object')] [ValidateScript( { if ($_ -in $( Get-OrbitAcSbGraphLicenseServicePlan @args )) { return $true } else { throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid ServicePlan. Use Intellisense for options or Get-Microsoft365LicenseServicePlan (ServicePlanName)' } })] [string[]]$Enable, [Parameter(HelpMessage = 'Service Plan(s) to be disabled on this Object')] [ValidateScript( { if ($_ -in $( Get-OrbitAcSbGraphLicenseServicePlan @args )) { return $true } else { throw [System.Management.Automation.ValidationMetadataException] 'Value must be a valid ServicePlan. Use Intellisense for options or Get-Microsoft365LicenseServicePlan (ServicePlanName)' } })] [string[]]$Disable, [Parameter(Mandatory = $false)] [switch]$PassThru ) #param begin { Show-OrbitFunctionStatus -Level Beta Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)" # Asserting Graph Connection if ( -not (Test-GraphConnection) ) { throw 'Connection to Microsoft Graph not established. Please validate connection' } # Setting Preference Variables according to Upstream settings if (-not $PSBoundParameters['Verbose']) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') } if (-not $PSBoundParameters['Confirm']) { $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference') } if (-not $PSBoundParameters['WhatIf']) { $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference') } $DebugPreference = if (-not $PSBoundParameters['Debug']) { $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { 'Continue' } $InformationPreference = if ( $PSBoundParameters['InformationAction']) { $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { 'Continue' } # Validating input if ($PSBoundParameters['Enable'] -and $PSBoundParameters['Disable']) { # Check if any are listed in both! Write-Verbose -Message 'Validating input for Enable and Disable (identifying inconsistencies)' foreach ($Lic in $Enable) { if ($Lic -in $Disable) { Write-Error -Message "Invalid combination. '$Lic' cannot be enabled AND disabled" -Category LimitsExceeded -RecommendedAction 'Please specify only once!' -ErrorAction Stop } } } # Querying licenses in the Tenant to compare SKUs try { Write-Verbose -Message 'Querying Licenses from the Tenant' $TenantLicenses = Get-Microsoft365TenantLicense -Detailed -ErrorAction STOP } catch { Write-Warning $_ return } } #begin process { Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)" #region ForEach Identity foreach ($ID in $UserPrincipalName) { #region Object Verification # Querying User try { $UserObject = Get-MgUser -UserId "$ID" -WarningAction SilentlyContinue -ErrorAction STOP Write-Verbose -Message "Processing Object '$($UserObject.UserPrincipalName)'" } catch { Write-Error -Message "User '$ID' - Account not valid" -Category ObjectNotFound -RecommendedAction 'Verify UserPrincipalName' continue } # License Query from Object # $UserObject.AssignedLicenses is available, though could not be validated. Get-MgUserLicenseDetail seems more reliable $ObjectAssignedLicenses = Get-MgUserLicenseDetail -UserId $UserObject.ObjectId -WarningAction SilentlyContinue if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - User '$ID' - Licenses assigned:", ($ObjectAssignedLicenses | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } # Enumerating all Serviceplans of assigned licenses that are not fully provisioned if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - User '$ID' - ServicePlans not fully provisioned:", ($ObjectAssignedLicenses.ServicePlans | Where-Object ProvisioningStatus -NE 'Success' | Sort-Object ProvisioningStatus | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } #endregion Write-Verbose -Message "Processing Object '$($UserObject.UserPrincipalName)' - Processing Licenses" # Iterating each License assigned to this Object [int]$ActionedLicensesToNotChange = $ActionedLicensesToEnable = $ActionedLicensesToDisable = 0 foreach ($L in $ObjectAssignedLicenses) { # Determine License Name $LicenseName = $null $LicenseName = ($TenantLicenses | Where-Object SkuPartNumber -EQ $L.SkuPartNumber).ProductName if ( -not $LicenseName ) { $LicenseName = ($TenantLicenses | Where-Object SkuPartNumber -EQ $L.SkuPartNumber).SkuPartNumber } Write-Verbose -Message "'$ID' - License '$LicenseName'" # Verifying the License is still available in the Tenant $StandardLicense = $null $StandardLicense = Get-MgSubscribedSku | Where-Object { $_.SkuId -eq $L.SkuId } if ( -not $StandardLicense) { Write-Warning -Message "User '$ID' - License '$LicenseName' - License not found in the Tenant!?" continue } # Creating a new License Object $License = $DisabledPlanObject = $null $License = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphAssignedLicense $License.SkuId += $Sku try { $DisabledPlanObject = $L.ServicePlans | Where-Object ProvisioningStatus -EQ 'Disabled' -ErrorAction Stop $License.DisabledPlans += $DisabledPlanObject.ServicePlanId if ( $DisabledPlanObject ) { if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - DisabledPlanObject:", ( $DisabledPlanObject | Format-List | Out-String).Trim() | Write-Debug } $License.DisabledPlans = $DisabledPlanObject | Select-Object ServicePlanId -ExpandProperty ServicePlanId if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - Current Disabled Plans for License Object:", ( $License.DisabledPlans | Format-List | Out-String).Trim() | Write-Debug } } else { $ActionedLicensesToNotChange++ Write-Verbose -Message "'$ID' - License '$LicenseName' - No disabled plans recorded!" continue } } catch { Write-Error "User '$ID' - License '$LicenseName' - Error encountered during population of disabled plans!" } try { #region Enable - Iterating all provided Service Plans to enable if ($PSBoundParameters['Enable']) { foreach ($S in $Enable) { # Checking Service Plan is valid Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' (checking status)" $ServicePlanToEnable = $null $ServicePlanToEnable = $StandardLicense.ServicePlans | Where-Object ServicePlanName -EQ "$S" if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - Service Plan '$S':", ( $ServicePlanToEnable | Format-Table | Out-String).Trim() | Write-Debug } if ( $ServicePlanToEnable) { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' (Flagging for enablement)" # Checking whether Service Plan is disabled if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - Service Plan '$S': Status", "ServicePlanToEnable: $($ServicePlanToEnable.ServicePlanId)" | Write-Debug 'List of Disabled Plan (to match against):', ( $License.DisabledPlans | Format-List | Out-String).Trim() | Write-Debug "Matching Status $( $ServicePlanToEnable.ServicePlanId -in $License.DisabledPlans )" | Write-Debug } if ( $ServicePlanToEnable.ServicePlanId -in $License.DisabledPlans ) { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' - Enabling Service Plan: '$($ServicePlanToEnable.ServicePlanId)'" $null = $License.DisabledPlans.Remove($ServicePlanToEnable.ServicePlanId) $ActionedLicensesToEnable++ if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - DisabledPlans:", ( $License.DisabledPlans | Format-List | Out-String).Trim() | Write-Debug } } else { Write-Information "INFO: User '$ID' - License '$LicenseName' - Service Plan '$S' is already enabled" continue } } else { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' not present" continue } } if ( $ActionedLicensesToEnable -eq 0 ) { Write-Verbose -Message "'$ID' - License '$LicenseName' - No Service Plans to enable" #continue } } #endregion #region Disable - Iterating all provided Service Plans to disable if ($PSBoundParameters['Disable']) { foreach ($S in $Disable) { # Checking Service Plan is valid Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' (checking status)" $ServicePlanToDisable = $null $ServicePlanToDisable = $StandardLicense.ServicePlans | Where-Object ServicePlanName -EQ "$S" if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - Service Plan '$S':", ( $ServicePlanToDisable | Format-Table | Out-String).Trim() | Write-Debug } if ( $ServicePlanToDisable) { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' (Flagging for Disablement)" # Checking whether Service Plan is disabled if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { Write-Verbose "User '$ID' - License '$LicenseName' - Service Plan: '$S' - Status $( $ServicePlanToEnable.ServicePlanId -in $License.DisabledPlans )" " Function: $($MyInvocation.MyCommand.Name) - Service Plan '$S': Status", "ServicePlanToEnable: $($ServicePlanToEnable.ServicePlanId)" | Write-Debug "List of Disabled Plan (to match against): $($License.DisabledPlans)" | Write-Debug } if (-not ($ServicePlanToDisable.ServicePlanId -in $License.DisabledPlans)) { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' - Disabling Service Plan: '$($ServicePlanToDisable.ServicePlanId)'" $License.DisabledPlans += $ServicePlanToDisable.ServicePlanId $ActionedLicensesToDisable++ if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - LicenseObject:", ( $License | Format-List | Out-String).Trim() | Write-Debug " Function: $($MyInvocation.MyCommand.Name) - DisabledPlans:", ($License.DisabledPlans | Format-List | Out-String).Trim() | Write-Debug } } else { Write-Information "INFO: User '$ID' - License '$LicenseName' - Service Plan '$S' is already disabled" continue } } else { Write-Verbose -Message "'$ID' - License '$LicenseName' - Service Plan: '$S' not present" continue } } if ( $ActionedLicensesToDisable -eq 0 ) { Write-Verbose -Message "'$ID' - License '$LicenseName' - No Service Plans to disable" #continue } } #endregion } catch { throw } # Catching non-assignments if ( $ActionedLicensesToEnable -eq 0 -and $ActionedLicensesToDisable -eq 0 ) { $ActionedLicensesToNotChange++ Write-Verbose -Message "'$ID' - License '$LicenseName' - No Service Plans to toggle." continue } # Executing Assignment if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - LicensesToAssign:", ($License | Format-List | Out-String).Trim() | Write-Debug " Function: $($MyInvocation.MyCommand.Name) - DisabledPlans:", ($License.DisabledPlans | Format-List | Out-String).Trim() | Write-Debug } if ($PSCmdlet.ShouldProcess("$ID", 'Set-MgUserLicense')) { #Assign $LicenseObject to each User try { #TEST this should work $SetMgUserLicenseParams = @{ UserId = "$ID" ErrorAction = 'Stop' AddLicenses = $AddLicenses } $SetMgUserLicenseOutput = Set-MgUserLicense @SetMgUserLicenseParams if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - SetMgUserLicenseOutput:", ($SetMgUserLicenseOutput | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } Write-Verbose -Message "'$ID' - Setting Licenses: Done" } catch { Write-Error -Message "$($_.Exception.Message)" continue } } } #Feedback of operation for this Object if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { " Function: $($MyInvocation.MyCommand.Name) - EnabledPlans: $ActionedLicensesToEnable" | Write-Debug " Function: $($MyInvocation.MyCommand.Name) - DisabledPlans: $ActionedLicensesToDisable" | Write-Debug " Function: $($MyInvocation.MyCommand.Name) - NoChanges: $ActionedLicensesToNotChange" | Write-Debug } [int]$ChangedLicenses = $ObjectAssignedLicenses.Count - $ActionedLicensesToNotChange if ( $ChangedLicenses -gt 0 ) { Write-Information "SUCCESS: '$ID' - Operation performed: $ChangedLicenses Licenses changed and ServicePlans enabled/disabled" } else { Write-Warning -Message "'$ID' - No Licenses changed. Please validate License Assignments with Get-GraphUserLicense or use switch PassThru" } #endregion # Output if ($PassThru) { #TEST either re-query or passing $SetMgUserLicenseOutput #Write-Object $SetMgUserLicenseOutput Get-GraphUserLicenseServicePlan -Identity "$Identity" } } } #process end { Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)" } #end } #Set-GraphUserLicenseServicePlan |