[object Object]

Power Platform pipelines went GA, then absorbed a lot of the simple multi-environment story. For a tenant with five environments and a couple of dozen solutions, the managed pipelines are sufficient. For anything past that — branching solutions, environment-specific config, integration with Azure DevOps gates — you outgrow the managed product and need the augmented pattern.

What managed pipelines do well

  • Promote a solution from one environment to the next via a UI in the Maker portal.
  • Run validation before deployment.
  • Track deployment history per solution.
  • Support pre and post-deployment Power Automate flows for hooks.

For a team that lives in the maker portal and has linear promotion (dev → test → prod), this works. The managed product is also the easiest way to get connection references and environment variables resolved across environments.

Where they fall short

  • No code-as-source-of-truth. The solution version that gets promoted is the one in dev, not a tagged source-controlled artifact.
  • No branching. Two feature solutions cannot promote independently if they share components.
  • Limited integration with Azure DevOps. The hooks are Power Automate flows, not pipeline tasks.
  • No artifact storage. Re-deploying the same solution version requires re-exporting from dev.
  • Approvals are simple yes/no. No multi-stage gates, no work-item linkage.

The augmented pattern

Keep managed pipelines for the promotion mechanics. Layer Azure DevOps for source, build, and approvals. The contract between the two is: every managed solution that goes into production was first built from source in Azure DevOps and exported as a versioned artifact.

The flow:

  1. Developer pushes a solution change to a feature branch.
  2. PR triggers a build that imports the solution-as-source into a build environment, exports it as a managed solution, and stores the .zip as an artifact tagged with the commit SHA.
  3. PR merge to main triggers managed pipeline promotion: dev → test, gated by automated tests.
  4. Manual approval gates promotion: test → production.
  5. Post-deployment hook calls Power Automate to run environment-specific config (connection references, variable values).

The build step

The Power Platform Build Tools include a pack-solution task. The solution lives in source as unpacked XML and YAML:

trigger:
  branches: { include: [main, feature/*] }

stages:
- stage: Build
  jobs:
  - job: PackSolution
    steps:
    - task: PowerPlatformToolInstaller@2

    - task: PowerPlatformPackSolution@2
      inputs:
        SolutionSourceFolder: 'src/solutions/SalesCore'
        SolutionOutputFile: '$(Build.ArtifactStagingDirectory)/SalesCore.zip'
        SolutionType: 'Managed'

    - task: PowerPlatformImportSolution@2
      inputs:
        authenticationType: 'PowerPlatformSPN'
        PowerPlatformSPN: 'BuildEnvironment'
        SolutionInputFile: '$(Build.ArtifactStagingDirectory)/SalesCore.zip'

    - task: PowerPlatformExportSolution@2
      inputs:
        authenticationType: 'PowerPlatformSPN'
        PowerPlatformSPN: 'BuildEnvironment'
        SolutionName: 'SalesCore'
        SolutionOutputFile: '$(Build.ArtifactStagingDirectory)/SalesCore-$(Build.SourceVersion).zip'
        Managed: true

    - publish: '$(Build.ArtifactStagingDirectory)'
      artifact: solution

The double export (build to verify, then export from build environment) catches a class of “solution exports cleanly from dev but not from a clean environment” bugs.

Environment-specific config

Connection references and environment variables resolve at import. Set values per environment via either:

  • The managed pipeline UI, once per environment.
  • A post-import script that writes via the Dataverse Web API.

The script path is more reproducible. We keep a env-config.yaml per target environment in source:

environment: prod
connection_references:
  cr_shared_office365:
    connection_id: '${secrets.office365_prod_conn}'
  cr_shared_sql:
    connection_id: '${secrets.sql_prod_conn}'
environment_variables:
  cr_api_base_url: 'https://api.contoso.com/v1'
  cr_log_level: 'warn'

The post-import task reads the file and applies via Web API. Idempotent on re-run.

The branching problem

When two feature branches both modify shared components in the same solution, the merge resolves the conflict in source. But the managed pipeline does not know that — it promotes the entire solution. If feature A merges and ships before feature B, then feature B merges, the production deployment for B will fail if B depends on a component A removed. Standard CI hygiene catches this; the managed pipeline alone does not.

The mitigation: a single trunk-based main branch for each solution, no long-lived feature branches, small frequent merges. The same discipline that works for application code works for Dataverse.

Test gates

Automated tests run in test environment between dev promotion and production promotion. The test set:

  • Plugin unit tests via FakeXrmEasy or similar.
  • UI tests via EasyRepro for critical form flows.
  • Integration tests via Postman/Newman against the Web API.

A failing test holds the production promotion. The managed pipeline does not surface this natively — your Azure DevOps stage gates it.

Rollback

Pipelines do not deliver clean rollback. Solutions are layered; a rollback means re-importing a previous version, which can still leave components from the failed version active.

The pragmatic pattern: every release tag corresponds to a managed solution artifact. Rolling back means importing the previous tag’s managed solution as an upgrade. Deferred-action upgrades let you stage the import and apply when you are ready. Test the rollback path the same way you test the forward path.

See also

Dependency hell in solutions — pipelines do not save you from dependency layering issues; they only ship them faster.

Pixel notes

Build a release dashboard. Each row is a solution, each column is an environment, cell color reflects current version state (current, lagging by N versions, failed last deploy). Click-through to the pipeline run. The dashboard is the single page that tells you whether prod and test are aligned.

Forge notes

Service principal authentication is mandatory in 2026. Interactive auth from a pipeline is a regression. Provision an app registration per environment, scope it as a Dataverse application user with a system administrator role in non-prod and a least-privilege custom role in prod.

Bottom line

  • Managed pipelines are good for promotion mechanics. Not enough for source-of-truth delivery.
  • Build in Azure DevOps, promote with managed pipelines, gate with Azure DevOps approvals.
  • Keep environment config in source per environment; apply via post-import script.
  • Trunk-based development on small solutions beats long-lived feature branches.
  • Test gates and rehearsed rollback are the difference between a pipeline and a deployment.
[object Object]
Share