Public/Grant-KritTcmGraphPermission.ps1

function Grant-KritTcmGraphPermission {
    <#
    .SYNOPSIS
        Grant Microsoft Graph application permissions to the TCM service principal so
        TCM can impersonate calls to workload endpoints during snapshot / monitor runs.

    .DESCRIPTION
        Reproduces the documented PowerShell snippet from
        https://learn.microsoft.com/en-us/graph/utcm-authentication-setup as a typed,
        idempotent function with structured output.

        Pass an array of Graph application permission names (e.g. 'User.ReadWrite.All',
        'Policy.Read.All'). The function looks up each AppRole on the Microsoft Graph
        service principal then creates the AppRoleAssignment on the TCM SP. Already-
        granted permissions are detected via existing AppRoleAssignment query and
        skipped.

        For Exchange Online permissions, additional configuration via Application
        Access Policies is required — see Microsoft docs link in the warning output.

    .PARAMETER Permissions
        Array of Graph application permission display names (the .Value field on AppRole).

    .EXAMPLE
        Grant-KritTcmGraphPermission -Permissions 'User.ReadWrite.All','Policy.Read.All' -WhatIf
        Grant-KritTcmGraphPermission -Permissions 'User.ReadWrite.All','Policy.Read.All'

    .OUTPUTS
        Per-permission PSCustomObject (Permission, Granted, AlreadyExisted, Error).
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
    param(
        [Parameter(Mandatory, ValueFromPipeline)][string[]]$Permissions
    )
    begin {
        $tcmAppId   = Get-KritTcmConstant -Name TcmServicePrincipalAppId
        $graphAppId = Get-KritTcmConstant -Name MsGraphAppId

        $graphSp = Get-MgServicePrincipal -Filter "AppId eq '$graphAppId'" -ErrorAction Stop
        if (-not $graphSp) { throw "Microsoft Graph service principal not found in tenant (AppId $graphAppId)." }
        $tcmSp = Get-MgServicePrincipal -Filter "AppId eq '$tcmAppId'" -ErrorAction Stop
        if (-not $tcmSp) { throw "TCM service principal not found. Run Initialize-KritTcmServicePrincipal first." }

        $existingAssignments = @(Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $tcmSp.Id -All -ErrorAction Stop)
    }
    process {
        foreach ($perm in $Permissions) {
            $row = [ordered]@{
                Action='Grant-TcmGraphPermission'; Permission=$perm; TcmSpObjectId=$tcmSp.Id
                Granted=$false; AlreadyExisted=$false; Error=$null
                Timestamp=(Get-Date).ToUniversalTime().ToString('o')
            }
            try {
                $appRole = $graphSp.AppRoles | Where-Object { $_.Value -eq $perm -and $_.AllowedMemberTypes -contains 'Application' } | Select-Object -First 1
                if (-not $appRole) {
                    $row.Error = "No application AppRole with value '$perm' on Microsoft Graph SP."
                    [PSCustomObject]$row; continue
                }
                $already = $existingAssignments | Where-Object { $_.AppRoleId -eq $appRole.Id -and $_.ResourceId -eq $graphSp.Id }
                if ($already) {
                    $row.AlreadyExisted = $true
                    [PSCustomObject]$row; continue
                }
                if ($PSCmdlet.ShouldProcess($perm, "Grant Graph application permission to TCM SP")) {
                    $body = @{ AppRoleId = $appRole.Id; ResourceId = $graphSp.Id; PrincipalId = $tcmSp.Id }
                    New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $tcmSp.Id -BodyParameter $body -ErrorAction Stop | Out-Null
                    $row.Granted = $true
                }
            } catch {
                $row.Error = $_.Exception.Message
            }
            [PSCustomObject]$row
        }
    }
    end {
        Write-Verbose "For Exchange Online permissions also configure: https://learn.microsoft.com/en-us/exchange/permissions-exo/application-rbac"
    }
}