functions/Add-PpacRbacRoleMember.ps1


<#
    .SYNOPSIS
        Add a service principal as a member of a PPAC RBAC role in a specific scope.
         
    .DESCRIPTION
        Adds a service principal as a member of a PPAC RBAC role in a specific scope. This command is used to grant permissions to service principals in Power Platform.
         
        The role assignment will be created in the tenant's root scope, but the specified scope will be used for permission evaluation.
         
    .PARAMETER ServicePrincipalId
        The object id of the service principal to which the role will be assigned.
         
    .PARAMETER Role
        The name of the PPAC RBAC role to which the service principal will be assigned. Use the command <c='em'>Get-PpacRbacRole</c> to see the list of valid role names.
         
    .PARAMETER Scope
        The scope in which the role assignment will be evaluated.
         
        Will be auto-populated based on the role. Anything beyond /tenants/{0} needs to be filled out by the user.
         
        For example, if the scope is /tenants/{0}/environments/{1}, the user needs to fill out the {1} (environmentId).
         
    .EXAMPLE
        PS C:\> Add-PpacRbacRoleMember -ServicePrincipalId "00000000-0000-0000-0000-000000000000" -Role "Power Platform Owner" -Scope "/tenants/{0}"
         
        This command assigns the service principal with object id "00000000-0000-0000-0000-000000000000" to the "Power Platform Owner" role in the tenant scope.
        The service principal will have owner permissions across all Power Platform environments in the tenant.
         
    .NOTES
        Author: Mötz Jensen (@Splaxi)
         
        Based on:
        https://learn.microsoft.com/en-us/power-platform/admin/programmability-tutorial-rbac-role-assignment?tabs=PowerShell
        https://learn.microsoft.com/en-us/power-platform/admin/programmability-authentication-v2?tabs=powershell%2Cpowershell-interactive%2Cpowershell-confidential
#>

function Add-PpacRbacRoleMember {
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    param (
        [Parameter(Mandatory = $true)]
        [string] $ServicePrincipalId,

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

        [Parameter(Mandatory = $true)]
        [string] $Scope
    )
    
    begin {
        # We need to translate the role name to roleDefinitionId.
        $pathMisc = Get-PSFConfigValue -FullName "d365bap.tools.internal.misc.path"

        $rbacRoles = Get-Content `
            -Path "$pathMisc\Ppac.Rbac.Roles.json" `
            -Raw | ConvertFrom-Json

        $roleDefinitionId = $rbacRoles | `
            Where-Object { $_.roleDefinitionName -eq $Role } | `
            Select-Object -First 1 -ExpandProperty roleDefinitionId
    
        if ($null -eq $roleDefinitionId) {
            $messageString = "The specified role: <c='em'>$Role</c> was not found. Please provide a valid role name. Try running the command <c='em'>Get-PpacRbacRole</c> to see the list of valid role names."
            Write-PSFMessage -Level Important -Message $MessageString
            Stop-PSFFunction -Message "Stopping because of invalid role name." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
            return
        }
    
        $token = Get-PSFConfigValue -FullName "d365bap.tools.internal.ppac.rbac.token"

        if ($null -eq $token) {
            Write-PSFMessage -Level Warning -Message "No authentication token found for PPAC RBAC operations. Please run <c='em'>Set-PpacRbacContext</c> to authenticate first."
            Stop-PSFFunction -Message "Stopping because of missing authentication token." -Exception $([System.Exception]::new("Missing authentication token for PPAC RBAC operations."))
            return
        }

        $headersPowerApi = @{ 'Content-Type' = 'application/json' }
        $headersPowerApi.Add('Authorization', $token)

        $tenantId = (Get-AzContext).Tenant.Id
    }
    
    process {
        if (Test-PSFFunctionInterrupt) { return }

        $tmpScope = $Scope.Replace('/tenants/{0}', "/tenants/$tenantId")

        if ($tmpScope -like "*{*}*") {
            $tmpText = $tmpScope.Replace('{', "<c='em'>--").Replace('}', "--</c>")
            $messageString = "The specified scope: '$tmpText' is not valid. You need to fill out the missing scope details."
            Write-PSFMessage -Level Important -Message $MessageString
            Stop-PSFFunction -Message "Stopping because of invalid scope." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
            return
        }

        $resTmp = Get-PpacRbacRoleMember `
            -ServicePrincipalId $ServicePrincipalId `
            -Role $Role

        if (-not ($null -eq $resTmp)) {
            $resTmp
            return
        }

        $payload = @{
            roleDefinitionId  = $roleDefinitionId
            principalObjectId = $ServicePrincipalId
            principalType     = "ApplicationUser"
            scope             = $tmpScope
        } | ConvertTo-Json

        Invoke-RestMethod -Method Post `
            -Uri "https://api.powerplatform.com/authorization/roleAssignments?api-version=2024-10-01" `
            -Headers $headersPowerApi `
            -ContentType $headersPowerApi.'Content-Type' `
            -Body $payload `
            -StatusCodeVariable statusAssign > $null 4> $null

        if (-not ($statusAssign -like "2*")) {
            $messageString = "Failed to assign the role to the service principal. Verify that the service principal exists and the scope is correct. Try running the command <c='em'>Get-PpacRbacRoleMember</c> to see existing role assignments for service principals."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because assigning the role to the service principal in the Power Platform environment failed." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
            return
        }

        Get-PpacRbacRoleMember `
            -ServicePrincipalId $ServicePrincipalId `
            -Role $Role
    }

    end {
        
    }
    
}