Functions/Public/Add-MsGDirectoryRoleAssignment.ps1

function Add-MsGDirectoryRoleAssignment {
    <#
    .SYNOPSIS
    Adds a member to a role in Entra ID.
    .DESCRIPTION
    This function adds a given member to a specified role in Entra ID.
    .PARAMETER RoleDefinitionName
    Name of the role to add the principal to.
    .PARAMETER RoleDefinitionId
    ID of the role to add the principal to.
    .PARAMETER DirectoryScopeId
    Scope of the role to add the principal to.
    .PARAMETER UserPrincipalName
    UserPrincipalName of the user to add to the role.
    .PARAMETER GroupName
    GroupName of the group to add to the role. Needs to be able to be assigned.
    .PARAMETER DirectoryObjectId
    ObjectId of the principal to add to the role.
    .PARAMETER Headers
    Authentication Headers to connect to Microsoft Graph.
    .PARAMETER Jwt
    Jwt to connect to Microsoft Graph.
    .EXAMPLE
    PS> Add-MsGDirectoryRoleAssignment -RoleDefinitionName "Global Administrator" -UserPrincipalName benjamin.cohn@testdomain.com

    Assigns the "Global Administrator" role to benjamin.cohn@testdomain.com.
    #>

    [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)][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

        $displayNameTable = @{}
    }
    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
                if ($displayNameTable.Keys -notcontains $userObject.id) {
                    $displayNameTable.Add($userObject.id, $userObject.displayName)
                }
                $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 ($displayNameTable.Keys -notcontains $groupObject.id) {
                    $displayNameTable.Add($groupObject.id, $groupObject.displayName)
                }
                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
                if ($displayNameTable.Keys -notcontains $dirObject.id) {
                    $displayNameTable.Add($dirObject.id, $dirObject.displayName)
                }
                $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 @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
        $membersToAdd = New-Object -TypeName System.Collections.Generic.List[string]
        if ($roleAssignments.Count -gt 0) {
            $comparison = $roleAssignments.id | Compare-Object -ReferenceObject $memberIDs
            $comparison | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object { $membersToAdd.Add($_.InputObject) }
        }
        else {
            $memberIDs | ForEach-Object { $membersToAdd.Add($_) }
        }
        # end region

        # Assign Role
        $endpoint = 'roleManagement/directory/roleAssignments'
        foreach ($memberId in $membersToAdd) {
            $memberDisplayName = $displayNameTable[$memberId]
            $body = @{
                '@odata.type'      = '#microsoft.graph.unifiedRoleAssignment'
                'roleDefinitionId' = $roleDefinition.id
                'principalId'      = $memberId
                'directoryScopeId' = $DirectoryScopeId
            }
            if ($PSCmdlet.ShouldProcess(('{0} [{1}]' -f $memberDisplayName, $memberId), "Add Directory Role Assignment")) {
                try {
                    Invoke-MsGRequest -Method Post -Endpoint $endpoint -Body $body @irmParams
                }
                catch {
                    $errorMessage = Get-MsGErrorMessage $_
                    $errorException = "{0} : Unable to assign '{1}' role to {2} [{3}]. Error: {4}" -f $MyInvocation.MyCommand.Name, $roleDefinition.displayName, $memberDisplayName, $memberId, $errorMessage
                    throw $errorException
                }
            }
        }
        # end region
    }
}