Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion setup/docs/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ Select the failed stage and keep existing resources. Before rerunning, use
| --- | --- |
| Account missing or wrong tenant | [Account picker](#the-expected-cached-account-or-subscription-is-missing), [directory context](#azure-cli-directory-context-does-not-match-the-selected-tenant) |
| Permission or prerequisite failure | [Preflight](#authentication-permission-or-runtime-preflight-fails), [Graph 403](#az-ad-or-az-rest-returns-403-forbidden), [PrincipalNotFound](#principalnotfound-for-the-azure-operator) |
| New or existing app reports an unsupported provider-tenant restriction | [Unrestricted application audience](#new-app-fails-with-an-unsupported-provider-tenant-restriction) |
| Region or hosting rejected | [Tenant scope and EU recommendations](#tenant-scope-discovery-or-eu-region-recommendations-fail), [quota](#deployment-fails-with-subscriptionisoverquotaforsku), [plan selection](#service-plan-selection-or-availability-fails), [FC1 object-reference error](#fc1-deployment-preflight-reports-an-object-reference-error), [EP1 location check](#appservice-list-locations-rejects-ep1) |
| Certificate or resource registration fails | [Certificate selection](#certificate-selection-reuse-or-cleanup-fails), [resource providers](#a-required-azure-resource-provider-is-not-registered) |
| Download, build, or parser failure | [Old package prompts](#setup-still-asks-for-packageurl-or-packagesha256), [checksum/download](#a-checksum-or-package-download-fails), [.NET](#net-build-fails), [Python](#python-remote-build-fails), [CLI JSON](#azure-cli-warnings-break-json-parsing) |
| Deployment stops after approval | [Publication and startup](#deployment-stops-after-approval) |
| Deployment stops after approval | [Combined Azure CLI paths](#azure-cli-paths-are-combined-during-deployment), [publication and startup](#deployment-stops-after-approval) |
| Endpoint rejects calls, does not deliver, or has no logs | [After-deployment triage](#after-deployment-triage), [401/live delivery](#the-endpoint-returns-401-or-live-delivery-fails), [credential acquisition](#provider-credential-acquisition-fails) |
| Optional regional routing fails | [Front Door checks](#optional-front-door-deployments) |

Expand Down Expand Up @@ -275,6 +276,25 @@ and clears temporary build settings.

After build or startup failure, setup closes public ingress. If that fails, inspect it immediately.

## Azure CLI paths are combined during deployment

**Symptom:** `Invoke-EppAz` reports an `EndInvoke` failure because a command such as
`/usr/bin/az /bin/az` is not recognized, even though earlier Azure CLI operations succeeded.

**Cause:** PowerShell can discover multiple Azure CLI paths. Older animated progress runners
passed all discovered paths to the worker as one command instead of selecting the first match.
The static status path does not have this problem.

**Fix:** use a setup source revision containing the command-resolution fix. The corrected runner
selects the first discovered executable, preserving its path and argument boundaries. Do not
reinstall Azure CLI, edit `PATH`, or change hosting plans to work around this error.

**Temporary workaround:** save the current `$ProgressPreference`, set it to `SilentlyContinue`,
and rerun the same reviewed launcher revision with the original deployment inputs. Restore the
previous preference afterward. This disables only progress animation, not deployment approval,
validation, or error reporting. Reuse the printed `-ApplicationId` and original `-ResourcePrefix`;
do not create another app or delete partially created resources.

## Azure CLI warnings break JSON parsing

**Fix:** refresh the reviewed launcher/source revision.
Expand All @@ -296,6 +316,29 @@ After build or startup failure, setup closes public ingress. If that fails, insp
stop; setup does not silently allow all tenants. Unattended runs still need the user session
and separate `-ApproveDeployment`.

## New app fails with an unsupported provider-tenant restriction

**Symptom:** setup creates the application and prints **Resolved Azure resource names**, then
stops with `The application has an unsupported provider-tenant restriction`. This can also
affect provider selection or preflight for an existing registration.

**Cause:** Microsoft Graph can return a
[`unrestrictedAudience`](https://learn.microsoft.com/graph/api/resources/unrestrictedaudience?view=graph-rest-beta)
object with `kind: unrestricted` instead of a null `signInAudienceRestrictions` value.
This is a valid unconfigured state; a single-tenant app still permits only its home tenant.
Older helpers accepted only null or an `allowedTenants` restriction and rejected this response.

**Recover:** use a setup source revision containing the fix. The launcher downloads its helpers
from `-SourceRepository` / `-SourceRef` (`main` by default), so an old pinned revision or a local
helper edit alone does not update the downloaded code. Rerun with the original `-ApplicationId`
and `-ResourcePrefix`, retaining the other deployment inputs. For a setup-created app, use the
client ID printed before the error or saved in `application-<client-id>.json`. Do not create
another registration, delete resources, or manually broaden tenant access to bypass the check.

The corrected helper recognizes the unrestricted state but still requires the approved provider
allowlist before successful deployment. Unknown or inconsistent restrictions and changes made
after plan approval still stop setup.

## `az ad` or `az rest` returns 403 Forbidden

Microsoft Graph requests use the Azure CLI user session. Verify that it belongs to the selected
Expand Down
26 changes: 23 additions & 3 deletions setup/support/Epp.Setup.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ function Invoke-EppAz {
$errorPath = Join-Path ([IO.Path]::GetTempPath()) "epp-az-$([Guid]::NewGuid().ToString('N')).stderr"
try {
if ($Activity -and (Test-EppAnimationEnabled)) {
$command = Get-Command az -CommandType Application, ExternalScript -ErrorAction Stop
$command = Get-Command az -CommandType Application, ExternalScript -ErrorAction Stop | Select-Object -First 1
$worker = [PowerShell]::Create()
$line = ''
try {
Expand Down Expand Up @@ -971,9 +971,29 @@ function Get-EppProviderRestrictionState {
param($Restriction)

if ($null -eq $Restriction) { return $null }
if ($Restriction -isnot [Collections.IDictionary] -or $Restriction['kind'] -cne 'allowedTenants' -or
$unsupported = 'The application has an unsupported provider-tenant restriction. Review it before running setup.'
if ($Restriction -isnot [Collections.IDictionary]) { throw $unsupported }
$kind = $Restriction['kind']
$odataType = $Restriction['@odata.type']
if (($null -ne $kind -and $kind -isnot [string]) -or
($null -ne $odataType -and $odataType -isnot [string])) { throw $unsupported }
if ($null -ne $odataType) {
$typeKind = switch -CaseSensitive ($odataType) {
'#microsoft.graph.unrestrictedAudience' { 'unrestricted' }
'#microsoft.graph.allowedTenantsAudience' { 'allowedTenants' }
default { throw $unsupported }
}
if ($null -ne $kind -and $kind -cne $typeKind) { throw $unsupported }
$kind = $typeKind
}
if ($kind -ceq 'unrestricted') {
if ($Restriction.Contains('allowedTenantIds') -or $Restriction.Contains('isHomeTenantAllowed')) { throw $unsupported }
# The pre-creation plan uses null for this same unconfigured state.
return $null
}
if ($kind -cne 'allowedTenants' -or
$Restriction['isHomeTenantAllowed'] -isnot [bool] -or $Restriction['allowedTenantIds'] -isnot [Array]) {
throw 'The application has an unsupported provider-tenant restriction. Review it before running setup.'
throw $unsupported
}
$ids = @($Restriction.allowedTenantIds | ForEach-Object { ConvertTo-EppGuid ([string]$_) })
$unique = @($ids | Sort-Object -Unique)
Expand Down
16 changes: 16 additions & 0 deletions setup/tests/Graph.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,22 @@ try {
Assert-Throws { New-EppApplication -Inputs $inputs -DisplayName $newApplication.displayName } 'Authorization_RequestDenied'
Assert ($script:Calls.Count -eq 1) 'Surface creation failures without retrying a non-idempotent POST.'

Reset-TestState
$script:Responses.Enqueue(@{
id = $script:ObjectId; signInAudience = 'AzureADMyOrg'
signInAudienceRestrictions = @{
'@odata.type' = '#microsoft.graph.unrestrictedAudience'
kind = 'unrestricted'
}
})
$restriction = Get-EppSignInAudienceRestrictions -Inputs $inputs -ApplicationObjectId $script:ObjectId
Assert ($script:Calls.Count -eq 1 -and $script:Calls[0].Method -ceq 'get' -and
$script:Calls[0].Url -ceq "https://graph.microsoft.com/beta/applications/$script:ObjectId" -and
$script:Calls[0].Query['$select'] -ceq 'id,signInAudience,signInAudienceRestrictions') 'Read the actual beta audience-restriction response for the selected application.'
Assert ($restriction -is [Collections.IDictionary] -and $restriction.kind -ceq 'unrestricted') 'Preserve the explicit unrestricted response through CLI JSON parsing.'
Assert ($null -eq (Get-EppProviderRestrictionState -Restriction $restriction)) 'Treat an unrestricted Graph response as unconfigured, not unsupported.'
foreach ($path in $script:Files) { Assert (-not (Test-Path -LiteralPath $path)) 'Remove temporary Graph query files.' }

Reset-TestState
Set-EppProviderTenantRestriction -Inputs $inputs -ApplicationObjectId $script:ObjectId -ProviderTenantId $script:User
Assert ($script:Calls[0].Method -ceq 'patch' -and $script:Calls[0].Url -like '*/beta/applications/*') 'Use beta only for the allowed-tenants preview.'
Expand Down
13 changes: 11 additions & 2 deletions setup/tests/Progress.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ exit 0
Assert (-not (Test-EppAnimationEnabled)) 'Do not animate redirected output.'
}
$script:FakeAz = $FakeAz
$script:CommandSources = @($FakeAz)
$script:Animate = $true
$script:Messages = [Collections.Generic.List[string]]::new()
$script:Warnings = [Collections.Generic.List[string]]::new()
Expand All @@ -60,7 +61,7 @@ exit 0
function script:Get-Command {
param($Name, $CommandType, $ErrorAction)
Assert ($Name -ceq 'az') 'Resolve only the Azure CLI command.'
return @{ Source = $script:FakeAz }
foreach ($source in $script:CommandSources) { @{ Source = $source } }
}
function script:az {
& $script:FakeAz @args
Expand Down Expand Up @@ -108,6 +109,14 @@ exit 0
Assert ([string]::IsNullOrEmpty((Invoke-EppAz -Activity 'Starting the Azure Function' empty))) 'Support successful commands with no stdout.'
}
$script:Animate = $true
$script:CommandSources = @($FakeAz, (Join-Path (Split-Path -Parent $FakeAz) 'unused az.ps1'))
$script:Messages.Clear()
$output = Invoke-EppAz -Activity 'Deploying Azure resources' success --parameters '@path with spaces & symbols.json' --output json
Assert ((($output | ConvertFrom-Json).arguments -join '|') -ceq 'success|--parameters|@path with spaces & symbols.json|--output|json|--only-show-errors') 'When command discovery returns multiple Azure CLI paths, invoke only the first and preserve argument boundaries.'
Assert ($script:Messages[-1] -match '^\r +\r$') 'Clear the indicator after choosing the first CLI path.'
Assert-Throws { Invoke-EppAz -Activity 'Deploying Azure resources' failure --output none } 'failed \(exit 7\)'
Assert ($script:Messages[-1] -match '^\r +\r$') 'Multiple discovered paths must not change CLI error propagation or indicator cleanup.'
$script:CommandSources = @($FakeAz)
$script:Messages.Clear()
$output = Invoke-EppAz success --output json
Assert ((($output | ConvertFrom-Json).arguments -join '|') -ceq 'success|--output|json|--only-show-errors') 'An omitted activity must not consume any native CLI argument.'
Expand All @@ -121,7 +130,7 @@ exit 0
Assert (-not (Test-Path -LiteralPath $path)) 'Remove captured CLI stderr files after success or failure.'
}
} $fakeCommand
Write-Host 'Deployment animation, argument preservation, stdout/stderr handling, redaction, failure, cancellation, and cleanup checks passed.'
Write-Host 'Deployment animation, command precedence, argument preservation, stdout/stderr handling, redaction, failure, cancellation, and cleanup checks passed.'
}
finally {
Remove-Module -ModuleInfo $module -Force
Expand Down
Loading
Loading