Legacy employee sync deployment notes
Warning
This earlier plan uses the Az.Accounts module. For the current implementation, follow the current setup guide or the Windows setup guide.
0. Confirm the runtime version
PowerShell 7.6 is currently preview-only and Windows-only (Premium/Dedicated/Consumption). Flex Consumption on Linux currently runs PowerShell 7.4. Check the Function App's FUNCTIONS_WORKER_RUNTIME_VERSION app setting — if it's not ~7.4, this code will still run fine (nothing here is 7.6-specific) but confirm before you deploy so there are no surprises at runtime resolution.
1. Vendor the Az.Accounts module
Flex Consumption does not support PowerShell managed dependencies, so requirements.psd1 alone will not fetch the module.
Run this on a machine with PowerShell 7.4+ and internet access, from the function app root:
Save-Module -Name Az.Accounts -Path ./Modules -Repository PSGallery
This creates ./Modules/Az.Accounts/<version>/.... That folder must be included in the deployment package (zip deploy) alongside host.json, profile.ps1, requirements.psd1, and HttpTrigger/. Final layout:
/
host.json
profile.ps1
requirements.psd1
HttpTrigger/
function.json
run.ps1
Modules/
Az.Accounts/
<version>/...
2. Enable a system-assigned managed identity on the Function App
Portal: Function App → Identity → System assigned → On.
3. Grant that identity two RBAC roles
4. Create and seed the organisationUnit mapping table
Create a second table in the same storage account — OrganisationUnitMappings by default (override with the OuMappingTableName app setting if you want a different name). Every row uses a fixed PartitionKey so the function's lookup is always a single-partition query:
RowKey is the 3-character prefix (uppercase — the function uppercases the incoming value before comparing, so casing in the source data doesn't matter). Add a row per prefix; any organisationunit value that doesn't match one gets logged as a warning and left unmapped rather than written with a blank/null id, so unmapped records are easy to find in the logs without failing the whole batch.
Seed it however's convenient — Storage Explorer's table editor is the quickest for a handful of rows, or Add-AzTableRow-style REST calls if you want it scripted.
5. App settings to configure on the Function App
Neither table is auto-created — create both once via the portal, Storage Explorer, or New-AzStorageTable.
6. Testing the bearer token
Auth is now a straightforward comparison — the ETL sends Authorization: Bearer <secret>, the Function compares it (constant-time) against ciaETLsecret from Key Vault. To sanity check before wiring up the real ETL step:
$uri = 'https://<functionapp>.azurewebsites.net/api/employees/sync'
$secret = 'the-actual-secret-value' # from Key Vault, for local testing only
$body = Get-Content ./sample-payload.xml -Raw
# Should succeed (200) with a summary of upserted/skipped/failed counts
Invoke-RestMethod -Method Post -Uri $uri -Headers @{ Authorization = "Bearer $secret" } -ContentType 'application/xml' -Body $body
# Should fail with 401 — confirms the check actually rejects bad tokens, not just accepts everything
try {
Invoke-RestMethod -Method Post -Uri $uri -Headers @{ Authorization = 'Bearer wrong-secret' } -ContentType 'application/xml' -Body $body
} catch {
$_.Exception.Response.StatusCode
}
If the first call doesn't come back 200, $_.ErrorDetails.Message in a catch block will show the JSON error body (Missing or malformed Authorization header, Invalid token, Could not load organisationUnit mapping table, etc.) rather than a generic HTTP error.
7. Endpoint
POST https://<functionapp>.azurewebsites.net/api/employees/sync (no function key required — the bearer token check is the actual gate; consider still restricting via authLevel + a function key as defense-in-depth if you want belt-and-suspenders).