functions/Set-BapEnvironmentSecurityRoleMember.ps1


<#
    .SYNOPSIS
        Set Security Role members from Entra Group in environment
         
    .DESCRIPTION
        Enables the user to set Security Role members in the Power Platform environment based on members from a Security Group in Azure AD / Entra ID.
         
    .PARAMETER EnvironmentId
        Id of the environment that you want to work against.
         
    .PARAMETER ObjectId
        The ObjectId or Display Name of the Security Group in Azure AD / Entra ID that you want to use as source for members to add to the Security Role.
         
    .PARAMETER Role
        The name of the Security Role in the Power Platform environment to which you want to add members from the specified Security Group.
         
    .EXAMPLE
        PS C:\> Set-BapEnvironmentSecurityRoleMember -EnvironmentId *uat* -ObjectId 12345678-90ab-cdef-1234-567890abcdef -Role "System Administrator"
         
        This will add all members from the Security Group with ObjectId "12345678-90ab-cdef-1234-567890abcdef" to the Security Role "System Administrator" in the environment with id containing "uat".
         
    .EXAMPLE
        PS C:\> Set-BapEnvironmentSecurityRoleMember -EnvironmentId *uat* -ObjectId "My Security Group" -Role "Basic User"
         
        This will add all members from the Security Group with Display Name "My Security Group" to the Security Role "Basic User" in the environment with id containing "uat".
         
    .NOTES
        author: Mötz Jensen (@Splaxi)
#>

function Set-BapEnvironmentSecurityRoleMember {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Parameter (Mandatory = $true)]
        [string] $EnvironmentId,

        [Parameter (Mandatory = $true)]
        [Alias('EntraGroup')]
        [string] $ObjectId,

        [Parameter (Mandatory = $true)]
        [Alias('RoleName')]
        [string] $Role
    )
    
    begin {
        # Make sure all *BapEnvironment* cmdlets will validate that the environment exists prior running anything.
        $envObj = Get-BapEnvironment -EnvironmentId $EnvironmentId | Select-Object -First 1

        if ($null -eq $envObj) {
            $messageString = "The supplied EnvironmentId: <c='em'>$EnvironmentId</c> didn't return any matching environment details. Please verify that the EnvironmentId is correct - try running the <c='em'>Get-BapEnvironment</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because environment was NOT found based on the id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }

        if (Test-PSFFunctionInterrupt) { return }

        $baseUri = $envObj.LinkedMetaPpacEnvUri

        $secureToken = (Get-AzAccessToken -ResourceUrl $baseUri -AsSecureString).Token
        $tokenWebApiValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureToken

        $headersWebApi = @{
            "Authorization" = "Bearer $($tokenWebApiValue)"
        }

        $secureTokenGraph = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com/" -AsSecureString).Token
        $tokenGraphValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureTokenGraph

        $headersGraphApi = @{
            "Authorization" = "Bearer $($tokenGraphValue)"
            "Content-Type"  = "application/json"
        }

        $uriGraphBase = 'https://graph.microsoft.com/v1.0/groups?$select=id,displayName&'
        if (Test-Guid -InputObject $ObjectId) {
            # Validate that the Security Group exists in Azure AD / Entra ID
            $uriGraph = "$uriGraphBase`$filter=id eq '$ObjectId'"
        }
        else {
            $uriGraph = "$uriGraphBase`$filter=startswith(displayName, '$ObjectId')"
        }

        $colGroups = Invoke-RestMethod -Method Get `
            -Uri $uriGraph `
            -Headers $headersGraphApi | Select-Object -ExpandProperty Value

        if ($colGroups.Count -eq 0) {
            $messageString = "The supplied ObjectId / Entra Group: <c='em'>$ObjectId</c> didn't return any matching Security Group in Azure AD / Entra ID. Please verify that the ObjectId is correct - try running the <c='em'>Get-AzADGroup</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because Security Group was NOT found based on the ObjectId." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }

        if ($colGroups.Count -gt 1) {
            $messageString = "The supplied ObjectId / Entra Group: <c='em'>$ObjectId</c> returned multiple matching Security Groups in Azure AD / Entra ID. Please verify that the ObjectId is correct - try running the <c='em'>Get-AzADGroup</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because multiple Security Groups were found based on the ObjectId." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }
        
        $uriGraphMembers = "https://graph.microsoft.com/v1.0/groups/$($colGroups[0].id)/transitiveMembers`?`$select=id,displayName,mail,userPrincipalName"
        $colMembers = Invoke-RestMethod -Method Get `
            -Uri $uriGraphMembers `
            -Headers $headersGraphApi | Select-Object -ExpandProperty Value

        if ($colMembers.Count -eq 0) {
            $messageString = "The Security Group: <c='em'>$($colGroups[0].displayName)</c> doesn't contain any members. Please verify that the Security Group has members."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because the Security Group has no members." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }

        $colSecurityRoles = Get-BapEnvironmentSecurityRole -EnvironmentId $envObj.PpacEnvId -Name $Role

        if ($colSecurityRoles.Count -eq 0) {
            $messageString = "The supplied Role Name / Id: <c='em'>$Role</c> didn't return any matching Security Role in the Power Platform environment. Please verify that the Role Name / Id is correct - try running the <c='em'>Get-BapEnvironmentSecurityRole</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because Security Role was NOT found based on the Role Name / Id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }

        if ($colSecurityRoles.Count -gt 1) {
            $messageString = "The supplied Role Name / Id: <c='em'>$Role</c> returned multiple matching Security Roles in the Power Platform environment. Please verify that the Role Name / Id is correct - try running the <c='em'>Get-BapEnvironmentSecurityRole</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because multiple Security Roles were found based on the Role Name / Id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }

        if (Test-PSFFunctionInterrupt) { return }
    }
    
    process {
        if (Test-PSFFunctionInterrupt) { return }

        $colUsers = Get-BapEnvironmentUser -EnvironmentId $envObj.PpacEnvId

        foreach ($usrObj in $colMembers | Where-Object { $_.'@odata.type' -eq "#microsoft.graph.user" }) {
            $matchedUser = $colUsers | Where-Object { $_.EntraObjectId -eq $usrObj.id } | Select-Object -First 1

            if ($null -eq $matchedUser) {
                $messageString = "The member: <c='em'>$($usrObj.displayName) - $($usrObj.userPrincipalName)</c> from the Security Group: <c='em'>$($colGroups[0].displayName)</c> was not found as a user in the Power Platform environment. Please verify that the user exists in the environment."
                Write-PSFMessage -Level Warning -Message $messageString
                continue
            }

            $payLoad = [PsCustomObject][ordered]@{
                "@odata.id" = $baseUri + "/api/data/v9.0/roles($($colSecurityRoles[0].PpacRoleId))"
            } | ConvertTo-Json -Depth 10

            $localUri = $baseUri + "/api/data/v9.0/systemusers($($matchedUser.PpacSystemUserId))/systemuserroles_association/`$ref"
           
            Invoke-RestMethod -Method Post `
                -Uri $localUri `
                -Headers $headersWebApi `
                -ContentType "application/json" `
                -Body $payLoad > $null
        }
    }
    
    end {
        
    }
}