Only Step 2 is scripted. Register the customer application manually, run one downloaded PowerShell script to deploy the endpoint, and activate policy manually after validation.
The customer does not clone this repository or download Bicep/support scripts separately.
Setup-Epp.ps1 retrieves those files and the selected provider's JSON from GitHub.
Choose SMS or voice, a Global or EU tenant scope, Telesign or Soprano, and an
Azure Function platform: Node.js, .NET, or Python. By default, setup resolves the source repository's latest stable
epp-packages-* release produced by CI. A private test branch can use its matching fork release.
There is no package URL or checksum to enter. Setup verifies SHA256SUMS.txt automatically and
performs the required build and publication for the selected language.
Provider profiles contain complete channel/region route objects. Telesign contains its supplied route URLs, tenant, authentication, and timings. Soprano contains its provider tenant, Global/EU routes, API application ID, scope, authentication, and timings. The provider files contain the complete deployment contract.
To test unpublished upstream changes, publish them to a public fork with a matching stable package
release, then use -SourceRepository <owner/repository> and
-SourceRef <branch-or-full-commit-sha>. Both options must identify the same source as the downloaded
launcher. Use -PackageReleaseTag if the fork contains more than one stable package release.
Unpublished worktree changes are not downloadable from GitHub.
Use a dedicated nonproduction tenant/subscription for the first deployment.
- In the customer tenant's Microsoft Entra admin center > App registrations, register a dedicated organizational application. No redirect URI, client secret, API permission, app role, or enterprise-application configuration is required manually.
- Record the Directory (tenant) ID and Application (client) ID. The script requires the client ID, not the application's object ID, and will not create a replacement registration.
- Complete provider purchase, account/sender registration, and onboarding for the selected adapter.
Telesign uses
telesign-api-keyandtelesign-customer-idin Key Vault. Soprano uses OAuth client-assertion exchange with the selected provider tenant/scope/application ID. Setup does not grant provider API consent or application roles.
After the single Step 2 approval, PowerShell makes the dedicated app organizational multi-tenant,
restricts it through the Entra allowed-tenants preview to its home tenant plus the selected provider
tenant from the provider JSON,
adds the Epp.Invoke application permission, creates/reuses its enterprise application, requires
assignment, creates/reuses the Microsoft phone-provider service principal, and assigns Epp.Invoke.
It also grants that Microsoft service principal tenant-wide Microsoft Graph Application.Read.All,
adds the hostname-based identifier URI and public JWE encryption certificate, and configures Easy
Auth to allow only the Microsoft phone-provider application. Soprano additionally creates the
disclosed outbound managed-identity federated credential.
- Windows with PowerShell 7+. Certificate generation/reuse uses the current user's Windows certificate store; this is not an Azure Cloud Shell or Linux customer deployment script.
- Azure CLI 2.48.1+ on
PATH, with access to GitHub, Azure, Microsoft Graph, and Key Vault. Setup installs the Azure CLI Bicep component after confirmation when it is missing. Azure CLI itself must be installed before running the script. Python additionally needs network access to SCM. - Microsoft Graph PowerShell modules
Microsoft.Graph.AuthenticationandMicrosoft.Graph.Applications. Setup installs missing 2.x+ modules from PSGallery for CurrentUser after a separate confirmation. - An Azure user account permitted to deploy at subscription scope, create the listed resources, and create the scoped Azure role assignments.
- A Microsoft Entra Privileged Role Administrator for granting the Microsoft first-party service
principal Graph
Application.Read.All, plus delegated Graph scopesUser.Read,Application.ReadWrite.All,Application.Read.All, andAppRoleAssignment.ReadWrite.All.User.Readis for the setup operator's/melookup; it is not granted to the first-party service principal or the endpoint app. - Microsoft Graph beta access for the Entra
signInAudienceRestrictionsallowed-tenants preview. The selected provider tenant is allowed in addition to the app's home tenant, which Entra always allows. - Linux Premium EP1 available in the chosen region. Setup registers missing required Azure
resource providers automatically after the single approval. The Azure account needs the
providers' subscription-scoped
/register/actionpermission (included in Contributor/Owner). - .NET selection only: install the .NET 8 SDK and allow NuGet access. Setup runs
dotnet publishautomatically forlinux-x64, packages the publish output, and deploys it. No manual build step or upload is required. JavaScript and Python do not require this SDK. - Python selection: Azure performs the Linux dependency build. No local Python, pip, or Windows dependency installation is needed. The source archive is never used directly as run-from-package.
| Choice | Azure runtime | Automatic deployment path |
|---|---|---|
| JavaScript | Node.js 22, Functions v4 | Verify and publish the ready ZIP with its production dependencies |
| .NET | .NET 8 isolated, Functions v4 | Verify source ZIP, publish for Linux with .NET 8, repackage and publish |
| Python | Python 3.11, Functions v4 | Verify source ZIP, request Azure remote build, validate/download built output, publish that output |
Package hashes are still checked; removing the customer prompt does not disable integrity verification. Source and deployed-package hashes are recorded separately when a build changes the bytes.
Setup normally detects these automatically. For unattended execution, allow installation explicitly:
.\Setup-Epp.ps1 -NonInteractive -InstallPrerequisites ...Install Azure CLI through its official installation instructions if necessary. Setup checks the
explicitly supplied subscription and tenant without changing the CLI's selected subscription. If no
matching Azure user session exists, it runs az login --tenant <tenant-id>. It separately requests
Graph sign-in before displaying the plan if the delegated session is missing required scopes,
including User.Read for operator identity readback. The
consent includes broad app-role-management scopes because the approved deployment grants
Application.Read.All to the Microsoft phone-provider service principal. Authentication, module
installation, Bicep installation, MFA, and consent prompts are not resource-creation approvals.
Use -ForceAuthentication when the machine has ambiguous cached identities. It requires interactive
device-code authentication for Azure CLI and Microsoft Graph, does not clear shared token caches,
and cannot be combined with -NonInteractive. Azure RBAC always uses the selected ARM token's
validated oid; Graph /me is tracked separately for application-management operations.
Download and inspect Setup-Epp.ps1, or save it from the upstream raw URL:
Invoke-WebRequest `
-Uri 'https://raw.githubusercontent.com/Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample/main/setup/Setup-Epp.ps1' `
-OutFile .\Setup-Epp.ps1
.\Setup-Epp.ps1Force explicit account selection when testing on a shared or multi-account computer:
.\Setup-Epp.ps1 -ForceAuthenticationThe flow is:
- Collect missing customer inputs: tenant, subscription, existing application client ID, Azure region, and resource prefix. Supplied values are reused without prompts. Credentials are never requested as ordinary string parameters.
- Choose SMS or voice, then the Global or EU tenant scope.
- Choose a provider, then an Azure Function platform: Node.js, .NET, or Python. Setup downloads the provider JSON,
resolves one complete route containing endpoint, authentication, app-ID/scope
when applicable, timeout, and retry interval. Explicit test values are allowed, shown as test
configuration, and passed to Azure settings. It also resolves the latest stable CI package
release and looks up the language asset name in
packages/catalog.json; there are noPackageUrlorPackageSha256inputs. Use-PackageReleaseTag epp-packages-<run>-<attempt>to pin a previous CI release. Malformed or disabled profiles still fail before resource creation. - Enter a resource prefix, such as
contoso. All resources created by the script start with this prefix. Use 2-8 lowercase letters or digits, starting with a letter. Every top-level resource name then adds the meaningfuleppmarker, for examplecontoso-epp-rg-<suffix>. A deterministic suffix derived from the subscription, application ID, and prefix reduces global-name collisions. Reruns use the same names. - Check prerequisites and sign in. Missing Graph modules or Bicep can be installed after a separate confirmation. Azure and Graph interactive sign-in starts only when the supplied tenant and subscription do not already have suitable user contexts.
- Review the complete plan, including resource names, tenant/subscription, language, automatic package verification/build, provider settings, scoped roles, certificate creation, and application configuration. Bicep receives these exact names; it does not independently calculate a different naming scheme. The plan also lists the six required Azure resource providers and their registration states. This is separate from the Telesign/Soprano provider selection.
- Type
Yesonce to deploy.Noor Enter cancels without Azure changes. Invalid answers prompt again; individual resources do not request additional approvals.
After approval, setup rechecks the selected subscription and registers only missing
Microsoft.Web, Microsoft.Storage, Microsoft.KeyVault, Microsoft.OperationalInsights,
Microsoft.Insights, and Microsoft.ManagedIdentity providers. Already registered providers are
left alone; existing registrations in progress are reused. Registration and regional checks happen
before certificate creation or Bicep deployment. The read-only preflight does not register anything.
Azure registers providers region by region. Setup does not unnecessarily wait for a global
Registered state when a provider is already Registering and exposes the requested region.
Registration metadata is polled with a bounded limit, and recognized regional registration
propagation errors are retried during capability checks/deployment. Permission failures and
unsupported regions remain explicit errors. Registration is subscription-wide and isn't undone
automatically if a later deployment step fails.
Supply known values to shorten the prompts:
.\Setup-Epp.ps1 `
-TenantId <customer-tenant-id> `
-SubscriptionId <subscription-id> `
-ApplicationId <existing-client-id> `
-Location westus2 `
-Language javascript `
-Provider telesign `
-ResourcePrefix contosoThe plan creates or updates a dedicated resource group, Linux Premium EP1 hosting plan, Function App,
storage account/private package container, Key Vault, Log Analytics workspace, Application Insights,
outbound managed identity, diagnostics, Easy Auth, and scoped role assignments. Storage/package
access uses managed identity, not account keys or SAS. Telemetry uses the system identity; the
outbound identity is selected explicitly, not through a global AZURE_CLIENT_ID.
The Function starts with public ingress disabled. Setup stores the private key in Key Vault and
configures application trust. It reads back and verifies Easy Auth before enabling ingress.
Python requires this access for its Entra-authenticated SCM remote build; SCM basic authentication
stays disabled. Setup validates the built Python payload, stores it in private Blob storage, and
switches to managed-identity run-from-package. It never mounts the unbuilt Python source ZIP.
For every language, setup restarts, synchronizes triggers, and verifies that SendOtp is registered.
On publication/startup failure it disables public ingress again; failure to close ingress is reported
explicitly rather than hidden.
The public certificate and a timestamped identifier
summary are saved to epp-output beside the downloaded script, or to -OutputDirectory.
Private keys remain in the user's certificate store and Key Vault, not in that summary.
For unattended runs, supply every input, authenticate both clients first, and explicitly authorize
the whole displayed plan with both -NonInteractive -ApproveDeployment. -NonInteractive
alone never approves changes. There is no -Stage, -Resume, -ConfigPath, or policy-approval switch.
-SourceRepository defaults to Azure-Samples/ExternalPhoneProvider-AzureFunction-Sample.
The small entry point resolves -SourceRef (default main) to a single commit in that repository. All supporting
PowerShell, Bicep, the catalog, and the selected provider profile are downloaded from that commit.
The package catalog supplies asset names, while setup resolves the latest stable epp-packages-*
release from the same repository and verifies the selected asset against that release's
SHA256SUMS.txt. The plan and saved summary record the concrete versioned URL and hashes.
For a fully repeatable deployment, use both a reviewed full commit SHA and
-PackageReleaseTag epp-packages-<run>-<attempt>. Provider JSON selects data only; it cannot
redirect execution to another script. Download failures stop setup, and temporary downloads are
removed on completion or failure. Select only a repository whose code you trust: its supporting
PowerShell is executed locally.
- Save the Step 2 summary and confirm its tenant, application client ID, endpoint URL, encryption key ID, and certificate with the EPP onboarding owner. Replace all test provider values and provision the adapter-named API credentials in Key Vault. Verify the package's channel routing and retry behavior.
- Validate the deployed endpoint with synthetic, non-delivering evaluation requests first. Missing/invalid credentials and unauthorized callers must be rejected by Easy Auth. An admitted caller's valid encrypted request must return the matching nonce. Then verify live SMS/voice provider acceptance and handset delivery through the supported test procedure. Never put phone numbers, messages, tokens, private keys, or nonce values in shared logs.
- An Authentication Policy Administrator, using the approved Microsoft Graph tool and delegated
Policy.ReadWrite.AuthenticationMethod, must read the selected channel configuration:https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/authenticationMethodConfigurations/Smsfor SMS or the same path ending in/Voicefor voice. If the selected configuration or itsurlandappIdproperties are unavailable, stop and obtain the supported onboarding procedure from Microsoft rather than sending a guessed update. - Save the existing channel configuration with the tenant ID and timestamp. Re-read it immediately
before a manual change, stop if it changed, and use
If-Matchwhen an ETag is available. - Update
urlwith the highlighted Function endpoint andappIdwith the highlighted endpoint application client ID printed by setup. Preserve all other properties, then read the configuration back and compare those values before considering activation complete.
Policy activation, policy backups, and policy rollback are administrator-owned manual operations. No policy API is called by the setup package. For rollback, restore only the reviewed prior EPP value through the still-supported contract; resource deletion is not a policy rollback.