Automate Dynamics 365 Solution Exports with Azure DevOps YAML

Introduction

Leveraging Azure DevOps YAML pipelines to Export Solutions from Dynamics CRM/Power Platform allows us to automate the deployment process, ensuring our solutions are always up-to-date in the repository. This blog post will guide you through setting up an Azure DevOps YAML pipeline to export Dynamics 365 solutions from different environments and commit them to your repository.

Unique features of this YAML Pipeline

  1. Parameterized Execution: Customize the pipeline execution using the following parameters.
    • Work Item Number: Identify the work item number associated with the ongoing work.
    • Commit Message: Specify a commit message for the updates of the considered solutions.
    • Work Type: Define the type of work in progress with options like 'work' or 'hotfix'.
    • Dynamics 365 Solutions: Choose the Dynamics 365 solutions to export.
    • Source Dynamics 365 Environment: Select the source Dynamics 365 environment for exporting solutions.
  2. Dynamic Variables: Utilize dynamic variables for various purposes.
    • Version Control: Automatically set the version of the solutions before exporting.
    • Branch Management: Define the branch name based on the work item number and type of work.
    • Folder Paths: Specify folder paths for unpacking solutions and storing deployment settings.
  3. PowerApps CLI Integration: Install and use the PowerApps CLI for additional operations like creating deployment settings.
  4. Multi-Environment Support: Export solutions from various Dynamics 365 environments based on the selected environment.
  5. Batch Export: Export multiple solutions simultaneously for improved efficiency.
  6. Artifact Publishing: Publish exported solutions as built artifacts for easy access and sharing.
  7. Git Integration: Commit and push changes to the remote repository automatically, ensuring version control and collaboration.

Pipeline YAML

Here's the YAML pipeline that accomplishes the above tasks.

# Pipeline for the export and unpack of Dynamics 365 solutions

# Manual trigger
trigger: none

# Parameters
parameters:
  - name: WorkItemNumberParameter
    displayName: Work item number
    type: string
  - name: CommitMessageParameter
    displayName: Commit message
    type: string
  - name: WorkTypeParameter
    displayName: Work type
    type: string
    default: work
    values:
      - work
      - hotfix
  - name: Dynamics365Solutions
    displayName: Dynamics 365 solutions
    type: object
    default:
      - solutionone
      - solutiontwo
      - solutionthree
  - name: Dynamics365EnvironmentParameter
    displayName: Source Dynamics 365 environment
    type: string
    default: dev
    values:
      - dev
      - dev01
      - dev02
      - dev03
      - dev04

# Variables
variables:
  - name: WorkItemNumber
    value: ${{ parameters.WorkItemNumberParameter }}
  - name: CommitMessage
    value: ${{ parameters.CommitMessageParameter }}
  - name: PowerPlatformSPN
    value: 'Project EO - Dynamics 365 - ${{ parameters.Dynamics365EnvironmentParameter }}'
  - name: SolutionVersion
    value: 1.0.$(version).0
  - name: BranchName
    value: '${{ parameters.WorkTypeParameter }}/dynamics365/$(WorkItemNumber)'
  - name: UnpackedSolutionFolder
    value: 'Solutions'
  - name: DeploymentSettingsFolder
    value: 'Configurations\DeploymentSettings\'
  - name: BuildConfiguration
    value: 'Release'
  - name: BuildPlatform
    value: 'Any CPU'
  - name: D365DevURL
    value: 'https://dharmicrm10-dev.crm8.dynamics.com/'

# Definition of the Build.BuildNumber variable value
name: ${{ variables.SolutionVersion }}

pool:
  vmImage: windows-latest

steps:
  # Install the Power Platform Build Tools
  - task: PowerPlatformToolInstaller@2
    displayName: 'Install Power Platform Build Tools'
    inputs:
      DefaultVersion: true
  # "Microsoft.PowerApps.CLI" setup
  - task: PowerShell@2
    displayName: 'PowerApps CLI setup'
    inputs:
      targetType: 'inline'
      script: |
        Write-Host 'Get Microsoft.PowerApps.CLI nugget package'
        $nugetPackage = 'Microsoft.PowerApps.CLI'
        $outFolder = 'pac'
        nuget install $nugetPackage -OutputDirectory $outFolder
                  
        $pacNugetFolder = Get-ChildItem $outFolder | Where-Object {$_.Name -match $nugetPackage + '.'}
        $pacPath = $pacNugetFolder.FullName + '/tools'
        
        echo "##vso[task.setvariable variable=powerAppsCliPath]$pacPath"
  # Update the version of the solution
  - ${{ each solution in parameters.Dynamics365Solutions }}:
    - task: PowerPlatformSetSolutionVersion@2
      displayName: 'Set version on ${{ solution }}'
      inputs:
        authenticationType: 'PowerPlatformSPN'
        PowerPlatformSPN: '$(PowerPlatformSPN)'
        SolutionName: ${{ solution }}
        SolutionVersionNumber: ${{ variables.SolutionVersion }}
  # Publish customizations
  - task: PowerPlatformPublishCustomizations@2
    inputs:
      authenticationType: 'PowerPlatformSPN'
      PowerPlatformSPN: '$(PowerPlatformSPN)'
  - task: PowerShell@2
    displayName: 'Checkout $(BranchName) branch'
    inputs:
      targetType: 'inline'
      script: |
        git config user.email '[email protected]'
        git config user.name 'Automatic Build'
        $branchFullName = 'origin/$(BranchName)'
        
        Write-Host 'List remote branches'
        git pull
        $remotebranches = git branch -r
            
        Write-Host 'Search for the following branch: $branchFullName'
        $branchExists = $remotebranches | where { $_.Replace(' ', '') -eq $branchFullName }
        
        Write-Host 'Create or checkout considered branch'
        # Create branch if does not exist
        if (!$branchExists) {
          Write-Host 'Create branch because it does not exist:'
          git checkout -b $(BranchName)
        }
        # Checkout branch if it exists
        else {
          Write-Host 'Checkout branch because it exists:'
          git checkout --track $branchFullName
        }
        
        Write-Host 'Verify the checkout branch:'
        git branch
  - ${{ each solution in parameters.Dynamics365Solutions }}:
     # Export considered solution as unmanaged
    - task: PowerPlatformExportSolution@2
      displayName: 'Export ${{ solution }} solution (Unmanaged)'
      inputs:
        authenticationType: 'PowerPlatformSPN'
        PowerPlatformSPN: '$(PowerPlatformSPN)'
        SolutionName: '${{ solution }}'
        SolutionOutputFile: '$(Build.ArtifactStagingDirectory)\${{ solution }}.zip'
        AsyncOperation: true
        MaxAsyncWaitTime: '60'
    # Export considered solution as managed
    - task: PowerPlatformExportSolution@2
      displayName: 'Export ${{ solution }} solution (Managed)'
      inputs:
        authenticationType: 'PowerPlatformSPN'
        PowerPlatformSPN: '$(PowerPlatformSPN)'
        SolutionName: '${{ solution }}'
        SolutionOutputFile: '$(Build.ArtifactStagingDirectory)\${{ solution }}_managed.zip'
        Managed: true
        AsyncOperation: true
        MaxAsyncWaitTime: '60'
    # "Microsoft.PowerApps.CLI" setup
    - task: PowerShell@2
      displayName: 'Generate deployment settings file for ${{ solution }}'
      inputs:
        targetType: 'inline'
        script: |
          Write-Host 'Generate deployment settings file'
          $env:PATH = $env:PATH + ';' + '$(powerAppsCliPath)'

          pac solution create-settings --solution-zip '$(Build.ArtifactStagingDirectory)\${{ solution }}.zip' --settings-file $(DeploymentSettingsFolder)\${{ solution }}\DeploymentSettings-dev.json
    # Unpack both exported solutions (unmanaged and managed)
    - task: PowerPlatformUnpackSolution@2
      displayName: 'Unpack ${{ solution }} solution (Both)'
      inputs:
        SolutionInputFile: '$(Build.ArtifactStagingDirectory)\${{ solution }}.zip'
        SolutionTargetFolder: '$(UnpackedSolutionFolder)\${{ solution }}'
        SolutionType: 'Both'
  # Publish Artifacts
  - task: PublishBuildArtifacts@1
    displayName: 'Publish Artifact - Solutions'
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'Solutions'
      publishLocation: 'Container'
  # Push new version of the unpacked exported solution
  - task: PowerShell@2
    displayName: 'Push changes on Dynamics Solutions to remote'
    inputs:
      targetType: 'inline'
      script: |
        Write-Host 'Commit all changes in the Dynamics Solutions'
        git add --all
        git commit -m 'Dynamics Solutions - ${{ variables.SolutionVersion }} - $(CommitMessage)'
        Write-Host 'Push all changes

While Running the Pipeline, give the required Parameters.

Parameters

The Sample Pipeline Output will be.

 Pipeline Output

Advantages of using this YAML pipeline for Dynamics 365 solution export

  1. Efficient Bulk Export: Export multiple solutions simultaneously for improved productivity.
  2. Dynamic Environment Selection: Seamlessly export solutions from different Dynamics 365 environments.
  3. Flexible Branch Management: Create or switch branches based on work item numbers and types for better organization.
  4. Automated Versioning: Automatically set solution versions for consistent tracking and management.
  5. Integrated PowerApps CLI: Execute Power Platform tasks seamlessly within the pipeline.
  6. Accessible Artifact Publishing: Share exported solutions easily with team members.
  7. Automated Git Operations: Commit and push changes to the repository automatically for streamlined version control.
  8. Customizable Parameters: Tailor pipeline execution to project needs with flexible parameterization.

In essence, this YAML pipeline simplifies Dynamics 365 solution export, offering efficiency, flexibility, and automation to development workflows.


Similar Articles