A Step-by-Step Kickstarter to building automated software development lifecycle workflows for Power Virtual Agent and the Power Platform

We will use at least four environments within this guide:

- “development” per developer to build your bot,

- “build” to generate a build artifact,

- “test” to test your solution as a managed solution and

- “production” to run the bot as a managed solution.

A GitHub repository to store your solution and all your workflow files.

Table of Contents

Excursus: words on governance

Please consider a governance solution like “Landing Zones for Power Platform” to create and manage your environments (Role-based Access Control [RBAC], apply policies, including data loss prevention, allowing connectors, or denying some), primarily if you work with fusion teams of pro and low-code developers. “Landing Zones for Power Platform” is Microsoft Reference architecture to govern Power Platform Deployments.

The idea behind the workflows

[1] We will export the solution from your build environment to a new branch of your GitHub repository with the first workflow and a manual trigger. After inspecting the changes on GitHub, we will create a pull request and merge the pull request.

[2] After merging a second workflow, we automatically build a managed solution within your build environment and import the managed solution into your test environment.

After end-to-end testing, we create a new release on GitHub, and

[3] the third workflow will automatically import the solution into your production environment.

Authentication basics and solution

Create a service principal account

To create a service principal account, we need to set up an App Registration in Azure Active Directory.

In the Azure portal, select “Azure Active Directory” in the left pane and select “App registrations”, and click on “New registration”.

On the “Register an application” page, enter your application’s registration information:

Upon creation of the application registration, please note and save the “Directory (tenant) ID” and the “Application (client) ID” of the application.

On the navigation panel of the Overview page, select “API permissions”.

Next, proceed to create a client secret. In the navigation panel, select “Certificates & secrets”:

Create an application user

To deploy solutions as part of a CI/CD pipeline, an “Application user” needs access to the environment.

An “Application user” represents an unlicensed user that is authenticated using the application registration completed in the previous steps.

With the “App registration” and the “Application User”, we can use “GitHub Actions” to manage our environments. Yes, environments as we need several to work safely and professionally. Please register this Application User in every environment you deploy (dev, staging, testing, and production).

Create a Solution

A solution is a kind of “application container”. In this “box”, you will find everything related to your project. Within the solution, you will find all your bots, flows, connector references, and other stuff a developer produced.

Create a new GitHub secret for Service Principal Authentication

Workflows

Create four yml-files in the .github/workflow directory of your repository.

Export from dev, unpack, prepare, commit, and push to a new branch

please update CLIENT_ID and TENANT_ID with your id´s and exchange the value for solution_name. Or store them as a secret in GitHub and integrate the secret here like the “PowerPlatformSPN” secret.

export-branch-solution.yml

name: export-and-branch-solution

name: export-and-branch-solution
# Export solution from DEV environment
# unpack it and prepare, commit, and push a git branch with the changes

on:
  workflow_dispatch:
    inputs:
      # Change this value
      solution_name:
        description: 'name of the solution to worked on from Power Platform'
        required: true
        default: DemoEnvironment
       #Do Not change these values
      solution_exported_folder:
        description: 'folder name for staging the exported solution *do not change*'
        required: true
        default: out/exported/
      solution_folder:
        description: 'staging the unpacked solution folder before check-in *do not change*'
        required: true
        default: out/solutions/
      solution_target_folder: 
       description: 'folder name to be created and checked in *do not change*'
       required: true
       default: solutions/
      DEV_ENVIRONMENT_URL:
        description: 'Development environment url.'
        type: string
        required: true 
env:
#edit your values here
  CLIENT_ID: 'your_client_id'
  TENANT_ID: 'your_tenant_id'

jobs:
  export-from-dev:
    runs-on: windows-latest
    # or you can say runs-on: ubuntu-latest
    env:
      RUNNER_DEBUG: 1

    steps:
    - uses: actions/checkout@v2
      with:
        lfs: true

    - name: who-am-i action
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
        app-id: ${{env.CLIENT_ID}}
        client-secret: ${{ secrets.PowerPlatformSPN }}
        tenant-id: ${{env.TENANT_ID}}

    - name: export-solution action
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
        app-id: ${{env.CLIENT_ID}}
        client-secret: ${{ secrets.PowerPlatformSPN }}
        tenant-id: ${{env.TENANT_ID}}
        solution-name: ${{ github.event.inputs.solution_name }}
        solution-output-file: ${{ github.event.inputs.solution_exported_folder}}/${{ github.event.inputs.solution_name }}.zip

    - name: unpack-solution action
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        solution-file: ${{ github.event.inputs.solution_exported_folder}}/${{ github.event.inputs.solution_name }}.zip
        solution-folder: ${{ github.event.inputs.solution_folder}}/${{ github.event.inputs.solution_name }}
        solution-type: 'Unmanaged'
        overwrite-files: true

    - name: branch-solution, prepare it for a PullRequest
      uses: microsoft/powerplatform-actions/branch-solution@v0
      with:
        solution-folder: ${{ github.event.inputs.solution_folder}}/${{ github.event.inputs.solution_name }}
        solution-target-folder: ${{ github.event.inputs.solution_target_folder}}/${{ github.event.inputs.solution_name }}
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        allow-empty-commit: true

To start this manual flow, go to “Action”, select the flow, click “Run workflow” and insert your values into the fields.

Convert the solution to managed, upload the solution to the GitHub artifacts and deploy to the xxx environment

reusable basic flow

release-solution-to-xxx-reusable.yml

name: release-solution-to-xxx-reusable
# Reusable workflow
# convert solution to managed (using a build PowerPlatform environment for the conversion)
# upload the solution to the GitHub artifacts and deploy to the xxx environment
on:
  workflow_call:
    inputs: 
      #Do Not change these values
      #Values are set by the caller
      #caller sample: release-action-call.ymnl
      solution_name:
        description: 'The solution name.'
        type: string
        default: DemoEnvironment       
      solution_shipping_folder:
        description: 'folder name for staging the exported solution *do not change*'        
        type: string
        default: out/ship/
      solution_outbound_folder:
        description: 'staging the unpacked solution folder before check-in *do not change*'
        type: string
        default: out/solutions/
      solution_source_folder: 
       description: 'folder name to be created and checked in *do not change*'
       type: string
       default: solutions/
      solution_release_folder:
       description: 'folder where the released binaries are going to be hosted *do not change*'
       type: string
       default: out/release
      BUILD_ENVIRONMENT_URL:
        description: 'Build environment url.'
        type: string
        required: true      
      TARGET_ENVIRONMENT_URL: 
        description: 'target environment url.'
        type: string
        required: true
      CLIENT_ID: 
        description: 'The client id'
        type: string
        required: true
      TENANT_ID: 
        description: 'The tenant id'
        type: string
        required: true
    secrets:
      envSecret:
        description: 'The secret value for authentication using SPN'
        required: true

jobs:
  convert-to-managed:
    runs-on: windows-latest
    # or you can say runs-on: ubuntu-latest
    env:
      RUNNER_DEBUG: 1

    steps:
    - uses: actions/checkout@v2
      with:
        lfs: true

    - name: Pack solution
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        solution-folder: ${{ inputs.solution_source_folder}}/${{ inputs.solution_name }}
        solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
        solution-type: Unmanaged

    - name: Import solution as unmanaged to build env
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.BUILD_ENVIRONMENT_URL}}
        app-id: ${{inputs.CLIENT_ID}}
        client-secret: ${{ secrets.envSecret }}
        tenant-id: ${{inputs.TENANT_ID}}
        solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
        force-overwrite: true
        publish-changes: true

    - name: Export solution as managed
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.BUILD_ENVIRONMENT_URL}}
        app-id: ${{inputs.CLIENT_ID}}
        client-secret: ${{ secrets.envSecret }} 
        tenant-id: ${{inputs.TENANT_ID}}
        solution-name: ${{ inputs.solution_name }}
        managed: true
        solution-output-file: ${{ inputs.solution_shipping_folder}}/${{ inputs.solution_name }}.zip

    - name: Upload the ready to ship solution to GH artifact store
      uses: actions/upload-artifact@v2
      with:
        name: managedSolutions
        path: ${{ inputs.solution_shipping_folder}}/${{ inputs.solution_name }}.zip

  release-to-target:
    needs: [ convert-to-managed ]
    runs-on: windows-latest
    env:
      RUNNER_DEBUG: 1

    steps:
    - uses: actions/checkout@v2
      with:
        lfs: true

    - name: Fetch the ready to ship solution from GH artifact store
      uses: actions/download-artifact@v2
      with:
        name: managedSolutions
        path: ${{ inputs.solution_release_folder}}
    - name: Import solution to target env
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.TARGET_ENVIRONMENT_URL}}
        app-id: ${{inputs.CLIENT_ID}}
        client-secret: ${{ secrets.envSecret }}
        tenant-id: ${{inputs.TENANT_ID}}
        solution-file: ${{ inputs.solution_release_folder}}/${{ inputs.solution_name }}.zip
        force-overwrite: true

Create a starter flow for “deploy to test” and “deploy to production”

please update CLIENT_ID, TENANT_ID, BUILD_ENVIRONMENT_URL and TARGET_ENVIRONMENT_URL with your id´s and exchange the value for solution_name. Or store them as a secret in GitHub and integrate it here like the “PowerPlatformSPN” secret.

release-to-test.yml

name: Release action to test
# Call the reusable workflow release-solution-to-xxx-reusable.yml
# Release your solution to staging.

on:
  pull_request:
    types:
      - closed
  workflow_dispatch:


jobs:
  Release-solution-staging:
    uses: ./.github/workflows/release-solution-to-xxx-reusable.yml
    with:
      #You can specify the solution name here
      solution_name: DemoEnvironment
      #Update your values here
      BUILD_ENVIRONMENT_URL: 'yourvalue'
      TARGET_ENVIRONMENT_URL: 'yourvalue'
      CLIENT_ID: 'yourvalue'
      TENANT_ID: 'yourvalue'  
    secrets:
     envSecret: ${{ secrets.PowerPlatformSPN }}

release-to-production.yml

name: Release action production
# Call the reusable workflow release-solution-to-xxx-reusable.yml
# Release your solution to staging.

on:
  release:
    types: [published]
  workflow_dispatch:


jobs:
  Release-solution-staging:
    uses: ./.github/workflows/release-solution-to-xxx-reusable.yml
    with:
      #You can specify the solution name here
      solution_name: DemoEnvironment
      #Update your values here
      BUILD_ENVIRONMENT_URL: 'yourvalue'
      TARGET_ENVIRONMENT_URL: 'yourvalue'
      CLIENT_ID: 'yourvalue'
      TENANT_ID: 'yourvalue'  
    secrets:
     envSecret: ${{ secrets.PowerPlatformSPN }}

Both actions are triggered automatically (merge or release) and can triggered manually (workflow_dispatch). For a manual run, go to “Action”, select the flow, click on “Run workflow” and insert your values into the fields

Optional: Deploy a solution to a new development environment

deploy-development.yml

name: deploy development environment
# Reusable workflow
# deploy solution to a development environment

on:
  workflow_dispatch:
    inputs: 
      #Do Not change these values

      solution_name:
        description: 'The solution name.'
        type: string
        default: DemoEnvironment       
      solution_outbound_folder:
        description: 'staging the unpacked solution folder before check-in *do not change*'
        type: string
        default: out/solutions/
      solution_source_folder: 
       description: 'folder name to be created and checked in *do not change*'
       type: string
       default: solutions/
      DEV_ENVIRONMENT_URL:
        description: 'Development environment url.'
        type: string
        required: true      
      CLIENT_ID: 
        description: 'The client id'
        type: string
        required: true
      TENANT_ID: 
        description: 'The tenant id'
        type: string
        required: true

jobs:
  convert-to-unmanaged:
    runs-on: windows-latest
    # or you can say runs-on: ubuntu-latest
    env:
      RUNNER_DEBUG: 1

    steps:
    - uses: actions/checkout@v2
      with:
        lfs: true

    - name: Pack solution
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        solution-folder: ${{ inputs.solution_source_folder}}/${{ inputs.solution_name }}
        solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
        solution-type: Unmanaged

    - name: who-am-i action
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
        app-id: ${{inputs.CLIENT_ID}}
        client-secret: ${{ secrets.PowerPlatformSPN }}
        tenant-id: ${{inputs.TENANT_ID}}

    - name: Import solution as unmanaged to dev env
      uses: microsoft/powerplatform-actions/[email protected]
      with:
        environment-url: ${{inputs.DEV_ENVIRONMENT_URL}}
        app-id: ${{inputs.CLIENT_ID}}
        client-secret: ${{ secrets.PowerPlatformSPN }}
        tenant-id: ${{inputs.TENANT_ID}}
        solution-file: ${{ inputs.solution_outbound_folder}}/${{ inputs.solution_name }}.zip
        force-overwrite: true
        publish-changes: true

To start this manual flow, go to “Action”, select the flow, click “Run workflow” and insert your values into the fields.

Assets

Please find sample scripts in my GitHub repository ALM-for-PVA

Additional Information — a curated list

Landing Zones for Power Platform — a Reference Architecture

Power Platform GitHub Actions — ALM for Developers


Featured Image by Andy Li on Unsplash

Also published here