functions/license/Disable-PSEntraIDGroupLicense.ps1

function Disable-PSEntraIDGroupLicense {
    <#
    .SYNOPSIS
        Disable a license on a group.

    .DESCRIPTION
        Remove a subscribed SKU license from 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 to remove.

    .PARAMETER SkuPartNumber
        Friendly name of the subscribedSku product to remove.

    .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-PSEntraIDGroupLicense -Identity group1 -SkuPartNumber ENTERPRISEPACK

        Remove license ENTERPRISEPACK from group group1.
    #>

    [OutputType([PSMicrosoftEntraID.Batch.Request])]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'InputObjectSkuPartNumber')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuId')]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'InputObjectSkuPartNumber')]
        [PSMicrosoftEntraID.Groups.Group[]] $InputObject,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuId')]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'IdentitySkuPartNumber')]
        [Alias('Id', 'GroupId', 'TeamId', 'MailNickName')]
        [ValidateGroupIdentity()]
        [string[]] $Identity,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuId')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuId')]
        [ValidateGuid()]
        [string[]] $SkuId,
        [Parameter(Mandatory = $true, ParameterSetName = 'InputObjectSkuPartNumber')]
        [Parameter(Mandatory = $true, ParameterSetName = 'IdentitySkuPartNumber')]
        [ValidateNotNullOrEmpty()]
        [string[]] $SkuPartNumber,
        [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

        # $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.
        # SkuId/SkuPartNumber are never pipeline-bound, so this is decidable in begin.
        if ($PSBoundParameters.ContainsKey('SkuId')) {
            [string[]] $bodySkuId = $SkuId
            [string] $skuTarget = ($bodySkuId | ForEach-Object { "'{0}'" -f $_ } | Join-String -Separator ',')
        }
        else {
            [System.Collections.Generic.List[string]] $bodySkuIdList = [System.Collections.Generic.List[string]]::new()
            [object[]] $subscribedLicenses = @(Get-PSEntraIDSubscribedLicense -EnableException:$EnableException)
            foreach ($skuPart in $SkuPartNumber) {
                [object] $matchedSku = @($subscribedLicenses | Where-Object { $_.SkuPartNumber -eq $skuPart })[0]
                if ($matchedSku) {
                    $bodySkuIdList.Add($matchedSku.SkuId)
                }
                else {
                    [string] $message = (Get-PSFLocalizedString -Module $script:ModuleName -Name SubscribedSku.SkuPartNumber.NotFound) -f $skuPart
                    if ($EnableException.IsPresent) {
                        Invoke-TerminatingException -Cmdlet $PSCmdlet -Message $message
                    }
                    else {
                        Write-PSFMessage -Level Warning -Message $message
                    }
                }
            }
            [string[]] $bodySkuId = $bodySkuIdList.ToArray()
            [string] $skuTarget = ($SkuPartNumber | ForEach-Object { "'{0}'" -f $_ } | Join-String -Separator ',')
        }
    }

    process {
        [hashtable] $body = @{
            addLicenses    = @()
            removeLicenses = $bodySkuId
        }
        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) -or [object]::Equals($groupObject.AssignedLicenses, $null) -or $groupObject.AssignedLicenses.Count -eq 0) {
                        continue
                    }
                    [string[]] $groupSkuIds = @($groupObject.AssignedLicenses | ForEach-Object { [string]$PSItem.SkuId })
                    [bool] $hasLicenseToRemove = $false
                    foreach ($skuToRemove in $bodySkuId) {
                        if ($groupSkuIds -contains $skuToRemove) {
                            $hasLicenseToRemove = $true
                            break
                        }
                    }
                    if ($hasLicenseToRemove) {
                        [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 'License.Disable' -ActionStringValues $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 {
                        if (-not ([object]::Equals($aADGroup.AssignedLicenses, $null)) -and $aADGroup.AssignedLicenses.Count -gt 0) {
                            [string[]] $groupSkuIds = @($aADGroup.AssignedLicenses | ForEach-Object { [string]$PSItem.SkuId })
                            [bool] $hasLicenseToRemove = $false
                            foreach ($skuToRemove in $bodySkuId) {
                                if ($groupSkuIds -contains $skuToRemove) {
                                    $hasLicenseToRemove = $true
                                    break
                                }
                            }
                            if ($hasLicenseToRemove) {
                                [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 'License.Disable' -ActionStringValues $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 }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}