Private/PartnerActions/Set-SAMConsent.ps1

function Set-SAMConsent {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [String]
        $CustomerTenantId,
        [Parameter()]
        [bool]$Retry
    )
    # Get SAM tokens if not already available
    begin {
        if (!$SAMTokens) {
            $SAMTokens = Get-SAMTokens
        }  
    }
    process {
        # Get the access token needed for subsequent requests
        $AccessToken = New-CustomPartnerAccessToken -Scopes "https://api.partnercenter.microsoft.com/user_impersonation" -TenantId $PartnerTenantId
        $AuthHeader = @{
            Authorization = "Bearer $($AccessToken.access_token)"
            Accept      = 'application/json'
        }
        try {
            # Get the relevant customer
            $Customers = (Invoke-RestMethod -Uri "https://api.partnercenter.microsoft.com/v1/customers?size=9999" -Headers $AuthHeader).items
            $Customer = $Customers | Where-Object { $_.companyProfile.tenantId -eq $CustomerTenantId }
        } catch {
            throw "Failed to find customer with ID $($CustomerTenantId): $_"
        }

        $ConsentBody = @{
            ApplicationId     = $($SAMTokens.ApplicationId)
            ApplicationGrants = @(
                @{
                    EnterpriseApplicationId = '00000003-0000-0000-c000-000000000000'
                    Scope                   = @(
                        'DelegatedPermissionGrant.ReadWrite.All',
                        'Directory.ReadWrite.All',
                        'AppRoleAssignment.ReadWrite.All'
                    ) -Join ','
                }
            )
        } | ConvertTo-Json

        $AppCreationResponse = Invoke-RestMethod -Uri "https://api.partnercenter.microsoft.com/v1/customers/$CustomerTenantId/applicationconsents" -Method POST -ContentType 'application/json' -Body $ConsentBody  -Headers $AuthHeader -SkipHttpErrorCheck -StatusCodeVariable StatusCode
        
        switch($StatusCode) {
            {$_ -eq "200" -or $_ -eq "201"} {
                Write-Host "Successfully created consent for $($Customer.companyProfile.companyName)" -ForegroundColor Green
                Write-Host "Waiting 60 seconds for consent to propogate fully..." -foregroundcolor yellow
                Start-Sleep -Seconds 5
            }
            409 {
                Write-Host "Consent already exists for $($Customer.companyProfile.companyName)" -ForegroundColor Yellow
                if(!$Retry) {
                    Write-Host "Trying to delete and recreate, in case customer has gotten new licenses (e.g. Teams, Exchange Online)..." -ForegroundColor Yellow
                    $ConsentUri = "https://api.partnercenter.microsoft.com/v1/customers/$CustomerTenantId/applicationconsents/$($SAMTokens.ApplicationId)"
                    Invoke-Restmethod -Uri $ConsentUri -Method DELETE -ContentType 'application/json' -Headers $AuthHeader -SkipHttpErrorCheck | Out-Null
                    Set-SAMConsent -CustomerTenantId $CustomerTenantId -Retry:$true
                }
            }
            400 {
                # This just means, that one of the applications we are trying to get consent for, does not exist in the tenant.
                # Microsoft still creates the consent for the applications that do exist, so we can ignore this error.
                if($AppCreationResponse.message -like "*doesnt exist in customer tenant*") {
                    Write-Host "Successfully created consent for $($Customer.companyProfile.companyName)" -ForegroundColor Green
                    Write-Host "Waiting 60 seconds for consent to propogate fully..." -foregroundcolor yellow
                    Start-Sleep -Seconds 5
                } else {
                    throw "Failed to create consent for $($Customer.companyProfile.companyName): $($_)"
                }
            }
            default {
                throw "Failed to create consent for $($Customer.companyProfile.companyName): $AppCreationResponse"
            }
        }

        Disconnect-Mggraph -ErrorAction SilentlyContinue | Out-Null
        $Global:GraphToken = $null
        Connect-CustomerGraph -CustomerTenantId $CustomerTenantId -AsApp:$false
    
        # Now that our application has consent to create new delegated permissions and app role assignments, we can do this.
        $ServicePrincipalList = Get-MgServicePrincipal -All
        $ServicePrincipal = $ServicePrincipalList | Where-Object { $_.AppId -eq $SAMTokens.ApplicationId }
        if(!$ServicePrincipal) {
            Write-Host "Service principal not found yet, waiting 10 seconds for it to be created..." -ForegroundColor Yellow
            Start-Sleep -Seconds 10
            $ServicePrincipalList = Get-MgServicePrincipal -All
            $ServicePrincipal = $ServicePrincipalList | Where-Object { $_.AppId -eq $SAMTokens.ApplicationId }
        }
        $CurrentRoles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipal.Id
        $CurrentDelegatedScopes = Get-MgServicePrincipalOauth2PermissionGrant -ServicePrincipalId $ServicePrincipal.Id

        $RequiredResourceAccess = (Get-Content '.\Private\PartnerActions\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess


        $Grants = foreach ($App in $RequiredResourceAccess) {
            $svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
            if (!$svcPrincipalId) {
                    #Write-Host "Creating service principal for $($App.resourceAppId)" -ForegroundColor Yellow
                    $svcPrincipalId = New-MgServicePrincipal -AppId $App.resourceAppId -ErrorAction SilentlyContinue
            }
            foreach ($SingleResource in $App.ResourceAccess | Where-Object -Property Type -EQ 'Role') {
                if ($SingleResource.id -In $CurrentRoles.appRoleId) { continue }
                [pscustomobject]@{
                    principalId = $($ServicePrincipal.id)
                    resourceId  = $($svcPrincipalId.id)
                    appRoleId   = "$($SingleResource.Id)"
                }
            }
        }
        $counter = 0
        foreach ($Grant in $Grants) {
            try {
                #Write-Host "New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $($ServicePrincipal.id) -AppRoleId $($Grant.appRoleId) -PrincipalId $($Grant.principalId) -ResourceId $($Grant.resourceId)" -ForegroundColor Green
                if($Grant.resourceId -eq $null -or $Grant.principalId -eq $null) {
                    #Write-Host "Skipping grant of $($Grant.appRoleId) to $($Grant.principalId) because the customer doesn't have licenses for this API." -ForegroundColor Yellow
                    continue
                }
                Write-Host "Adding $($Grant.appRoleId) to $($Grant.resourceId)" -ForegroundColor Yellow
                $SettingsRequest = New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ServicePrincipal.id -AppRoleId $Grant.appRoleId -PrincipalId $Grant.principalId -ResourceId $Grant.resourceId
                $counter++
            } catch {
                Write-Host "Failed to grant $($Grant.appRoleId) to $($Grant.resourceId)" -ForegroundColor Yellow
            }
        }
        #Write-Host "Added $counter Application permissions to $($ServicePrincipal.displayName)"

        # Add delegated permissions
        $Translator = Get-Content '.\Private\PartnerActions\PermissionsTranslator.json' | ConvertFrom-Json
        
        foreach ($App in $RequiredResourceAccess) {
            $svcPrincipalId = $ServicePrincipalList | Where-Object -Property appId -EQ $App.resourceAppId
            if (!$svcPrincipalId) {
                try {
                    $Body = @{
                        appId = $App.resourceAppId
                    } | ConvertTo-Json -Compress
                    #Write-Host "Attempting to create service principal for $($App.resourceAppId)" -ForegroundColor Yellow
                    $svcPrincipalId = New-MgServicePrincipal -AppId $App.resourceAppId -ErrorAction SilentlyContinue
                } catch {
                    Write-Host "Failed to create service principal for $($App.resourceAppId)" -ForegroundColor Yellow
                    continue
                }
            }

            $DelegatedScopes = $App.resourceAccess | Where-Object -Property type -EQ 'Scope'
            if ($AdditionalScopes) {
                $NewScope = (@(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value) -join ' ')
            } else {
                if ($NoTranslateRequired) {
                    $NewScope = @($DelegatedScopes | ForEach-Object { $_.id } | Sort-Object -Unique) -join ' '
                } else {
                    $NewScope = @(($Translator | Where-Object { $_.id -in $DelegatedScopes.id }).value | Sort-Object -Unique) -join ' '
                }
            }
    
            $OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id)

            if(!$svcPrincipalId) {
                Write-Host "Skipping grant of $NewScope because the customer doesn't have licenses for this API." -ForegroundColor Yellow
                continue
            }
    
            if (!$OldScope) {
                #Write-Host "clientId: $($ServicePrincipal.id) resourceId: $($svcPrincipalId.id) scope: $NewScope" -ForegroundColor Yellow
                $CreateRequest = New-MgOauth2PermissionGrant -ClientId $ServicePrincipal.id -ResourceId $svcPrincipalId.id -Scope $NewScope -ConsentType 'AllPrincipals' -ErrorAction SilentlyContinue
                Write-Host "Added delegated permissions for $($svcPrincipalId.displayName)" -ForegroundColor Green
            } else {
                $compare = Compare-Object -ReferenceObject $OldScope.scope.Split(' ') -DifferenceObject $NewScope.Split(' ')
                if (!$compare) {
                    Write-Host "All delegated permissions exist for $($svcPrincipalId.displayName)" -ForegroundColor Green
                    continue
                }
                $null = Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $OldScope.id -Scope $NewScope -ErrorAction SilentlyContinue
                # Added permissions
                #$Added = ($Compare | Where-Object { $_.SideIndicator -eq '=>' }).InputObject -join ' '
                #$Removed = ($Compare | Where-Object { $_.SideIndicator -eq '<=' }).InputObject -join ' '
                Write-Host "Successfully updated permissions for $($svcPrincipalId.displayName)" -ForegroundColor Green
                #Write-Host "Added: $Added" -ForegroundColor Green
                #Write-Host "Removed: $Removed" -ForegroundColor Yellow
            }
        }

    }
}