functions/license/Disable-PSEntraIDGroupLicenseServicePlan.ps1

function Disable-PSEntraIDGroupLicenseServicePlan {
    <#
    .SYNOPSIS
        Disable service plans of a license on a group.

    .DESCRIPTION
        Disable one or more service plans within a subscribed SKU license assigned to a Microsoft 365 group.

    .PARAMETER InputObject
        PSMicrosoftEntraID.Groups.Group object in tenant/directory.

    .PARAMETER Identity
        MailNickName or Id of the group.

    .PARAMETER SkuId
        Office 365 product GUID of the subscribedSku.

    .PARAMETER SkuPartNumber
        Friendly name of the subscribedSku product.

    .PARAMETER ServicePlanId
        GUID of the service plan to disable.

    .PARAMETER ServicePlanName
        Friendly name of the service plan to disable.

    .PARAMETER EnableException
        This parameter disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.

    .PARAMETER WhatIf
        Enables the function to simulate what it will do instead of actually executing.

    .PARAMETER Force
        The Force switch suppresses the confirmation prompt before the Shell modifies the object.

    .PARAMETER Confirm
        Prompts for confirmation before the command makes a change. -Confirm:$false
        suppresses that prompt.

        Bound explicitly it wins over -Force, whatever its value - so -Confirm:$true
        prompts even alongside -Force, and the two are alternatives rather than a pair.

        Left unbound, the decision belongs to this command's ConfirmImpact and the
        session ConfirmPreference, which is the PowerShell default behaviour.

    .PARAMETER PassThru
        When specified, the cmdlet will not execute the action but will instead
        return a `PSMicrosoftEntraID.Batch.Request` object for batch processing.

    .EXAMPLE
        PS C:\> Disable-PSEntraIDGroupLicenseServicePlan -Identity group1 -SkuPartNumber ENTERPRISEPACK -ServicePlanName EXCHANGE_S_ENTERPRISE

        Disable the EXCHANGE_S_ENTERPRISE service plan of the ENTERPRISEPACK SKU on group group1.
    #>

    [OutputType([PSMicrosoftEntraID.Batch.Request])]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'InputObjectSkuPartNumberPlanName')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuIdServicePlanId')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuIdServicePlanName')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanName')]
        [PSMicrosoftEntraID.Groups.Group[]] $InputObject,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuIdServicePlanId')]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuIdServicePlanName')]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuPartNumberPlanName')]
        [Alias('Id', 'GroupId', 'TeamId', 'MailNickName')]
        [ValidateGroupIdentity()]
        [string[]] $Identity,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuIdServicePlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuIdServicePlanName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuIdServicePlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuIdServicePlanName')]
        [ValidateGuid()]
        [string] $SkuId,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuPartNumberPlanName')]
        [ValidateNotNullOrEmpty()]
        [string] $SkuPartNumber,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuIdServicePlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuPartNumberPlanId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuIdServicePlanId')]
        [ValidateGuid()]
        [string[]] $ServicePlanId,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuIdServicePlanName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuPartNumberPlanName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuIdServicePlanName')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuPartNumberPlanName')]
        [ValidateNotNullOrEmpty()]
        [string[]] $ServicePlanName,
        [Parameter()]
        [switch] $EnableException,
        [Parameter()]
        [switch] $Force,
        [Parameter()]
        [switch] $PassThru
    )

    begin {
        [string] $service = Get-PSFConfigValue -FullName ('{0}.Settings.DefaultService' -f $script:ModuleName)
        Assert-EntraConnection -Service $service -Cmdlet $PSCmdlet
        [int] $commandRetryCount = Get-PSFConfigValue -FullName ('{0}.Settings.Command.RetryCount' -f $script:ModuleName)
        [System.TimeSpan] $commandRetryWait = New-TimeSpan -Seconds (Get-PSFConfigValue -FullName ('{0}.Settings.Command.RetryWaitInSeconds' -f $script:ModuleName))
        [hashtable] $header = @{ 'Content-Type' = 'application/json' }
        [hashtable] $cmdLetConfirm = Resolve-PSEntraIDConfirmPreference -BoundParameters $PSBoundParameters -Force:$Force -Confirm:$Confirm

        [PSMicrosoftEntraID.License.SubscriptionSkuLicense[]] $subscribedLicenses = @(Get-PSEntraIDSubscribedLicense)
        # $PSBoundParameters, not $PSCmdlet.ParameterSetName: with pipeline input the
        # set is not resolved until the first object arrives in process, so a switch on
        # it here matches nothing and $bodySkuId stays empty for every piped target.
        # None of these four parameters is pipeline-bound, so this is decidable in begin.
        if ($PSBoundParameters.ContainsKey('SkuId')) {
            [string] $bodySkuId = $SkuId
            [string] $skuTarget = $SkuId
        }
        else {
            [PSMicrosoftEntraID.License.SubscriptionSkuLicense] $matchedSku = @($subscribedLicenses | Where-Object { $_.SkuPartNumber -eq $SkuPartNumber })[0]
            [string] $bodySkuId = $matchedSku.SkuId
            [string] $skuTarget = $SkuPartNumber
        }

        [PSMicrosoftEntraID.License.SubscriptionSkuLicense] $targetSubscribedLicense = @($subscribedLicenses | Where-Object { $_.SkuId -eq $bodySkuId })[0]
        if ($PSBoundParameters.ContainsKey('ServicePlanId')) {
            [string] $servicePlanTarget = ($ServicePlanId | ForEach-Object { "'{0}'" -f $_ } | Join-String -Separator ',')
            [string[]] $bodyServicePlanId = $ServicePlanId
        }
        else {
            [string] $servicePlanTarget = ($ServicePlanName | ForEach-Object { "'{0}'" -f $_ } | Join-String -Separator ',')
            [string[]] $bodyServicePlanId = @($targetSubscribedLicense.ServicePlans | Where-Object { $ServicePlanName -contains $PSItem.ServicePlanName } | ForEach-Object { $_.ServicePlanId })
        }
    }

    process {
        switch -Regex ($PSCmdlet.ParameterSetName) {
            'InputObject\w' {
                foreach ($itemInputObject in $InputObject) {
                    $groupObject = $itemInputObject
                    if ((-not $itemInputObject.PSObject.Properties['AssignedLicenses'] -or [object]::Equals($itemInputObject.AssignedLicenses, $null)) -and -not [string]::IsNullOrWhiteSpace($itemInputObject.Id)) {
                        $groupObject = Get-PSEntraIDGroup -Identity $itemInputObject.Id -EnableException:$EnableException
                    }
                    if ([object]::Equals($groupObject, $null)) {
                        Write-PSFMessage -Level Warning -String 'License.Target.NotFound' -StringValues $itemInputObject.Id
                        continue
                    }
                    $assignedLicense = @($groupObject.AssignedLicenses | Where-Object { ([string]$PSItem.SkuId) -eq $bodySkuId }) | Select-Object -First 1
                    if ([object]::Equals($assignedLicense, $null)) {
                        # Not an error: the SKU simply is not on this target. Say so, though -
                        # silence here reads as success and it is not.
                        Write-PSFMessage -Level Warning -String 'License.Sku.NotAssigned' -StringValues $skuTarget, $(if ($groupObject.DisplayName) { $groupObject.DisplayName } else { $groupObject.Id })
                        continue
                    }
                    [System.Collections.ArrayList] $bodyDisabledServicePlanList = [System.Collections.ArrayList]::new()
                    [string[]] $existingDisabledServicePlanList = @($assignedLicense.DisabledPlans | ForEach-Object { [string]$_ })
                    if (-not [object]::Equals($existingDisabledServicePlanList, $null)) {
                        [string[]] $bodyNewDisabledServicePlanList = $bodyServicePlanId | Where-Object { $PSItem -notin $existingDisabledServicePlanList }
                        $existingDisabledServicePlanList | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                        $bodyNewDisabledServicePlanList | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                    }
                    else {
                        $bodyServicePlanId | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                    }
                    if ($bodyDisabledServicePlanList.Count -gt 0) {
                        [hashtable] $body = @{ addLicenses = @(@{ disabledPlans = @($bodyDisabledServicePlanList | Select-Object -Unique); skuId = $bodySkuId }); removeLicenses = @() }
                        [string] $path = ('groups/{0}/{1}' -f $groupObject.Id, 'assignLicense')
                        if ($PassThru.IsPresent) {
                            [PSMicrosoftEntraID.Batch.Request]@{ Method = 'POST'; Url = ('/{0}' -f $path); Body = $body; Headers = $header }
                        }
                        else {
                            $groupTarget = if (-not [string]::IsNullOrWhiteSpace($groupObject.DisplayName)) { $groupObject.DisplayName } else { $groupObject.Id }
                            Invoke-PSFProtectedCommand -ActionString 'LicenseServicePlan.Disable' -ActionStringValues $servicePlanTarget, $skuTarget -Target $groupTarget -ScriptBlock {
                                [void] (Invoke-EntraRequest -Service $service -Path $path -Header $header -Body $body -Method Post -ErrorAction Stop)
                            } -EnableException:$EnableException @cmdLetConfirm -PSCmdlet $PSCmdlet -Continue -RetryCount $commandRetryCount -RetryWait $commandRetryWait
                            if (Test-PSFFunctionInterrupt) { return }
                        }
                    }
                }
            }
            'Identity\w' {
                foreach ($group in $Identity) {
                    [PSMicrosoftEntraID.Groups.Group] $aADGroup = Get-PSEntraIDGroup -Identity $group
                    if ([object]::Equals($aADGroup, $null)) {
                        if ($EnableException.IsPresent) {
                            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message ((Get-PSFLocalizedString -Module $script:ModuleName -Name Group.Get.Failed) -f $group)
                        }
                    }
                    else {
                        $assignedLicense = @($aADGroup.AssignedLicenses | Where-Object { ([string]$PSItem.SkuId) -eq $bodySkuId }) | Select-Object -First 1
                        if ([object]::Equals($assignedLicense, $null)) {
                            # Not an error: the SKU simply is not on this target. Say so, though -
                            # silence here reads as success and it is not.
                            Write-PSFMessage -Level Warning -String 'License.Sku.NotAssigned' -StringValues $skuTarget, $group
                            continue
                        }
                        [System.Collections.ArrayList] $bodyDisabledServicePlanList = [System.Collections.ArrayList]::new()
                        [string[]] $existingDisabledServicePlanList = @($assignedLicense.DisabledPlans | ForEach-Object { [string]$_ })
                        if (-not [object]::Equals($existingDisabledServicePlanList, $null)) {
                            [string[]] $bodyNewDisabledServicePlanList = $bodyServicePlanId | Where-Object { $PSItem -notin $existingDisabledServicePlanList }
                            $existingDisabledServicePlanList | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                            $bodyNewDisabledServicePlanList | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                        }
                        else {
                            $bodyServicePlanId | ForEach-Object { [void] $bodyDisabledServicePlanList.Add($PSItem) }
                        }
                        if ($bodyDisabledServicePlanList.Count -gt 0) {
                            [hashtable] $body = @{ addLicenses = @(@{ disabledPlans = @($bodyDisabledServicePlanList | Select-Object -Unique); skuId = $bodySkuId }); removeLicenses = @() }
                            [string] $path = ('groups/{0}/{1}' -f $aADGroup.Id, 'assignLicense')
                            if ($PassThru.IsPresent) {
                                [PSMicrosoftEntraID.Batch.Request]@{ Method = 'POST'; Url = ('/{0}' -f $path); Body = $body; Headers = $header }
                            }
                            else {
                                Invoke-PSFProtectedCommand -ActionString 'LicenseServicePlan.Disable' -ActionStringValues $servicePlanTarget, $skuTarget -Target $group -ScriptBlock {
                                    [void] (Invoke-EntraRequest -Service $service -Path $path -Header $header -Body $body -Method Post -ErrorAction Stop)
                                } -EnableException:$EnableException @cmdLetConfirm -PSCmdlet $PSCmdlet -Continue -RetryCount $commandRetryCount -RetryWait $commandRetryWait
                                if (Test-PSFFunctionInterrupt) { return }
                            }
                        }
                    }
                }
            }
        }
    }
}