functions/policies/authenticationMethodsPolicies/Export-TmfAuthenticationMethodsPolicy.ps1

<#
.SYNOPSIS
Exports the tenant authentication methods policy into TMF configuration objects or JSON.
.DESCRIPTION
Retrieves the singleton authenticationMethodsPolicy (v1.0 by default; beta when -ForceBeta for fallbacks) and its authenticationMethodConfigurations, converting to the TMF shape. Returns object unless -OutPath is supplied.
.PARAMETER SpecificResources
Optional filter by display name (singleton semantics; rarely needed).
.PARAMETER OutPath
Root folder to write the export. When omitted, object is returned instead of writing files.
.PARAMETER Append
Add content to an existing file
.PARAMETER ForceBeta
Use beta Graph endpoint for initial retrieval (fallback to beta already occurs when v1.0 lacks detail).
.PARAMETER Cmdlet
Internal pipeline parameter; do not supply manually.
.EXAMPLE
Export-TmfAuthenticationMethodsPolicy -OutPath C:\temp\tmf
.EXAMPLE
Export-TmfAuthenticationMethodsPolicy | ConvertTo-Json -Depth 15
#>

function Export-TmfAuthenticationMethodsPolicy {

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueSwitchParameter")]

    [CmdletBinding()] param(
        [string[]] $SpecificResources,
        [Alias('OutPutPath')] [string] $OutPath,
        [switch] $Append,
        [switch] $ForceBeta = $true,
        [System.Management.Automation.PSCmdlet] $Cmdlet = $PSCmdlet
    )

    begin {
        Test-GraphConnection -Cmdlet $Cmdlet
        $resourceName = 'authenticationMethodsPolicies'
        $parentName = 'policies'
  
        function Convert-AuthenticationMethodsPolicy {
            param(
                [Parameter(Mandatory)] [object] $policy
            )

            $obj = [ordered]@{ present = $true }

            if ($policy.PSObject.Members.Match('displayName') -and $null -ne $policy.displayName -and $policy.displayName -ne '') {
                $obj.displayName = $policy.displayName
            }

            # registrationEnforcement (keep as-is if present)
            if ($policy.PSObject.Members.Match('registrationEnforcement') -and $null -ne $policy.registrationEnforcement) {
                $obj.registrationEnforcement = $policy.registrationEnforcement
            }

            # authenticationMethodConfigurations
            if ($policy.PSObject.Members.Match('authenticationMethodConfigurations') -and $null -ne $policy.authenticationMethodConfigurations) {
                $converted = @()
                foreach ($cfg in $policy.authenticationMethodConfigurations) {
                    if ($null -eq $cfg) {
                        continue
                    }
                    $entry = [ordered]@{}
                    # Always keep id
                    if ($cfg.PSObject.Members.Match('id') -and $null -ne $cfg.id) {
                        $entry.id = $cfg.id
                    }

                    # Known common properties across methods
                    foreach ($p in @('state', 'isSelfServiceRegistrationAllowed', 'isAttestationEnforced', 'defaultLifetimeInMinutes', 'defaultLength', 'minimumLifetimeInMinutes', 'maximumLifetimeInMinutes', 'isUsableOnce', 'allowExternalIdToUseEmailOtp', 'certificateUserBindings', 'authenticationModeConfiguration')) {
                        if ($cfg.PSObject.Members.Match($p) -and $null -ne $cfg.$p) {
                            $entry[$p] = $cfg.$p
                        }
                    }

                    # Copy any remaining note properties (excluding @odata.type and id) not already set
                    $noteProps = ($cfg | Get-Member -MemberType NoteProperty).Name
                    foreach ($m in $noteProps) {
                        if ($m -in '@odata.type', 'id') {
                            continue
                        }
                        if (-not $entry.Contains($m)) {
                            $entry[$m] = $cfg.$m
                        }
                    }

                    if ($entry.Count -gt 0) {
                        $converted += [pscustomobject]$entry
                    }
                }
                if ($converted.Count -gt 0) {
                    $obj.authenticationMethodConfigurations = $converted
                }
            }

            return [pscustomobject]$obj
        }
    }
    process {
        # Fetch singleton from v1.0 with expanded configurations
        $graphBase = if ($ForceBeta) {
            $script:graphBaseUrl
        } else {
            $script:graphBaseUrl1
        }
        try {
            $policy = Invoke-MgGraphRequest -Method GET -Uri ("$graphBase/policies/authenticationMethodsPolicy?`$expand=authenticationMethodConfigurations")
        } catch {
            throw $_
        }

        if (-not $policy) {
            return @()
        }

        # Build from IDs on the policy response to fetch full properties for each configuration
        if ($policy.PSObject.Members.Match('authenticationMethodConfigurations') -and $null -ne $policy.authenticationMethodConfigurations) {
            $hasProps = $false
            foreach ($itm in $policy.authenticationMethodConfigurations) {
                if ($null -eq $itm) {
                    continue
                }
                $names = (($itm | Get-Member -MemberType NoteProperty).Name | Where-Object { $_ -ne 'id' -and $_ -ne '@odata.type' })
                if ($names.Count -gt 0) {
                    $hasProps = $true; break
                }
            }

            if (-not $hasProps) {
                $ids = @()
                foreach ($item in $policy.authenticationMethodConfigurations) {
                    if ($null -eq $item) {
                        continue
                    }
                    if ($item -is [string]) {
                        $ids += $item; continue
                    }
                    if ($item.PSObject.Members.Match('id') -and $null -ne $item.id -and $item.id -ne '') {
                        $ids += $item.id
                    }
                }
                $ids = $ids | Where-Object { $_ } | Select-Object -Unique

                if ($ids.Count -gt 0) {
                    $full = @()
                    foreach ($id in $ids) {
                        try {
                            $cfg = Invoke-MgGraphRequest -Method GET -Uri ("$script:graphBaseUrl1/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/$id")
                            if ($cfg) {
                                $full += $cfg
                            }
                        } catch {
                            Write-PSFMessage -Level Verbose -FunctionName 'Export-TmfAuthenticationMethodsPolicy' -Message "Skipping configuration $id due to error: $_"
                        }
                    }

                    # If v1.0 per-id fetch still returns id-only, try beta per-id as a last resort (when allowed)
                    $needsBeta = $false
                    if ($full.Count -gt 0) {
                        $allIdOnly = $true
                        foreach ($cfg in $full) {
                            if ($null -eq $cfg) {
                                continue
                            }
                            $pn = (($cfg | Get-Member -MemberType NoteProperty).Name | Where-Object { $_ -ne 'id' -and $_ -ne '@odata.type' })
                            if ($pn.Count -gt 0) {
                                $allIdOnly = $false; break
                            }
                        }
                        if ($allIdOnly) {
                            $needsBeta = $true
                        }
                    } else {
                        $needsBeta = $true
                    }

                    if ($needsBeta) {
                        $betaFull = @()
                        foreach ($id in $ids) {
                            try {
                                $cfgB = Invoke-MgGraphRequest -Method GET -Uri ("$script:graphBaseUrl/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/$id")
                                if ($cfgB) {
                                    $betaFull += $cfgB
                                }
                            } catch {
                                Write-PSFMessage -Level Verbose -FunctionName 'Export-TmfAuthenticationMethodsPolicy' -Message "Skipping configuration $id (beta) due to error: $_"
                            }
                        }
                        if ($betaFull.Count -gt 0) {
                            $full = $betaFull
                        }
                    }

                    if ($full.Count -gt 0) {
                        $policy.authenticationMethodConfigurations = $full
                    }
                }
            }
        }

        $exportObject = Convert-AuthenticationMethodsPolicy -policy $policy

        # Optional filtering by display name (singleton semantics)
        if ($SpecificResources -and ($SpecificResources -notcontains $exportObject.displayName) -and ($SpecificResources -notcontains '*')) {
            # Nothing to export if filter doesn't match
            if (-not $OutPutPath) {
                return @()
            } else {
                return
            }
        }

        if (-not $OutPath) {
            return @($exportObject)
        }
    }
    end {
        Write-PSFMessage -Level Verbose -FunctionName 'Export-TmfAuthenticationMethodsPolicy' -Message "Exporting authentication methods policy. ForceBeta=$ForceBeta"
        if (-not $OutPath) {
            return @($exportObject)
        }
        if ($exportObject) {
            if ($Append) {
                Write-TmfExportFile -OutPath $OutPath -ParentPath $parentName -ResourceName $resourceName -Data @($exportObject) -Append
            }
            else {
                Write-TmfExportFile -OutPath $OutPath -ParentPath $parentName -ResourceName $resourceName -Data @($exportObject)
            }
        }
    }
}