Subchapter 42.34
references/services/functions/consumption-to-flex/windows/automation/app-settings.mdMarkdown12 KBView on GitHub
🔒 Security: App settings contain secrets (connection strings, storage keys, API keys). The skill first reviews setting names only. If the user chooses to copy settings, a local pipeline streams values directly from the source ARM endpoint to the new app’s ARM endpoint via process memory only — no temp file is ever written to disk.
Phase 1 — Skill reviews setting names (no values)
az functionapp config appsettings list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[].name" -o tsv⚠️ Skill instruction: Confirm none of the names above look surprising before proceeding — the 4c filter already strips Flex-deprecated names. Cross-reference Deprecated Settings if anything is unfamiliar. Do NOT request values.
Linux app-setting names cannot contain
:. For every surviving source name containing:, show the user the planned:→__mapping before the copy decision (for example,FunctionsHostingConfig:CustomSetting→FunctionsHostingConfig__CustomSetting). .NET configuration maps__back to:on Linux. For other runtimes, record the rename for application-specific validation.Before reading values, compare mapped source names case-insensitively. If two source names map to the same target name, stop and use
ask_user; never silently choose between them. An existing target name is not a collision: after the user approves the mapping, the source value replaces it using the same idempotent merge behavior as an unchanged setting name.Surface this once as a post-migration follow-up (do not block 4c on it): connection strings or keys for Azure services (Storage, Service Bus, Event Hubs, Cosmos DB, Key Vault) can be replaced with identity-based connections (opens in a new tab) for better security — see the identities-instead-of-secrets tutorial (opens in a new tab). The user owns the conversion (code + role assignments), and it only applies to Azure services that support Microsoft Entra ID auth — settings for third-party services stay as-is.
This optional follow-up doesn’t override storage authentication requirements. If host or deployment storage prohibits Shared Key authentication, Step 4b must remain blocked until identity-first provisioning is verified; don’t defer that requirement until after migration.
Decision — Copy or defer source app settings
Before reading any setting values or changing the target, use ask_user:
Should I copy the filtered source app settings to
<NEW_APP_NAME>now?
- Copy filtered settings — preserves application configuration, but may connect the target code to the same production dependencies after deployment. Target-owned storage and Application Insights settings remain unchanged.
- Defer source settings — leaves only target-generated settings in place so you can configure isolated or target-specific dependencies before deployment.
Record the choice in <UPGRADE_DIR>/upgrade-status.md.
Phase 2 — Copy locally (values never leave the terminal, never touch disk)
The az functionapp config appsettings set CLI surface only accepts KEY=VALUE pairs or @file.json, so PATCH the underlying ARM endpoint directly via az rest. Fetch both apps’ settings, filter the source settings, and merge them into the target’s existing dictionary. This preserves target-owned settings created by Step 4b, including AzureWebJobsStorage, DEPLOYMENT_STORAGE_CONNECTION_STRING, and the target-specific APPLICATIONINSIGHTS_CONNECTION_STRING.
The deny list and storage prefix are normalized to lowercase at runtime, so source apps with non-canonical key casing (e.g. azurewebjobsstorage) are stripped correctly and future contributors can add new deny-list entries in whichever casing reads best. Surviving source names replace every : with __ before merging.
Bash:
(
set -e
trap 'unset SOURCE_SETTINGS TARGET_SETTINGS BODY' EXIT
SUB=$(az account show --query id -o tsv)
API='2024-04-01'
SOURCE_SETTINGS=$(az rest --method post --uri "/subscriptions/$SUB/resourceGroups/<SOURCE_RESOURCE_GROUP>/providers/Microsoft.Web/sites/<SOURCE_APP_NAME>/config/appsettings/list?api-version=$API")
TARGET_SETTINGS=$(az rest --method post --uri "/subscriptions/$SUB/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings/list?api-version=$API")
BODY=$(printf '%s\n%s\n' "$SOURCE_SETTINGS" "$TARGET_SETTINGS" | jq -s '
(["WEBSITE_CONTENTAZUREFILECONNECTIONSTRING","WEBSITE_CONTENTSHARE","WEBSITE_CONTENTOVERVNET","WEBSITE_SKIP_CONTENTSHARE_VALIDATION","WEBSITE_RUN_FROM_PACKAGE","WEBSITE_RUN_FROM_ZIP","WEBSITE_USE_ZIP","FUNCTIONS_EXTENSION_VERSION","FUNCTIONS_WORKER_RUNTIME","FUNCTIONS_WORKER_RUNTIME_VERSION","FUNCTIONS_MAX_HTTP_CONCURRENCY","FUNCTIONS_WORKER_PROCESS_COUNT","FUNCTIONS_WORKER_DYNAMIC_CONCURRENCY_ENABLED","WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT","WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED","WEBSITE_LOAD_CERTIFICATES","WEBSITE_MOUNT_ENABLED","ENABLE_ORYX_BUILD","SCM_DO_BUILD_DURING_DEPLOYMENT","WEBSITE_DNS_SERVER","WEBSITE_NODE_DEFAULT_VERSION","WEBSITE_VNET_ROUTE_ALL","AzureWebJobsDashboard","APPLICATIONINSIGHTS_CONNECTION_STRING"] | map(ascii_downcase)) as $deny |
(.[0].properties | to_entries | map(select((.key | ascii_downcase) as $k | ($deny | index($k) | not) and ($k | startswith("azurewebjobsstorage") | not))) | map(. + {targetKey: (.key | gsub(":"; "__"))})) as $mapped |
($mapped | group_by(.targetKey | ascii_downcase) | map(select(length > 1) | map(.key))) as $sourceCollisions |
if ($sourceCollisions | length) > 0
then error("Mapped app-setting name collision; review setting names before retrying.")
else {properties: (.[1].properties + ($mapped | map({key: .targetKey, value: .value}) | from_entries))}
end')
az rest --method patch --uri "/subscriptions/$SUB/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings?api-version=$API" --headers 'Content-Type=application/json' --body "$BODY" --output none
)💡 App Insights:
APPLICATIONINSIGHTS_CONNECTION_STRINGis intentionally excluded to preserve the target-specific value created by Step 4b. Step 4k verifies monitoring and preserves AAD ingestion only when the source already used it.
💡 Storage (
AzureWebJobsStorage*): Every setting whose name starts withAzureWebJobsStorageis intentionally excluded, matching the published migration filter. Preserve the target storage configuration verified by Step 4b, whether connection-string-based or identity-based. When converting a permitted connection-string target, 4b.opt re-derives identity settings against<STORAGE_NAME>; don’t copy source storage settings or downgrade a user-provisioned identity-based target.
💡 Casing is preserved on the wire: The
ascii_downcasestep is comparison-only. Surviving settings keep the exact key casing the source app had — they’re PATCHed back unchanged.
PowerShell 7:
$sourceJson = $targetJson = $sourceSettings = $targetSettings = $body = $accessToken = $secureToken = $null
try {
$sub = az account show --query id -o tsv
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sub)) { throw 'Failed to resolve the current subscription.' }
$api = '2024-04-01'
🔒 No file or secret-bearing command argument: Unlike a
--settings @file.jsonflow, nothing here touches disk. PowerShell sends the JSON body withInvoke-RestMethod, avoiding native command-line JSON escaping and keeping app-setting values out of process arguments on Windows, Linux, and macOS. Secret-bearing variables and the ARM access token are cleared whether the PATCH succeeds or fails. If the PATCH fails, re-run the entire block; both GETs and the merged PATCH are idempotent.If Azure returns extended error
04072(AppSetting with name ... is not allowed), inspect the rejected name for Linux-invalid characters. A:rejection does not prove that its prefix is reserved; map:to__, rerun collision checks, and retry the full block.