Public/Remove-PurviewCollectionRoleMember.ps1

function Remove-PurviewCollectionRoleMember {
    <#
    .SYNOPSIS
    Removes a principal from a role in a Microsoft Purview collection's metadata policy.
 
    .DESCRIPTION
    Retrieves the current metadata policy for the specified collection, removes the principal
    from the designated role's attribute rule, then commits the updated policy back to Purview
    via PUT.
 
    Accepted principal types:
    - User (default): users and service principals, stored under 'principal.microsoft.id'
    - Group: Entra ID security groups, stored under 'principal.microsoft.groups.id'
 
    This function is idempotent. If the principal does not hold the role, no API write is made
    and the function completes silently. Use -Verbose to observe the no-op.
 
    .PARAMETER AccountName
    The name of the Microsoft Purview account (the subdomain portion of
    https://<AccountName>.purview.azure.com).
 
    .PARAMETER CollectionName
    The collection to remove the role assignment from. Accepts either the 6-character system
    name (e.g. 'abc123') or the friendly display name (e.g. 'Finance Team'). Friendly names
    are resolved automatically via the account/collections API. Pass the system name directly
    to avoid that extra API call in performance-sensitive loops.
 
    .PARAMETER RoleId
    The fully-qualified Purview metadata role ID from which to remove the principal. Use
    Get-PurviewMetadataRole to list available roles. Built-in role IDs follow the pattern:
    'purviewmetadatarole_builtin_<role-name>'
 
    .PARAMETER PrincipalId
    The Entra ID Object ID (GUID) of the user, service principal, or group to remove.
 
    .PARAMETER PrincipalType
    Whether the principal is a 'User' (covers both users and service principals) or a
    'Group' (Entra ID security group). Defaults to 'User'. Must match the type used when
    the principal was originally assigned, as users and groups are stored under different
    attribute conditions in the policy JSON.
 
    .PARAMETER SkipRoleValidation
    Skips the pre-flight API call that validates the RoleId exists. Useful in batch
    operations where the same validated role ID is reused across many collections.
 
    .OUTPUTS
    None. The function writes no output on success. Use -Verbose for operational detail.
 
    .NOTES
    Idempotent: safe to call multiple times with the same arguments.
    Requires an active Az.Accounts session (Connect-AzAccount or a managed identity context)
    with the Purview Collection Administrator role on the target collection.
 
    .EXAMPLE
    Remove-PurviewCollectionRoleMember `
        -AccountName 'contoso-purview' `
        -CollectionName 'abc123' `
        -RoleId 'purviewmetadatarole_builtin_data-curator' `
        -PrincipalId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
 
    Removes a user or service principal from the Data Curator role on collection 'abc123'
    using the 6-character system name.
 
    .EXAMPLE
    Remove-PurviewCollectionRoleMember `
        -AccountName 'contoso-purview' `
        -CollectionName 'Finance Team' `
        -RoleId 'purviewmetadatarole_builtin_data-curator' `
        -PrincipalId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
 
    Same removal using the collection's friendly display name.
 
    .EXAMPLE
    Remove-PurviewCollectionRoleMember `
        -AccountName 'contoso-purview' `
        -CollectionName 'Finance Team' `
        -RoleId 'purviewmetadatarole_builtin_purview-reader' `
        -PrincipalId 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' `
        -PrincipalType Group
 
    Removes an Entra ID security group from the Purview Reader role. PrincipalType must
    match the type used during assignment — groups and users are stored separately.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true)]
        [string]$AccountName,

        [Parameter(Mandatory = $true)]
        [string]$CollectionName,

        [Parameter(Mandatory = $true)]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

            $builtInRoles = @(
                'purviewmetadatarole_builtin_collection-administrator'
                'purviewmetadatarole_builtin_data-source-administrator'
                'purviewmetadatarole_builtin_data-curator'
                'purviewmetadatarole_builtin_purview-reader'
                'purviewmetadatarole_builtin_data-share-contributor'
                'purviewmetadatarole_builtin_policy-author'
                'purviewmetadatarole_builtin_workflow-administrator'
                'purviewmetadatarole_builtin_insights-reader'
            )

            $builtInRoles | Where-Object { $_ -like "$wordToComplete*" }
        })]
        [string]$RoleId,

        [Parameter(Mandatory = $true)]
        [string]$PrincipalId,

        [Parameter(Mandatory = $false)]
        [ValidateSet('User', 'Group')]
        [string]$PrincipalType = 'User',

        [Parameter(Mandatory = $false)]
        [switch]$SkipRoleValidation
    )

    # Validate role exists (unless skipped for performance)
    if (-not $SkipRoleValidation) {
        Write-Verbose "Validating role '$RoleId' exists..."
        if (-not (Test-PurviewMetadataRoleExists -AccountName $AccountName -RoleId $RoleId)) {
            $validRoles = (Get-PurviewMetadataRoleIds -AccountName $AccountName) -join "`n "
            throw "Role '$RoleId' does not exist in account '$AccountName'. Valid roles:`n $validRoles"
        }
    }

    Write-Verbose "Fetching metadata policy for collection: $CollectionName"
    $Policy = Get-PurviewMetadataPolicy -AccountName $AccountName -CollectionName $CollectionName

    $UpdateResult = Update-PurviewPolicyRoleMemberInternal -Policy $Policy -RoleId $RoleId -PrincipalId $PrincipalId -Action Remove -PrincipalType $PrincipalType

    if ($UpdateResult.Updated) {
        Write-Verbose "Policy modified. Pushing update to Purview."

        $PolicyId = $UpdateResult.Policy.id
        if ([string]::IsNullOrWhiteSpace($PolicyId)) {
            throw "Could not determine Policy ID from the retrieved policy object."
        }

        if ($PSCmdlet.ShouldProcess("$AccountName/$CollectionName", "Remove principal '$PrincipalId' ($PrincipalType) from role '$RoleId'")) {
            Update-PurviewMetadataPolicy -AccountName $AccountName -PolicyId $PolicyId -PolicyObject $UpdateResult.Policy
        }
    } else {
        Write-Verbose "No changes needed. Principal '$PrincipalId' not present in role '$RoleId'."
    }
}