Recently I’ve been working with a Dynamics 365 Sales Pro project. In addition to some Azure artifacts (e.g. Logic Apps) for integration and migration purposes, our delivery contained plenty of customizations in a Power Platform solution package. In the project we are using three Power Platform Environments: one for development, one for testing and one for production – the first two being Sandbox environments. In my opinion we’re using quite fancy Azure DevOps Pipelines to get the solution moving between environments as well as having the solution unpacked and pushed to Git repository. Not perfect, I’m sure (I’ll get back to this at the end of the blog post), but I still thought I’d share the solution if it’s of any use for anyone.
Now, let’s fast-forward and jump all the way to the end result. I’ll first explain the steps for the consultant to take to get the solution deployed to the test and/or to the production and then I’ll show what’s the automation behind the scenes to get things working as they are.
Deploy solution from development to test environment and get it version controlled
When the consultant is ready with current customizations in the development environment and wants to publish the version to the test environment to eventually get some feedback from the customer, the following are the needed to be taken to make this happen:
Go to shared flows listing in the development environment and start Customizations to TEST or PRODUCTION flow.
The user must then give at least some comment to describe the changes made to the solution package.
The comments will be included in the version commit in Git. This way it is really easy to compare source code versions later and see the description of the change given by the consultant who initiated the deployment.
After the flow is run and the Azure DevOps Pipeline initiated by it completed, this is the result:
- The version number of the solution package in the development environment is updated
- The solution from the development environment is exported both as an unmanaged package and as a managed package
- The unmanaged version of the package is unpacked and committed to DevOps repository
- The managed version of the package is imported into the test environment and solution upgrade initiated
- Both the unmanaged and managed versions of the package are added as build artifacts into the build
Release the current test version into production
To get the latest version of the solution package that was deployed to test released into the production environment, consultant – once again – start the Customizations to TEST or PRODUCTION flow. But this time Production deployment? switch should be turned on.
After the flow is run and Azure Release Pipeline initiated by it completed, the version of the package that was last successfully deployed to the test environment is imported into the production environment and upgraded.
Benefits of the approach
Some of the main benefits of this approach are listed below.
Deployment is really easy to start. Often functional consultants doing customizations in the Power Platform environment don’t (want to) know anything about build pipelines or version control systems. And by being able to initiate the deployments via a Power Automate flow they don’t necessarily even have to – they can start the deployment right within the same Power Platform environment they are currently working in.
Solution packages get version controlled. While the solution gets deployed from the development environment into the test environment, the version gets unpacked and pushed into a centralized Git repository. This has naturally many benefits, such as:
- If somebody deletes the development environment, we don’t lose the solution package. We can get the latest version from repository, pack it with Solution Packager and import it into a new development environment!
- Consultants are forced to describe their changes when deployment to the test is initiated.
- You can compare two versions and see what has changed (see the screenshot below as an example).
Both managed and unmanaged versions of the solution package are stored as build artifacts. In addition to being available for the release pipeline, the packages are stored and easily acquired for later use if needed. For example, consider a scenario where you need to check whether something was working in a different way at a specific point of time? Well, now it is fairly easy to spin up a fresh Power Platform environment, identify the correct build, get the stored solution package from it and deploy it into the created environment!
You can have pre-deployment approvals. It is sometimes a lot of power given to a consultant when allowing him/her to initiate a production deployment. Thankfully we can define pre-deployment approval before the final deployment is carried out! You can simply configue this as a pre-deployment condition in your pipeline.
Further development ideas
Something that I’m still missing in this setup, is the ability to easily commit any version of the solution package into the repository. Currently, the packge gets committed only when build pipeline for the test deployment is run. Abviously, it’d be a good thing to get versions from the development environment stored in the repository more often. Well, this could be quite easily achieved for example by creating another flow and build pipeline to extract the dev version, unpack it and push to source control. Should it be manually triggered? Or automatically run every night? Any ideas? Could we get it triggered whenever consultant publishes all changes in the solution package?
Implementation details: the flow
Implementation of the flow to start either the build pipeline to get the solution deployed to the test or to start the release pipeline to do the production deployment is really simple. Depending on the choice either a Queue a new build or Create a new release action of Azure DevOps connector is used.
Here is the action to queue a new build:
And here is the action to start a new release:
One caveat you should be aware of when passing parameters to an Azure DevOps Build Pipeline: you can’t have the versionComments parameter really a parameter in the build definition – at least I couldn’t get it working that way. What worked for me was to use the received parameter just like a variable in the YAML, like below:
Implementation details: the build pipeline
Implementation of the actual build pipeline was a bit more work and took quite a few re-runs to get it finally working without hickups. Below is the code snippet of the build definition.
trigger: none pool: vmImage: 'windows-latest' variables: packageVersion: '1.0.$(Build.BuildNumber)' steps: - checkout: self persistCredentials: true - task: PowerPlatformToolInstaller@0 inputs: DefaultVersion: true displayName: Install PP tools - task: PowerPlatformWhoAmi@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics DEV' displayName: "DEV connection check" - task: PowerPlatformWhoAmi@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics TEST' displayName: "TEST connection check" - task: PowerPlatformSetSolutionVersion@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics DEV' SolutionName: 'CloudrivenCustomizations' SolutionVersionNumber: $(packageVersion) displayName: Update version number - task: PowerPlatformExportSolution@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics DEV' SolutionName: 'CloudrivenCustomizations' SolutionOutputFile: 'solution.zip' AsyncOperation: true MaxAsyncWaitTime: '60' displayName: Export unmanaged solution package - task: PublishBuildArtifacts@1 inputs: PathtoPublish: 'solution.zip' ArtifactName: 'Unmanaged Cloudriven Customizations' publishLocation: 'Container' displayName: Store unmanaged package as an artifact - task: PowerPlatformUnpackSolution@0 inputs: SolutionInputFile: 'solution.zip' SolutionTargetFolder: '.\Solution' displayName: Unpack solution - task: CmdLine@2 inputs: script: rm solution.zip displayName: Remove solution.zip - task: CmdLine@2 inputs: script: | git config user.email "email@example.com" git config user.name "DevOps Pipeline" git add --all git commit --allow-empty -a -m "$(versionComments) [skip ci]" git push origin HEAD:test displayName: Commit and push to test branch - task: PowerPlatformExportSolution@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics DEV' SolutionName: 'CloudrivenCustomizations' SolutionOutputFile: 'solution_managed.zip' Managed: true AsyncOperation: true MaxAsyncWaitTime: '60' displayName: Export managed solution from DEV - task: PublishBuildArtifacts@1 inputs: PathtoPublish: 'solution_managed.zip' ArtifactName: 'Managed TEST version of Cloudriven Customizations' publishLocation: 'Container' displayName: Store managed package as an artifact - task: PowerPlatformImportSolution@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics TEST' SolutionInputFile: 'solution_managed.zip' AsyncOperation: true MaxAsyncWaitTime: '60' HoldingSolution: true displayName: Import solution to TEST - task: PowerPlatformApplySolutionUpgrade@0 inputs: authenticationType: 'PowerPlatformSPN' PowerPlatformSPN: 'Dynamics TEST' SolutionName: 'CloudrivenCustomizations' AsyncOperation: true MaxAsyncWaitTime: '60' displayName: Upgrade solution
Install PP tools – First off, you’ll need install Power Platform Build Tools for Azure DevOps. Note, that you’ll need to be running on Windows agent!
DEV connection check / TEST connection check – Validate that service connections to required Power Platform environments are OK.
Update version number – Update package version number in the development (source) environment. I’m using syntax ‘1.0.$(Build.BuildNumber)’, which by default get values like ‘1.0.20210314.1’, ‘1.0.20210314.2’, ‘1.0.20210315.1’…
Export unmanaged solution package – We most definitely want a version of the unmanaged solution package, se let’s export that first.
Store unmanaged package as an artifact – Let’s save the unmanaged package as a build artifact. This is not used later by any pipeline, but might come handy at some point…
Unpack solution – Unpack the solution into bunch of xml and json files to prepare for commit.
Remove solution.zip – Remove solution package (we don’t need that to be pushed into the repository)
Commit and push to test branch – Make a commit to the test branch. After this it’s ALL GOOD! We’re safe, our solution is stored in the repository! Note, that we are also using versionComments variable as the commit message!
Export managed solution from DEV – Now it’s time to export the actual managed package that we are going to use to do the deployment.
Store managed package as an artifact – This is important, as we will be using this artifact to create a release (if later requested)!
Import solution to TEST – Finally, let’s make an import to the target environment! Note, we are only staging the solution for upgrade by defining HoldingSolution: true.
Upgrade solution – The last action needed is to apply the staged solution.
Implementation details: the release pipeline
The release pipeline created for the project is as below.
- It is using the managed solution package from the artifacts of the last successfull build of the test deployment
- The below three tasks are carried out in Stage 1