#!/usr/bin/env pwsh [CmdletBinding()] param( [Alias("n")] [string]$AppName, [switch]$UsePowershellModules, [Alias("h")] [switch]$Help ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Show-Usage { Write-Host "Usage: ./New-PublicClientApplication.ps1 -AppName " Write-Host "Options:" Write-Host " -AppName, -n Application display name (required)" Write-Host " -UsePowershellModules Use Az.Accounts/Az.Resources cmdlets instead of Azure CLI" Write-Host " -Help, -h Show this help message and exit" } function Get-RequiredResourceAccess { param( [Parameter(Mandatory = $true)] [string]$M365GraphAppId, [Parameter(Mandatory = $true)] [string]$M365GraphScopeId, [Parameter(Mandatory = $true)] [string]$AzureDevOpsAppId, [Parameter(Mandatory = $true)] [string]$AzureDevOpsScopeId, [Parameter(Mandatory = $true)] [string]$AzureServiceMgmtAppId, [Parameter(Mandatory = $true)] [string]$AzureServiceMgmtScopeId ) return @( @{ resourceAppId = $M365GraphAppId resourceAccess = @( @{ id = $M365GraphScopeId type = "Scope" } ) }, @{ resourceAppId = $AzureDevOpsAppId resourceAccess = @( @{ id = $AzureDevOpsScopeId type = "Scope" } ) }, @{ resourceAppId = $AzureServiceMgmtAppId resourceAccess = @( @{ id = $AzureServiceMgmtScopeId type = "Scope" } ) } ) } if ($Help) { Show-Usage exit 0 } if ([string]::IsNullOrWhiteSpace($AppName)) { Write-Error "Application name is required." Show-Usage exit 1 } $m365GraphAppId = "00000003-0000-0000-c000-000000000000" $m365GraphScopeId = "0e263e50-5827-48a4-b97c-d940288653c7" $azureServiceMgmtAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" $azureServiceMgmtScopeId = "41094075-9dad-400e-a0bd-54e686782033" $azureDevOpsAppId = "499b84ac-1321-427f-aa17-267ca6975798" $azureDevOpsScopeId = "ee69721e-6c3a-468f-a9ec-302d16a4c599" if ($UsePowershellModules) { if (-not (Get-Command Get-AzADApplication -ErrorAction SilentlyContinue)) { throw "Get-AzADApplication cmdlet not found. Install Az.Resources." } if (-not (Get-Command New-AzADApplication -ErrorAction SilentlyContinue)) { throw "New-AzADApplication cmdlet not found. Install Az.Resources." } if (-not (Get-Command Update-AzADApplication -ErrorAction SilentlyContinue)) { throw "Update-AzADApplication cmdlet not found. Install Az.Resources." } $azContext = Get-AzContext if ($null -eq $azContext) { throw "No Azure context found. Run Connect-AzAccount first." } $existingApp = Get-AzADApplication -DisplayName $AppName -First 1 if ($null -ne $existingApp) { Write-Error "Application '$AppName' already exists." exit 1 } $requiredResourceAccess = Get-RequiredResourceAccess ` -M365GraphAppId $m365GraphAppId ` -M365GraphScopeId $m365GraphScopeId ` -AzureDevOpsAppId $azureDevOpsAppId ` -AzureDevOpsScopeId $azureDevOpsScopeId ` -AzureServiceMgmtAppId $azureServiceMgmtAppId ` -AzureServiceMgmtScopeId $azureServiceMgmtScopeId $webConfig = @{ implicitGrantSettings = @{ enableAccessTokenIssuance = $true enableIdTokenIssuance = $true } } # Create first to obtain appId needed for msal://auth redirect URI. $newApp = New-AzADApplication ` -DisplayName $AppName ` -SignInAudience "AzureADMyOrg" ` -IsFallbackPublicClient ` -PublicClientRedirectUri @("http://localhost") ` -RequiredResourceAccess $requiredResourceAccess ` -Web $webConfig if ($null -eq $newApp -or [string]::IsNullOrWhiteSpace($newApp.AppId)) { throw "Failed to create application '$AppName' via Az.Resources." } $appId = $newApp.AppId Update-AzADApplication ` -ApplicationId $appId ` -SignInAudience "AzureADMyOrg" ` -IsFallbackPublicClient ` -RequiredResourceAccess $requiredResourceAccess ` -PublicClientRedirectUri @("http://localhost", "msal${appId}://auth") ` -Web $webConfig | Out-Null } else { # Find the app by name $existingAppId = az ad app list --display-name $AppName --query "[0].appId" -o tsv if ($LASTEXITCODE -ne 0) { throw "Failed to query existing applications." } if (-not [string]::IsNullOrWhiteSpace($existingAppId)) { Write-Error "Application '$AppName' already exists." exit 1 } # Create the app $appId = az ad app create --display-name $AppName --query "appId" -o tsv if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($appId)) { throw "Failed to create application '$AppName'." } $requiredResourceAccess = Get-RequiredResourceAccess ` -M365GraphAppId $m365GraphAppId ` -M365GraphScopeId $m365GraphScopeId ` -AzureDevOpsAppId $azureDevOpsAppId ` -AzureDevOpsScopeId $azureDevOpsScopeId ` -AzureServiceMgmtAppId $azureServiceMgmtAppId ` -AzureServiceMgmtScopeId $azureServiceMgmtScopeId | ConvertTo-Json -Depth 10 -Compress $publicClientRedirectUris = @( "http://localhost", "msal${appId}://auth" ) | ConvertTo-Json -Compress # Configure app to match "Azure Node Playground Public". az ad app update ` --id $appId ` --set ` "signInAudience=AzureADMyOrg" ` "isFallbackPublicClient=true" ` "requiredResourceAccess=$requiredResourceAccess" ` "publicClient.redirectUris=$publicClientRedirectUris" ` "web.implicitGrantSettings.enableAccessTokenIssuance=true" ` "web.implicitGrantSettings.enableIdTokenIssuance=true" | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to configure application '$AppName'." } } Write-Host "Created application '$AppName'" Write-Host "appId: $appId"