Files
sk-az-tools/scripts/New-PublicClientApplication.ps1

171 lines
5.1 KiB
PowerShell

#!/usr/bin/env pwsh
[CmdletBinding()]
param(
[Alias("n")]
[string]$AppName,
[Alias("h")]
[switch]$Help
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Show-Usage {
Write-Host "Usage: ./New-PublicClientApplication.ps1 -AppName <name>"
Write-Host "Options:"
Write-Host " -AppName, -n <name> Application display name (required)"
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 (-not (Get-Command az -ErrorAction SilentlyContinue)) {
throw "Azure CLI 'az' is required."
}
# 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)) {
$confirmation = Read-Host "Application '$AppName' already exists. Update it? [y/N]"
if ($confirmation -notmatch '^(?i:y|yes)$') {
Write-Host "Canceled."
exit 0
}
$appId = $existingAppId
} else {
# 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
$requiredResourceAccessFile = [System.IO.Path]::GetTempFileName()
Set-Content -Path $requiredResourceAccessFile -Value $requiredResourceAccess -NoNewline
# Configure app to match "Azure Node Playground Public".
az ad app update `
--id $appId `
--sign-in-audience AzureADMyOrg `
--is-fallback-public-client true `
--required-resource-accesses "@$requiredResourceAccessFile" `
--public-client-redirect-uris "http://localhost" "msal${appId}://auth" `
--enable-access-token-issuance true `
--enable-id-token-issuance true | Out-Null
if ($LASTEXITCODE -ne 0) {
Remove-Item -Path $requiredResourceAccessFile -Force -ErrorAction SilentlyContinue
throw "Failed to configure application '$AppName'."
}
Remove-Item -Path $requiredResourceAccessFile -Force -ErrorAction SilentlyContinue
# Azure CLI is used to grant admin consent.
# Ensure service principal exists before granting tenant-wide admin consent.
az ad sp create --id $appId | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to ensure service principal exists for '$AppName' ($appId)."
}
# Grant admin consent for configured delegated permissions.
az ad app permission admin-consent --id $appId | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Failed to grant admin consent for '$AppName' ($appId)."
}
$tenantId = az account show --query tenantId -o tsv
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($tenantId)) {
throw "Failed to resolve tenantId from current Azure CLI context."
}
if ([string]::IsNullOrWhiteSpace($existingAppId)) {
Write-Host "Created application '$AppName'"
} else {
Write-Host "Updated application '$AppName'"
}
Write-Host "appId: $appId"
$configTemplate = @"
export const config = {
"appName": "$AppName",
"tenantId": "$tenantId",
"clientId": "$appId"
};
"@
Write-Output $configTemplate