This commit is contained in:
2026-08-21 00:50:56 -04:00
parent aeedb7170a
commit c30ef6ec24
3 changed files with 57 additions and 24 deletions
@@ -149,40 +149,47 @@ function Get-PersonaGraphStatusCode {
return $null
}
function Test-PersonaTargetAttributeUnavailable {
function Test-PersonaEnumerationRecoverable {
<#
.SYNOPSIS
True when a Graph 400 means the target attribute does not exist in this
tenant, rather than some other client error.
True when a Graph 400 during user enumeration is plausibly caused by the
target attribute not existing in this tenant, and safe to retry without it.
.DESCRIPTION
A dev tenant with no app registration has no persona extension property.
Requesting it in $select then fails with 400 and a message naming the
property. This distinguishes that specific, recoverable case from every
other 400 (bad query syntax, an unrelated bad property, a permission denial
phrased as 400), which must still fail loudly rather than being swallowed.
A dev tenant with no app registration has no persona extension property, and
requesting it in $select then fails with 400. The obvious approach - reading
the offending property name out of the error - does not hold up in practice:
Graph often returns a 400 with an empty body, and Invoke-MgGraphRequest then
reports only "Response status code does not indicate success: BadRequest
(Bad Request)." with no property name and not even a numeric status in the
text (Get-PersonaGraphStatusCode still resolves it, from the structured
Response.StatusCode rather than the message).
So this checks the only two facts actually available: the status was 400,
and the target attribute was one of the properties requested. That is not
proof the attribute is the cause, but confirming it is impossible from what
Graph sends back, and the retry this justifies is cheap, read-only, and
confined to preview mode - if the attribute was not the problem, the retry
fails too and the original error still surfaces untouched.
.PARAMETER ErrorRecord
The error caught from Invoke-PersonaGraphRequest.
The error caught from Get-PersonaUsers.
.PARAMETER TargetAttribute
The configured target attribute name.
.PARAMETER SelectProperties
The $select list the failing request used.
#>
[CmdletBinding()]
[OutputType([bool])]
param(
[Parameter(Mandatory)] $ErrorRecord,
[Parameter(Mandatory)] [string] $TargetAttribute
[Parameter(Mandatory)] [string] $TargetAttribute,
[Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $SelectProperties
)
if ((Get-PersonaGraphStatusCode -ErrorRecord $ErrorRecord) -ne 400) { return $false }
$text = @($ErrorRecord.Exception.Message, $ErrorRecord.ErrorDetails.Message) -join ' '
if ([string]::IsNullOrWhiteSpace($text)) { return $false }
if ($text -notmatch [regex]::Escape($TargetAttribute)) { return $false }
[bool]($text -match '(?i)could not find a property|invalid property|is not a valid property|does not exist on type')
((Get-PersonaGraphStatusCode -ErrorRecord $ErrorRecord) -eq 400) -and ($SelectProperties -ccontains $TargetAttribute)
}
function Get-PersonaRetryAfterMs {
+2 -2
View File
@@ -98,7 +98,7 @@ function Invoke-PersonaEngineRun {
: @(Get-PersonaUsers -SelectProperties $selectProperties)
}
catch {
if ((-not $IsEnforcing) -and (Test-PersonaTargetAttributeUnavailable -ErrorRecord $_ -TargetAttribute $TargetAttribute)) {
if ((-not $IsEnforcing) -and (Test-PersonaEnumerationRecoverable -ErrorRecord $_ -TargetAttribute $TargetAttribute -SelectProperties $selectProperties)) {
# Dev/test tenants often have no app registration yet, so the persona
# extension property was never created. Preview mode never writes
# regardless of what StoredPersona holds, so treating the attribute as
@@ -106,7 +106,7 @@ function Invoke-PersonaEngineRun {
# Enforcement still fails loudly - IsEnforcing gates this precisely
# because writing to an attribute that does not exist must never be
# silently tolerated.
Write-Warning "Target attribute '$TargetAttribute' was not found in this tenant (no app registration / extension property?). Continuing in What-If mode with it treated as null for every account."
Write-Warning "Graph rejected the request with a 400 while '$TargetAttribute' was in the requested properties. Retrying without it and treating it as null for this What-If run - if the retry also fails, the underlying error will be reported."
$fallbackProperties = @($selectProperties | Where-Object { $_ -cne $TargetAttribute })