Public/New-MgGraphCommunityAppRegistration.ps1

function New-MgGraphCommunityAppRegistration {
    <#
    .SYNOPSIS
        Creates an Entra ID app registration ready for WAM-free MgGraphCommunity sign-in.

    .DESCRIPTION
        Creates a single-tenant public client application via Microsoft Graph
        (POST /v1.0/applications) configured for this module's Interactive (PKCE +
        loopback) and DeviceCode flows:

            signInAudience = AzureADMyOrg
            isFallbackPublicClient = true
            publicClient redirect = http://localhost (matches any loopback port)

        No Graph permissions are pre-configured; the delegated flows request scopes
        dynamically and users consent at sign-in, exactly as with the built-in
        Microsoft client ID.

        Why this exists: Microsoft has announced changes to the default app used
        for delegated authentication (msgraph-sdk-powershell issue #3629). An app
        registration you own keeps your sign-ins independent of that change.

        Requires an active MgGraphCommunity connection whose token carries the
        delegated Application.ReadWrite.All scope:

            Connect-MgGraphCommunity -Scopes 'Application.ReadWrite.All'

        After creation the app is polled until it is readable via Graph. The very
        first sign-in can still take a minute or two while Entra ID propagates the
        new app to its token service; if you see "unauthorized_client", wait and retry.

    .PARAMETER DisplayName
        Display name for the new app registration. Defaults to 'MgGraphCommunity'.

    .PARAMETER AddWamRedirectUri
        Also add the WAM broker redirect URI
        (ms-appx-web://Microsoft.AAD.BrokerPlugin/{appId}) so the same app works
        with the official Microsoft.Graph SDK's WAM sign-in. MgGraphCommunity
        itself never uses WAM.

    .PARAMETER SetAsDefault
        Persist the new app's ClientId via Set-MgGraphCommunityDefaultClientId so
        future Connect-MgGraphCommunity calls use it automatically.

    .EXAMPLE
        Connect-MgGraphCommunity -Scopes 'Application.ReadWrite.All'
        New-MgGraphCommunityAppRegistration -SetAsDefault

    .EXAMPLE
        New-MgGraphCommunityAppRegistration -DisplayName 'Contoso Graph PowerShell' -AddWamRedirectUri

    .OUTPUTS
        [pscustomobject] with ClientId, ObjectId, DisplayName, TenantId, RedirectUris, SetAsDefault.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [Alias('New-MgcApp')]
    param(
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$DisplayName = 'MgGraphCommunity',

        [switch]$AddWamRedirectUri,

        [switch]$SetAsDefault
    )

    if (-not $script:MgcActiveSession) {
        throw "Not connected. Run Connect-MgGraphCommunity -Scopes 'Application.ReadWrite.All' first."
    }

    if (-not $PSCmdlet.ShouldProcess($DisplayName, 'Create Entra ID app registration')) { return }

    $payload = @{
        displayName            = $DisplayName
        signInAudience         = 'AzureADMyOrg'
        isFallbackPublicClient = $true
        publicClient           = @{
            # Plain http://localhost matches any loopback port, so the flow's
            # OS-assigned redirect port needs no fixed registration.
            redirectUris = @('http://localhost')
        }
    }

    $app = $null
    try {
        $app = Invoke-MgGraphCommunityRequest -Method POST -Uri '/applications' -V1 -Body $payload
    } catch {
        if ("$_" -match 'Authorization_RequestDenied|Insufficient privileges|403') {
            throw ("Creating the app registration was denied. The current token lacks the delegated " +
                   "Application.ReadWrite.All scope (or your account cannot create app registrations). " +
                   "Reconnect with: Connect-MgGraphCommunity -Scopes 'Application.ReadWrite.All'. Details: $_")
        }
        throw
    }

    $redirectUris = @('http://localhost')
    if ($AddWamRedirectUri) {
        # The WAM broker URI embeds the appId, so it can only be added after creation.
        $redirectUris += "ms-appx-web://Microsoft.AAD.BrokerPlugin/$($app.appId)"
        $null = Invoke-MgGraphCommunityRequest -Method PATCH -Uri "/applications/$($app.id)" -V1 -Body @{
            publicClient = @{ redirectUris = $redirectUris }
        }
    }

    # Directory propagation: poll until the app is readable by appId. Readable
    # here does not guarantee the token service knows it yet, but it is the best
    # signal Graph offers.
    $ready    = $false
    $deadline = (Get-Date).AddSeconds(60)
    while (-not $ready -and (Get-Date) -lt $deadline) {
        try {
            $null  = Invoke-MgGraphCommunityRequest -Method GET -Uri "/applications(appId='$($app.appId)')" -V1
            $ready = $true
        } catch {
            Start-Sleep -Seconds 3
        }
    }
    if (-not $ready) {
        Write-Warning "App created but not yet readable via Graph after 60s. It exists; propagation is still in progress."
    }

    if ($SetAsDefault) {
        Set-MgGraphCommunityDefaultClientId -ClientId $app.appId
    }

    [pscustomobject]@{
        ClientId     = $app.appId
        ObjectId     = $app.id
        DisplayName  = $app.displayName
        TenantId     = $script:MgcContext.TenantId
        RedirectUris = $redirectUris
        SetAsDefault = [bool]$SetAsDefault
    }
}