Public/Set-RBACAccess.ps1

function Set-RBACAccess {
    <#
    .SYNOPSIS
        Discovers, evaluates, and (optionally) grants the least-privilege RBAC a
        caller needs to run a command.
 
    .DESCRIPTION
        End-to-end orchestration over the probe providers:
 
          1. Resolve the requirement (preflight, or -LiveProbe for Azure).
          2. Test whether -CallerId already holds each required role at the scope.
          3. Generate idempotent grant / revoke snippets.
          4. With -WhatIf, only report. Otherwise grant missing roles, honouring
             -Confirm. Automatic granting is implemented for Azure
             (New-AzRoleAssignment); for Graph / Fabric / Purview the AddScript is
             returned for review and the result is marked Applied = $false, because
             those grants are intentionally not executed automatically.
 
        Designed for Zero Trust provisioning: a framework identity holding only
        the relevant *roleAssignments write* permission grants the caller exactly
        the roles an operation needs - and the RemoveScript revokes them after.
 
    .PARAMETER Platform
        Platform name or alias.
 
    .PARAMETER Command
        The command whose requirement is evaluated.
 
    .PARAMETER CallerId
        The identity to evaluate and grant.
 
    .PARAMETER Scope / -SubscriptionId / -ResourceGroupName / -ManagementGroupId / -ResourceId
        Scope, or the parts to build an ARM scope from.
 
    .PARAMETER ArgumentList
        Arguments for -LiveProbe (and recorded on output).
 
    .PARAMETER LiveProbe
        Derive the requirement from a live Azure failure (Azure only).
 
    .PARAMETER RoleAssignment
        Pre-fetched assignments/roles for offline evaluation.
 
    .PARAMETER Options
        Provider hashtable (e.g. @{ WorkspaceId='...' }).
 
    .PARAMETER MapPath
        Alternate knowledge-base path (testing).
 
    .PARAMETER TenantId / -RunAs*
        Probe-identity context (see Get-RBACRequirement).
 
    .EXAMPLE
        Set-RBACAccess -Platform Azure -SubscriptionId SUB1 -CallerId a@b.com `
            -Command New-AzResourceGroup -WhatIf
 
    .OUTPUTS
        PSCustomObject (PSAutoRBAC.AccessResult) per required role.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Platform,

        [Parameter(Mandatory)]
        [ValidateNotNull()]
        [object]$Command,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$CallerId,

        [Parameter()]
        [string]$Scope,

        [Parameter()]
        [string]$SubscriptionId,

        [Parameter()]
        [string]$ResourceGroupName,

        [Parameter()]
        [string]$ManagementGroupId,

        [Parameter()]
        [string]$ResourceId,

        [Parameter()]
        [Alias('Args')]
        [object[]]$ArgumentList,

        [Parameter()]
        [switch]$LiveProbe,

        [Parameter()]
        [object[]]$RoleAssignment,

        [Parameter()]
        [hashtable]$Options,

        [Parameter()]
        [string]$MapPath,

        [Parameter()]
        [string]$TenantId,

        [Parameter()]
        [pscredential]$RunAsCredential,

        [Parameter()]
        [pscredential]$RunAsServicePrincipal,

        [Parameter()]
        [string]$RunAsTenantId,

        [Parameter()]
        [switch]$RunAsManagedIdentity,

        [Parameter()]
        [string]$RunAsManagedIdentityClientId
    )

    $provider = Get-RBACProviderInternal -Platform $Platform
    $context  = Initialize-RBACContext -BoundParameters $PSBoundParameters
    $commandName = if ($Command -is [scriptblock]) { '<scriptblock>' } else { [string]$Command }
    Write-PSFMessage -Level Verbose -Message "Set-RBACAccess: platform '$($provider.Name)', command '$commandName', caller '$CallerId'." -Tag 'PSAutoRBAC', 'Public', 'Access'

    try {
        $resolvedScope = Resolve-RBACScope -Scope $Scope -SubscriptionId $SubscriptionId `
            -ResourceGroupName $ResourceGroupName -ManagementGroupId $ManagementGroupId `
            -ResourceId $ResourceId -AllowTenantRoot

        $opts = @{}
        if ($Options) { $opts = $Options.Clone() }
        if ($MapPath) { $opts['MapPath'] = $MapPath }

        if ($LiveProbe -and $provider.SupportsLiveProbe -and
            $PSCmdlet.ShouldProcess("$commandName (as probe identity)", 'Execute command to derive RBAC requirement (LiveProbe)')) {
            $requirement = & $provider.ProbeLive $Command $ArgumentList $resolvedScope $context
        }
        else {
            $requirement = & $provider.ResolveRequirement $commandName $context $opts
        }

        $roles = @($requirement.Roles | Where-Object { $_ })
        $testOpts = $opts.Clone()
        if ($PSBoundParameters.ContainsKey('RoleAssignment')) { $testOpts['RoleAssignment'] = $RoleAssignment }
        $states = if ($roles.Count) { @(& $provider.TestAccess $CallerId $roles $resolvedScope $context $testOpts) } else { @() }

        foreach ($state in $states) {
            $scripts = & $provider.NewGrantScript $CallerId $state.Role $resolvedScope $opts

            $applied = $false
            if ($state.HasAccess -ne $true) {
                Write-PSFMessage -Level Verbose -Message "Set-RBACAccess: '$CallerId' is missing '$($state.Role)' at '$resolvedScope'." -Tag 'PSAutoRBAC', 'Public', 'Access'
                if ($PSCmdlet.ShouldProcess("$CallerId at $resolvedScope", "Grant '$($state.Role)' on $($provider.Name)")) {
                    if ($provider.Name -eq 'Azure' -and (Get-Command New-AzRoleAssignment -ErrorAction SilentlyContinue)) {
                        $azp = @{ SignInName = $CallerId; RoleDefinitionName = $state.Role; Scope = $resolvedScope; ErrorAction = 'Stop' }
                        if ($context.AzContext) { $azp['DefaultProfile'] = $context.AzContext }
                        New-AzRoleAssignment @azp | Out-Null
                        $applied = $true
                        Write-PSFMessage -Level Significant -Message "Set-RBACAccess: granted '$($state.Role)' to '$CallerId' at '$resolvedScope'." -Tag 'PSAutoRBAC', 'Public', 'Access'
                    }
                    else {
                        Write-PSFMessage -Level Warning -Message "Automatic grant is not performed for '$($provider.Name)'. Run the AddScript on the result from an identity with the necessary management permission." -Tag 'PSAutoRBAC', 'Public', 'Access'
                    }
                }
            }
            else {
                Write-PSFMessage -Level Debug -Message "Set-RBACAccess: '$CallerId' already holds '$($state.Role)'; nothing to do." -Tag 'PSAutoRBAC', 'Public', 'Access'
            }

            [pscustomobject]@{
                PSTypeName   = 'PSAutoRBAC.AccessResult'
                Platform     = $provider.Name
                Command      = $commandName
                CallerId     = $CallerId
                Scope        = $resolvedScope
                Role         = $state.Role
                HasAccess    = $state.HasAccess
                Applied      = $applied
                IsKnown      = $requirement.IsKnown
                Source       = $requirement.Source
                Permissions  = $requirement.Permissions
                Notes        = $requirement.Notes
                AddScript    = $scripts.AddScript
                RemoveScript = $scripts.RemoveScript
            }
        }
    }
    finally {
        if ($context.IsRunAs) { & $context.Disconnect }
    }
}