Private/Invoke-AppConsentFlow.ps1

function Invoke-AppConsentFlow {
    <#
    .SYNOPSIS
    Initiates the app consent flow for Microsoft Partner Center API access across customer tenants.
 
    .DESCRIPTION
    This function automates the process of obtaining admin consent for Partner Center API permissions
    across specified customer tenants or all customer tenants. It handles the OAuth2 authorization flow,
    including opening the consent page in a browser and capturing the authorization response.
 
    .PARAMETER tenantId
    The tenant ID of the Microsoft Partner (MSP) organization.
 
    .PARAMETER appId
    The application (client) ID of the registered application in Azure AD.
 
    .PARAMETER redirectUri
    The redirect URI configured in the app registration. Defaults to 'http://localhost:8400'.
 
    .PARAMETER scope
    The permission scope requested. Defaults to 'https://api.partnercenter.microsoft.com/.default'.
 
    .PARAMETER customerTenants
    A hashtable containing customer tenant IDs as keys and display names as values.
    Required when not using -AllCustomers switch.
 
    .PARAMETER AllCustomers
    Switch to process consent flow for all customers. When specified, the function will retrieve
    all customer tenants from Partner Center automatically.
 
    .PARAMETER AddSAM
    Switch to use SAM configuration for tenantId and appId.
 
    .PARAMETER samDisplayName
    The display name of the SAM configuration.
 
    .EXAMPLE
    # Process specific customers
    $customers = @{
        "tenant-id-1" = "Customer1 Name"
        "tenant-id-2" = "Customer2 Name"
    }
    Invoke-AppConsentFlow -tenantId "msp-tenant-id" -appId "app-id" -customerTenants $customers
 
    .EXAMPLE
    # Process all customers
    Invoke-AppConsentFlow -tenantId "msp-tenant-id" -appId "app-id" -AllCustomers
 
    .NOTES
    Requires appropriate Partner Center API permissions and admin consent from customer tenants.
    #>


    [CmdletBinding(DefaultParameterSetName = 'Specific')]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Specific')]
        [Parameter(Mandatory = $false, ParameterSetName = 'All')]
        [string]$tenantId, #MSP TenantId

        [Parameter(Mandatory = $false, ParameterSetName = 'Specific')]
        [Parameter(Mandatory = $false, ParameterSetName = 'All')]
        [string]$appId,

        [Parameter(ParameterSetName = 'Specific')]
        [Parameter(ParameterSetName = 'All')]
        [string]$redirectUri = 'http://localhost:8400', #Needs to match redirect uri of your app registration

        [Parameter(ParameterSetName = 'Specific')]
        [Parameter(ParameterSetName = 'All')]
        [string]$scope = 'https://api.partnercenter.microsoft.com/.default',

        [Parameter(Mandatory, ParameterSetName = 'Specific')]
        [hashtable]$customerTenants, #Hashtable of CustomerTenantId and DisplayName

        [Parameter(Mandatory, ParameterSetName = 'All')]
        [switch]$AllCustomers,

        [Parameter(ParameterSetName = 'Specific')]
        [Parameter(ParameterSetName = 'All')]
        [string]$samDisplayName,

        [Parameter(ParameterSetName = 'Specific')]
        [Parameter(ParameterSetName = 'All')]
        [switch]$AddSAM
    )

    # Get SAM configuration
    $samConfig = (Get-ConfigData).SAM
    if (-not $samConfig) {
        Write-Output "Error: Unable to retrieve SAM configuration"
        return
    }

    # Set tenantId, appId, and samDisplayName if using AddSAM switch
    if ($AddSAM) {
        $tenantId = $samConfig.MSPTenantId
        $appId = $samConfig.ApplicationId
        $appSecret = $samConfig.ApplicationSecret
        $samDisplayName = $samConfig.SAMDisplayName
    }
    
    # Validate required parameters if not using AddSAM
    if (-not $AddSAM -and (-not $tenantId -or -not $appId)) {
        Write-Output "Error: When not using -AddSAM, both -tenantId and -appId parameters are required"
        return
    }

    try {
        
        if ($AllCustomers) {
            # Get MSP token
            $graphParams = @{
                ApplicationId     = $samConfig.ApplicationId
                ApplicationSecret = $samConfig.ApplicationSecret
                RefreshToken      = $samConfig.RefreshToken
                TenantID          = $samConfig.MSPTenantId
            }
            $MSPtoken = Connect-GraphApiSAM @graphParams

            Write-Output "Retrieving all customers from Partner Center..."
            $tenants = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/contracts?`$top=999" -Method GET -Headers $MSPtoken).value
            $customerTenants = @{}
            foreach ($tenant in $tenants) {
                $customerTenants[$tenant.customerId] = $tenant.displayName
            }
            Write-Output "Found $($customerTenants.Count) customers"
        }
    }
    catch {
        Write-Output "Error connecting to Graph API: $_"
        return
    }

    # Get authorization code if not already present
    if (-not $script:authCode) {
        try {
            $authParams = @{
                tenantId     = $tenantId
                appId        = $appId
                appSecret    = $appSecret
                redirectUri  = $redirectUri
                scope        = $scope
            }
            $script:authCode = Get-SAMAuthorizationCode @authParams
            
            Write-Output "Authorization Code obtained successfully"
        }
        catch {
            Write-Output "Error obtaining authorization code: $_"
            return
        }
    }

    $headers = @{
        Authorization = $script:authCode.Authorization  # Using the Authorization property directly
        'Accept'      = 'application/json'
    }

    # Process each customer tenant
    $customerTenants.GetEnumerator() | ForEach-Object {
        $currentTenant = $_
        Write-Output "Processing tenant: $($currentTenant.Value)"

        $body = @{
            applicationGrants = @(
                @{
                    enterpriseApplicationId = "00000003-0000-0000-c000-000000000000"
                    scope                   = "Directory.AccessAsUser.All,Application.ReadWrite.All,Directory.ReadWrite.All"
                },
                @{
                    enterpriseApplicationId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
                    scope                   = "user_impersonation"
                }
            )
            applicationId     = $appId
            displayName      = $samDisplayName
        } | ConvertTo-Json -Compress

        $uri = "https://api.partnercenter.microsoft.com/v1/customers/$($currentTenant.Key)/applicationconsents"

        try {
            $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method POST -Body $body -ContentType 'application/json' -ErrorAction Stop
            Write-Output "Successfully processed tenant $($currentTenant.Value)"
            Write-Output $response
        }
        catch {
            $errorDetails = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
            Write-Output "Error processing tenant $($currentTenant.Value):"
            Write-Output "Status Code: $($_.Exception.Response.StatusCode.value__)"
            Write-Output "Error Message: $($errorDetails.message)"
            Write-Output "Activity ID: $($errorDetails.activityId)"
        }
    }
}