Functions/Public/Remove-MsGDirectoryRoleAssignment.ps1

function Remove-MsGDirectoryRoleAssignment {
    <#
    .SYNOPSIS
    Removes a member from a role in Entra ID.
    .DESCRIPTION
    This function removes a given member from a specified role in Entra ID.
    .PARAMETER RoleDefinitionName
    Name of the role to remove the principal from.
    .PARAMETER RoleDefinitionId
    ID of the role to remove the principal from.
    .PARAMETER DirectoryScopeId
    Scope of the role to remove the principal from.
    .PARAMETER UserPrincipalName
    UserPrincipalName of the user to remove from the role.
    .PARAMETER GroupName
    GroupName of the group to remove from the role.
    .PARAMETER DirectoryObjectId
    ObjectId of the principal to remove from the role.
    .PARAMETER Force
    Bypasses the confirmation check.
    .PARAMETER Headers
    Authentication Headers to connect to Microsoft Graph.
    .PARAMETER Jwt
    Jwt to connect to Microsoft Graph.
    .EXAMPLE
    PS> Remove-MsGDirectoryRoleAssignment -RoleDefinitionName "Global Administrator" -UserPrincipalName benjamin.cohn@testdomain.com -Force

    This will remove "benjamin.cohn@testdomain.com" as a "Global Administrator" in the tenant.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ParameterSetName = 'Name', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string]$RoleDefinitionName,
        [parameter(Mandatory = $true, ParameterSetName = 'Id', Position = 1)][string]$RoleDefinitionId,
        [parameter(Mandatory = $false, Position = 2)][string]$DirectoryScopeId = '/',
        [parameter(Mandatory = $false, Position = 3)][string[]]$UserPrincipalName,
        [parameter(Mandatory = $false, Position = 4)][string[]]$GroupName,
        [parameter(Mandatory = $false, Position = 5)][string[]]$DirectoryObjectId,
        [parameter(Mandatory = $false)][switch]$Force,
        [parameter(Mandatory = $false)][hashtable]$Headers,
        [parameter(Mandatory = $false)][string]$Jwt
    )
    BEGIN {
        # Session Checks
        if ([string]::IsNullOrEmpty($Headers) -and [string]::IsNullOrEmpty($Jwt)) {
            if ($null -ne $myGraphToken) {
                Write-Verbose "Setting Jwt to Global: $myGraphToken"
                $Jwt = $myGraphToken
            }
        }
        if ([string]::IsNullOrEmpty($Headers) -and -not([string]::IsNullOrEmpty($Jwt))) {
            $Headers = @{
                'Authorization' = 'Bearer {0}' -f $Jwt
                'Content-Type'  = 'application/json'
            }
        }
        if ([string]::IsNullOrEmpty($Headers)) {
            $errorMessage = '{0} : Unable to run command. Headers are empty.' -f $MyInvocation.MyCommand.Name
            throw $errorMessage
        }

        $irmParams = @{
            'Headers'     = $Headers
            'ErrorAction' = 'Stop'
        }
        # end Session Checks
    }
    PROCESS {
        # Retrieve role definition.
        try {
            switch ($PSCmdlet.ParameterSetName) {
                "Name" {
                    $specifiedException = "Unable to retrieve role definition name '{0}' from Entra ID." -f $RoleDefinitionName
                    $roleDefinition = Get-MsGDirectoryRoleDefinition -DisplayName $RoleDefinitionName @irmParams
                }
                "Id" {
                    $specifiedException = "Unable to retrieve role definition id '{0}' from Entra ID." -f $RoleDefinitionId
                    $roleDefinition = Get-MsGDirectoryRoleDefinition -ObjectId $RoleDefinitionId @irmParams
                }
            }
        }
        catch {
            $errorMessage = Get-MsGErrorMessage $_
            $errorException = '{0} : {1} Error: {2}' -f $MyInvocation.MyCommand.Name, $specifiedException, $errorMessage
            throw $errorException
        }
        # end region

        # Retrieve IDs
        $memberIDs = New-Object -TypeName System.Collections.Generic.List[string]
        foreach ($UPN in $UserPrincipalName) {
            try {
                $userObject = Get-MsGUser -UserPrincipalName $UPN @irmParams
                $memberIDs.Add($userObject.id)
            }
            catch {
                $warningMessage = "{0} not found. Skipping user." -f $UPN
                Write-Warning $warningMessage
            }
        }
        foreach ($Group in $GroupName) {
            try {
                $groupObject = Get-MsGGroup -DisplayName $Group @irmParams
                if ($groupObject.isAssignableToRole) {
                    $memberIDs.Add($groupObject.id)
                }
                else {
                    $warningMessage = "{0} is not able to be assigned to roles. Skipping group." -f $Group
                    Write-Warning $warningMessage
                }
            }
            catch {
                $warningMessage = "{0} not found. Skipping group." -f $Group
                Write-Warning $warningMessage
            }
        }
        foreach ($dirObjId in $DirectoryObjectId) {
            try {
                $dirObject = Get-MsGDirectoryObject -Id $dirObjId @irmParams
                $memberIDs.Add($dirObject.id)
            }
            catch {
                $warningMessage = "{0} not found. Skipping id." -f $dirObjId
                Write-Warning $warningMessage
            }
        }
        if ($memberIDs.Count -lt 1) {
            $errorException = '{0} : No valid members were found.' -f $MyInvocation.MyCommand.Name
            throw $errorException
        }
        # end region

        # Retrieve current role assignments.
        try {
            $roleAssignments = Get-MsGDirectoryRoleAssignment -ObjectId $roleDefinition.id -DirectoryScopeId $DirectoryScopeId @irmParams
        }
        catch {
            $errorMessage = Get-MsGErrorMessage $_
            $errorException = "{0} : Unable to retrieve role assignments for '{1}' role. Error: {2}" -f $MyInvocation.MyCommand.Name, $roleDefinition.displayName, $errorMessage
            throw $errorException
        }
        # end region

        # Compare Role Assignments
        $membersToRemove = New-Object -TypeName System.Collections.Generic.List[psobject]
        if ($roleAssignments.Count -gt 0) {
            $roleAssignments | Where-Object { $_.id -in $memberIDs } | ForEach-Object { $membersToRemove.Add($_) }
        }
        else {
            $errorException = '{0} : No users are assigned to {1}.' -f $MyInvocation.MyCommand.Name, $roleDefinition.displayName
            throw $errorException
        }
        # end region

        # Remove Role
        foreach ($member in $membersToRemove) {
            $endpoint = 'roleManagement/directory/roleAssignments/{0}' -f $member.roleAssignmentId
            $memberId = $member.id
            $memberDisplayName = $member.displayName
            if (($PSCmdlet.ShouldProcess(('{0} [{1}]' -f $memberDisplayName, $memberId), 'Remove Directory Role Assignment')) -and ($Force.IsPresent -or ($PSCmdlet.ShouldContinue("Remove $('{0} [{1}]' -f $memberDisplayName, $memberId) from the $($roleDefinition.displayName) role in Entra ID.", 'Are you sure you want to proceed?')))) {
                try {
                    Invoke-MsGRequest -Method Delete -Endpoint $endpoint @irmParams > $null
                }
                catch {
                    $errorMessage = Get-MsGErrorMessage $_
                    $errorException = "{0} : Unable to remove '{1}' role from {2} [{3}]. Error: {4}" -f $MyInvocation.MyCommand.Name, $roleDefinition.displayName, $memberDisplayName, $memberId, $errorMessage
                    throw $errorException
                }
            }
        }
        # end region
    }
}