Skip to content

Fix change-safety dynamicparam forwarding for custom-fronted cmdlets - #1555

Merged
Yabo Hu (VeryEarly) merged 2 commits into
Azure:mainfrom
YangAn-microsoft:fix/change-safety-custom-cmdlet-dynamicparam
Aug 13, 2026
Merged

Fix change-safety dynamicparam forwarding for custom-fronted cmdlets#1555
Yabo Hu (VeryEarly) merged 2 commits into
Azure:mainfrom
YangAn-microsoft:fix/change-safety-custom-cmdlet-dynamicparam

Conversation

@YangAn-microsoft

@YangAn-microsoft YangAn-microsoft commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a gap in the Stage D change-safety generator work (#1549): the outer generated proxy for custom-fronted cmdlets (cmdlets whose public implementation is a hand-written function in custom/ rather than the generated private cmdlet) never emitted a dynamicparam forwarding block. As a result, change-safety parameters (-AcquirePolicyToken / -ChangeReference) declared on the wrapped custom function's own dynamicparam block never surfaced on the public command, and binding them failed with 'A parameter cannot be found that matches parameter name...'.

Root cause

DynamicParamOutput.HasDynamicParameters() only checked whether a variant's compiled cmdlet implemented IDynamicParameters. For a custom-fronted cmdlet, the wrapped command is a PowerShell function, not a compiled cmdlet, so this check always returned false and no forwarding block was emitted on the outer proxy — even though the custom function itself already had its own dynamicparam block (added separately).

Fix

  • HasDynamicParameters() now also inspects function-backed variants: unwraps FunctionInfo.ScriptBlock.Ast (a FunctionDefinitionAst) to its Body (ScriptBlockAst) and checks .DynamicParamBlock != null.
  • The outer proxy's $ExecutionContext.InvokeCommand.GetCommand(...) lookup now resolves both Cmdlet and Function command types so it can reflect a custom function's dynamic parameters, not just a compiled cmdlet's.

Validation — live test evidence

Consuming PR: Azure/azure-powershell#29999 (Az.Cdn). Rebuilt Az.Cdn.private.dll with this fix, regenerated exports/*.ps1, and tested against a real Azure subscription (f758ac53-3e63-4317-a956-0997793808d7, tenant 4f00b3b6-2940-4f2c-b037-94637c180d30) — not mocked/recorded.

Before the fix:

PS> New-AzFrontDoorCdnProfile -ResourceGroupName rg-cs-cdn-e2e -Name fdp-cs-13865 -SkuName Standard_AzureFrontDoor -Location Global -AcquirePolicyToken -Confirm:$false
New-AzFrontDoorCdnProfile: A parameter cannot be found that matches parameter name 'AcquirePolicyToken'.

Confirmed root cause — the outer public proxy had no dynamicparam block at all:

PS> (Get-Command New-AzFrontDoorCdnProfile).ScriptBlock.Ast.Body.DynamicParamBlock -eq $null
True

After the fix:

PS> (Get-Command New-AzFrontDoorCdnProfile).ScriptBlock.Ast.Body.DynamicParamBlock -ne $null
True

PS> New-AzFrontDoorCdnProfile -ResourceGroupName rg-cs-cdn-e2e-v2 -Name csv2profile -SkuName Standard_AzureFrontDoor -Location Global -AcquirePolicyToken -Confirm:$false
SUCCESS: csv2profile

The profile was created successfully end-to-end, confirming -AcquirePolicyToken now binds and the change-safety token acquisition path executes — the same wire behavior already validated for the generated, non-custom cmdlet path (e.g. New-AzFrontDoorCdnEndpoint -AcquirePolicyToken, which already worked before this fix).

Deeper verification — proof the token acquisition pipeline actually fired

Binding the parameter and getting a successful create doesn't, by itself, prove ARM ever received the policy token — the write could succeed either way on a subscription that doesn't mandate it. The AutoRest-generated cmdlet pipeline hook (ContextAdapter.AddChangeSafetyPolicyTokenHandler, azure-powershell side) has no built-in -Debug observability (unlike the SDK/RM cmdlet path), so a temporary, env-var-gated trace was added to PolicyTokenAcquirer.StampPolicyTokenAsync's call site (reverted afterward, verified via clean git status) to get definitive proof:

[PolicyTokenAcquirer] Intercept PUT https://management.azure.com/subscriptions/.../resourceGroups/rg-cs-cdn-debug-test/providers/Microsoft.Cdn/profiles/csdebugtest85182?api-version=2026-04-01-preview
[PolicyTokenAcquirer] Payload prepared.
[PolicyTokenAcquirer] POST acquirePolicyToken https://management.azure.com/subscriptions/.../providers/Microsoft.Authorization/acquirePolicyToken?api-version=2025-03-01
[PolicyTokenAcquirer] Response 200 OK
[PolicyTokenAcquirer] Token acquired and header added.
[PolicyTokenAcquirer] Intercept GET https://management.azure.com/subscriptions/.../operationresults/...  (async operation poll)
[PolicyTokenAcquirer] Skip: verb not allowed for token acquisition.

This confirms: the outgoing PUT was intercepted, a real POST to ARM's acquirePolicyToken endpoint was made and returned 200 OK, the token was stamped onto the request as the x-ms-policy-external-evaluations header, and the subsequent async-poll GET was correctly skipped by the write-verb gate — proving this generator fix results in the dynamic parameter both binding and driving real change-safety behavior, not just silently accepted.

The outer generated proxy for custom-fronted cmdlets (a hand-written
function in custom/ instead of a private cmdlet) never emitted a
dynamicparam forwarding block, so change-safety parameters declared on
the wrapped custom function's own dynamicparam block never surfaced on
the public command.

HasDynamicParameters() now also detects function-backed variants whose
body declares a dynamicparam block (previously only IDynamicParameters
on a compiled cmdlet was checked). The outer proxy's GetCommand lookup
now resolves both Cmdlet and Function command types so it can reflect
the wrapped custom function's dynamic parameters.
YangAn-microsoft added a commit to YangAn-microsoft/azure-powershell that referenced this pull request Aug 12, 2026
…ile cmdlets

Regenerated Az.Cdn with the autorest.powershell generator fix (see
Azure/autorest.powershell#1555): the outer proxy for custom-fronted
write cmdlets (New/Remove/Update-Az{,FrontDoor}CdnProfile) now forwards
the wrapped custom function's dynamicparam block, so -AcquirePolicyToken
and -ChangeReference actually bind on these commands. Previously they
were rejected with 'A parameter cannot be found that matches parameter
name AcquirePolicyToken', even though the module was already opted into
enable-change-safety.

Live-validated: New-AzFrontDoorCdnProfile -AcquirePolicyToken now
successfully creates a profile in a real subscription.
The BuildTime runtime source (PsProxyOutputs.cs) is copied as a shared
resource into every generated test-emitter case, so the previous commit's
fix changes generated/runtime/BuildTime/Models/PsProxyOutputs.cs in all
43 whitelisted cases. Refreshed via EmitterTest.ps1 -AllowList and copied
each case's regenerated PsProxyOutputs.cs over its committed target/
baseline (same process as commit 0ef7785).
@VeryEarly
Yabo Hu (VeryEarly) merged commit 45a4747 into Azure:main Aug 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants