├── .github ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── pr-loop.ps1 │ └── pr-loop.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CanvasTestFramework ├── Microsoft.ApplicationInsights.dll ├── Microsoft.PowerApps.TestAutomation.Api.dll ├── Microsoft.PowerApps.TestAutomation.Browser.dll ├── Microsoft.PowerApps.TestAutomation.Tests.dll ├── Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll ├── Newtonsoft.Json.dll ├── Newtonsoft.Json.xml ├── SeleniumExtras.WaitHelpers.dll ├── System.Diagnostics.DiagnosticSource.dll ├── System.Diagnostics.DiagnosticSource.xml ├── WebDriver.Support.dll ├── WebDriver.Support.xml ├── WebDriver.dll ├── WebDriver.xml └── patestautomation.runsettings ├── LICENSE ├── Pipelines ├── Templates │ ├── Hooks │ │ ├── CustomTemplates │ │ │ ├── output-build-parameters.yml │ │ │ ├── output-deploy-parameters.yml │ │ │ ├── output-environment-variables.yml │ │ │ ├── output-export-parameters.yml │ │ │ └── output-runner-file-system-structure.yml │ │ ├── build-solution-manipulate-source-pre-hook.yml │ │ ├── build-solution-pack-pre-hook.yml │ │ ├── build-solution-post-hook.yml │ │ ├── build-solution-pre-hook.yml │ │ ├── deploy-power-page-pre-hook.yml │ │ ├── deploy-solution-configure-pre-hook.yml │ │ ├── deploy-solution-import-pre-hook.yml │ │ ├── deploy-solution-post-hook.yml │ │ ├── deploy-solution-pre-hook.yml │ │ ├── export-power-page-post-hook.yml │ │ ├── export-power-page-pre-hook.yml │ │ ├── export-solution-commit-pre-hook.yml │ │ ├── export-solution-manipulate-pre-hook.yml │ │ ├── export-solution-post-hook.yml │ │ ├── export-solution-pre-hook.yml │ │ ├── export-solution-unpack-pre-hook.yml │ │ └── export-solution-update-deploymentsettings-pre-hook.yml │ ├── activate-flows.yml │ ├── build-Solution.yml │ ├── build-deploy-Solution-To-Environment-Common-Pipelines.yml │ ├── build-deploy-Solution-To-Environment-Common.yml │ ├── build-deploy-Solution-To-Environment-Hosted-Pipelines.yml │ ├── build-deploy-Solution-To-Environment-Hosted.yml │ ├── build-deploy-Solution-To-Environment-Pipelines.yml │ ├── build-deploy-Solution-To-Environment.yml │ ├── build-run-unit-test-projects.yml │ ├── build-unmanaged-Solution.yml │ ├── build-unmanaged-managed-Solution.yml │ ├── canvas-ownership.yml │ ├── consent-canvas-app.yml │ ├── create-release-github.yml │ ├── deploy-Solution-To-Environment.yml │ ├── deploy-Solution.yml │ ├── disable-flows.yml │ ├── enable-disable-solution-flows.yml │ ├── export-Power-Page.yml │ ├── export-Solution-github.yml │ ├── export-Solution.yml │ ├── import-Solution-To-Dev-Environment.yml │ ├── import-configuration-migration-data.yml │ ├── install-pac.yml │ ├── install-powershell-modules.yml │ ├── prepare-appsource-release.yml │ ├── run-canvas-tests.yml │ ├── set-dataverse-aad-group-teams.yml │ ├── set-deployment-configuration-paths.yml │ ├── set-deployment-variable.yml │ ├── set-environment-id.yml │ ├── set-service-connection-url.yml │ ├── set-solution-exists.yml │ ├── set-spn-token.yml │ ├── set-tools-paths.yml │ ├── set-website-variable.yml │ ├── share-canvas-app-with-aad-group.yml │ ├── share-connectors-with-group-team.yml │ ├── share-flows-with-group-team.yml │ ├── update-canvas-app-ownership.yml │ ├── update-deployment-settings.yml │ ├── update-sdk-messages.yml │ ├── update-solution-component-ownership.yml │ └── update-webhook-urls.yml ├── build-SampleSolution.yml ├── build-deploy-dev-SampleSolution.yml ├── build-deploy-prod-Pipelines.yml ├── build-deploy-prod-SampleSolution.yml ├── build-deploy-test-Pipelines.yml ├── build-deploy-test-SampleSolution.yml ├── build-deploy-validation-Pipelines.yml ├── build-deploy-validation-SampleSolution.yml ├── delete-unmanaged-solution-and-components.yml ├── deploy-prod-SampleSolution.yml ├── deploy-prod-pipelineartifact-SampleSolution.yml ├── deploy-test-SampleSolution.yml ├── deploy-test-pipelineartifact-SampleSolution.yml ├── deploy-validation-SampleSolution.yml ├── export-solution-to-git-pipelines.yml ├── export-solution-to-git.yml ├── import-unmanaged-to-dev-environment.yml ├── sync-pipeline-repo.yml └── trigger-SampleSolution.yml ├── PowerShell ├── .vscode │ └── launch.json ├── activate-flows.ps1 ├── app-source-helper.ps1 ├── archive-configuration-migration-data.ps1 ├── branch-pipeline-policy.ps1 ├── build-deploy-solution-functions.ps1 ├── build-test-automation-urls.ps1 ├── canvas-unpack-pack.ps1 ├── code-first-functions.ps1 ├── dataverse-aad-group-teams-functions.ps1 ├── dataverse-webapi-functions.ps1 ├── enable-disable-solution-flows.ps1 ├── export-solution-functions.ps1 ├── flow-xml-validation.ps1 ├── load-save-pipeline-parameters.ps1 ├── pipeline-functions.ps1 ├── portal-functions.ps1 ├── sdk-message-helper.ps1 ├── share-rows-with-group-team.ps1 ├── tests │ ├── TestRunners │ │ ├── activate-flows.tests.runner.ps1 │ │ ├── archive-configuration-migration-data.tests.runner.ps1 │ │ ├── canvas-unpack-pack.tests.runner.ps1 │ │ ├── e2e-pipeline.tests.runner.ps1 │ │ ├── enable-disable-solution-flows.tests.runner.ps1 │ │ ├── load-save-pipeline-parameters.tests.runner.ps1 │ │ ├── update-deployment-settings.tests.runner.ps1 │ │ └── update-solution-component-owner.tests.runner.ps1 │ ├── activate-flows.tests.ps1 │ ├── archive-configuration-migration-data.tests.ps1 │ ├── e2e-pipeline.tests.ps1 │ ├── enable-disable-solution-flows.tests.ps1 │ ├── load-save-build-parameters.tests.ps1 │ ├── load-save-deploy-parameters.tests.ps1 │ ├── load-save-export-parameters.tests.ps1 │ ├── update-deployment-settings.tests.ps1 │ ├── update-solution-component-owner.tests.ps1 │ └── utilities.tests.ps1 ├── update-deployment-settings.ps1 ├── update-solution-component-owner.ps1 ├── util.ps1 └── webhookurl-helper.ps1 ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── deployment-settings.sample.json ├── deployment-settings.schema.json ├── devops-tasks-schema.json ├── jeschro-1540-readme.md └── version.json /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Issues 4 | url: https://github.com/microsoft/coe-starter-kit/issues 5 | about: Please file issues in the microsoft/coe-starter-kit repository. -------------------------------------------------------------------------------- /.github/workflows/pr-loop.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-EndToEndPipelineTest ($Data) { 2 | $path = './PowerShell/tests/e2e-pipeline.tests.ps1' 3 | $container = New-PesterContainer -Path $path -Data $Data 4 | Invoke-Pester -Container $container 5 | } -------------------------------------------------------------------------------- /.github/workflows/pr-loop.yml: -------------------------------------------------------------------------------- 1 | name: pr-loop 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ "main", "hotfix" ] 7 | jobs: 8 | pr-loop: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: run e2e pipeline test 14 | env: 15 | AZDO_ORG: ${{ secrets.AZDO_ORG }} 16 | AZDO_PROJECT: ${{ secrets.AZDO_PROJECT }} 17 | AZDO_REPO: ${{ secrets.AZDO_PROJECT }} 18 | AZURE_DEVOPS_EXT_PAT: ${{ secrets.AZDO_PAT }} 19 | PR_LOOP_DATA: ${{ secrets.PR_LOOP_DATA }} 20 | AZDO_SVC_CONN_NAME: ${{ secrets.AZDO_SVC_CONN_NAME }} 21 | AZDO_SVC_CONN_URL: ${{ secrets.AZDO_SVC_CONN_NAME }} 22 | PR_LOOP_EMAIL: ${{ secrets.PR_LOOP_EMAIL }} 23 | PR_LOOP_USERNAME: ${{ secrets.PR_LOOP_USERNAME }} 24 | run: | 25 | . ./.github/workflows/pr-loop.ps1 26 | $pair = ":$($env:AZURE_DEVOPS_EXT_PAT)" 27 | $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) 28 | 29 | $data = @{ 30 | Org = $env:AZDO_ORG 31 | Project = $env:AZDO_PROJECT 32 | BranchToTest = $env:GITHUB_HEAD_REF 33 | SourceBranch = 'ALMAcceleratorSampleSolution' 34 | ExportPipelineName = 'export-solution-to-git' 35 | BranchToCreate = 'pr-loop-e2e-test-' + (New-Guid).Guid 36 | CommitMessage = 'pr-loop-e2e-test' 37 | Data = $env:PR_LOOP_DATA 38 | Email = $env:PR_LOOP_EMAIL 39 | Repo = $env:AZDO_REPO 40 | ServiceConnection = $env:AZDO_SVC_CONN_NAME 41 | SolutionName = "ALMAcceleratorSampleSolution" 42 | UserName = $env:PR_LOOP_USERNAME 43 | PortalSiteName = 'ALM Accelerator Sample - almacceleratorsample' 44 | PublishCustomizations = 'false' 45 | CommitScope = '1' 46 | BasicAccessToken = $encodedCreds 47 | } 48 | Invoke-EndToEndPipelineTest -Data $data 49 | shell: pwsh -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "**/Pipelines/**/*.yml": "azure-pipelines" 4 | }, 5 | "azure-pipelines.customSchemaFile": "./devops-tasks-schema.json" 6 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CanvasTestFramework/Microsoft.ApplicationInsights.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Microsoft.ApplicationInsights.dll -------------------------------------------------------------------------------- /CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Api.dll -------------------------------------------------------------------------------- /CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Browser.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Browser.dll -------------------------------------------------------------------------------- /CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Tests.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Microsoft.PowerApps.TestAutomation.Tests.dll -------------------------------------------------------------------------------- /CanvasTestFramework/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll -------------------------------------------------------------------------------- /CanvasTestFramework/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /CanvasTestFramework/SeleniumExtras.WaitHelpers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/SeleniumExtras.WaitHelpers.dll -------------------------------------------------------------------------------- /CanvasTestFramework/System.Diagnostics.DiagnosticSource.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/System.Diagnostics.DiagnosticSource.dll -------------------------------------------------------------------------------- /CanvasTestFramework/WebDriver.Support.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/WebDriver.Support.dll -------------------------------------------------------------------------------- /CanvasTestFramework/WebDriver.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/59abea30f78b9c05b29d5bba7b1a2b562f9bff56/CanvasTestFramework/WebDriver.dll -------------------------------------------------------------------------------- /CanvasTestFramework/patestautomation.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | 7 | ..\TestResults 8 | 9 | 11 | x86 12 | 13 | 14 | Framework45 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | .*CPPUnitTestAutomation.* 29 | 30 | 31 | 32 | True 33 | True 34 | True 35 | False 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/CustomTemplates/output-build-parameters.yml: -------------------------------------------------------------------------------- 1 | # This is a sample Custom Hook Template for testing hooks in the ALM Accelerator pipelines. The intention of the Custom Templates is to provide 2 | # a library of custom hook templates that can be used by organizations in their pipelines via the hooks in their pipeline. Contributions to these 3 | # custom templates are welcome if you have created a hook that you believe would be useful to others. 4 | steps: 5 | - pwsh: | 6 | . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 7 | Write-Host "Loading pipeline parameters from $(Agent.BuildDirectory)\build-pipeline-parameters.json" 8 | $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\build-pipeline-parameters.json" 9 | Write-Host "buildType:" $parameters.buildType 10 | Write-Host "serviceConnectionName:" $parameters.serviceConnectionName 11 | Write-Host "serviceConnectionUrl:" $parameters.serviceConnectionUrl 12 | Write-Host "solutionName:" $parameters.solutionName 13 | displayName: 'Output Build Parameters' -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/CustomTemplates/output-deploy-parameters.yml: -------------------------------------------------------------------------------- 1 | # This is a sample Custom Hook Template for testing hooks in the ALM Accelerator pipelines. The intention of the Custom Templates is to provide 2 | # a library of custom hook templates that can be used by organizations in their pipelines via the hooks in their pipeline. Contributions to these 3 | # custom templates are welcome if you have created a hook that you believe would be useful to others. 4 | steps: 5 | - pwsh: | 6 | . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 7 | Write-Host "Loading pipeline parameters from $(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 8 | $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 9 | Write-Host "serviceConnectionName:" $parameters.serviceConnectionName 10 | Write-Host "serviceConnectionUrl:" $parameters.serviceConnectionUrl 11 | Write-Host "environmentName:" $parameters.environmentName 12 | Write-Host "solutionName:" $parameters.solutionName 13 | Write-Host "importUnmanaged:" $parameters.importUnmanaged 14 | Write-Host "overwriteUnmanagedCustomizations:" $parameters.overwriteUnmanagedCustomizations 15 | Write-Host "skipBuildToolsInstaller:" $parameters.skipBuildToolsInstaller 16 | Write-Host "cacheEnabled:" $parameters.cacheEnabled 17 | displayName: 'Output Deploy Parameters' -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/CustomTemplates/output-environment-variables.yml: -------------------------------------------------------------------------------- 1 | # This is a sample Custom Hook Template for testing hooks in the ALM Accelerator pipelines. The intention of the Custom Templates is to provide 2 | # a library of custom hook templates that can be used by organizations in their pipelines via the hooks in their pipeline. Contributions to these 3 | # custom templates are welcome if you have created a hook that you believe would be useful to others. 4 | steps: 5 | - pwsh: | 6 | Get-ChildItem -Path Env:\ | Format-List 7 | displayName: 'Output Environment Variables' -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/CustomTemplates/output-export-parameters.yml: -------------------------------------------------------------------------------- 1 | # This is a sample Custom Hook Template for testing hooks in the ALM Accelerator pipelines. The intention of the Custom Templates is to provide 2 | # a library of custom hook templates that can be used by organizations in their pipelines via the hooks in their pipeline. Contributions to these 3 | # custom templates are welcome if you have created a hook that you believe would be useful to others. 4 | steps: 5 | - pwsh: | 6 | . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 7 | $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 8 | Write-Host "gitAccessUrl:" $parameters.gitAccessUrl 9 | Write-Host "project:" $parameters.project 10 | Write-Host "repo:" $parameters.repo 11 | Write-Host "branch:" $parameters.branch 12 | Write-Host "branchToCreate:" $parameters.branchToCreate 13 | Write-Host "commitMessage:" $parameters.commitMessage 14 | Write-Host "email:" $parameters.email 15 | Write-Host "serviceConnectionName:" $parameters.serviceConnectionName 16 | Write-Host "serviceConnectionUrl:" $parameters.serviceConnectionUrl 17 | Write-Host "solutionName:" $parameters.solutionName 18 | Write-Host "userName:" $parameters.userName 19 | Write-Host "configurationData:" $parameters.configurationData | ConvertTo-Json 20 | displayName: 'Output Export Parameters' -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/CustomTemplates/output-runner-file-system-structure.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - pwsh: | 3 | Get-ChildItem -Path 'D:\' -recurse -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/build-solution-manipulate-source-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (build-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\build-pipeline-parameters.json" 6 | steps: 7 | - script: echo Build Solution Pre Hook 8 | displayName: 'Build Solution Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/build-solution-pack-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (build-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\build-pipeline-parameters.json" 6 | steps: 7 | - script: echo Build Solution Pack Pre Hook 8 | displayName: 'Build Solution Pack Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/build-solution-post-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (build-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\build-pipeline-parameters.json" 6 | steps: 7 | - script: echo Build Solution Post Hook 8 | displayName: 'Build Solution Post Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/build-solution-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (build-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\build-pipeline-parameters.json" 6 | steps: 7 | - script: echo Build Solution Pre Hook 8 | displayName: 'Build Solution Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/deploy-power-page-pre-hook.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: echo Deploy Power Pages Pre Hook 3 | displayName: 'Deploy Power Pages Pre Hook' 4 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/deploy-solution-configure-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (deploy-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 6 | steps: 7 | - script: echo Deploy Solution Configure Pre Hook 8 | displayName: 'Deploy Solution Configure Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/deploy-solution-import-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (deploy-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 6 | steps: 7 | - script: echo Deploy Solution Import Pre Hook 8 | displayName: 'Deploy Solution Import Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/deploy-solution-post-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks. 3 | # Parameters passed to the parent pipeline (deploy-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 6 | steps: 7 | - script: echo Deploy Solution Post Hook 8 | displayName: 'Deploy Solution Post Hook' 9 | enabled: false 10 | -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/deploy-solution-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (deploy-Solution.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\deploy-pipeline-parameters.json" 6 | steps: 7 | - script: echo Deploy Solution Pre Hook 8 | displayName: 'Deploy Solution Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-power-page-post-hook.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: echo Export Power Pages Post Hook 3 | displayName: 'Export Power Pages Post Hook' 4 | enabled: false 5 | -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-power-page-pre-hook.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: echo Export Power Pages Pre Hook 3 | displayName: 'Export Power Pages Pre Hook' 4 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-commit-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Commit Pre Hook 8 | displayName: 'Export Solution Commit Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-manipulate-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Manipulate Pre Hook 8 | displayName: 'Export Solution Manipulate Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-post-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Post Hook 8 | displayName: 'Export Solution Post Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Pre Hook 8 | displayName: 'Export Solution Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-unpack-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Unpack Pre Hook 8 | displayName: 'Export Solution Unpack Pre Hook' 9 | enabled: false -------------------------------------------------------------------------------- /Pipelines/Templates/Hooks/export-solution-update-deploymentsettings-pre-hook.yml: -------------------------------------------------------------------------------- 1 | # The step(s) below can be replaced with your custom steps. You must have at least one step in your hook. The step(s) below demonstrate examples of custom hooks. 2 | # Be sure when upgrading to the latest pipelines that you don't overwrite your custom hooks with these samples hooks 3 | # Parameters passed to the parent pipeline (export-solution-to-git.yml) can be accessed via the following command. 4 | # . "$env:POWERSHELLPATH/load-save-pipeline-parameters.ps1" 5 | # $parameters = Read-Pipeline-Parameters "$(Agent.BuildDirectory)\export-pipeline-parameters.json" 6 | steps: 7 | - script: echo Export Solution Update Deploymentsettings Pre Hook 8 | displayName: 'Export Solution Update Deploymentsettings Pre Hook' 9 | enabled: true -------------------------------------------------------------------------------- /Pipelines/Templates/activate-flows.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: solutionName 3 | type: string 4 | - name: serviceConnection 5 | type: string 6 | - name: solutionComponentOwnershipConfiguration 7 | type: string 8 | - name: connectionReferences 9 | type: string 10 | - name: activateFlowConfiguration 11 | type: string 12 | 13 | steps: 14 | # Enable a specific list of 'child' flows that must be turned on before their parent flows. 15 | - powershell: | 16 | # load PowerShell files into memory 17 | . "$env:POWERSHELLPATH/util.ps1" 18 | . "$env:POWERSHELLPATH/activate-flows.ps1" 19 | try { 20 | Invoke-ActivateFlows '$(connectionVariables.BuildTools.DataverseConnectionString)' '${{parameters.serviceConnection}}' '$(CoETools_Microsoft_Xrm_Data_PowerShell)' '$(XrmDataPowerShellVersion)' '$(CoETools_Microsoft_PowerApps_Administration_PowerShell)' '$(PowerAppsAdminModuleVersion)' '$(connectionVariables.BuildTools.TenantId)' '$(connectionVariables.BuildTools.ApplicationId)' '$(connectionVariables.BuildTools.ClientSecret)' '${{parameters.solutionName}}' '$(BuildTools.EnvironmentId)' '${{parameters.solutionComponentOwnershipConfiguration}}' '${{parameters.connectionReferences}}' '${{parameters.activateFlowConfiguration}}' "$env:MAPPED_SPN_Token" 21 | } catch { 22 | Write-Host "##vso[task.logissue type=warning]Errors occurred while activating flows. Please review the log for specific errors and verify your deployment configuration settings." 23 | Write-Host $_ 24 | exit 1; 25 | } 26 | displayName: 'Activate Flows' 27 | condition: and(succeeded(), ne(variables['EnableFlows'], 'false')) 28 | env: 29 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment-Common-Pipelines.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | parameters: 6 | - name: pipelineDeploymentStage 7 | type: string 8 | default: "PreDeployment" 9 | steps: 10 | - template: set-service-connection-url.yml 11 | parameters: 12 | serviceConnectionName: $(ServiceConnection) 13 | serviceConnectionUrl: $(ServiceConnectionUrl) 14 | 15 | - checkout: self 16 | displayName: 'Checkout Source Branch' 17 | 18 | - checkout: PipelineRepo 19 | path: 'PipelineUtils' 20 | displayName: 'Checkout Pipeline Branch' 21 | 22 | # Use set-connection-variables task to get values from AzDO Service Connection to be used in scripts that don't use tasks 23 | - task: microsoft-IsvExpTools.PowerPlatform-BuildTools.set-connection-variables.PowerPlatformSetConnectionVariables@2 24 | displayName: 'Set Pipeline Connection Variables' 25 | name: pipelineHostVariables 26 | inputs: 27 | authenticationType: 'PowerPlatformSPN' 28 | PowerPlatformSPN: '$(PipelineServiceConnectionName)' 29 | condition: and(succeeded(), ne(variables['PipelineServiceConnectionName'], ''), not(contains(variables['PipelineServiceConnectionName'], 'PipelineServiceConnectionName'))) 30 | 31 | # Use set-connection-variables task to get values from AzDO Service Connection to be used in scripts that don't use tasks 32 | - task: microsoft-IsvExpTools.PowerPlatform-BuildTools.set-connection-variables.PowerPlatformSetConnectionVariables@2 33 | displayName: 'Set Environment Connection Variables' 34 | name: environmentHostVariables 35 | inputs: 36 | authenticationType: 'PowerPlatformSPN' 37 | PowerPlatformSPN: '$(ServiceConnection)' 38 | condition: and(succeeded(), ne(variables['ServiceConnection'], ''), not(contains(variables['ServiceConnection'], 'ServiceConnection'))) 39 | 40 | - template: set-spn-token.yml 41 | parameters: 42 | serviceConnection: $(ServiceConnectionUrl) 43 | tenantId: $(environmentHostVariables.BuildTools.TenantId) 44 | clientId: $(environmentHostVariables.BuildTools.ApplicationId) 45 | clientSecret: $(environmentHostVariables.BuildTools.ClientSecret) 46 | 47 | - pwsh: | 48 | Write-Host "Copying deployment settings to artifacts folder: $(Build.ArtifactStagingDirectory)\drop\" 49 | $splitName = "$(Build.Repository.Name)".Split("/") 50 | $repoName = $splitName[$splitName.Length - 1] 51 | $sourceDirectory = "$(Build.SourcesDirectory)\$repoName\$(SolutionName)\config\*" 52 | $destinationDirectory = "$(Pipeline.Workspace)\drop\" 53 | if(!(Test-Path $destinationDirectory)) { 54 | New-Item -ItemType Directory -Force -Path $destinationDirectory 55 | } 56 | Copy-item -Force -Recurse -Verbose $sourceDirectory -Destination $destinationDirectory 57 | Write-Host "Running the Post Deployment Settings Updates" 58 | Write-Host "##vso[task.setvariable variable=SkipSolutionImport]true" 59 | displayName: "Setting SkipSolutionImport to true for the Post Deployment Settings Updates" 60 | condition: and(succeeded(), eq('${{ parameters.pipelineDeploymentStage }}', 'PostDeployment')) 61 | 62 | # Run the post deployment settings updates 63 | - ${{ if eq(parameters.pipelineDeploymentStage, 'PostDeployment') }}: 64 | - template: deploy-Solution.yml 65 | parameters: 66 | serviceConnectionName: '$(ServiceConnection)' 67 | serviceConnectionUrl: '$(ServiceConnectionUrl)' 68 | environmentName: '$(EnvironmentName)' 69 | solutionName: '$(SolutionName)' 70 | cacheEnabled: 'true' 71 | 72 | # Mark as succeeded 73 | - pwsh: | 74 | . "$env:POWERSHELLPATH/pipeline-functions.ps1" 75 | Invoke-Pre-Deployment-Status-Update "$(PipelineStageRunId)" "20" "$(PipelineServiceConnectionUrl)" "$(AADHost)" "$(pipelineHostVariables.BuildTools.TenantId)" "$(pipelineHostVariables.BuildTools.ApplicationId)" "$(pipelineHostVariables.BuildTools.ClientSecret)" 76 | displayName: "Mark the deployment as successful in Pipelines" 77 | condition: and(succeeded(), ne(variables['PipelineStageRunId'], ''), not(contains(variables['PipelineStageRunId'], 'PipelineStageRunId'))) 78 | 79 | # Mark as failed 80 | - pwsh: | 81 | . "$env:POWERSHELLPATH/pipeline-functions.ps1" 82 | Invoke-Pre-Deployment-Status-Update "$(PipelineStageRunId)" "30" "$(PipelineServiceConnectionUrl)" "$(AADHost)" "$(pipelineHostVariables.BuildTools.TenantId)" "$(pipelineHostVariables.BuildTools.ApplicationId)" "$(pipelineHostVariables.BuildTools.ClientSecret)" 83 | displayName: "Mark the deployment as failed in Pipelines" 84 | condition: and(failed(), ne(variables['PipelineStageRunId'], ''), not(contains(variables['PipelineStageRunId'], 'PipelineStageRunId'))) -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment-Common.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | steps: 6 | 7 | - template: build-unmanaged-managed-Solution.yml 8 | 9 | # If ImportUnmanaged variable passed in pipeline; Pass 'importUnmanaged' param to deploy-Solution.yml 10 | - pwsh: | 11 | $isunmanaged = 'false' 12 | if('$(ImportUnmanaged)'.Contains("ImportUnmanaged")) { 13 | write-Host "ImportUnmanaged variable is not passed" 14 | } 15 | else { 16 | if('$(ImportUnmanaged)' -eq 'true'){ 17 | Write-Host "ImportUnmanaged is passed - " $(ImportUnmanaged) 18 | $isunmanaged = 'true' 19 | } 20 | } 21 | 22 | Write-Host "##vso[task.setVariable variable=IsUnmanaged]$isunmanaged" 23 | displayName: "Set IsUnmanaged parameter" 24 | 25 | - template: deploy-Solution.yml 26 | parameters: 27 | serviceConnectionName: $(ServiceConnection) 28 | serviceConnectionUrl: $(ServiceConnectionUrl) 29 | solutionName: $(SolutionName) 30 | environmentName: $(EnvironmentName) 31 | skipBuildToolsInstaller: 'true' 32 | importUnmanaged: $(IsUnmanaged) 33 | -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment-Hosted-Pipelines.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | stages: 6 | parameters: 7 | - name: pipelineDeploymentStage 8 | type: string 9 | default: "PreDeployment" 10 | - stage: build_and_deploy 11 | displayName: 'Build and Deploy' 12 | jobs: 13 | - job: build_and_deploy_job 14 | timeoutInMinutes: 0 15 | steps: 16 | - template: build-deploy-Solution-To-Environment-Common-Pipelines.yml 17 | parameters: 18 | pipelineDeploymentStage: '${{parameters.pipelineDeploymentStage}}' -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment-Hosted.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | stages: 6 | - stage: build_and_deploy 7 | displayName: 'Build and Deploy' 8 | jobs: 9 | - job: build_and_deploy_job 10 | timeoutInMinutes: 0 11 | steps: 12 | - template: build-deploy-Solution-To-Environment-Common.yml -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment-Pipelines.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | parameters: 6 | - name: pipelineDeploymentStage 7 | type: string 8 | default: "PreDeployment" 9 | stages: 10 | - stage: build_and_deploy 11 | displayName: 'Build and Deploy' 12 | jobs: 13 | - job: build_and_deploy_job 14 | timeoutInMinutes: 0 15 | pool: 16 | vmImage: 'windows-latest' 17 | steps: 18 | - template: build-deploy-Solution-To-Environment-Common-Pipelines.yml 19 | parameters: 20 | pipelineDeploymentStage: '${{parameters.pipelineDeploymentStage}}' -------------------------------------------------------------------------------- /Pipelines/Templates/build-deploy-Solution-To-Environment.yml: -------------------------------------------------------------------------------- 1 | # Multi-stage starter template for: 2 | # -Building the solution when changes to the contents of the solution folder are triggered (see trigger-SolutionName.yml for example trigger) 3 | # -Deploying the solution to a specific environment based on pipeline variables 4 | # This is most commonly used for PR Validation where both the build and deploy must suceeed in order for the PR checks to succeed. 5 | stages: 6 | - stage: build_and_deploy 7 | displayName: 'Build and Deploy' 8 | jobs: 9 | - job: build_and_deploy_job 10 | timeoutInMinutes: 0 11 | pool: 12 | vmImage: 'windows-latest' 13 | steps: 14 | - template: build-deploy-Solution-To-Environment-Common.yml -------------------------------------------------------------------------------- /Pipelines/Templates/build-unmanaged-Solution.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: branch 3 | type: string 4 | - name: project 5 | type: string 6 | - name: repo 7 | type: string 8 | - name: serviceConnectionName 9 | type: string 10 | - name: serviceConnectionUrl 11 | type: string 12 | - name: solutionName 13 | type: string 14 | - name: buildType 15 | type: string 16 | 17 | steps: 18 | # Checkout our Branch 19 | - checkout: git://${{parameters.project}}/${{parameters.repo}}@${{parameters.branch}} 20 | displayName: 'Checkout Source Branch' 21 | 22 | # Get the solution package from artifact uploaded by the build pipeline 23 | - template: build-Solution.yml 24 | parameters: 25 | buildType: ${{parameters.buildType}} 26 | serviceConnectionName: ${{parameters.serviceConnectionName}} 27 | serviceConnectionUrl: ${{parameters.serviceConnectionUrl}} 28 | solutionName: ${{parameters.solutionName}} 29 | repositoryName: ${{parameters.repo}} 30 | skipSolutionVersioning: 'true' 31 | 32 | 33 | # Moved this logic to deploy-Solution.yml 34 | #- pwsh: | 35 | # Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)" -Filter "${{parameters.solutionName}}*.zip" | 36 | # ForEach-Object { 37 | # If (-not $_.FullName.Contains("_managed")) 38 | # { 39 | # $unmanagedSolutionPath = $_.FullName 40 | # } 41 | # } 42 | # Write-Host "##vso[task.setVariable variable=UnmanagedSolutionPath]$unmanagedSolutionPath" 43 | # Write-Host $unmanagedSolutionPath 44 | # displayName: 'Get unmanaged solution zip path' -------------------------------------------------------------------------------- /Pipelines/Templates/build-unmanaged-managed-Solution.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - template: set-service-connection-url.yml 3 | parameters: 4 | serviceConnectionName: $(ServiceConnection) 5 | serviceConnectionUrl: $(ServiceConnectionUrl) 6 | 7 | - checkout: PipelineRepo 8 | path: 'PipelineUtils' 9 | displayName: 'Checkout Pipeline Branch' 10 | 11 | - checkout: self 12 | displayName: 'Checkout Source Branch' 13 | 14 | # If Managed solution, buildType='Both' else buildType='Unmanaged' 15 | - pwsh: | 16 | $buildType = 'Both' 17 | if('$(ImportUnmanaged)'.Contains("ImportUnmanaged")) { 18 | write-Host "ImportUnmanaged variable is not passed" 19 | } 20 | else { 21 | if('$(ImportUnmanaged)' -eq 'true'){ 22 | Write-Host "ImportUnmanaged is passed - " $(ImportUnmanaged) 23 | $buildType = 'Unmanaged' 24 | } 25 | } 26 | 27 | Write-Host "##vso[task.setVariable variable=BuildType]$buildType" 28 | displayName: Set buildType based on solution type (Managed vs Unmanaged) 29 | 30 | - template: build-Solution.yml 31 | parameters: 32 | buildType: $(BuildType) 33 | serviceConnectionName: $(ServiceConnection) 34 | serviceConnectionUrl: $(ServiceConnectionUrl) 35 | solutionName: $(SolutionName) -------------------------------------------------------------------------------- /Pipelines/Templates/canvas-ownership.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: solutionName 3 | type: string 4 | - name: solutionComponentOwnershipConfiguration 5 | type: string 6 | 7 | steps: 8 | # Enable a specific list of 'child' flows that must be turned on before their parent flows. 9 | - powershell: | 10 | . "$env:POWERSHELLPATH/canvas-unpack-pack.ps1" 11 | try { 12 | Set-OwnerCanvasApps "${{parameters.solutionComponentOwnershipConfiguration}}" "$(CoETools_Microsoft_PowerApps_Administration_PowerShell)" "$(PowerAppsAdminModuleVersion)" "$(connectionVariables.BuildTools.TenantId)" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(CoeTools_Microsoft_Xrm_Data_Powershell)" "$(XrmDataPowerShellVersion)" "$(connectionVariables.BuildTools.DataverseConnectionString)" "$(BuildTools.EnvironmentId)" 13 | } catch { 14 | Write-Host "##vso[task.logissue type=warning]Errors occurred while setting App ownership. Please review the log for specific errors and verify your deployment configuration settings." 15 | Write-Host $_ 16 | exit 1; 17 | } 18 | displayName: 'Set Canvas App Ownership' 19 | condition: and(succeeded(), ne(variables['EnableFlows'], 'false')) 20 | env: 21 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/consent-canvas-app.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: solutionName 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | . "$env:POWERSHELLPATH/canvas-unpack-pack.ps1" 10 | ByPass-Canvas-App-Consents "$(CoETools_Microsoft_PowerApps_Administration_PowerShell)" "$(PowerAppsAdminModuleVersion)" "$(connectionVariables.BuildTools.TenantId)" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(CoeTools_Microsoft_Xrm_Data_Powershell)" "$(XrmDataPowerShellVersion)" "${{parameters.serviceConnection}}" "$(BuildTools.EnvironmentId)" "$(connectionVariables.BuildTools.DataverseConnectionString)" "${{parameters.solutionName}}" 11 | displayName: 'Bypass Canvas App Consent' 12 | condition: and(succeeded(), eq(variables['BypassAppConsent'],'true')) -------------------------------------------------------------------------------- /Pipelines/Templates/deploy-Solution-To-Environment.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: deploy 3 | displayName: 'Deploy to Environment' 4 | jobs: 5 | - deployment: 6 | pool: 7 | vmImage: 'windows-latest' 8 | environment: '$(EnvironmentName)' 9 | strategy: 10 | runOnce: 11 | deploy: 12 | steps: 13 | - template: set-service-connection-url.yml 14 | parameters: 15 | serviceConnectionName: $(ServiceConnection) 16 | serviceConnectionUrl: $(ServiceConnectionUrl) 17 | 18 | - checkout: PipelineRepo 19 | path: 'PipelineUtils' 20 | displayName: 'Checkout Pipeline Branch' 21 | 22 | - template: deploy-Solution.yml 23 | parameters: 24 | serviceConnectionName: '$(ServiceConnection)' 25 | serviceConnectionUrl: '$(ServiceConnectionUrl)' 26 | environmentName: '$(EnvironmentName)' 27 | solutionName: '$(SolutionName)' 28 | cacheEnabled: 'false' 29 | - publish: $(Pipeline.Workspace)/buildPipeline/drop/ 30 | artifact: drop -------------------------------------------------------------------------------- /Pipelines/Templates/disable-flows.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: solutionName 3 | type: string 4 | #- name: activateFlowsOnTheSolutionImport 5 | # type: string 6 | #- name: skipSolutionImport 7 | # type: string 8 | #- name: isUnmanaged 9 | # type: string 10 | #- name: solutionExists 11 | # type: string 12 | 13 | steps: 14 | - powershell: | 15 | . "$env:POWERSHELLPATH/activate-flows.ps1" 16 | 17 | $microsoftPowerAppsAdministrationPowerShellModule = '$(CoETools_Microsoft_PowerApps_Administration_PowerShell)' 18 | Import-Module $microsoftPowerAppsAdministrationPowerShellModule -Force -RequiredVersion $(PowerAppsAdminModuleVersion) -ArgumentList @{ NonInteractive = $true } 19 | Add-PowerAppsAccount -TenantID $(connectionVariables.BuildTools.TenantId) -ApplicationId $(connectionVariables.BuildTools.ApplicationId) -ClientSecret $(connectionVariables.BuildTools.ClientSecret) 20 | $microsoftXrmDataPowerShellModule = '$(CoETools_Microsoft_Xrm_Data_PowerShell)' 21 | Import-Module $microsoftXrmDataPowerShellModule -Force -RequiredVersion $(XrmDataPowerShellVersion) -ArgumentList @{ NonInteractive = $true } 22 | $conn = Get-CrmConnection -ConnectionString "$(connectionVariables.BuildTools.DataverseConnectionString)" 23 | 24 | Deactivate-FlowsFromSolution $conn "${{parameters.solutionName}}" 25 | displayName: 'Disable flows' 26 | condition: and(succeeded(),eq(variables['ActivateFlowsOnTheSolutionImport'],'false'),ne(variables['SkipSolutionImport'],'true'),ne(variables['IsUnmanaged'], 'true'),eq(variables['SolutionExists'], 'true')) 27 | -------------------------------------------------------------------------------- /Pipelines/Templates/enable-disable-solution-flows.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: buildSourceDirectory 3 | type: string 4 | - name: repo 5 | type: string 6 | - name: solutionName 7 | type: string 8 | - name: disableAllFlows 9 | type: string 10 | - name: activateFlowConfiguration 11 | type: string 12 | - name: commitScope 13 | type: string 14 | default: '1' 15 | 16 | steps: 17 | - pwsh: | 18 | # load PowerShell files into memory 19 | . "$env:POWERSHELLPATH/enable-disable-solution-flows.ps1" 20 | Write-Host '${{parameters.activateFlowConfiguration}}' 21 | Set-EnableDisableSolutionFlows '${{parameters.buildSourceDirectory}}' '${{parameters.repo}}' '${{parameters.solutionName}}' '${{parameters.disableAllFlows}}' '${{parameters.activateFlowConfiguration}}' 22 | displayName: 'Disable / Enable Solution Flows' 23 | condition: and(succeeded(), ne('${{parameters.activateFlowConfiguration}}', '')) 24 | #condition: and(succeeded(), ne('${{parameters.activateFlowConfiguration}}', ''), ne('${{parameters.commitScope}}', '3')) -------------------------------------------------------------------------------- /Pipelines/Templates/export-Power-Page.yml: -------------------------------------------------------------------------------- 1 | #We are going to set websiteId as a pipeline variable only if there is a Power Page in the Dataverse 2 | 3 | parameters: 4 | - name: websiteId 5 | type: string 6 | - name: repo 7 | type: string 8 | - name: serviceConnectionUrl 9 | type: string 10 | - name: serviceConnectionName 11 | type: string 12 | - name: solutionName 13 | type: string 14 | - name: websiteName 15 | type: string 16 | - name: commitScope 17 | type: string 18 | default: '1' 19 | - name: configurationData 20 | type: string 21 | - name: buildSourceDirectory 22 | type: string 23 | steps: 24 | # ALM Accelerator currently supports ALM of single website. 25 | # PowerPages folder contain one subfolder at depth 1, which is the unpacked WebSite folder. 26 | # Check if unpacked website folder already exists; If exists Clean the folder in the next step 27 | - pwsh: | 28 | . "$env:POWERSHELLPATH/portal-functions.ps1" 29 | Process-and-Download-Websites "${{parameters.buildSourceDirectory}}" "${{parameters.repo}}" "${{parameters.solutionName}}" "$(pacPath)" "${{parameters.serviceConnectionUrl}}" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(connectionVariables.BuildTools.TenantId)" "${{parameters.websiteName}}" "$env:MAPPED_SPN_Token" 30 | displayName: "Download website(s)" 31 | #condition: and(succeeded(), ne('${{parameters.commitScope}}', '3')) 32 | condition: and(succeeded(), ne('${{parameters.commitScope}}', '3'), and(ne('${{parameters.websiteName}}', 'NA'), ne('${{parameters.websiteName}}', ''))) 33 | env: 34 | MAPPED_SPN_Token: $(SpnToken) 35 | 36 | # Logic to read and set deployment profiles yml files under .\PowerPages\Websitename\deployment-profiles 37 | - pwsh: | 38 | . "$env:POWERSHELLPATH/portal-functions.ps1" 39 | . "$env:POWERSHELLPATH/dataverse-webapi-functions.ps1" 40 | $websiteName = Get-Website-Name "${{parameters.buildSourceDirectory}}" "${{parameters.repo}}" "${{parameters.solutionName}}" 41 | Write-Host "websiteName - $websiteName" 42 | . "$env:POWERSHELLPATH/update-deployment-settings.ps1" 43 | Set-PortalSettings-Files "${{parameters.buildSourceDirectory}}" "${{parameters.repo}}" "${{parameters.serviceConnectionUrl}}" "${{parameters.solutionName}}" "$websiteName" "$env:MAPPED_SPN_Token" 44 | env: 45 | DEPLOYMENT_SETTINGS: ${{parameters.configurationData}} 46 | MAPPED_SPN_Token: $(SpnToken) 47 | displayName: 'Fetch and set deployment profiles' 48 | condition: and(succeeded(), ne('${{parameters.commitScope}}', '3'), and(ne('${{parameters.websiteName}}', 'NA'), ne('${{parameters.websiteName}}', ''))) -------------------------------------------------------------------------------- /Pipelines/Templates/export-Solution-github.yml: -------------------------------------------------------------------------------- 1 | # The following parameters need to be set when the pipeline is queued to run: 2 | # Branch: The name of the Azure DevOps Branch in the Repo above to which we are exporting our solution. 3 | # BranchToCreate: The name of the new Azure DevOps Branch to create in the Repo above to which we are exporting our solution (Optional). 4 | # CommitMessage: The commit message for this commit. 5 | # Data: Additional Data (e.g. JSON Payload) for the export to configure deployment pipelines 6 | # Email: The email of the user performing the commit. 7 | # Project: The name of the Azure DevOps Project to which we are exporting our solution. 8 | # Repo: The name of the Azure DevOps Repo to which we are exporting our solution. 9 | # ServiceConnectionName: The name of the service connection to Power Platform from which we'll be pulling the solution. 10 | # ServiceConnectionUrl: The url of the service connection to Power Platform from which we'll be pulling the solution. 11 | # SolutionName: The name of the solution being exported (i.e. Not the Display Name). 12 | # UserName: The Display name of the user performing the commit. 13 | # PortalSiteName: Power Apps Portal/Power Pages website name 14 | parameters: 15 | - name: gitAccessUrl 16 | type: string 17 | default: '' 18 | - name: project 19 | type: string 20 | default: '' 21 | - name: repo 22 | type: string 23 | default: '' 24 | - name: branch 25 | type: string 26 | default: '' 27 | - name: branchToCreate 28 | type: string 29 | default: '' 30 | - name: commitMessage 31 | type: string 32 | default: '' 33 | - name: email 34 | type: string 35 | default: '' 36 | - name: serviceConnectionName 37 | type: string 38 | default: '' 39 | - name: serviceConnectionUrl 40 | type: string 41 | default: '' 42 | - name: solutionName 43 | type: string 44 | default: '' 45 | - name: userName 46 | type: string 47 | default: '' 48 | - name: configurationData 49 | type: string 50 | default: '' 51 | - name: portalSiteName 52 | type: string 53 | default: '' 54 | - name: publishCustomizations 55 | type: string 56 | default: 'true' 57 | - name: commitScope 58 | type: string 59 | default: "1" 60 | - name: agentPool 61 | type: string 62 | default: 'Azure Pipelines' 63 | - name: artifactUrl 64 | type: string 65 | default: "" 66 | - name: pipelinesServiceConnectionName 67 | type: string 68 | default: "" 69 | - name: pipelinesServiceConnectionUrl 70 | type: string 71 | default: "" 72 | steps: 73 | - template: export-Solution.yml@PipelineRepo 74 | parameters: 75 | gitAccessUrl: $(GitAccessUrl) 76 | project: ${{parameters.project}} 77 | repo: ${{parameters.repo}} 78 | branch: ${{parameters.branch}} 79 | branchToCreate: ${{parameters.branchToCreate}} 80 | commitMessage: ${{parameters.commitMessage}} 81 | email: ${{parameters.email}} 82 | serviceConnectionName: ${{parameters.serviceConnectionName}} 83 | serviceConnectionUrl: ${{parameters.serviceConnectionUrl}} 84 | solutionName: ${{parameters.solutionName}} 85 | userName: ${{parameters.userName}} 86 | configurationData: ${{parameters.configurationData}} 87 | publishCustomizations: ${{parameters.publishCustomizations}} 88 | commitScope: ${{parameters.commitScope}} 89 | createSolutionBranch: 'true' 90 | agentPool: ${{parameters.agentPool}} 91 | 92 | # Checkout our Azure DevOps Branch 93 | - checkout: git://${{parameters.project}}/${{parameters.repo}}@${{parameters.branch}} 94 | path: 'DevopsSource' 95 | displayName: 'Checkout Source Branch' 96 | condition: and(succeeded(), ne('${{parameters.branchToCreate}}', 'Commit to existing branch specified in Branch parameter'), ne('${{parameters.branchToCreate}}', '${{parameters.solutionName}}')) # If an empty value is passed for the BranchToCreate variable, then skip this task 97 | 98 | 99 | # If BranchToCreate variable value is not '', then push to the branch specified in the BranchToCreate variable 100 | - script: | 101 | git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" fetch --all 102 | git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" checkout -b ${{parameters.branchToCreate}} 103 | git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push --set-upstream origin ${{parameters.branchToCreate}} 104 | workingDirectory: $(Pipeline.Workspace)\DevopsSource 105 | displayName: 'Push to Azure DevOps ${{parameters.branchToCreate}} Remote Branch' 106 | condition: and(succeeded(), ne('${{parameters.branchToCreate}}', 'Commit to existing branch specified in Branch parameter'), ne('${{parameters.branchToCreate}}', '${{parameters.solutionName}}')) # If an empty value is not passed for the BranchToCreate variable, then run this task 107 | 108 | -------------------------------------------------------------------------------- /Pipelines/Templates/import-Solution-To-Dev-Environment.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: branch 3 | type: string 4 | - name: project 5 | type: string 6 | - name: repo 7 | type: string 8 | - name: serviceConnectionName 9 | type: string 10 | - name: serviceConnectionUrl 11 | type: string 12 | - name: solutionName 13 | type: string 14 | - name: environmentName 15 | type: string 16 | default: "NA" 17 | - name: importUnmanaged 18 | type: string 19 | default: "true" 20 | 21 | steps: 22 | - template: set-service-connection-url.yml 23 | parameters: 24 | serviceConnectionName: ${{parameters.serviceConnectionName}} 25 | serviceConnectionUrl: ${{parameters.serviceConnectionUrl}} 26 | 27 | - pwsh: | 28 | Write-Host "##vso[task.setvariable variable=RepoName]${{parameters.repo}}" 29 | displayName: 'Set Source Repo Name' 30 | 31 | # Set IsUnmanaged variable to 'True' 32 | - pwsh: | 33 | Write-Host "importUnmanaged - " ${{parameters.importUnmanaged}} 34 | 35 | $buildType = 'Both' 36 | $isunmanaged = 'false' 37 | if('${{parameters.importUnmanaged}}' -eq 'true'){ 38 | Write-Host "importUnmanaged is requested - " ${{parameters.importUnmanaged}} 39 | $buildType = 'Unmanaged' 40 | $isunmanaged = 'true' 41 | } 42 | 43 | Write-Host "##vso[task.setVariable variable=BuildType]$buildType" 44 | Write-Host "##vso[task.setVariable variable=IsUnmanaged]$isunmanaged" 45 | displayName: "Set build type parameter" 46 | 47 | - template: build-unmanaged-Solution.yml 48 | parameters: 49 | project: ${{parameters.project}} 50 | repo: ${{parameters.repo}} 51 | branch: ${{parameters.branch}} 52 | serviceConnectionName: ${{parameters.serviceConnectionName}} 53 | serviceConnectionUrl: ${{parameters.serviceConnectionUrl}} 54 | solutionName: ${{parameters.solutionName}} 55 | buildType: $(BuildType) 56 | 57 | - template: deploy-Solution.yml 58 | parameters: 59 | environmentName: ${{parameters.environmentName}} 60 | serviceConnectionName: ${{parameters.serviceConnectionName}} 61 | serviceConnectionUrl: ${{parameters.serviceConnectionUrl}} 62 | solutionName: ${{parameters.solutionName}} 63 | importUnmanaged: $(IsUnmanaged) 64 | skipBuildToolsInstaller: 'true' 65 | 66 | - template: update-canvas-app-ownership.yml 67 | parameters: 68 | solutionName: ${{parameters.solutionName}} -------------------------------------------------------------------------------- /Pipelines/Templates/import-configuration-migration-data.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: environmentName 5 | type: string 6 | 7 | steps: 8 | - pwsh: | 9 | $path = "$(ArtifactDropPath)/ConfigurationMigrationData-${{parameters.environmentName}}.zip" 10 | . "$env:POWERSHELLPATH/export-solution-functions.ps1" 11 | Invoke-Check-If-Configuration-Migration-Data-Exists "$path" "$(ArtifactDropPath)" 12 | displayName: 'Check if Configuration Migration Data exists' 13 | 14 | - task: microsoft-IsvExpTools.PowerPlatform-BuildTools.import-data.PowerPlatformImportData@2 15 | #skip task if there is no ConfigurationMigrationData in the downloaded artifact 16 | condition: and(succeeded(), ne(variables['ConfigurationMigrationFilePath'], '')) 17 | displayName: 'Import Configuration Migration Data' 18 | inputs: 19 | authenticationType: 'PowerPlatformSPN' 20 | PowerPlatformSPN: ${{ parameters.serviceConnection }} 21 | DataFile: $(ConfigurationMigrationFilePath) -------------------------------------------------------------------------------- /Pipelines/Templates/install-pac.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: canvasUnpackVersion 3 | type: string 4 | steps: 5 | # TEMPORARY WORKAROUND: Power Apps CLI functionality will eventually be surfaced in Azure DevOps tasks. 6 | # However, to use functionality ahead of the tasks, we install the CLI. Once the task is available, we move to using the task. 7 | - powershell: | 8 | #NOTE: Add new versions of canvas unpack and the associated pac CLI version to the versionDictionary to ensure unpacked versions are packed correctly. 9 | #.24 is the only version supported in the release of our pipelines the latest version of PAC CLI should be used until a new version is available at which time we 10 | #would add new versions here 11 | $versionDictionary = @{ "0.24" = ""} 12 | 13 | $nugetPackage = "Microsoft.PowerApps.CLI" 14 | $nugetPackageVersion = '$(pacVersion)' 15 | if('${{ parameters.canvasUnpackVersion }}' -ne '') { 16 | $nugetPackageVersion = $versionDictionary['${{ parameters.canvasUnpackVersion }}'] 17 | } 18 | elseif($nugetPackageVersion.Contains("pacVersion")) { 19 | $nugetPackageVersion = "" 20 | } 21 | $outFolder = "pac" 22 | if($nugetPackageVersion -ne '') { 23 | nuget install $nugetPackage -Version $nugetPackageVersion -OutputDirectory $outFolder 24 | } 25 | else { 26 | nuget install $nugetPackage -OutputDirectory $outFolder 27 | } 28 | $pacNugetFolder = Get-ChildItem $outFolder | Where-Object {$_.Name -match $nugetPackage + "."} 29 | $pacPath = $pacNugetFolder.FullName + "\tools" 30 | Write-Host "##vso[task.setvariable variable=pacPath]$pacPath" 31 | displayName: 'Install pac cli' 32 | -------------------------------------------------------------------------------- /Pipelines/Templates/install-powershell-modules.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - pwsh: | 3 | $xrmDataPowerShellVersion = "2.8.14" 4 | $powerAppsAdminModuleVersion = "2.0.148" 5 | 6 | # Installs modules into specified path. 7 | function Install-PowerShellModule { 8 | [CmdletBinding()] 9 | param( 10 | [string][ValidateNotNullOrEmpty()]$RootPath, 11 | [string][ValidateNotNullOrEmpty()]$ModuleName, 12 | [string][ValidateNotNullOrEmpty()]$ModuleVersion, 13 | [string][ValidateNotNullOrEmpty()]$SubPath 14 | ) 15 | 16 | Write-Host "Installing PS module $ModuleName - $ModuleVersion ..." 17 | $savedModulePath = [IO.Path]::Combine($RootPath, $ModuleName, $ModuleVersion) 18 | # MAX_PATH limitation could be lifted by prefixing with: '\\?\', but is not compatible with PowerShell 19 | # see: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#paths 20 | # using minified path parts where possible 21 | $fixedModulePath = [IO.Path]::Combine($RootPath, $SubPath, $ModuleVersion, $ModuleName) 22 | 23 | if (!(Test-Path -Path $fixedModulePath)) { 24 | Save-Module -Name $ModuleName -Path $RootPath -RequiredVersion $ModuleVersion -Force 25 | 26 | Write-Verbose "Moving module into $fixedModulePath ..." 27 | $moduleItems = Get-ChildItem -Path $savedModulePath 28 | New-Item $fixedModulePath -ItemType Directory -Force | Out-Null 29 | $moduleItems | Move-Item -Destination $fixedModulePath -Force 30 | Remove-Item -Path ([IO.Path]::GetDirectoryName($($savedModulePath))) -Recurse -Force | Out-Null 31 | } else { 32 | Write-Verbose "Found module already installed, nothing to do." 33 | } 34 | 35 | # Set pipeline variable to be used in subsequent tasks when importing module 36 | $modulePath = Join-Path ([IO.Path]::GetDirectoryName($($fixedModulePath))) $ModuleName 37 | Write-Host "##vso[task.setvariable variable=CoETools_$($ModuleName.Replace('.','_'))]$modulePath" 38 | } 39 | 40 | New-Item $(powerPlatformToolsPath) -ItemType Directory -Force | Out-Null 41 | 42 | Install-PowerShellModule -RootPath $(powerPlatformToolsPath) -ModuleName "Microsoft.Xrm.Data.PowerShell" -ModuleVersion $xrmDataPowerShellVersion -SubPath "da" 43 | Install-PowerShellModule -RootPath $(powerPlatformToolsPath) -ModuleName "Microsoft.PowerApps.Administration.PowerShell" -ModuleVersion $powerAppsAdminModuleVersion -SubPath "pa" 44 | 45 | Write-Host "##vso[task.setvariable variable=PowerAppsAdminModuleVersion]$powerAppsAdminModuleVersion" 46 | Write-Host "##vso[task.setvariable variable=XrmDataPowerShellVersion]$xrmDataPowerShellVersion" 47 | displayName: 'Install PowerShell Modules' -------------------------------------------------------------------------------- /Pipelines/Templates/run-canvas-tests.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: aadHost 5 | type: string 6 | default: login.microsoftonline.com 7 | - name: solutionName 8 | type: string 9 | - name: testAccountUsername 10 | type: string 11 | - name: testAccountPassword 12 | type: string 13 | - name: makerPortalUrl 14 | type: string 15 | default: https://make.powerapps.com 16 | - name: loginMethod 17 | type: string 18 | # default: CloudIdentity 19 | # values: 20 | # - CloudIdentity # Uses the default Office 365 Sign-In Service for authentication 21 | # - FederatedIdentity # Uses a Custom Login Action method using AD FS 2.0 or other Security Token Services for authentication. Custom code is required. 22 | # - PassThrough # Federated login scenario where the provided user credential is automatically logged in, requires special agent configurations 23 | 24 | steps: 25 | - pwsh: | 26 | # only attempt to run tests if a test username was provided as a parameter 27 | $runTests = -not ('${{ parameters.testAccountUsername }}'.Contains("TestAutomationUsername")) 28 | Write-Host "RunTests - $runTests" 29 | if ($runTests) { 30 | Write-Host "Running tests..." 31 | $pipelineUtilsPath = "$(Agent.BuildDirectory)\PipelineUtils" 32 | $canvasTestFrameworkPath = "$pipelineUtilsPath\CanvasTestFramework" 33 | Write-Host "##vso[task.setvariable variable=CanvasTestFrameworkPath]$canvasTestFrameworkPath" 34 | 35 | # load PowerShell files into memory 36 | . "$env:POWERSHELLPATH\dataverse-webapi-functions.ps1" 37 | . "$env:POWERSHELLPATH\build-test-automation-urls.ps1" 38 | 39 | $solutionName = "${{parameters.solutionName}}" 40 | $canvasAppsPath = "$(Build.SourcesDirectory)\$(Build.Repository.Name)\${{parameters.solutionName}}\SolutionPackage\src\CanvasApps" 41 | $serviceConnection = "${{parameters.serviceConnection}}" 42 | 43 | Write-Host "SolutionName - $solutionName" 44 | Write-Host "CanvasAppsPath - $canvasAppsPath" 45 | Write-Host "ServiceConnection - $serviceConnection" 46 | 47 | $microsoftPowerAppsAdministrationPowerShellModule = '$(CoETools_Microsoft_PowerApps_Administration_PowerShell)' 48 | Import-Module $microsoftPowerAppsAdministrationPowerShellModule -Force -RequiredVersion $(PowerAppsAdminModuleVersion) -ArgumentList @{ NonInteractive = $true } 49 | Add-PowerAppsAccount -TenantID $(connectionVariables.BuildTools.TenantId) -ApplicationId $(connectionVariables.BuildTools.ApplicationId) -ClientSecret $(connectionVariables.BuildTools.ClientSecret) 50 | 51 | Write-Host "Calling Set-CanvasTestAutomationURLs" 52 | Set-CanvasTestAutomationURLs $env:MAPPED_SPN_Token $serviceConnection $solutionName $canvasAppsPath $(BuildTools.EnvironmentId) 53 | 54 | $jsonPath = (Get-Location).Path + "\CanvasTestAutomationURLs.json" 55 | Write-Host "##vso[task.setvariable variable=JsonPath]$jsonPath" 56 | 57 | Write-Host "JsonPath - $jsonPath" 58 | 59 | $hasTests = (Get-ChildItem -Filter CanvasTestAutomationURLs.json) -ne $null 60 | Write-Host "##vso[task.setvariable variable=RunTests]$hasTests" 61 | Write-Host "HasTests - $hasTests" 62 | } 63 | else { 64 | Write-Host "##vso[task.setvariable variable=RunTests]$runTests" 65 | } 66 | 67 | $hasTestOverride = -not ('$(TestOverride)'.Contains('TestOverride')) 68 | Write-Host "HasTestOverride - $hasTestOverride" 69 | $isPullRequest = "$(Build.Reason)" -eq "PullRequest" 70 | Write-Host "Build reason - $(Build.Reason)" 71 | Write-Host "isPullRequest - $isPullRequest" 72 | $prOrOverride = 'false' 73 | if ($hasTestOverride -or $isPullRequest) { 74 | $prOrOverride = 'true' 75 | } 76 | 77 | Write-Host "prOrOverride - $prOrOverride" 78 | Write-Host "##vso[task.setvariable variable=PrOrOverride]$prOrOverride" 79 | displayName: "Evaluate Running Canvas Tests" 80 | env: 81 | MAPPED_SPN_Token: $(SpnToken) 82 | 83 | # TODO: Figure out how to get this running on ubuntu 84 | - pwsh: | 85 | # Set agent screen resolution to 1920x1080 to avoid sizing issues with Portal 86 | Set-DisplayResolution -Width 1920 -Height 1080 -Force 87 | 88 | # Wait 10 seconds 89 | Start-Sleep -s 10 90 | 91 | # Verify Screen Resolution is set to 1920x1080 92 | Get-DisplayResolution 93 | ignoreLASTEXITCODE: true 94 | displayName: "Set Agent Screen Resolution to 1920x1080" 95 | condition: and(succeeded(), eq(variables.RunTests, 'True'), eq(variables.PrOrOverride,'true')) 96 | 97 | - task: VSTest@2 # Only running chrome tests for now since Power Apps is already cross browser. These test are for UI / Integration testing. Can revisit if we discover we need cross browser later. 98 | displayName: "Run Power Apps Test Automation Tests via Chrome" 99 | condition: and(succeeded(), eq(variables.RunTests, 'true'), eq(variables.PrOrOverride,'true')) 100 | inputs: 101 | testAssemblyVer2: '$(CanvasTestFrameworkPath)\Microsoft.PowerApps.TestAutomation.Tests.dll' 102 | testFiltercriteria: "TestCategory=PowerAppsTestAutomation" 103 | uiTests: true 104 | runSettingsFile: '$(CanvasTestFrameworkPath)\patestautomation.runsettings' 105 | overrideTestrunParameters: '-OnlineUsername ${{ parameters.testAccountUsername }} -OnlinePassword ${{parameters.testAccountPassword}} -BrowserType "Chrome" -OnlineUrl ${{ parameters.makerPortalUrl }} -UsePrivateMode true -TestAutomationURLFilePath "$(JsonPath)" -DriversPath $(ChromeWebDriver) -LoginMethod ${{ parameters.loginMethod }}' 106 | testRunTitle: "Run Power Apps Test Automation Tests via Chrome" 107 | continueOnError: false 108 | -------------------------------------------------------------------------------- /Pipelines/Templates/set-dataverse-aad-group-teams.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: aadGroupTeamConfiguration 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | . "$env:POWERSHELLPATH/dataverse-aad-group-teams-functions.ps1" 10 | Set-Dataverse-AAD-Group-Teams "$(CoETools_Microsoft_PowerApps_Administration_PowerShell)" "$(PowerAppsAdminModuleVersion)" "$(connectionVariables.BuildTools.TenantId)" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(CoeTools_Microsoft_Xrm_Data_Powershell)" "$(XrmDataPowerShellVersion)" "${{parameters.serviceConnection}}" "${{parameters.aadGroupTeamConfiguration}}" "$env:MAPPED_SPN_Token" "$(connectionVariables.BuildTools.DataverseConnectionString)" 11 | displayName: 'Set Dataverse AAD Group Teams' 12 | condition: and(succeeded(), not(contains(variables['outAadGroupTeamConfiguration'], '$('))) 13 | env: 14 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/set-deployment-configuration-paths.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: configPath 3 | type: string 4 | - name: environmentName 5 | type: string 6 | - name: commitScope 7 | type: string 8 | default: '1' 9 | steps: 10 | 11 | #Check for deployment settings for this environment 12 | - pwsh: | 13 | . "$env:POWERSHELLPATH/build-deploy-solution-functions.ps1" 14 | Get-Does-Deployment-Settings-Exist '${{parameters.environmentName}}' '${{parameters.configPath}}' 15 | displayName: 'Check if Deployment Settings Exist' 16 | condition: and(succeeded(), or(eq('${{parameters.commitScope}}', '1'),eq('${{parameters.commitScope}}', '3'))) -------------------------------------------------------------------------------- /Pipelines/Templates/set-deployment-variable.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: variableName 3 | type: string 4 | - name: deploymentSettingsPath 5 | type: string 6 | - name: deploymentSettingsNode 7 | type: string 8 | - name: displayName 9 | type: string 10 | - name: commitScope 11 | type: string 12 | default: '1' 13 | 14 | steps: 15 | - pwsh: | 16 | . "$env:POWERSHELLPATH/build-deploy-solution-functions.ps1" 17 | Set-Deployment-Variable "${{parameters.deploymentSettingsPath}}" "${{parameters.deploymentSettingsNode}}" "${{parameters.variableName}}" "$(Agent.BuildDirectory)" 18 | displayName: ${{parameters.displayName}} 19 | condition: and(succeeded(), or(eq('${{parameters.commitScope}}', '1'),eq('${{parameters.commitScope}}', '3'))) -------------------------------------------------------------------------------- /Pipelines/Templates/set-environment-id.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | 5 | steps: 6 | - task: microsoft-IsvExpTools.PowerPlatform-BuildTools.whoami.PowerPlatformWhoAmi@2 7 | displayName: "Set EnvironmentId" 8 | inputs: 9 | authenticationType: 'PowerPlatformSPN' 10 | PowerPlatformSPN: '${{parameters.serviceConnection}}' -------------------------------------------------------------------------------- /Pipelines/Templates/set-service-connection-url.yml: -------------------------------------------------------------------------------- 1 | #This template will default the ServiceConnectionUrl to the ServiceConnection variable supplied in a deployment pipeline for backward compatibility 2 | #The July 2022 release includes the ability for a single service connection to be used for multiple environments rather than requiring a service connection 3 | #per environment. This update provides the ability to specify a ServiceConnection (i.e. the Power Platform Service Connection Name to use for the deployment) 4 | #and a ServiceConnectionUrl on the deployment pipeline and leverages the BuildTools.EnvironmentUrl to dynamically set the environment url for a given service connection. 5 | parameters: 6 | - name: serviceConnectionName 7 | type: string 8 | - name: serviceConnectionUrl 9 | type: string 10 | 11 | steps: 12 | - pwsh: | 13 | # Workaround for git Filename too long issue 14 | git config --system core.longpaths true 15 | if('${{parameters.serviceConnectionUrl}}'.Contains('$(')) { 16 | Write-Host "##vso[task.setvariable variable=ServiceConnectionUrl]${{parameters.serviceConnectionName}}" 17 | Write-Host "##vso[task.setvariable variable=BuildTools.EnvironmentUrl]${{parameters.serviceConnectionName}}" 18 | } 19 | else { 20 | Write-Host "##vso[task.setvariable variable=BuildTools.EnvironmentUrl]${{parameters.serviceConnectionUrl}}" 21 | } 22 | displayName: "Set Service Connection Url" 23 | -------------------------------------------------------------------------------- /Pipelines/Templates/set-solution-exists.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: solutionName 3 | type: string 4 | 5 | steps: 6 | - powershell: | 7 | $microsoftPowerAppsAdministrationPowerShellModule = '$(CoETools_Microsoft_PowerApps_Administration_PowerShell)' 8 | Import-Module $microsoftPowerAppsAdministrationPowerShellModule -Force -RequiredVersion $(PowerAppsAdminModuleVersion) -ArgumentList @{ NonInteractive = $true } 9 | Add-PowerAppsAccount -TenantID $(connectionVariables.BuildTools.TenantId) -ApplicationId $(connectionVariables.BuildTools.ApplicationId) -ClientSecret $(connectionVariables.BuildTools.ClientSecret) 10 | $microsoftXrmDataPowerShellModule = '$(CoETools_Microsoft_Xrm_Data_PowerShell)' 11 | Import-Module $microsoftXrmDataPowerShellModule -Force -RequiredVersion $(XrmDataPowerShellVersion) -ArgumentList @{ NonInteractive = $true } 12 | $conn = Get-CrmConnection -ConnectionString "$(connectionVariables.BuildTools.DataverseConnectionString)" 13 | 14 | $solutions = Get-CrmRecords -conn $conn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "${{parameters.solutionName}}" -Fields solutionid 15 | if ($solutions.Count -eq 0){ 16 | # Set the SolutionExists as a global variable for use in other templates 17 | Write-Host "##vso[task.setvariable variable=SolutionExists]false" 18 | } 19 | else { 20 | # Set the SolutionExists as a global variable for use in other templates 21 | Write-Host "##vso[task.setvariable variable=SolutionExists]true" 22 | } 23 | displayName: 'Set Solution Exists' 24 | -------------------------------------------------------------------------------- /Pipelines/Templates/set-spn-token.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: tenantId 5 | type: string 6 | - name: clientId 7 | type: string 8 | - name: clientSecret 9 | type: string 10 | steps: 11 | - pwsh: | 12 | $pipelineUtilsPath = "$(Agent.BuildDirectory)\PipelineUtils" 13 | $powerShellPath = "$pipelineUtilsPath\PowerShell" 14 | Write-Host "##vso[task.setvariable variable=PowerShellPath]$powerShellPath" 15 | 16 | if($env:MAPPED_SPN_Token.Contains('$(')) { 17 | . "$powerShellPath\dataverse-webapi-functions.ps1" 18 | Set-SpnTokenVariableWithinAgent "${{parameters.tenantId}}" "${{parameters.clientId}}" "${{parameters.clientSecret}}" "${{parameters.serviceConnection}}" "$(AADHost)" 19 | } 20 | displayName: "Set SpnToken for use by other tasks that need one" 21 | env: 22 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/set-tools-paths.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | #Set pipeline variables for each of the tools folders that dependencies are installed to. 3 | #These variables will be used for in the caching tasks 4 | - pwsh: | 5 | if (Test-Path Env:VSTS_TOOLS_PATH) { 6 | $toolsPath = $Env:VSTS_TOOLS_PATH 7 | } 8 | elseif (Test-Path Env:PIPELINE_WORKSPACE) { 9 | $toolsPath = $Env:PIPELINE_WORKSPACE 10 | } 11 | elseif (Test-Path Env:AGENT_BUILDDIRECTORY ) { 12 | $toolsPath = $Env:AGENT_BUILDDIRECTORY 13 | } 14 | else { 15 | $toolsPath = Get-Location 16 | } 17 | 18 | $powerPlatformToolsSubPath = "_coe\ALM_ACC" 19 | $powerPlatformToolsPath = "$toolsPath\$powerPlatformToolsSubPath" 20 | Write-Host "##vso[task.setvariable variable=powerPlatformToolsSubPath]$powerPlatformToolsSubPath" 21 | Write-Host "##vso[task.setvariable variable=powerPlatformToolsPath]$powerPlatformToolsPath" 22 | displayName: 'Set toolsPaths' 23 | -------------------------------------------------------------------------------- /Pipelines/Templates/set-website-variable.yml: -------------------------------------------------------------------------------- 1 | #We are going to set websiteId as a pipeline variable only if there is a Power Page in the Dataverse 2 | parameters: 3 | - name: serviceConnectionUrl 4 | type: string 5 | - name: websiteName 6 | type: string 7 | - name: commitScope 8 | type: string 9 | default: '1' 10 | 11 | steps: 12 | - pwsh: | 13 | Write-Host "Portal Website provided as variable. Website ID - $(PowerPageWebsiteId)" 14 | Write-Host "##vso[task.setvariable variable=WebsiteId;]$(PowerPageWebsiteId)" 15 | condition: and(succeeded(), ne(variables['PowerPageWebsiteId'], ''),ne('${{parameters.commitScope}}', '3')) 16 | displayName: "Fetch Power Pages Website ID from Variable" 17 | 18 | - pwsh: | 19 | . "$env:POWERSHELLPATH/portal-functions.ps1" 20 | Get-Website-ID "${{parameters.websiteName}}" "${{parameters.serviceConnectionUrl}}" "$env:MAPPED_SPN_Token" 21 | condition: and(succeeded(), eq(variables['PowerPageWebsiteId'], ''),ne('${{parameters.commitScope}}', '3')) 22 | displayName: "Fetch Power Pages Website ID from Dataverse" 23 | env: 24 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/share-canvas-app-with-aad-group.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: aadGroupCanvasConfiguration 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | . "$env:POWERSHELLPATH/canvas-unpack-pack.ps1" 10 | 11 | $skipShareError = $true 12 | # If 'FailPluginIfAppShareError' is passed, fail pipeline 13 | if(!'$(FailPluginIfAppShareError)'.Contains('$(')) { 14 | $failPluginIfAppShareErrorValue = "$(FailPluginIfAppShareError)" 15 | if($failPluginIfAppShareErrorValue -eq 'true') 16 | { 17 | Write-Host "FailPluginIfAppShareError variable passed as true . Will fail the pipeline." 18 | $skipShareError = $false 19 | }else{ 20 | Write-Host "FailPluginIfAppShareError variable passed as false . Will suppress the pipeline failure." 21 | } 22 | } 23 | try { 24 | Write-Host "SkipShareError - $skipShareError" 25 | Invoke-Share-Canvas-App-with-AAD-Group "$(CoETools_Microsoft_PowerApps_Administration_PowerShell)" "$(PowerAppsAdminModuleVersion)" "$(connectionVariables.BuildTools.TenantId)" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(CoeTools_Microsoft_Xrm_Data_Powershell)" "$(XrmDataPowerShellVersion)" "${{parameters.serviceConnection}}" "${{parameters.aadGroupCanvasConfiguration}}" "$(BuildTools.EnvironmentId)" "$(connectionVariables.BuildTools.DataverseConnectionString)" $skipShareError 26 | } catch { 27 | Write-Host "##vso[task.logissue type=warning]Errors occurred while sharing the App. Please review the log for specific errors and verify your deployment configuration settings." 28 | Write-Host $_ 29 | if ($skipShareError -eq $false) { 30 | Write-Host "Throwing exception - Main" 31 | exit 1 32 | } 33 | } 34 | displayName: 'Share Canvas App with AAD Group' 35 | condition: and(succeeded(), not(contains(variables['outAadGroupCanvasConfiguration'], '$('))) -------------------------------------------------------------------------------- /Pipelines/Templates/share-connectors-with-group-team.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: connectorShareWithGroupTeamConfiguration 5 | type: string 6 | 7 | steps: 8 | - pwsh: | 9 | $connectorShareWithGroupTeamConfiguration = '${{parameters.connectorShareWithGroupTeamConfiguration}}' 10 | if($connectorShareWithGroupTeamConfiguration -ne '') { 11 | # load PowerShell files into memory 12 | . "$env:POWERSHELLPATH/dataverse-webapi-functions.ps1" 13 | . "$env:POWERSHELLPATH/share-rows-with-group-team.ps1" 14 | 15 | $config = Get-Content '${{parameters.connectorShareWithGroupTeamConfiguration}}' | ConvertFrom-Json 16 | $dataverseHost = Get-HostFromUrl '${{parameters.serviceConnection}}' 17 | 18 | foreach ($c in $config) { 19 | if($c.aadGroupTeamName -ne '' -and $c.solutionComponentUniqueName -ne '') { 20 | Write-Host "Sharing connector - " $c.solutionComponentUniqueName " with " $c.aadGroupTeamName 21 | Grant-AccessToConnector "$env:MAPPED_SPN_Token" "$dataverseHost" $c.aadGroupTeamName $c.solutionComponentUniqueName 22 | } 23 | } 24 | } 25 | displayName: "Share Connectors with Group Team" 26 | condition: and(succeeded(), not(contains(variables['outConnectorShareWithGroupTeamConfiguration'], '$('))) 27 | env: 28 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/share-flows-with-group-team.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: flowShareWithGroupTeamConfiguration 5 | type: string 6 | 7 | steps: 8 | - pwsh: | 9 | $flowShareWithGroupTeamConfiguration = '${{parameters.flowShareWithGroupTeamConfiguration}}' 10 | if($flowShareWithGroupTeamConfiguration -ne ''){ 11 | # load PowerShell files into memory 12 | . "$env:POWERSHELLPATH/dataverse-webapi-functions.ps1" 13 | . "$env:POWERSHELLPATH/share-rows-with-group-team.ps1" 14 | 15 | $config = Get-Content '${{parameters.flowShareWithGroupTeamConfiguration}}' | ConvertFrom-Json 16 | $dataverseHost = Get-HostFromUrl '${{parameters.serviceConnection}}' 17 | 18 | foreach ($c in $config) { 19 | if($c.aadGroupTeamName -ne '' -and $c.solutionComponentUniqueName -ne '') { 20 | Write-Host "Sharing flow - " $c.solutionComponentUniqueName " with " $c.aadGroupTeamName 21 | try { 22 | Grant-AccessToWorkflow "$env:MAPPED_SPN_Token" "$dataverseHost" $c.aadGroupTeamName $c.solutionComponentUniqueName 23 | } catch { 24 | Write-Host "Grant-AccessToWorkflow execution failed - $($_.Exception.Message)" 25 | } 26 | } 27 | } 28 | } 29 | displayName: "Share Flows with Group Team" 30 | condition: and(succeeded(), not(contains(variables['outFlowShareWithGroupTeamConfiguration'], '$('))) 31 | env: 32 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/update-canvas-app-ownership.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: solutionName 3 | type: string 4 | steps: 5 | # TEMPORARY WORKAROUND: Currently Canvas Apps cannot be owned by an app user, so we have to set an interactive user owner. 6 | - powershell: | 7 | . "$env:POWERSHELLPATH/canvas-unpack-pack.ps1" 8 | try { 9 | Update-Canvas-App-Ownership "$(CoETools_Microsoft_PowerApps_Administration_PowerShell)" "$(PowerAppsAdminModuleVersion)" "$(connectionVariables.BuildTools.TenantId)" "$(connectionVariables.BuildTools.ApplicationId)" "$(connectionVariables.BuildTools.ClientSecret)" "$(CoeTools_Microsoft_Xrm_Data_Powershell)" "$(XrmDataPowerShellVersion)" "$(connectionVariables.BuildTools.DataverseConnectionString)" "$(BuildTools.EnvironmentId)" "$(Build.RequestedForEmail)" "${{parameters.solutionName}}" 10 | } catch { 11 | Write-Host "##vso[task.logissue type=warning]Errors occurred while updating App ownership. Please review the log for specific errors and verify your deployment configuration settings." 12 | Write-Host $_ 13 | exit 1; 14 | } 15 | 16 | displayName: 'Update Canvas App Ownership' -------------------------------------------------------------------------------- /Pipelines/Templates/update-deployment-settings.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: orgUrl 3 | type: string 4 | - name: projectName 5 | type: string 6 | - name: repo 7 | type: string 8 | - name: serviceConnection 9 | type: string 10 | - name: solutionName 11 | type: string 12 | - name: configurationData 13 | type: string 14 | - name: useDeploymentSettingsPlaceholders 15 | type: string 16 | default: 'true' 17 | - name: commitScope 18 | type: string 19 | default: '1' 20 | - name: currentBranch 21 | type: string 22 | - name: branchToCreate 23 | type: string 24 | - name: createSolutionBranch 25 | type: string 26 | default: 'true' 27 | - name: agentPool 28 | type: string 29 | default: 'Azure Pipelines' 30 | - name: pipelineServiceConnectionName 31 | type: string 32 | default: '' 33 | - name: pipelineServiceConnectionUrl 34 | type: string 35 | default: '' 36 | - name: pipelineStageRunId 37 | type: string 38 | default: '' 39 | steps: 40 | - powershell: | 41 | # load PowerShell files into memory 42 | . "$env:POWERSHELLPATH/update-deployment-settings.ps1" 43 | 44 | # We need the project and repository where the yaml for this pipeline exists. Unfortunately, there's no way to get this directly from any of the available 45 | # predefined variables. We can get the repository name from $(Build.Repository.Name). However, there's no similar variable for the project name associated 46 | # with the repository as $(System.TeamProject) refers to where the pipeline that consumes this YAML was created. In the case that the pipelines are hosted in 47 | # another project the only way to get both of these is to parse $(Build.Repository.Uri) which has both the project and repo name. 48 | # Name : $(Build.Repository.Uri) 49 | # Current Value : https://[ORGNAME]@dev.azure.com/[ORGNAME]/[PROJECTNAME]/_git/[REPONAME] 50 | # Legacy Value : https://[ORGNAME].visualstudio.com/[PROJECTNAME]/_git/[REPONAME] 51 | # Parsing this could be brittle and have side effects so we've included the ability to set variables to override this parsing (i.e. PipelineProject and PipelineRepository) 52 | # These can be set as global variables in the alm-accelerator-variable-group. 53 | 54 | #Default to pipeline project and repo to the project where the pipeline is running and the and repo of this yaml 55 | $pipelineProject = "$(System.TeamProject)" 56 | $pipelineRepo = "$(Build.Repository.Name)" 57 | 58 | [string[]]$segments = ([System.Uri]"$(Build.Repository.Uri)").Segments | Select-Object -Last 3 | ForEach-Object { $_.TrimEnd("/") } 59 | 60 | #Use the parsed value from the repo uri if it follows the expected format 61 | if ($segments.Length -gt 2) { 62 | $pipelineProject = [uri]::UnescapeDataString($segments[0]) 63 | $pipelineRepo = [uri]::UnescapeDataString($segments[2]) 64 | } 65 | 66 | #Finally, use the override variables to bypass the options above 67 | if(!'$(PipelineProject)'.Contains('$(')) { 68 | $pipelineProject = "$(PipelineProject)" 69 | } 70 | if(!'$(PipelineRepository)'.Contains('$(')) { 71 | $pipelineRepo = "$(PipelineRepository)" 72 | } 73 | 74 | $currentBranch = '${{parameters.currentBranch}}' 75 | if(-not ('${{parameters.branchToCreate}}' -match 'Commit to existing branch specified in Branch parameter')) { 76 | $currentBranch = '${{parameters.branchToCreate}}' 77 | } 78 | #Call the deployment settings powershell 79 | Set-DeploymentSettingsConfiguration '$(Build.SourcesDirectory)' '$(Agent.BuildDirectory)\PipelineUtils' "$pipelineProject" "$pipelineRepo" '$(connectionVariables.BuildTools.DataverseConnectionString)' '$(XrmDataPowerShellVersion)' '$(CoETools_Microsoft_Xrm_Data_PowerShell)' '${{parameters.orgUrl}}' '${{parameters.projectName}}' '${{parameters.repo}}' 'Bearer' '${{parameters.serviceConnection}}' '${{parameters.solutionName}}' $currentBranch '${{parameters.createSolutionBranch}}' '${{parameters.pipelineServiceConnectionName}}' '${{parameters.pipelineServiceConnectionUrl}}' '${{parameters.pipelineStageRunId}}' '${{parameters.agentPool}}' '$(Agent.OS)' '${{parameters.useDeploymentSettingsPlaceholders}}' 80 | env: 81 | SYSTEM_ACCESSTOKEN: $(System.AccessToken) 82 | DEPLOYMENT_SETTINGS: ${{parameters.configurationData}} 83 | displayName: 'Update Deployment Settings' 84 | condition: and(succeeded(), ne(variables.GenerateDeploymentSettings, 'false'),or(eq('${{parameters.commitScope}}', '1'),eq('${{parameters.commitScope}}', '3'))) -------------------------------------------------------------------------------- /Pipelines/Templates/update-sdk-messages.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: sdkMessageConfiguration 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | # load PowerShell files into memory 10 | . "$env:POWERSHELLPATH/sdk-message-helper.ps1" 11 | try { 12 | Update-Sdk-Message-Configurations '$(connectionVariables.BuildTools.DataverseConnectionString)' '${{parameters.serviceConnection}}' '$(CoETools_Microsoft_Xrm_Data_PowerShell)' '$(XrmDataPowerShellVersion)' '${{parameters.sdkMessageConfiguration}}' "$env:MAPPED_SPN_Token" 13 | } catch { 14 | Write-Host "##vso[task.logissue type=warning]Errors occurred while updating SDK Message Configurations. Please review the log for specific errors and verify your deployment configuration settings." 15 | Write-Host $_ 16 | exit 1; 17 | } 18 | displayName: 'Update SDK Message Configurations' 19 | condition: and(succeeded(), not(contains(variables['outWebHookUrls'], '$('))) 20 | env: 21 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/Templates/update-solution-component-ownership.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: solutionComponentOwnershipConfiguration 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | # load PowerShell files into memory 10 | . "$env:POWERSHELLPATH/util.ps1" 11 | . "$env:POWERSHELLPATH/activate-flows.ps1" 12 | . "$env:POWERSHELLPATH/update-solution-component-owner.ps1" 13 | try { 14 | Invoke-UpdateSolutionComponentOwner '$(connectionVariables.BuildTools.DataverseConnectionString)' '${{parameters.serviceConnection}}' '$(CoETools_Microsoft_Xrm_Data_PowerShell)' '$(XrmDataPowerShellVersion)' '${{parameters.solutionComponentOwnershipConfiguration}}' 15 | } catch { 16 | Write-Host "##vso[task.logissue type=warning]Errors occurred while updating solution component ownership. Please review the log for specific errors and verify your deployment configuration settings." 17 | Write-Host $_ 18 | exit 1; 19 | } 20 | displayName: 'Update Solution Component Ownership' 21 | condition: and(succeeded(), not(contains(variables['outSolutionComponentOwnershipConfiguration'], '$('))) -------------------------------------------------------------------------------- /Pipelines/Templates/update-webhook-urls.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: serviceConnection 3 | type: string 4 | - name: webHookConfiguration 5 | type: string 6 | 7 | steps: 8 | - powershell: | 9 | # load PowerShell files into memory 10 | . "$env:POWERSHELLPATH/webhookurl-helper.ps1" 11 | try { 12 | Update-WebHookUrls '$(connectionVariables.BuildTools.DataverseConnectionString)' '${{parameters.serviceConnection}}' '$(CoETools_Microsoft_Xrm_Data_PowerShell)' '$(XrmDataPowerShellVersion)' '${{parameters.webHookConfiguration}}' "$env:MAPPED_SPN_Token" 13 | } catch { 14 | Write-Host "##vso[task.logissue type=warning]Errors occurred while updating Webhook Urls. Please review the log for specific errors and verify your deployment configuration settings." 15 | Write-Host $_ 16 | exit 1; 17 | } 18 | displayName: 'Update WebHook Urls' 19 | condition: and(succeeded(), not(contains(variables['outWebHookUrls'], '$('))) 20 | env: 21 | MAPPED_SPN_Token: $(SpnToken) -------------------------------------------------------------------------------- /Pipelines/build-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | resources: 3 | repositories: 4 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 5 | type: git 6 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 7 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 8 | trigger: 9 | branches: 10 | include: 11 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 12 | - SampleSolutionName 13 | - main 14 | paths: 15 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 16 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 17 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 18 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 19 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 20 | 21 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 22 | 23 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 24 | variables: 25 | - group: alm-accelerator-variable-group 26 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 27 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 28 | 29 | stages: 30 | - stage: build 31 | displayName: Build 32 | jobs: 33 | - job: buildjob 34 | pool: 35 | ${{ if eq(variables.Agent.Name, 'Azure Pipelines') }}: 36 | vmImage: 'windows-latest' 37 | steps: 38 | - template: Pipelines\Templates\build-unmanaged-managed-Solution.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above -------------------------------------------------------------------------------- /Pipelines/build-deploy-dev-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # variables passed in by API: 2 | # - Project 3 | # - Repo 4 | # - Branch 5 | # - SolutionName 6 | # - ServiceConnection 7 | # - BuildDefinitionId 8 | # - PipelineId 9 | # - EnvironmentName 10 | parameters: 11 | - name: Branch 12 | type: string 13 | - name: BranchToCreate 14 | type: string 15 | default: "Commit to existing branch specified in Branch parameter" 16 | - name: CommitMessage 17 | type: string 18 | - name: Data 19 | type: string 20 | default: "[]" 21 | - name: Email 22 | type: string 23 | - name: Project 24 | type: string 25 | - name: Repo 26 | type: string 27 | - name: ServiceConnectionName 28 | type: string 29 | - name: ServiceConnectionUrl 30 | type: string 31 | - name: SolutionName 32 | type: string 33 | - name: UserName 34 | type: string 35 | - name: PipelineId 36 | type: string 37 | default: "NA" 38 | - name: EnvironmentName 39 | type: string 40 | default: "NA" 41 | - name: BuildDefinitionId 42 | type: string 43 | default: "NA" 44 | - name: ImportUnmanaged 45 | type: string 46 | default: "true" 47 | - name: PortalSiteName 48 | type: string 49 | default: "NA" 50 | - name: PublishCustomizations 51 | type: string 52 | default: "NA" 53 | - name: CommitScope 54 | type: string 55 | default: "1" 56 | - name: AgentPool 57 | type: string 58 | default: "Azure Pipelines" 59 | - name: VMImage 60 | type: string 61 | default: "windows-latest" 62 | trigger: none 63 | pr: none 64 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 65 | resources: 66 | repositories: 67 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 68 | type: git 69 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 70 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 71 | 72 | name: import-${{parameters.SolutionName}} 73 | 74 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 75 | variables: 76 | - group: alm-accelerator-variable-group 77 | - name: SolutionName 78 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 79 | 80 | stages: 81 | - stage: import_unmanaged_solution 82 | displayName: 'Import unmanaged solution' 83 | jobs: 84 | - job: import_unmanaged_solution 85 | 86 | pool: 87 | ${{ if and(ne(parameters.AgentPool, 'NA'), ne(parameters.AgentPool, '')) }}: 88 | name: ${{parameters.AgentPool}} 89 | ${{ if and(ne(parameters.VMImage, 'NA'), ne(parameters.VMImage, '')) }}: 90 | vmImage: ${{parameters.VMImage}} 91 | steps: 92 | - checkout: PipelineRepo 93 | path: 'PipelineUtils' 94 | displayName: 'Checkout Pipeline Branch' 95 | 96 | - template: Pipelines\Templates\import-Solution-To-Dev-Environment.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above 97 | parameters: 98 | branch: ${{parameters.Branch}} 99 | project: ${{parameters.Project}} 100 | repo: ${{parameters.Repo}} 101 | serviceConnectionName: ${{parameters.ServiceConnectionName}} 102 | serviceConnectionUrl: ${{parameters.ServiceConnectionUrl}} 103 | solutionName: ${{parameters.SolutionName}} 104 | environmentName: ${{parameters.EnvironmentName}} 105 | importUnmanaged: ${{parameters.ImportUnmanaged}} -------------------------------------------------------------------------------- /Pipelines/build-deploy-prod-Pipelines.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | parameters: 3 | - name: PipelineDeploymentStage 4 | type: string 5 | default: "PreDeployment" 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 11 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | trigger: 13 | branches: 14 | include: 15 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 16 | - main 17 | paths: 18 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 19 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 20 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 21 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 22 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 23 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 24 | 25 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 26 | 27 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 28 | variables: 29 | - group: alm-accelerator-variable-group 30 | - name: SolutionName 31 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 32 | 33 | stages: 34 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment-Pipelines.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above 35 | parameters: 36 | pipelineDeploymentStage: '${{parameters.PipelineDeploymentStage}}' 37 | -------------------------------------------------------------------------------- /Pipelines/build-deploy-prod-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | resources: 3 | repositories: 4 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 5 | type: git 6 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 7 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 8 | trigger: 9 | branches: 10 | include: 11 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 12 | - main 13 | paths: 14 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 15 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 16 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 17 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 18 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 19 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 20 | 21 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 22 | 23 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 24 | variables: 25 | - group: alm-accelerator-variable-group 26 | - name: SolutionName 27 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 28 | 29 | stages: 30 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above -------------------------------------------------------------------------------- /Pipelines/build-deploy-test-Pipelines.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | parameters: 3 | - name: PipelineDeploymentStage 4 | type: string 5 | default: "PreDeployment" 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 11 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | trigger: 13 | branches: 14 | include: 15 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 16 | - SampleSolutionName 17 | paths: 18 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 19 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 20 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 21 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 22 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 23 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 24 | 25 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 26 | 27 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 28 | variables: 29 | - group: alm-accelerator-variable-group 30 | - name: SolutionName 31 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 32 | 33 | stages: 34 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment-Pipelines.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above 35 | parameters: 36 | pipelineDeploymentStage: '${{parameters.PipelineDeploymentStage}}' -------------------------------------------------------------------------------- /Pipelines/build-deploy-test-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | resources: 3 | repositories: 4 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 5 | type: git 6 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 7 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 8 | trigger: 9 | branches: 10 | include: 11 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 12 | - SampleSolutionName 13 | paths: 14 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 15 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 16 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 17 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 18 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 19 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 20 | 21 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 22 | 23 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 24 | variables: 25 | - group: alm-accelerator-variable-group 26 | - name: SolutionName 27 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 28 | 29 | stages: 30 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above -------------------------------------------------------------------------------- /Pipelines/build-deploy-validation-Pipelines.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | parameters: 3 | - name: PipelineDeploymentStage 4 | type: string 5 | default: "PreDeployment" 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 11 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | trigger: 13 | branches: 14 | include: 15 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 16 | - refs/pull/* 17 | paths: 18 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 19 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 20 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 21 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 22 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 23 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 24 | 25 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 26 | 27 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 28 | variables: 29 | - group: alm-accelerator-variable-group 30 | - name: SolutionName 31 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 32 | 33 | stages: 34 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment-Pipelines.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above 35 | parameters: 36 | pipelineDeploymentStage: '${{parameters.PipelineDeploymentStage}}' -------------------------------------------------------------------------------- /Pipelines/build-deploy-validation-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | resources: 3 | repositories: 4 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 5 | type: git 6 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 7 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 8 | trigger: 9 | branches: 10 | include: 11 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 12 | - refs/pull/* 13 | paths: 14 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 15 | include: # Add any additional paths to include (e.g. SampleSolutionName/PCFControls etc.) 16 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 17 | - SampleSolutionName/Plugins/ # Replace SolutionFolder with actual folder name in repo 18 | - SampleSolutionName/WebResources/ # Replace SolutionFolder with actual folder name in repo 19 | - SampleSolutionName/config/ # Replace SolutionFolder with actual folder name in repo 20 | 21 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 22 | 23 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 24 | variables: 25 | - group: alm-accelerator-variable-group 26 | - name: SolutionName 27 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 28 | 29 | stages: 30 | - template: Pipelines\Templates\build-deploy-Solution-To-Environment.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above -------------------------------------------------------------------------------- /Pipelines/deploy-prod-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # Starter template for: 2 | # Deploying the solution to a production environment 3 | # Deployment is triggered by build completion so no trigger or pr should be specified 4 | trigger: none 5 | pr: none 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 11 | name: RepositoryContainingTheBuildTemplates # This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | pipelines: # The pipelines specify which pipeline this pipeline will trigger off of upon completion. In this case we will run deploy after the build pipeline succeeds 13 | - pipeline: buildPipeline # pipeline name (DO NOT CHANGE THIS VALUE) 14 | source: build-SampleSolutionName # This is the name of the build pipeline that triggers this deployment pipeline 15 | trigger: 16 | branches: 17 | include: #Include the branches you want to trigger on to deploy to the environment specified in the Pipeline variables. 18 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 19 | - main 20 | 21 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 22 | variables: 23 | - group: alm-accelerator-variable-group 24 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 25 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 26 | 27 | stages: 28 | - template: Pipelines\Templates\deploy-Solution-To-Environment.yml@PipelineRepo -------------------------------------------------------------------------------- /Pipelines/deploy-prod-pipelineartifact-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # Starter template for: 2 | # Deploying the solution to a validation environment 3 | # Deployment is triggered by build completion so no trigger or pr should be specified 4 | trigger: 5 | branches: 6 | include: 7 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a production deployment. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 8 | - main 9 | pr: none 10 | resources: 11 | repositories: 12 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 13 | type: git 14 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 15 | name: RepositoryContainingTheBuildTemplates # This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 16 | pipelines: # The pipelines specify which pipeline this pipeline will pull artifacts from. In this case we will deploy from the test build's artifacts 17 | - pipeline: buildPipeline # pipeline name (DO NOT CHANGE THIS VALUE) 18 | source: deploy-test-SampleSolution # This is the name of the build pipeline that contains the artifacts to be deployed by this pipeline. NOTE: There must be a successful build for this pipeline available in order to run this pipeline 19 | 20 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 21 | variables: 22 | - group: alm-accelerator-variable-group 23 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 24 | value: 'SampleSolution' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 25 | 26 | stages: 27 | - template: Pipelines\Templates\deploy-Solution-To-Environment.yml@PipelineRepo -------------------------------------------------------------------------------- /Pipelines/deploy-test-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # Starter template for: 2 | # Deploying the solution to a validation environment 3 | # Deployment is triggered by build completion so no trigger or pr should be specified 4 | trigger: none 5 | pr: none 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 11 | name: RepositoryContainingTheBuildTemplates # This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | pipelines: # The pipelines specify which pipeline this pipeline will trigger off of upon completion. In this case we will run deploy after the build pipeline succeeds 13 | - pipeline: buildPipeline # pipeline name (DO NOT CHANGE THIS VALUE) 14 | source: build-SampleSolutionName # This is the name of the build pipeline that triggers this deployment pipeline 15 | trigger: 16 | branches: 17 | include: #Include the branches you want to trigger on to deploy to the environment specified in the Pipeline variables. refs/pull/* will run on PR Builds 18 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 19 | - SampleSolutionName 20 | 21 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 22 | variables: 23 | - group: alm-accelerator-variable-group 24 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 25 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 26 | 27 | stages: 28 | - template: Pipelines\Templates\deploy-Solution-To-Environment.yml@PipelineRepo -------------------------------------------------------------------------------- /Pipelines/deploy-test-pipelineartifact-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # Starter template for: 2 | # Deploying the solution to a validation environment 3 | # Deployment is triggered by build completion so no trigger or pr should be specified 4 | trigger: 5 | branches: 6 | include: 7 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a production deployment. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 8 | - SampleSolution 9 | pr: none 10 | resources: 11 | repositories: 12 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 13 | type: git 14 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise the default branch will be used by default. 15 | name: RepositoryContainingTheBuildTemplates # This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 16 | pipelines: # The pipelines specify which pipeline this pipeline will pull artifacts from. In this case we will deploy from the test build's artifacts 17 | - pipeline: buildPipeline # pipeline name (DO NOT CHANGE THIS VALUE) 18 | source: deploy-validation-SampleSolution # This is the name of the build pipeline that contains the artifacts to be deployed by this pipeline. NOTE: There must be a successful build for this pipeline available in order to run this pipeline 19 | 20 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 21 | variables: 22 | - group: alm-accelerator-variable-group 23 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 24 | value: 'SampleSolution' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 25 | 26 | stages: 27 | - template: Pipelines\Templates\deploy-Solution-To-Environment.yml@PipelineRepo -------------------------------------------------------------------------------- /Pipelines/deploy-validation-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | # Starter template for: 2 | # Deploying the solution to a validation environment 3 | # Deployment is triggered by build completion so no trigger or pr should be specified 4 | trigger: none 5 | pr: none 6 | resources: 7 | repositories: 8 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 9 | type: git 10 | ref: BranchContainingTheBuildTemplates # If your pipeline templates are in a branch other than the default branch specify the branch here. Otherwise remove this parameter. 11 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 12 | pipelines: # The pipelines specify which pipeline this pipeline will trigger off of upon completion. In this case we will run deploy after the build pipeline succeeds 13 | - pipeline: buildPipeline # pipeline name (DO NOT CHANGE THIS VALUE) 14 | source: build-SampleSolutionName # This is the name of the build pipeline that triggers this deployment pipeline 15 | trigger: 16 | branches: 17 | include: #Include the branches you want to trigger on to deploy to the environment specified in the Pipeline variables. refs/pull/* will run on PR Builds 18 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 19 | - refs/pull/* 20 | 21 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 22 | variables: 23 | - group: alm-accelerator-variable-group 24 | - name: SolutionName # variable Name (DO NOT CHANGE THIS VALUE) 25 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 26 | 27 | stages: 28 | - template: Pipelines\Templates\deploy-Solution-To-Environment.yml@PipelineRepo -------------------------------------------------------------------------------- /Pipelines/export-solution-to-git.yml: -------------------------------------------------------------------------------- 1 | # This pipeline gets triggered manually or via an API call. 2 | # It is a general purpose automation that allows you to export a solution from a Dataverse environment and commit it to a git branch. 3 | # It facilitates: 4 | # -Ensuring user can be part of the commit (instead of a generic user), thereby enabling tracking of who made what changes 5 | # -Reuse for any solution in any Dataverse environment 6 | 7 | # The following variables need to be set when the pipeline is queued to run: 8 | # Project: The name of the Azure DevOps Project to which we are exporting our solution. 9 | # Repo: The name of the Azure DevOps Repo to which we are exporting our solution. 10 | # Branch: The name of the Azure DevOps Branch in the Repo above to which we are exporting our solution. 11 | # BranchToCreate: The name of the new Azure DevOps Branch to create in the Repo above to which we are exporting our solution (Optional). 12 | # CommitMessage: The commit message for this commit. 13 | # Email: The email of the user performing the commit. 14 | # ServiceConnectionUrl: The url of the service connection to Power Platform from which we'll be pulling the solution. 15 | # ServiceConnectionName: The name of the service connection to Power Platform from which we'll be pulling the solution. 16 | # SolutionName: The name of the solution being exported (i.e. Not the Display Name). 17 | # UserName: The Display name of the user performing the commit. 18 | # Data: Additional Data (e.g. JSON Payload) for the export to configure deployment pipelines 19 | # PipelineId to create a Github release for the CoE Starter Kit solution 20 | # PortalSiteName: Power Apps Portal/Power Pages website name 21 | # CommitScope: Options are Commit Solution and Settings (1), Commit Solution Only (2), Commit Settings Only (3) 22 | parameters: 23 | - name: Branch 24 | type: string 25 | - name: BranchToCreate 26 | type: string 27 | default: "Commit to existing branch specified in Branch parameter" 28 | - name: CommitMessage 29 | type: string 30 | - name: Data 31 | type: string 32 | default: "[]" 33 | - name: Email 34 | type: string 35 | - name: Project 36 | type: string 37 | - name: Repo 38 | type: string 39 | - name: ServiceConnectionName 40 | type: string 41 | - name: ServiceConnectionUrl 42 | type: string 43 | - name: SolutionName 44 | type: string 45 | - name: UserName 46 | type: string 47 | - name: PipelineId 48 | type: string 49 | default: "NA" 50 | - name: EnvironmentName 51 | type: string 52 | default: "NA" 53 | - name: BuildDefinitionId 54 | type: string 55 | default: "NA" 56 | - name: ImportUnmanaged 57 | type: string 58 | default: "NA" 59 | - name: PortalSiteName 60 | type: string 61 | default: "NA" 62 | - name: PublishCustomizations 63 | type: string 64 | default: "true" 65 | - name: CommitScope 66 | type: string 67 | default: "1" 68 | - name: AgentPool 69 | type: string 70 | default: "Azure Pipelines" 71 | - name: VMImage 72 | type: string 73 | default: "windows-latest" 74 | trigger: none 75 | pr: none 76 | 77 | name: export-${{parameters.SolutionName}}-to-git-branch 78 | 79 | variables: 80 | - group: alm-accelerator-variable-group 81 | 82 | stages: 83 | - stage: export_solution_to_git 84 | displayName: 'Export solution to git' 85 | jobs: 86 | - job: export_solution_to_git 87 | 88 | pool: 89 | ${{ if and(ne(parameters.AgentPool, 'NA'), ne(parameters.AgentPool, '')) }}: 90 | name: ${{parameters.AgentPool}} 91 | ${{ if and(ne(parameters.VMImage, 'NA'), ne(parameters.VMImage, '')) }}: 92 | vmImage: ${{parameters.VMImage}} 93 | steps: 94 | - pwsh: | 95 | # Workaround for git Filename too long issue 96 | git config --system core.longpaths true 97 | displayName: "Configuring core.longpaths true" 98 | 99 | # Checkout Pipelines 100 | - checkout: self 101 | path: 'PipelineUtils' 102 | displayName: 'Checkout Pipeline Branch' 103 | 104 | # Checkout our Branch 105 | - checkout: git://${{parameters.Project}}/${{parameters.Repo}}@${{parameters.Branch}} 106 | displayName: 'Checkout Source Branch' 107 | 108 | # Other tasks, which use the PowerApps PowerShell cmdlets, need the environment guid. Setting it for future use. 109 | - template: Templates\export-Solution.yml 110 | parameters: 111 | project: ${{parameters.Project}} 112 | repo: ${{parameters.Repo}} 113 | branch: ${{parameters.Branch}} 114 | branchToCreate: ${{parameters.BranchToCreate}} 115 | commitMessage: ${{parameters.CommitMessage}} 116 | email: ${{parameters.Email}} 117 | serviceConnectionName: ${{parameters.ServiceConnectionName}} 118 | serviceConnectionUrl: ${{parameters.ServiceConnectionUrl}} 119 | solutionName: ${{parameters.SolutionName}} 120 | userName: ${{parameters.UserName}} 121 | configurationData: ${{parameters.Data}} 122 | portalSiteName: ${{parameters.PortalSiteName}} 123 | publishCustomizations: ${{parameters.PublishCustomizations}} 124 | commitScope: ${{parameters.CommitScope}} 125 | createSolutionBranch: 'true' 126 | agentPool: ${{parameters.AgentPool}} -------------------------------------------------------------------------------- /Pipelines/import-unmanaged-to-dev-environment.yml: -------------------------------------------------------------------------------- 1 | # variables passed in by API: 2 | # - Project 3 | # - Repo 4 | # - Branch 5 | # - SolutionName 6 | # - ServiceConnection 7 | # - BuildDefinitionId 8 | # - PipelineId 9 | # - EnvironmentName 10 | parameters: 11 | - name: Branch 12 | type: string 13 | - name: BranchToCreate 14 | type: string 15 | default: "Commit to existing branch specified in Branch parameter" 16 | - name: CommitMessage 17 | type: string 18 | - name: Data 19 | type: string 20 | default: "[]" 21 | - name: Email 22 | type: string 23 | - name: Project 24 | type: string 25 | - name: Repo 26 | type: string 27 | - name: ServiceConnectionName 28 | type: string 29 | - name: ServiceConnectionUrl 30 | type: string 31 | - name: SolutionName 32 | type: string 33 | - name: UserName 34 | type: string 35 | - name: PipelineId 36 | type: string 37 | default: "NA" 38 | - name: EnvironmentName 39 | type: string 40 | default: "NA" 41 | - name: BuildDefinitionId 42 | type: string 43 | default: "NA" 44 | - name: ImportUnmanaged 45 | type: string 46 | default: "true" 47 | - name: PortalSiteName 48 | type: string 49 | default: "NA" 50 | - name: PublishCustomizations 51 | type: string 52 | default: "NA" 53 | - name: CommitScope 54 | type: string 55 | default: "1" 56 | - name: AgentPool 57 | type: string 58 | default: "Azure Pipelines" 59 | - name: VMImage 60 | type: string 61 | default: "windows-latest" 62 | trigger: none 63 | pr: none 64 | 65 | variables: 66 | - group: alm-accelerator-variable-group 67 | 68 | name: import-${{parameters.SolutionName}} 69 | 70 | stages: 71 | - stage: import_unmanaged_solution 72 | displayName: 'Import unmanaged solution' 73 | jobs: 74 | - job: import_unmanaged_solution 75 | 76 | pool: 77 | ${{ if and(ne(parameters.AgentPool, 'NA'), ne(parameters.AgentPool, '')) }}: 78 | name: ${{parameters.AgentPool}} 79 | ${{ if and(ne(parameters.VMImage, 'NA'), ne(parameters.VMImage, '')) }}: 80 | vmImage: ${{parameters.VMImage}} 81 | steps: 82 | - checkout: self 83 | path: 'PipelineUtils' 84 | displayName: 'Checkout Pipeline Branch' 85 | 86 | - template: Templates\import-Solution-To-Dev-Environment.yml 87 | parameters: 88 | branch: ${{parameters.Branch}} 89 | project: ${{parameters.Project}} 90 | repo: ${{parameters.Repo}} 91 | serviceConnectionName: ${{parameters.ServiceConnectionName}} 92 | serviceConnectionUrl: ${{parameters.ServiceConnectionUrl}} 93 | solutionName: ${{parameters.SolutionName}} 94 | environmentName: ${{parameters.EnvironmentName}} 95 | importUnmanaged: ${{parameters.ImportUnmanaged}} -------------------------------------------------------------------------------- /Pipelines/sync-pipeline-repo.yml: -------------------------------------------------------------------------------- 1 | # This pipeline gets triggered manually or via an API call. 2 | # The pipeline is designed to synchronize a github repository into an Azure DevOps git repositiory. 3 | # It facilitates: 4 | # -Synchronization of main branch or a tag from source git repository into the Azure DevOps git repository 5 | # -Create a Pull Request to merge changes 6 | # -Assign Pull Request to the user that requested the pipeline to run. 7 | 8 | # The following variables need to be set when the pipeline is queued to run: 9 | # Template-repo: The source github repository to be synced into the Azure DevOps git repository that this pipeline is in. 10 | # Example: https://github.com/microsoft/coe-alm-accelerator-templates 11 | 12 | # The following parameters are required for pipeline to run: 13 | # SyncFrom: Either Branch or Tag to define if sync is from a branch or a tag 14 | # SourceBranchOrTag: The branch or tag in remote repository to sync from. If SyncFrom is Branch a branc name is expected. If SyncFrom is Tag a tag is expected 15 | # BranchToCreate: The name of the new Azure DevOps Branch to create in the Repo above to which we are pulling main branch from Template-repo. 16 | # TargetBranch: The name of the Azure DevOps Branch to target for the Pull Request. 17 | 18 | parameters: 19 | - name: SyncFrom 20 | type: string 21 | values: 22 | - Branch 23 | - Tag 24 | default: Branch 25 | - name: SourceBranchOrTag 26 | type: string 27 | default: 'main' 28 | - name: BranchToCreate 29 | type: string 30 | default: 'update-from-original-repo' 31 | - name: TargetBranch 32 | type: string 33 | default: 'main' 34 | - name: AutoCompletePR 35 | type: string 36 | values: 37 | - false 38 | - true 39 | default: false 40 | - name: DeleteSourceBranch 41 | type: string 42 | values: 43 | - false 44 | - true 45 | default: false 46 | 47 | trigger: none 48 | pr: none 49 | 50 | stages: 51 | - stage: sync 52 | displayName: Sync 53 | jobs: 54 | - job: syncjob 55 | pool: 56 | vmImage: 'windows-latest' 57 | steps: 58 | - checkout: self 59 | persistCredentials: true 60 | 61 | - pwsh: | 62 | Write-Host "##vso[task.setvariable variable=syncTemplateRepo]$(TEMPLATE-REPO)" 63 | displayName: 'Sync from $(TEMPLATE-REPO)' 64 | condition: ne(variables['TEMPLATE-REPO'], '') 65 | 66 | - pwsh: | 67 | Write-Host "##vso[task.setvariable variable=syncTemplateRepo]https://github.com/microsoft/coe-alm-accelerator-templates" 68 | displayName: 'Sync from https://github.com/microsoft/coe-alm-accelerator-templates' 69 | condition: eq(variables['TEMPLATE-REPO'], '') 70 | 71 | 72 | # add Azure DevOps extension 73 | - script: az extension add -n azure-devops 74 | displayName: 'Install Azure DevOps Extension' 75 | 76 | # sign in to Azure DevOps 77 | - script: echo $(System.AccessToken) | az devops login 78 | env: 79 | AZURE_DEVOPS_CLI_PAT: $(System.AccessToken) 80 | displayName: 'Login Azure DevOps Extension' 81 | 82 | # Configure email/name 83 | - script: | 84 | git config --global user.email "$(Build.RequestedForEmail)" 85 | git config --global user.name "$(Build.RequestedFor)" 86 | displayName: 'Set git user info' 87 | 88 | # Add remote and fetch source branch 89 | - script: | 90 | git remote add template-repo $(syncTemplateRepo) 91 | git fetch template-repo ${{parameters.SourceBranchOrTag}} 92 | displayName: 'Fetch branch from cloned repository' 93 | condition: and(succeeded(), eq('${{parameters.SyncFrom}}', 'Branch')) 94 | 95 | # Add remote and fetch source tag 96 | - script: | 97 | git remote add template-repo $(syncTemplateRepo) 98 | git fetch template-repo refs/tags/${{parameters.SourceBranchOrTag}}:refs/tags/${{parameters.SourceBranchOrTag}} 99 | displayName: 'Fetch tag from cloned repository' 100 | condition: and(succeeded(), eq('${{parameters.SyncFrom}}', 'Tag')) 101 | 102 | # Create new branch and push to origin 103 | - script: | 104 | git checkout -b ${{parameters.BranchToCreate}} template-repo/${{parameters.SourceBranchOrTag}} 105 | git push origin +${{parameters.BranchToCreate}} 106 | displayName: 'Push branch to ${{parameters.BranchToCreate}} to AzDO repo' 107 | condition: and(succeeded(), eq('${{parameters.SyncFrom}}', 'Branch')) 108 | 109 | # Create new branch from tag and push to origin 110 | - script: | 111 | git checkout tags/${{parameters.SourceBranchOrTag}} -b ${{parameters.BranchToCreate}} 112 | git push origin +${{parameters.BranchToCreate}} 113 | displayName: 'Push tag to ${{parameters.BranchToCreate}} to AzDO repo' 114 | condition: and(succeeded(), eq('${{parameters.SyncFrom}}', 'Tag')) 115 | 116 | # Set default org and project 117 | - powershell: az devops configure --defaults organization=$(System.CollectionUri) project="$(System.TeamProject)" 118 | displayName: 'Set az devops defaults' 119 | 120 | # Create Pull Requests 121 | - powershell: az repos pr create --source-branch ${{parameters.BranchToCreate}} --delete-source-branch ${{parameters.DeleteSourceBranch}} --auto-complete ${{parameters.autoCompletePR}} --target-branch ${{parameters.TargetBranch}} --reviewers="$(Build.RequestedForEmail)" --title 'Sync from cloned repository' 122 | displayName: 'Create PR - ${{parameters.BranchToCreate}} to ${{parameters.TargetBranch}}' 123 | -------------------------------------------------------------------------------- /Pipelines/trigger-SampleSolution.yml: -------------------------------------------------------------------------------- 1 | #You can use this sample template if you plan to store the pipeline templates in one repo and your pipeline YAML in another. This is useful for when you have multiple repos and don't want to maintain the templates in multiple places.' 2 | resources: 3 | repositories: 4 | - repository: PipelineRepo # repository name (DO NOT CHANGE THIS VALUE) 5 | type: git 6 | name: RepositoryContainingTheBuildTemplates #This is the name of the repo in the current project in Azure Devops that has the pipeline templates. If the repo is in a different project you can specify the project and repo using the format ProjectContainingTheBuildTemplates/RepositoryContainingTheBuildTemplates (https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#type) 7 | trigger: 8 | branches: 9 | include: 10 | # Replace the following with actual branch name(s) in your repo for which you want to trigger a build. The assumption here is that you only want to only trigger on a change to a specific branch rather than a change on any branch which would include your working branch for development.' 11 | - SampleSolutionName 12 | - main 13 | paths: 14 | # Replace 'SolutionFolderInBranch' with actual folder name in repo (i.e. this will be the folder that contains your unpacked solution. In the case where you are using a repo per solution you may not need to specify this value) 15 | include: 16 | - SampleSolutionName/SolutionPackage/ # Replace SolutionFolder with actual folder name in repo 17 | 18 | name: 1.0.$(Date:yyyyMMdd)$(Rev:.r) 19 | 20 | # NOTE: If you want to use different values for these variables, you can remove the variable group and attach them directly to this pipeline. The group specified below is a variable group defined in the Library for the Pipelines 21 | variables: 22 | - group: alm-accelerator-variable-group 23 | - name: SolutionName 24 | value: 'SampleSolutionName' #Replace with the actual name of the solution you are building. (NOTE: Not the Display Name) 25 | 26 | stages: 27 | - template: Pipelines\Templates\build-deploy-Solution.yml@PipelineRepo #The @PipelineRepo tells the pipeline where to look for the templates based on the name of the repo you specified above -------------------------------------------------------------------------------- /PowerShell/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Update Deployment Settings Test", 9 | "type": "PowerShell", 10 | "request": "launch", 11 | "script": "${workspaceFolder}/update-deployment-settings-test.ps1", 12 | "cwd": "${workspaceFolder}" 13 | }, 14 | { 15 | "name": "Enable Disable Solution Flows Test", 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "script": "${workspaceFolder}/enable-disable-solution-flows-test.ps1", 19 | "cwd": "${workspaceFolder}" 20 | }, 21 | { 22 | "name": "Canvas Unpack / Pack Test", 23 | "type": "PowerShell", 24 | "request": "launch", 25 | "script": "${workspaceFolder}/canvas-unpack-pack-test.ps1", 26 | "cwd": "${workspaceFolder}" 27 | }, 28 | { 29 | "type": "PowerShell", 30 | "request": "launch", 31 | "name": "PowerShell Launch Current File", 32 | "script": "${file}", 33 | "args": [], 34 | "cwd": "${file}" 35 | } 36 | 37 | ] 38 | } -------------------------------------------------------------------------------- /PowerShell/archive-configuration-migration-data.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function archives configuration migration data. 3 | #> 4 | function Invoke-ArchiveConfigurationMigrationData { 5 | param ( 6 | [Parameter(Mandatory)] [String]$buildSourceDirectory, 7 | [Parameter(Mandatory)] [String]$artifactStagingDirectory, 8 | [Parameter(Mandatory)] [String]$repo, 9 | [Parameter(Mandatory)] [String]$solutionName 10 | ) 11 | $path = "$buildSourceDirectory\$repo\$solutionName\config\ConfigurationMigrationData" 12 | if(Test-Path $path) { 13 | $compress = @{ 14 | Path = $path + '\*.*' 15 | CompressionLevel = 'Fastest' 16 | DestinationPath = "$artifactStagingDirectory/ConfigurationMigrationData.zip" 17 | } 18 | Compress-Archive @compress 19 | } 20 | $settingFiles = @("deploymentSettings","customDeploymentSettings") 21 | 22 | foreach ($settingFile in $settingFiles) { 23 | $path = "$buildSourceDirectory\$repo\$solutionName\config\$settingFile.json" 24 | if(Test-Path $path) { 25 | Copy-Item $path "$artifactStagingDirectory/$settingFile.json" 26 | } 27 | } 28 | 29 | if(Test-Path "$buildSourceDirectory\$repo\$solutionName\config") { 30 | Get-ChildItem -Path "$buildSourceDirectory\$repo\$solutionName\config" | 31 | ForEach-Object { 32 | $environment = $_.Name 33 | $path = "$buildSourceDirectory\$repo\$solutionName\config\$environment\ConfigurationMigrationData" 34 | if(Test-Path $path) { 35 | $compress = @{ 36 | Path = $path + '\*.*' 37 | CompressionLevel = 'Fastest' 38 | DestinationPath = "$artifactStagingDirectory/ConfigurationMigrationData-" + $environment + ".zip" 39 | } 40 | Compress-Archive @compress 41 | } 42 | foreach ($settingFile in $settingFiles) { 43 | $path = "$buildSourceDirectory\$repo\$solutionName\config\$environment\$settingFile.json" 44 | if(Test-Path $path) { 45 | Copy-Item $path "$artifactStagingDirectory/$settingFile-$environment.json" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /PowerShell/build-test-automation-urls.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function sets the canvas test Automation URLs. 3 | Testable outside of agent. 4 | #> 5 | function Set-CanvasTestAutomationURLs { 6 | param ( 7 | [Parameter(Mandatory)] [String]$token, 8 | [Parameter(Mandatory)] [String]$url, 9 | [Parameter(Mandatory)] [String]$solutionName, 10 | [Parameter(Mandatory)] [String]$canvasAppsPath, 11 | [Parameter(Mandatory)] [String]$environmentId 12 | ) 13 | $filter = ".meta.xml" 14 | [System.Collections.ArrayList]$testUrls = @() 15 | $testUrlsObject = New-Object -TypeName PSObject 16 | $testUrlsObject | Add-Member -MemberType NoteProperty -Name TestURLs -Value $testUrls 17 | 18 | Write-Host "CanvasAppsPath - $canvasAppsPath" 19 | if(Test-Path $canvasAppsPath) { 20 | $canvasApps = Get-ChildItem "$canvasAppsPath" -File -Recurse | Where-Object { $_.Name -like "*$filter" } 21 | Write-Host "Printing matched $filter files" 22 | foreach ($file in $canvasApps) { 23 | Write-Host "File: $($file.Name)" 24 | } 25 | $asTestCase = "As TestCase" 26 | $hostUrl = Get-HostFromUrl $url 27 | foreach ($app in $canvasApps) { 28 | Write-Host "App Name $($app.Name)" 29 | $appName = $app.Name.Replace($filter, "") 30 | $appDirectory = $app.Directory.ToString() 31 | $appSrcDirectory = "$appDirectory\src\$appName\Src\Tests" 32 | 33 | Write-Host "AppName - $appName" 34 | Write-Host "AppDirectory - $appDirectory" 35 | Write-Host "AppSrcDirectory - $appSrcDirectory" 36 | 37 | if(Test-Path $appSrcDirectory) { 38 | $testFiles = Get-ChildItem $appSrcDirectory 39 | $appId = $null 40 | 41 | foreach ($testFile in $testFiles) { 42 | Write-Host "Fetching content of file - "$($testFile.FullName) 43 | $lines = Get-Content $testFile.FullName 44 | 45 | foreach ($line in $lines) { 46 | if ($line.Contains($asTestCase)) { 47 | Write-Host "Test files contains $asTestCase" 48 | $pipeDelimeter = "|" 49 | $testCaseId = $line.Replace($asTestCase,$pipeDelimeter).Split($pipeDelimeter)[0].Replace("`"", "").Replace("'", "").Trim() 50 | $canvasAppResponse = Get-CanvasAppData $token $hostUrl $appName 51 | $testUrl = $canvasAppResponse.value[0].appopenuri 52 | $canvasAppId = $canvasAppResponse.value[0].canvasappid 53 | #TestURL format https://apps.powerapps.com/play/{appId}?tenantId={tenantId}&__PATestCaseId={testCaseId} 54 | $tenantId = [regex]::Match($testUrl, "tenantId=([^&]+)").Groups[1].Value 55 | $playHost = "https://apps.powerapps.com/play/" 56 | $testUrl = "$($playHost)$($canvasAppId)?tenantId=$tenantId&__PATestCaseId=$testCaseId&source=testStudioLink" 57 | Write-Host "Adding TestUrl - $testUrl" 58 | $testUrls.Add($testUrl) 59 | # We need to bypass consent. Otherwise the test might fail. 60 | if ($null -eq $appId) { 61 | $appId = $canvasAppId 62 | Write-Host "AppId - $appId" 63 | try 64 | { 65 | Set-AdminPowerAppApisToBypassConsent -EnvironmentName $environmentId -AppName $appId 66 | Write-Host "Bypassed consent for $appId" 67 | }catch { 68 | Write-Host "Set-AdminPowerAppApisToBypassConsent execution failed - $($_.Exception.Message)" 69 | } 70 | } 71 | } 72 | } 73 | } 74 | }else{ 75 | Write-Host "AppSrcDirectory - $appSrcDirectory is unavailable." 76 | } 77 | } 78 | } 79 | 80 | $json = ConvertTo-Json $testUrlsObject 81 | if ($PSVersionTable.PSVersion.Major -gt 5) { 82 | Set-Content -Path "CanvasTestAutomationURLs.json" -Value $json -Force -Encoding utf8NoBOM 83 | } 84 | else { 85 | $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $false 86 | $jsonBytes = $utf8NoBomEncoding.GetBytes($json) 87 | Set-Content -Path "CanvasTestAutomationURLs.json" -Value $jsonBytes -Encoding Byte 88 | } 89 | 90 | $CanvasTestAutomationURLsContent = Get-Content -Path "CanvasTestAutomationURLs.json" 91 | Write-Host "CanvasTestAutomationURLsContent - $CanvasTestAutomationURLsContent" 92 | } 93 | 94 | <# 95 | This function gets the canvas app play url. 96 | #> 97 | function Get-CanvasAppData { 98 | param ( 99 | [Parameter(Mandatory)] [String]$token, 100 | [Parameter(Mandatory)] [String]$hostUrl, 101 | [Parameter(Mandatory)] [String]$canvasName 102 | ) 103 | $odataQuery = 'canvasapps?$filter=name eq ''' + $canvasName + '''&$select=appopenuri,canvasappid' 104 | $response = Invoke-DataverseHttpGet $token $hostUrl $odataQuery 105 | return $response 106 | } -------------------------------------------------------------------------------- /PowerShell/dataverse-webapi-functions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function returns the Access Token. 3 | Testable outside of agent 4 | #> 5 | function Get-SpnToken { 6 | param ( 7 | [Parameter(Mandatory)] [String]$tenantId, 8 | [Parameter(Mandatory)] [String]$clientId, 9 | [Parameter(Mandatory)] [String]$clientSecret, 10 | [Parameter(Mandatory)] [String]$dataverseHost, 11 | [Parameter(Mandatory)] [String]$aadHost 12 | ) 13 | $body = @{client_id = $clientId; client_secret = $clientSecret; grant_type = "client_credentials"; scope = "https://$dataverseHost/.default"; } 14 | $OAuthReq = Invoke-RestMethod -Method Post -Uri "https://$aadHost/$tenantId/oauth2/v2.0/token" -Body $body 15 | 16 | return $OAuthReq.access_token 17 | } 18 | 19 | <# 20 | This function splits the environment url and returns the Host. 21 | #> 22 | function Get-HostFromUrl { 23 | param ( 24 | [Parameter(Mandatory)] [String]$url 25 | ) 26 | $options = [System.StringSplitOptions]::RemoveEmptyEntries 27 | return $url.Split("://", $options)[1].Split("/")[0] 28 | } 29 | 30 | <# 31 | This function reads the Spn Token and sets SpnToken variable. 32 | #> 33 | function Set-SpnTokenVariableWithinAgent { 34 | param ( 35 | [Parameter(Mandatory)] [String]$tenantId, 36 | [Parameter(Mandatory)] [String]$clientId, 37 | [Parameter(Mandatory)] [String]$clientSecret, 38 | [Parameter(Mandatory)] [String]$serviceConnection, 39 | [Parameter(Mandatory)] [String]$aadHost 40 | ) 41 | $dataverseHost = Get-HostFromUrl $serviceConnection 42 | 43 | $spnToken = Get-SpnToken $tenantId $clientId $clientSecret $dataverseHost $aadHost 44 | 45 | Write-Host "##vso[task.setvariable variable=SpnToken;issecret=true]$spnToken" 46 | } 47 | 48 | <# 49 | This function sets the header parameters. 50 | #> 51 | function Set-DefaultHeaders { 52 | param ( 53 | [Parameter(Mandatory)] [String]$token 54 | ) 55 | $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 56 | $headers.Add("Authorization", "Bearer $token") 57 | $headers.Add("Content-Type", "application/json") 58 | return $headers 59 | } 60 | 61 | <# 62 | This function sets the url by joining the host url and requestUrlRemainder. 63 | #> 64 | function Set-RequestUrl { 65 | param ( 66 | [Parameter(Mandatory)] [String]$dataverseHost, 67 | [Parameter(Mandatory)] [String]$requestUrlRemainder 68 | ) 69 | $requestUrl = "https://$dataverseHost/api/data/v9.2/$requestUrlRemainder" 70 | return $requestUrl 71 | } 72 | 73 | <# 74 | This function invokes Dataverse web api GET. 75 | #> 76 | function Invoke-DataverseHttpGet { 77 | param ( 78 | [Parameter(Mandatory)] [String]$token, 79 | [Parameter(Mandatory)] [String]$dataverseHost, 80 | [Parameter(Mandatory)] [String]$requestUrlRemainder 81 | ) 82 | $headers = Set-DefaultHeaders $token 83 | $requestUrl = Set-RequestUrl $dataverseHost $requestUrlRemainder 84 | $response = Invoke-RestMethod $requestUrl -Method 'GET' -Headers $headers 85 | return $response 86 | } 87 | 88 | <# 89 | This function invokes Dataverse web api POST. 90 | #> 91 | function Invoke-DataverseHttpPost { 92 | param ( 93 | [Parameter(Mandatory)] [String]$token, 94 | [Parameter(Mandatory)] [String]$dataverseHost, 95 | [Parameter(Mandatory)] [String]$requestUrlRemainder, 96 | [Parameter(Mandatory)] [String]$body 97 | ) 98 | $headers = Set-DefaultHeaders $token 99 | $requestUrl = Set-RequestUrl $dataverseHost $requestUrlRemainder 100 | $response = Invoke-RestMethod $requestUrl -Method 'POST' -Headers $headers -Body $body 101 | return $response 102 | } 103 | 104 | <# 105 | This function triggers to download the unmanaged and managed solution pipeline artifacts. 106 | #> 107 | function Invoke-Download-Solution-Artifact{ 108 | param ( 109 | [Parameter(Mandatory)] [String]$pipelineConnectionUrl, 110 | [Parameter(Mandatory)] [String]$aadHost, 111 | [Parameter(Mandatory)] [String]$clientId, 112 | [Parameter(Mandatory)] [String]$clientSecret, 113 | [Parameter(Mandatory)] [String]$tenantID, 114 | [Parameter(Mandatory)] [String]$solutionZipDirectory, 115 | [Parameter(Mandatory)] [String]$repo, 116 | [Parameter(Mandatory)] [String]$solutionName, 117 | [Parameter(Mandatory)] [String]$artifactUrl 118 | ) 119 | 120 | #Download the unmanaged and managed solution zips 121 | $dataverseHost = Get-HostFromUrl "$pipelineConnectionUrl" 122 | $spnToken = Get-SpnToken "$tenantID" "$clientId" "$clientSecret" "$dataverseHost" "$aadHost" 123 | 124 | $headers = Set-DefaultHeaders $spnToken 125 | $response = Invoke-RestMethod $artifactUrl -Method 'GET' -Headers $headers 126 | 127 | $bytes = [Convert]::FromBase64String($response.value) 128 | [IO.File]::WriteAllBytes("$solutionZipDirectory\$solutionName" + "_managed.zip", $bytes) 129 | 130 | $unmanagedArtifactUrl = "$artifactUrl".Replace("artifactfile", "artifactfileunmanaged") 131 | $response = Invoke-RestMethod "$unmanagedArtifactUrl" -Method 'GET' -Headers $headers 132 | $bytes = [Convert]::FromBase64String($response.value) 133 | [IO.File]::WriteAllBytes("$solutionZipDirectory\$solutionName.zip", $bytes) 134 | } 135 | -------------------------------------------------------------------------------- /PowerShell/enable-disable-solution-flows.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function either enables or disables flows based on deployment settings. 3 | #> 4 | function Set-EnableDisableSolutionFlows { 5 | param ( 6 | [Parameter(Mandatory)] [String]$buildSourceDirectory, 7 | [Parameter(Mandatory)] [String]$repo, 8 | [Parameter(Mandatory)] [String]$solutionName, 9 | [Parameter(Mandatory)] [String]$disableAllFlows, 10 | [Parameter()] [String]$activateFlowConfigJson 11 | ) 12 | 13 | $workflowspath = "$buildSourceDirectory\$repo\$solutionName\SolutionPackage\src\Workflows"; 14 | 15 | if(-not [string]::IsNullOrEmpty($workflowspath)) 16 | { 17 | if ($disableAllFlows -eq 'true') { 18 | Get-ChildItem -Path "$workflowspath" -Recurse -Filter *.xml | 19 | ForEach-Object { 20 | If(Test-Path $_.FullName){ 21 | $xml = [xml](Get-Content $_.FullName) 22 | $workflowNode = $xml.SelectSingleNode("//Workflow") 23 | $workflowNode.StateCode = '0' 24 | $workflowNode.StatusCode = '1' 25 | $xml.Save($_.FullName) 26 | } 27 | else{ 28 | Write-Host "Path unavailable - " $_.FullName 29 | } 30 | } 31 | } 32 | else { 33 | Write-Host $activateFlowConfigJson 34 | if ($activateFlowConfigJson -ne '') { 35 | #Disable / Enable flows based on configuration 36 | If(Test-Path $activateFlowConfigJson){ 37 | $activateFlowConfigs = Get-Content $activateFlowConfigJson | ConvertFrom-Json 38 | Write-Host "Retrieved " $activateFlowConfigs.Length " flow activation configurations" 39 | foreach ($activateFlowConfig in $activateFlowConfigs) { 40 | $filter = "*" + $activateFlowConfig.solutionComponentUniqueName + "*.xml" 41 | Get-ChildItem -Path "$workflowspath" -Recurse -Filter $filter | 42 | ForEach-Object { 43 | if(Test-Path $_.FullName){ 44 | $xml = [xml](Get-Content $_.FullName) 45 | $workflowNode = $xml.SelectSingleNode("//Workflow") 46 | if ($activateFlowConfig.activate -eq 'false') { 47 | Write-Host "Disabling flow " $activateFlowConfig.solutionComponentName 48 | $workflowNode.StateCode = '0' 49 | $workflowNode.StatusCode = '1' 50 | } 51 | elseif ($activateFlowConfig.activate -eq 'true') { 52 | Write-Host "Enabling flow " $activateFlowConfig.solutionComponentName 53 | $workflowNode.StateCode = '1' 54 | $workflowNode.StatusCode = '2' 55 | } 56 | $xml.Save($_.FullName) 57 | } 58 | else{ 59 | Write-Host "Path unavailable - " $_.FullName 60 | } 61 | } 62 | } 63 | } 64 | else{ 65 | Write-Host "ActivateFlowConfigJson Path unavailable - " $activateFlowConfigJson 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /PowerShell/load-save-pipeline-parameters.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function sets parameters in export pipeline. 3 | #> 4 | function Write-Export-Pipeline-Parameters { 5 | param ( 6 | [Parameter(Mandatory)] [String]$filePath, 7 | [Parameter()] [String]$gitAccessUrl, 8 | [Parameter()] [String]$project, 9 | [Parameter()] [String]$repo, 10 | [Parameter()] [String]$branch, 11 | [Parameter()] [String]$branchToCreate, 12 | [Parameter()] [String]$commitMessage, 13 | [Parameter()] [String]$email, 14 | [Parameter()] [String]$serviceConnectionName, 15 | [Parameter()] [String]$serviceConnectionUrl, 16 | [Parameter()] [String]$solutionName, 17 | [Parameter()] [String]$userName 18 | ) 19 | 20 | $configurationData = $env:DEPLOYMENT_SETTINGS | ConvertFrom-Json 21 | $pipelineParameterObject = [PSCustomObject]@{"gitAccessUrl"=$gitAccessUrl; "project"="$project"; "repo"="$repo"; "branch"="$branch"; "branchToCreate"="$branchToCreate"; "commitMessage"="$commitMessage"; "email"="$email"; "serviceConnectionName"="$serviceConnectionName"; "serviceConnectionUrl"="$serviceConnectionUrl"; "solutionName"="$solutionName"; "userName"="$userName"; "configurationData"=$configurationData } 22 | Write-Pipeline-Parameters $filePath $pipelineParameterObject 23 | } 24 | 25 | <# 26 | This function sets parameters in deploy pipeline. 27 | #> 28 | function Write-Deploy-Pipeline-Parameters { 29 | param ( 30 | [Parameter(Mandatory)] [String]$filePath, 31 | [Parameter()] [String]$serviceConnectionName, 32 | [Parameter()] [String]$serviceConnectionUrl, 33 | [Parameter()] [String]$environmentName, 34 | [Parameter()] [String]$solutionName, 35 | [Parameter()] [String]$importUnmanaged, 36 | [Parameter()] [String]$overwriteUnmanagedCustomizations, 37 | [Parameter()] [String]$skipBuildToolsInstaller, 38 | [Parameter()] [String]$cacheEnabled 39 | ) 40 | $pipelineParameterObject = [PSCustomObject]@{"serviceConnectionName"="$serviceConnectionName"; "serviceConnectionUrl"="$serviceConnectionUrl"; "environmentName"="$environmentName"; "solutionName"="$solutionName"; "importUnmanaged"="$importUnmanaged"; "overwriteUnmanagedCustomizations"="$overwriteUnmanagedCustomizations"; "skipBuildToolsInstaller"="$skipBuildToolsInstaller"; "cacheEnabled"="$cacheEnabled" } 41 | Write-Pipeline-Parameters $filePath $pipelineParameterObject 42 | } 43 | 44 | function Write-Build-Pipeline-Parameters { 45 | param ( 46 | [Parameter(Mandatory)] [String]$filePath, 47 | [Parameter()] [String]$buildType, 48 | [Parameter()] [String]$serviceConnectionName, 49 | [Parameter()] [String]$serviceConnectionUrl, 50 | [Parameter()] [String]$solutionName 51 | ) 52 | $pipelineParameterObject = [PSCustomObject]@{"buildType"=$buildType; "serviceConnectionName"="$serviceConnectionName"; "serviceConnectionUrl"="$serviceConnectionUrl"; "solutionName"="$solutionName" } 53 | Write-Pipeline-Parameters $filePath $pipelineParameterObject 54 | } 55 | 56 | <# 57 | This function writes pipeline parameters. 58 | #> 59 | function Write-Pipeline-Parameters { 60 | param ( 61 | [Parameter(Mandatory)] [String]$filePath, 62 | [Parameter(Mandatory)] [PSCustomObject]$pipelineParameterObject 63 | ) 64 | Write-Host "Saving Pipeline Parameters to $filePath" 65 | if (Test-Path $filePath) { 66 | Remove-Item $filePath 67 | } 68 | [string]$pipelineParameterJson = $pipelineParameterObject | ConvertTo-Json -depth 100 69 | if ($PSVersionTable.PSVersion.Major -gt 5) { 70 | $pipelineParameterJson | Out-File "$filePath" -Encoding utf8NoBOM 71 | } 72 | else { 73 | $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $false 74 | [System.IO.File]::WriteAllText($filePath, $pipelineParameterJson, $utf8NoBomEncoding) 75 | # PowerShell < v6 does not support writing UTF8 without BOM using Out-File 76 | # $pipelineParameterJson | Out-File "$filePath" 77 | } 78 | } 79 | 80 | <# 81 | This function reads the pipeline parameters. 82 | #> 83 | function Read-Pipeline-Parameters { 84 | param ( 85 | [Parameter(Mandatory)] [String]$filePath 86 | ) 87 | 88 | return Get-Content $filePath | ConvertFrom-Json 89 | } -------------------------------------------------------------------------------- /PowerShell/pipeline-functions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This marks the pipeline deployment stage run as succeeded or failed. 3 | #> 4 | function Invoke-Pre-Deployment-Status-Update{ 5 | param ( 6 | [Parameter(Mandatory)] [String]$pipelineStageRunId, 7 | [Parameter(Mandatory)] [String]$stageStatus, 8 | [Parameter(Mandatory)] [String]$pipelineServiceConnectionUrl, 9 | [Parameter(Mandatory)] [String]$aadHost, 10 | [Parameter(Mandatory)] [String]$tenantId, 11 | [Parameter(Mandatory)] [String]$applicationId, 12 | [Parameter(Mandatory)] [String]$clientSecret 13 | ) 14 | . "$env:POWERSHELLPATH/dataverse-webapi-functions.ps1" 15 | $dataverseHost = Get-HostFromUrl "$pipelineServiceConnectionUrl" 16 | $spnToken = Get-SpnToken "$tenantId" "$applicationId" "$clientSecret" "$dataverseHost" "$aadHost" 17 | 18 | # Set up the request body 19 | $requestBody = @{ 20 | StageRunId = "$pipelineStageRunId" 21 | PreDeploymentStepStatus = "$stageStatus" 22 | } 23 | $jsonBody = $requestBody | ConvertTo-Json 24 | 25 | Invoke-DataverseHttpPost "$spnToken" "$dataverseHost" "UpdatePreDeploymentStepStatus" "$jsonBody" 26 | } 27 | <# 28 | Creates a new Pull Request based on the source and target branches and conditionally auto completes it. 29 | #> 30 | function New-Pull-Request { 31 | param ( 32 | [Parameter(Mandatory)] [String]$solutionName, 33 | [Parameter(Mandatory)] [String]$org, 34 | [Parameter(Mandatory)] [String]$project, 35 | [Parameter(Mandatory)] [String]$repo, 36 | [Parameter(Mandatory)] [String]$branch, 37 | [Parameter(Mandatory)] [String]$sourceBranch, 38 | [Parameter(Mandatory)] [String]$targetBranch, 39 | [Parameter(Mandatory)][AllowEmptyString()] [String]$encodedCommitMessage, 40 | [Parameter(Mandatory)] [String]$autocompletePR, 41 | [Parameter(Mandatory)] [String]$accessToken 42 | ) 43 | . "$env:POWERSHELLPATH/util.ps1" 44 | Write-Host "Source Branch: $sourceBranch" 45 | if("$sourceBranch" -match "Commit to existing branch specified in Branch parameter") { 46 | $sourceBranch = "refs/heads/$branch" 47 | } 48 | Write-Host "Source Branch: $sourceBranch" 49 | 50 | # Check for existing active PR for the branch and repo 51 | $uri = "$org/$project/_apis/git/pullrequests?searchCriteria.repositoryId=$repo&searchCriteria.sourceRefName=$sourceBranch&searchCriteria.status=active&searchCriteria.targetRefName=$targetBranch&api-version=7.0" 52 | $response = Invoke-RestMethod $uri -Method Get -Headers @{ 53 | Authorization = "Bearer $accessToken" 54 | } 55 | if($response.value.length -eq 0) { 56 | # Define API endpoint 57 | $uri = "$org/$project/_apis/git/repositories/$repo/pullrequests?api-version=6.0" 58 | 59 | # Define request body 60 | $body = @{ 61 | sourceRefName = "$sourceBranch"; 62 | targetRefName = "$targetBranch"; 63 | title = "$solutionName - Deployment Approval Pull Request"; 64 | description = $encodedCommitMessage 65 | } | ConvertTo-Json 66 | 67 | Write-Host "Body: $body" 68 | 69 | # Send API request 70 | $createPRResponse = Invoke-RestMethod -Uri $uri -Method Post -Headers @{ 71 | Authorization = "Bearer $accessToken" 72 | "Content-Type" = "application/json; charset=utf-8" 73 | } -Body $body 74 | 75 | if("$autocompletePR" -eq "true") { 76 | # Define API endpoint for update 77 | $uri = "$org/$project/_apis/git/repositories/$repo/pullrequests/" + $createPRResponse.pullRequestId + "?api-version=6.0" 78 | 79 | Write-Host $uri 80 | # Set the PR to auto-complete 81 | $body = @{ 82 | autoCompleteSetBy = @{ 83 | id = $createPRResponse.createdBy.id 84 | } 85 | completionOptions = @{ 86 | deleteSourceBranch = $true 87 | bypassPolicy = $false 88 | } 89 | } | ConvertTo-Json 90 | 91 | Write-Host $body 92 | Invoke-RestMethod -Uri $uri -Method Patch -Headers @{ 93 | Authorization = "Bearer $accessToken" 94 | "Content-Type" = "application/json" 95 | } -Body $body 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /PowerShell/share-rows-with-group-team.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function grants read access to team to the workflow. 3 | Testable outside of agent 4 | #> 5 | function Grant-AccessToWorkflow { 6 | param ( 7 | [Parameter()] [String]$token, 8 | [Parameter(Mandatory)] [String]$dataverseHost, 9 | [Parameter(Mandatory)] [String]$teamName, 10 | [Parameter(Mandatory)] [String]$workflowId 11 | ) 12 | #Load util function 13 | . "$env:POWERSHELLPATH/util.ps1" 14 | 15 | Write-Host "teamName - $teamName" 16 | Write-Host "workflowId - $workflowId" 17 | $validatedId = Invoke-Validate-And-Clean-Guid $workflowId 18 | if (!$validatedId) { 19 | Write-Host "Invalid workflowId GUID. Exiting from Grant-AccessToWorkflow." 20 | return 21 | } 22 | $teamId = Get-TeamId $token $dataverseHost $teamName 23 | Write-Host "teamId - $teamId" 24 | if($teamId -ne '') { 25 | $body = "{ 26 | `n `"Target`":{ 27 | `n `"workflowid`":`"$validatedId`", 28 | `n `"@odata.type`": `"Microsoft.Dynamics.CRM.workflow`" 29 | `n }, 30 | `n `"PrincipalAccess`":{ 31 | `n `"Principal`":{ 32 | `n `"teamid`": `"$teamId`", 33 | `n `"@odata.type`": `"Microsoft.Dynamics.CRM.team`" 34 | `n }, 35 | `n `"AccessMask`": `"ReadAccess`" 36 | `n } 37 | `n}" 38 | 39 | $requestUrlRemainder = "GrantAccess" 40 | Invoke-DataverseHttpPost $token $dataverseHost $requestUrlRemainder $body 41 | } 42 | } 43 | 44 | <# 45 | This function grants read access to team to the connector. 46 | #> 47 | function Grant-AccessToConnector { 48 | param ( 49 | [Parameter()] [String]$token, 50 | [Parameter(Mandatory)] [String]$dataverseHost, 51 | [Parameter(Mandatory)] [String]$teamName, 52 | [Parameter(Mandatory)] [String]$connectorId 53 | ) 54 | #Load util function 55 | . "$env:POWERSHELLPATH/util.ps1" 56 | 57 | Write-Host "TeamName - $teamName" 58 | Write-Host "ConnectorId - $connectorId" 59 | $validatedId = Invoke-Validate-And-Clean-Guid $connectorId 60 | if (!$validatedId) { 61 | Write-Host "Invalid ConnectorId GUID. Exiting from Grant-AccessToConnector." 62 | return 63 | } 64 | 65 | $teamId = Get-TeamId $token $dataverseHost $teamName 66 | if($teamId -ne '') { 67 | $body = "{ 68 | `n `"Target`":{ 69 | `n `"connectorid`":`"$validatedId`", 70 | `n `"@odata.type`": `"Microsoft.Dynamics.CRM.connector`" 71 | `n }, 72 | `n `"PrincipalAccess`":{ 73 | `n `"Principal`":{ 74 | `n `"teamid`": `"$teamId`", 75 | `n `"@odata.type`": `"Microsoft.Dynamics.CRM.team`" 76 | `n }, 77 | `n `"AccessMask`": `"ReadAccess`" 78 | `n } 79 | `n}" 80 | 81 | $requestUrlRemainder = "GrantAccess" 82 | Invoke-DataverseHttpPost $token $dataverseHost $requestUrlRemainder $body 83 | } 84 | } 85 | 86 | <# 87 | This function fetches the team guid from the team name. 88 | #> 89 | function Get-TeamId { 90 | param ( 91 | [Parameter(Mandatory)] [String]$token, 92 | [Parameter(Mandatory)] [String]$dataverseHost, 93 | [Parameter(Mandatory)] [String]$teamName 94 | ) 95 | $teamId = '' 96 | $odataQuery = 'teams?$filter=name eq ' + "'$teamName'" + '&$select=teamid' 97 | $response = Invoke-DataverseHttpGet $token $dataverseHost $odataQuery 98 | if($response.value.length -gt 0) { 99 | $teamId = $response.value[0].teamid 100 | } 101 | return $teamId 102 | } -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/activate-flows.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | #Run in Windows Powershell 2 | function Invoke-ActivateFlows-Test() 3 | { 4 | Set-Location -Path "..\" 5 | $testConfig = Get-Content ".\TestData\activate-flows-test.config.json" | ConvertFrom-Json 6 | 7 | $path = './activate-flows.tests.ps1' 8 | $data = @{ 9 | MicrosoftXrmDataPowerShellModule = $testConfig.microsoftXrmDataPowerShellModule 10 | XrmDataPowerShellVersion = $testConfig.xrmDataPowerShellVersion 11 | MicrosoftPowerAppsAdministrationPowerShellModule = $testConfig.microsoftPowerAppsAdministrationPowerShellModule 12 | PowerAppsAdminModuleVersion = $testConfig.powerAppsAdminModuleVersion 13 | ActivationConfigPath = $testConfig.activationConfigPath 14 | ComponentOwnerConfigPath = $testConfig.componentOwnerConfigPath 15 | ConnectionReferenceConfigPath = $testConfig.connectionReferenceConfigPath 16 | DataverseConnectionString = $testConfig.dataverseConnectionString 17 | ServiceConnection = $testConfig.serviceConnection 18 | TenantId = $testConfig.tenantId 19 | ClientId = $testConfig.clientId 20 | ClientSecret = $testConfig.clientSecret 21 | SolutionName = $testConfig.solutionName 22 | EnvironmentId = $testConfig.environmentId 23 | } 24 | 25 | $container = New-PesterContainer -Path $path -Data $data 26 | Invoke-Pester -Container $container 27 | } 28 | 29 | Invoke-ActivateFlows-Test -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/archive-configuration-migration-data.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-ArchiveConfigurationMigrationData-Test() 2 | { 3 | Set-Location -Path "..\" 4 | $testConfig = Get-Content ".\TestData\archive-configuration-migration-data-test.config.json" | ConvertFrom-Json 5 | 6 | $path = './archive-configuration-migration-data.tests.ps1' 7 | $data = @{ 8 | ArtifactStagingDirectory = $testConfig.artifactStagingDirectory 9 | BuildSourceDirectory = $testConfig.buildSourceDirectory 10 | Repo = $testConfig.repo 11 | SolutionName = $testConfig.solutionName 12 | } 13 | 14 | $container = New-PesterContainer -Path $path -Data $data 15 | Invoke-Pester -Container $container 16 | } 17 | Invoke-ArchiveConfigurationMigrationData-Test 18 | -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/canvas-unpack-pack.tests.runner.ps1: -------------------------------------------------------------------------------- 1 |  2 | #Run in PowerShell Core 3 | function Invoke-CanvasUnpackPack-Test() 4 | { 5 | Set-Location -Path "..\" 6 | $testConfig = Get-Content ".\TestData\canvas-unpack-pack-test.config.json" | ConvertFrom-Json 7 | 8 | $RepackedMsAppPath = $testConfig.repackedMsAppPath 9 | $MsAppPath = $testConfig.msAppPath 10 | $MsAppSourcePath = $testConfig.msAppSourcePath 11 | 12 | $path = './canvas-unpack-pack.tests.ps1' 13 | $data = @{ 14 | RepackedMsAppPath = $RepackedMsAppPath 15 | MsAppPath = $MsAppPath 16 | MsAppSourcePath = $MsAppSourcePath 17 | } 18 | $container = New-PesterContainer -Path $path -Data $data 19 | Invoke-Pester -Container $container 20 | } 21 | Invoke-CanvasUnpackPack-Test 22 | -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/e2e-pipeline.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | #Run in PowerShell Core 2 | function Invoke-E2E-Pipeline-Tests-Test($solutionName) 3 | { 4 | Set-Location -Path "..\" 5 | 6 | $testConfig = Get-Content ".\TestData\e2e-pipeline.tests-test.config.json" | ConvertFrom-Json 7 | $testData = Get-Content ".\TestData\e2e-pipeline.tests-test-data.json" | Out-String 8 | 9 | az login --allow-no-subscriptions 10 | $path = './e2e-pipeline.tests.ps1' 11 | $data = @{ 12 | Org = $testConfig.orgUrl 13 | Project = $testConfig.projectName 14 | BranchToTest = $testConfig.githubHeadRef 15 | SourceBranch = $solutionName 16 | BranchToCreate = 'pr-loop-e2e-test-' + (New-Guid).Guid 17 | CommitMessage = 'pr-loop-e2e-test' 18 | Data = $testData 19 | Email = $testConfig.email 20 | Repo = $testConfig.repo 21 | ServiceConnection = $testConfig.serviceConnection 22 | SolutionName = $solutionName 23 | UserName = $testConfig.user 24 | PortalSiteName = '' 25 | CommitScope = '1' 26 | PublishCustomizations = 'false' 27 | } 28 | $container = New-PesterContainer -Path $path -Data $data 29 | Invoke-Pester -Container $container 30 | } 31 | Invoke-E2E-Pipeline-Tests-Test("ALMAcceleratorSampleSolution") 32 | -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/enable-disable-solution-flows.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | #Run in Windows Powershell 2 | 3 | function Invoke-EnableDisableSolutionFlows-Test() 4 | { 5 | Set-Location -Path "..\" 6 | $testConfig = Get-Content ".\TestData\enable-disable-solution-flows-test.config.json" | ConvertFrom-Json 7 | 8 | $path = './enable-disable-solution-flows.tests.ps1' 9 | $data = @{ 10 | BuildSourceDirectory = $testConfig.buildSourceDirectory 11 | Repo = $testConfig.repo 12 | SolutionName = $testConfig.solutionName 13 | DisableAllFlows = $testConfig.disableAllFlows 14 | ActivationConfigPath = $testConfig.activationConfigPath 15 | } 16 | $container = New-PesterContainer -Path $path -Data $data 17 | Invoke-Pester -Container $container 18 | } 19 | Invoke-EnableDisableSolutionFlows-Test 20 | -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/load-save-pipeline-parameters.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | #Run in PowerShell Core 2 | function Write-Export-Pipeline-Parameters-Test() 3 | { 4 | Set-Location -Path "..\" 5 | $testConfig = Get-Content ".\TestData\save-export-pipeline-parameter-test.config.json" | ConvertFrom-Json 6 | $testDeploymentConfig = Get-Content ".\TestData\update-deployment-settings-test-deployment.json" 7 | 8 | $path = './load-save-export-parameters.tests.ps1' 9 | $data = @{ 10 | ConfigFileOutPath = $testConfig.configFileOutPath #".\TestData\out-export-pipeline-parameters.json" 11 | GitAccessUrl = $testConfig.gitAccessUrl 12 | project = $testConfig.project 13 | Repo = $testConfig.repo 14 | Branch = $testConfig.branch 15 | branchToCreate = $testConfig.branchToCreate 16 | commitMessage = $testConfig.commitMessage 17 | Email = $testConfig.email 18 | ServiceConnectionName = $testConfig.serviceConnectionName 19 | ServiceConnectionUrl = $testConfig.serviceConnectionUrl 20 | SolutionName = $testConfig.solutionName 21 | UserName = $testConfig.userName 22 | DeploymentSettingsData = $testDeploymentConfig 23 | } 24 | $container = New-PesterContainer -Path $path -Data $data 25 | Invoke-Pester -Container $container 26 | } 27 | 28 | function Write-Build-Pipeline-Parameters-Test() 29 | { 30 | $testConfig = Get-Content ".\TestData\save-build-pipeline-parameter-test.config.json" | ConvertFrom-Json 31 | 32 | $path = './load-save-build-parameters.tests.ps1' 33 | $data = @{ 34 | ConfigFileOutPath = $testConfig.configFileOutPath #".\TestData\out-build-pipeline-parameters.json" 35 | BuildType = $testConfig.buildType 36 | ServiceConnectionName = $testconfig.serviceConnectionName 37 | ServiceConnectionUrl = $testConfig.serviceConnectionUrl 38 | SolutionName = $testConfig.solutionName 39 | } 40 | $container = New-PesterContainer -Path $path -Data $data 41 | Invoke-Pester -Container $container 42 | } 43 | 44 | function Write-Deploy-Pipeline-Parameters-Test() 45 | { 46 | $testConfig = Get-Content ".\TestData\save-deploy-pipeline-parameter-test.config.json" | ConvertFrom-Json 47 | 48 | $path = './load-save-deploy-parameters.tests.ps1' 49 | $data = @{ 50 | ConfigFileOutPath = $testConfig.configFileOutPath #".\TestData\out-deploy-pipeline-parameters.json" 51 | ServiceConnectionName = $testConfig.serviceConnectionName 52 | ServiceConnectionUrl = $testConfig.serviceConnectionUrl 53 | EnvironmentName = $testConfig.environmentName 54 | SolutionName = $testConfig.solutionName 55 | ImportUnmanaged = $testConfig.importUnmanaged 56 | OverwriteUnmanagedCustomizations = $testConfig.overwriteUnmanagedCustomizations 57 | SkipBuildToolsInstaller = $testConfig.skipBuildToolsInstaller 58 | CacheEnabled = $testConfig.cacheEnabled 59 | } 60 | $container = New-PesterContainer -Path $path -Data $data 61 | Invoke-Pester -Container $container 62 | } 63 | 64 | Write-Export-Pipeline-Parameters-Test 65 | Write-Build-Pipeline-Parameters-Test 66 | Write-Deploy-Pipeline-Parameters-Test -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/update-deployment-settings.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | #Run in Windows Powershell 2 | function Invoke-DeploymentSettingsConfiguration-Test($usePlaceholders, $path) 3 | { 4 | Set-Location -Path $path 5 | 6 | $testConfig = Get-Content ".\TestData\update-deployment-settings-test.config.json" | ConvertFrom-Json 7 | $testDeploymentConfig = Get-Content ".\TestData\update-deployment-settings-test-deployment.json" 8 | 9 | $pat = $testConfig.accessToken 10 | $token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($pat)")) 11 | 12 | $path = './update-deployment-settings.tests.ps1' 13 | $data = @{ 14 | DeploymentConfig = $testDeploymentConfig 15 | BuildSourceDirectory = $testConfig.buildSourceDirectory 16 | PipelineSourceDirectory = $testConfig.pipelineSourceDirectory 17 | BuildProjectName = $testConfig.buildProjectName 18 | BuildRepositoryName = $testConfig.buildRepositoryName 19 | DataverseConnectionString = $testConfig.dataverseConnectionString 20 | XrmDataPowerShellVersion = $testConfig.xrmDataPowerShellVersion 21 | MicrosoftXrmDataPowerShellModule = $testConfig.microsoftXrmDataPowerShellModule 22 | OrgUrl = $testConfig.orgUrl 23 | ProjectName = $testConfig.projectName 24 | Repo = $testConfig.repo 25 | AuthType = "Basic" 26 | ServiceConnection = $testConfig.serviceConnection 27 | SolutionName = $testConfig.solutionName 28 | UsePlaceholders = $usePlaceholders 29 | AccessToken = $token 30 | Pat = $pat 31 | AgentOS = "Windows" 32 | } 33 | $container = New-PesterContainer -Path $path -Data $data 34 | Invoke-Pester -Container $container 35 | } 36 | 37 | Invoke-DeploymentSettingsConfiguration-Test 'true' '../' 38 | Invoke-DeploymentSettingsConfiguration-Test 'false' './tests' -------------------------------------------------------------------------------- /PowerShell/tests/TestRunners/update-solution-component-owner.tests.runner.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-UpdateSolutionComponentOwner-Test() 2 | { 3 | Set-Location -Path "..\" 4 | $testConfig = Get-Content ".\TestData\update-solution-component-owner-test.config.json" | ConvertFrom-Json 5 | 6 | $path = './update-solution-component-owner.tests.ps1' 7 | $data = @{ 8 | DataverseConnectionString = $testConfig.dataverseConnectionString 9 | ServiceConnection = $testConfig.serviceConnection 10 | MicrosoftXrmDataPowerShellModule = $testConfig.microsoftXrmDataPowerShellModule 11 | XrmDataPowerShellVersion = $testConfig.xrmDataPowerShellVersion 12 | SolutionComponentOwnershipConfigurationPath = $testConfig.componentOwnerConfigPath 13 | } 14 | $container = New-PesterContainer -Path $path -Data $data 15 | Invoke-Pester -Container $container 16 | } 17 | 18 | Invoke-UpdateSolutionComponentOwner-Test -------------------------------------------------------------------------------- /PowerShell/tests/activate-flows.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $MicrosoftXrmDataPowerShellModule, $XrmDataPowerShellVersion, $MicrosoftPowerAppsAdministrationPowerShellModule, $PowerAppsAdminModuleVersion, 3 | $ActivationConfigPath, $ComponentOwnerConfigPath, $ConnectionReferenceConfigPath, $DataverseConnectionString, $ServiceConnection, $TenantId, 4 | $ClientId, $ClientSecret, $SolutionName, $EnvironmentId 5 | ) 6 | 7 | Describe 'Activate-Flows-Test' { 8 | It 'ActivateFlows' -Tag 'ActivateFlows' { 9 | . ..\activate-flows.ps1 10 | 11 | Install-Module $MicrosoftXrmDataPowerShellModule -RequiredVersion $XrmDataPowerShellVersion -Force -AllowClobber 12 | Install-Module $MicrosoftPowerAppsAdministrationPowerShellModule -RequiredVersion $PowerAppsAdminModuleVersion -Force -AllowClobber 13 | 14 | . .\utilities.tests.ps1 15 | 16 | $activationConfig = Invoke-SetDeploymentVariable "$ActivationConfigPath" "ActivateFlowConfiguration" 17 | $componentOwnerConfig = Invoke-SetDeploymentVariable "$ComponentOwnerConfigPath" "SolutionComponentOwnershipConfiguration" 18 | $connectionReferenceConfig = Invoke-SetDeploymentVariable "$ConnectionReferenceConfigPath" "ConnectionReferences" 19 | 20 | #Deactivate the flows to test 21 | $activationConfigs = Get-Content $activationConfig | ConvertFrom-Json 22 | Write-Host "Importing PowerShell Module: $MicrosoftXrmDataPowerShellModule - $XrmDataPowerShellVersion" 23 | Import-Module $MicrosoftXrmDataPowerShellModule -Force -RequiredVersion $XrmDataPowerShellVersion -ArgumentList @{ NonInteractive = $true } 24 | 25 | $connectionString = $DataverseConnectionString 26 | 27 | $conn = Get-CrmConnection -ConnectionString $connectionString 28 | foreach ($activateConfig in $activationConfigs){ 29 | if($activateConfig.solutionComponentUniqueName -ne ''){ 30 | $workflow = Get-CrmRecord -conn $conn -EntityLogicalName workflow -Id $activateConfig.solutionComponentUniqueName -Fields clientdata,category,statecode 31 | Set-CrmRecordState -conn $conn -EntityLogicalName workflow -Id $workflow.workflowid -StateCode 0 -StatusCode 1 32 | } 33 | } 34 | Invoke-ActivateFlows $DataverseConnectionString $ServiceConnection $MicrosoftXrmDataPowerShellModule $XrmDataPowerShellVersion $MicrosoftPowerAppsAdministrationPowerShellModule $PowerAppsAdminModuleVersion $TenantId $ClientId $ClientSecret $SolutionName $EnvironmentId $componentOwnerConfig $connectionReferenceConfig $activationConfig 35 | } 36 | } -------------------------------------------------------------------------------- /PowerShell/tests/archive-configuration-migration-data.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $ArtifactStagingDirectory, $BuildSourceDirectory, $Repo, $SolutionName 3 | ) 4 | 5 | Describe 'Activate-Flows-Test' { 6 | It 'ActivateFlows' -Tag 'ActivateFlows' { 7 | . ..\archive-configuration-migration-data.ps1 8 | Remove-Item $ArtifactStagingDirectory -Recurse -Force 9 | New-Item $ArtifactStagingDirectory -ItemType Directory 10 | Invoke-ArchiveConfigurationMigrationData $BuildSourceDirectory $ArtifactStagingDirectory $Repo $SolutionName 11 | } 12 | } -------------------------------------------------------------------------------- /PowerShell/tests/enable-disable-solution-flows.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $BuildSourceDirectory, $Repo, $SolutionName, $DisableAllFlows, $ActivationConfigPath 3 | ) 4 | 5 | Describe 'Enable-Disable-Solution-Flows-Test' { 6 | It 'EnablesDisablesFlows' -Tag 'EnablesDisablesFlows' { 7 | 8 | . ..\enable-disable-solution-flows.ps1 9 | . .\utilities.tests.ps1 10 | $activationConfig = Invoke-SetDeploymentVariable "$ActivationConfigPath" "ActivateFlowConfiguration" 11 | Set-EnableDisableSolutionFlows $BuildSourceDirectory $Repo $SolutionName $DisableAllFlows $ActivationConfig 12 | } 13 | } -------------------------------------------------------------------------------- /PowerShell/tests/load-save-build-parameters.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $ConfigFileOutPath, $BuildType, $ServiceConnectionName, $ServiceConnectionUrl, $SolutionName 3 | ) 4 | 5 | Describe 'Load-Save-Build-Parameters' { 6 | It 'LoadsAndSavedBuildParameters' -Tag 'LoadsAndSavedBuildParameters' { 7 | 8 | . ..\load-save-pipeline-parameters.ps1 9 | Write-Build-Pipeline-Parameters "$ConfigFileOutPath" $BuildType $ServiceConnectionName $ServiceConnectionUrl $SolutionName 10 | Read-Pipeline-Parameters "$ConfigFileOutPath" 11 | } 12 | } -------------------------------------------------------------------------------- /PowerShell/tests/load-save-deploy-parameters.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $ConfigFileOutPath, $ServiceConnectionName, $ServiceConnectionUrl, $EnvironmentName, $SolutionName, 3 | $ImportUnmanaged, $OverwriteUnmanagedCustomizations, $SkipBuildToolsInstaller, $CacheEnabled 4 | ) 5 | 6 | Describe 'Load-Save-Deploy-Parameters' { 7 | It 'LoadsAndSavedDeployParameters' -Tag 'LoadsAndSavedDeployParameters' { 8 | 9 | . ..\load-save-pipeline-parameters.ps1 10 | Write-Deploy-Pipeline-Parameters $ConfigFileOutPath $ServiceConnectionName $ServiceConnectionUrl $EnvironmentName $SolutionName $ImportUnmanaged $OverwriteUnmanagedCustomizations $SkipBuildToolsInstaller $CacheEnabled 11 | Read-Pipeline-Parameters "$ConfigFileOutPath" 12 | } 13 | } -------------------------------------------------------------------------------- /PowerShell/tests/load-save-export-parameters.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $ConfigFileOutPath, $GitAccessUrl, $Project, $Repo, $Branch, $BranchToCreate, $CommitMessage, $Email, $ServiceConnectionName, $ServiceConnectionUrl, 3 | $SolutionName, $UserName, $DeploymentSettingsData 4 | ) 5 | 6 | Describe 'Load-Save-Deploy-Parameters' { 7 | It 'LoadsAndSavedDeployParameters' -Tag 'LoadsAndSavedDeployParameters' { 8 | . ..\load-save-pipeline-parameters.ps1 9 | $ENV:DEPLOYMENT_SETTINGS = $DeploymentSettingsData 10 | Write-Export-Pipeline-Parameters "$ConfigFileOutPath" $GitAccessUrl $Project $Repo $Branch $BranchToCreate $CommitMessage $Email $ServiceConnectionName $ServiceConnectionUrl $SolutionName $UserName 11 | Read-Pipeline-Parameters "$ConfigFileOutPath" 12 | } 13 | } -------------------------------------------------------------------------------- /PowerShell/tests/update-deployment-settings.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $DeploymentConfig, $BuildSourceDirectory, $PipelineSourceDirectory, $BuildProjectName, $BuildRepositoryName, $DataverseConnectionString, 3 | $XrmDataPowerShellVersion, $MicrosoftXrmDataPowerShellModule, $OrgUrl, $ProjectName, $Repo, $AuthType, 4 | $ServiceConnection, $SolutionName, $UsePlaceholders, $AgentOS, $AccessToken, $Pat 5 | ) 6 | 7 | Describe 'Update-Deployment-Settings-Test' { 8 | It 'UpdateDeploymentSettings' -Tag 'UpdateDeploymentSettings' { 9 | Set-Location -Path "..\" 10 | Install-Module $MicrosoftXrmDataPowerShellModule -RequiredVersion $XrmDataPowerShellVersion -Force -AllowClobber 11 | $env:SYSTEM_ACCESSTOKEN = $AccessToken 12 | $env:DEPLOYMENT_SETTINGS = $DeploymentConfig 13 | 14 | #Delete the current pipelines to validate they are recreated by update settings 15 | $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 16 | $headers.Add("Authorization", "Basic $AccessToken") 17 | $headers.Add("Content-Type", "application/json") 18 | $requestUrl = "$OrgUrl$ProjectName/_apis/build/folders?api-version=6.0-preview.2&path=$SolutionName" 19 | $response = Invoke-RestMethod $requestUrl -Method 'DELETE' -Headers $headers 20 | $response | ConvertTo-Json -Depth 10 21 | 22 | #Run Update Deployment Settings 23 | . .\update-deployment-settings.ps1 24 | Set-DeploymentSettingsConfiguration $BuildSourceDirectory $PipelineSourceDirectory $BuildProjectName $BuildRepositoryName $DataverseConnectionString $XrmDataPowerShellVersion $MicrosoftXrmDataPowerShellModule $OrgUrl $ProjectName $Repo $AuthType $ServiceConnection $SolutionName '' $AgentOS $UsePlaceholders $Pat 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PowerShell/tests/update-solution-component-owner.tests.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $DataverseConnectionString, $ServiceConnection, $MicrosoftXrmDataPowerShellModule, $XrmDataPowerShellVersion, $SolutionComponentOwnershipConfigurationPath 3 | ) 4 | 5 | Describe 'Enable-Disable-Solution-Flows-Test' { 6 | It 'EnablesDisablesFlows' -Tag 'EnablesDisablesFlows' { 7 | 8 | . ..\activate-flows.ps1 9 | . ..\update-solution-component-owner.ps1 10 | . .\utilities.tests.ps1 11 | 12 | $componentOwnerConfig = Invoke-SetDeploymentVariable "$SolutionComponentOwnershipConfigurationPath" "SolutionComponentOwnershipConfiguration" 13 | Invoke-UpdateSolutionComponentOwner $DataverseConnectionString $ServiceConnection $MicrosoftXrmDataPowerShellModule $XrmDataPowerShellVersion $componentOwnerConfig 14 | } 15 | } -------------------------------------------------------------------------------- /PowerShell/tests/utilities.tests.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-SetDeploymentVariable 2 | { 3 | param ( 4 | [Parameter(Mandatory)] [String]$deploymentSettingsPath, 5 | [Parameter(Mandatory)] [String]$deploymentSettingsNode 6 | ) 7 | 8 | if($deploymentSettingsPath -ne '') 9 | { 10 | $deploymentSettings = Get-Content $deploymentSettingsPath | ConvertFrom-Json 11 | $settingsNode = $deploymentSettings.$deploymentSettingsNode 12 | $settingsJson = ConvertTo-Json($settingsNode) -Compress 13 | if ($settingsJson) { 14 | if(Test-Path -Path "$deploymentSettingsNode.json") { 15 | Remove-Item -Path "$deploymentSettingsNode.json" -Force 16 | } 17 | if ($PSVersionTable.PSVersion.Major -gt 5) { 18 | $settingsJson | Out-File "$deploymentSettingsNode.json" -Encoding utf8NoBOM 19 | } 20 | else { 21 | $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $false 22 | [System.IO.File]::WriteAllText("$deploymentSettingsNode.json", $settingsJson, $utf8NoBomEncoding) 23 | # PowerShell < v6 does not support writing UTF8 without BOM using Out-File 24 | # $settingsJson | Out-File "$deploymentSettingsNode.json" 25 | } 26 | 27 | return "$deploymentSettingsNode.json" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /PowerShell/update-solution-component-owner.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function updates solution component owner. 3 | #> 4 | function Invoke-UpdateSolutionComponentOwner { 5 | param ( 6 | [Parameter()] [String]$dataverseConnectionString, 7 | [Parameter()] [String]$serviceConnection, 8 | [Parameter()] [String]$microsoftXrmDataPowerShellModule, 9 | [Parameter()] [String]$xrmDataPowerShellVersion, 10 | [Parameter()] [String]$solutionComponentOwnershipConfiguration 11 | ) 12 | 13 | if($solutionComponentOwnershipConfiguration -ne ''){ 14 | Write-Host "Importing PowerShell Module: $microsoftXrmDataPowerShellModule - $xrmDataPowerShellVersion" 15 | Import-Module $microsoftXrmDataPowerShellModule -Force -RequiredVersion $xrmDataPowerShellVersion -ArgumentList @{ NonInteractive = $true } 16 | 17 | $conn = Get-CrmConnection -ConnectionString "$dataverseConnectionString" 18 | 19 | $flowsToSetOwners = [System.Collections.ArrayList]@() 20 | Get-OwnerFlowActivations $solutionComponentOwnershipConfiguration "" $conn $flowsToSetOwners 21 | 22 | foreach ($ownershipConfig in $flowsToSetOwners) { 23 | $validatedId = Invoke-Validate-And-Clean-Guid $ownershipConfig.solutionComponentUniqueName 24 | if (!$validatedId) { 25 | Write-Host "Invalid flow GUID $($ownershipConfig.solutionComponentUniqueName). Exiting from Invoke-UpdateSolutionComponentOwner." 26 | return 27 | } 28 | Write-Host "OwnershipConfig.solutionComponentType - $($ownershipConfig.solutionComponentType)" 29 | # Skip this for Canvas Apps 30 | if($ownershipConfig.solutionComponentType -eq 300) 31 | { 32 | Write-Host "Skipping this for Canvas App $($ownershipConfig.solutionComponentName)." 33 | }else{ 34 | #Need to deactivate the flow before setting ownership if currently active 35 | if ($ownershipConfig.solutionComponent.statecode_Property.Value -ne 0) { 36 | Write-Host "Deactivating the Flow - $($ownershipConfig.solutionComponentName)" 37 | Set-CrmRecordState -conn $conn -EntityLogicalName workflow -Id $validatedId -StateCode 0 -StatusCode 1 38 | } 39 | Write-Host "Setting flow - $validatedId owner to $($ownershipConfig.impersonationCallerId)" 40 | Set-CrmRecordOwner -conn $conn $ownershipConfig.solutionComponent $ownershipConfig.impersonationCallerId 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /PowerShell/util.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function returns an encoded text. 3 | #> 4 | function Get-Encoded-Text 5 | { 6 | param ( 7 | [Parameter(Mandatory)] [String]$originalText 8 | ) 9 | 10 | Write-Host "OriginalText - $originalText" 11 | # Encode the text using Base64 12 | $encodedBytes = [System.Text.Encoding]::UTF8.GetBytes($originalText) 13 | $encodedText = [System.Convert]::ToBase64String($encodedBytes) 14 | 15 | Write-Host "EncodedText - $encodedText" 16 | return $encodedText 17 | } 18 | 19 | <# 20 | This function returns a decoded text. 21 | #> 22 | function Get-Decoded-Text 23 | { 24 | param ( 25 | [Parameter(Mandatory)] [String]$encodedText 26 | ) 27 | 28 | Write-Host "EncodedText - $encodedText" 29 | # Decode the Base64 encoded text 30 | $decodedBytes = [System.Convert]::FromBase64String($encodedText) 31 | $decodedText = [System.Text.Encoding]::UTF8.GetString($decodedBytes) 32 | 33 | Write-Host "DecodedText - $decodedText" 34 | return $decodedText 35 | } 36 | 37 | <# 38 | This function checks if repetative 'Names' mentioned in 'UserSettings'. 39 | If yes, returns the index 40 | #> 41 | function Update-IndicesOfNodesWithValue { 42 | param ( 43 | [System.Object]$jsonArray, 44 | [string]$searchName, 45 | [string]$searchValue, 46 | [string]$searchProperty = "Name" 47 | ) 48 | 49 | try { 50 | Write-Host "Inside Update-IndicesOfNodesWithValue" 51 | Write-Host "SearchName - $searchName" 52 | Write-Host "SearchValue - $searchValue" 53 | $matchingIndex = -1 54 | 55 | $nameMatches = @() 56 | 57 | for ($i = 0; $i -lt $jsonArray.Count; $i++) { 58 | if ($jsonArray[$i].($searchProperty) -eq $searchName) { 59 | $nameMatches += ($i + 1) 60 | } 61 | } 62 | 63 | Write-Host "nameMatches count - $($nameMatches.Count)" 64 | if ($nameMatches.Count -gt 1) { 65 | for ($j = 0; $j -lt $nameMatches.Count; $j++) { 66 | $index = $nameMatches[$j] - 1 67 | Write-Host "jsonArray[index].Value - $($jsonArray[$index].Value)" 68 | Write-Host "searchValue - $searchValue" 69 | if ($jsonArray[$index].Value -eq $searchValue) { 70 | Write-Host "Matched found for SearchValue - $searchValue" 71 | $matchingIndex = ($j + 1) 72 | break 73 | } 74 | } 75 | } 76 | 77 | if ($matchingIndex -ne -1) { 78 | Write-Host "Multiple $configurationVariableName defined." 79 | $searchName = $searchName + "_$matchingIndex" 80 | Write-Host "Appended the MatchingIndices - $matchingIndex. Update Variable Name - $searchName" 81 | } else { 82 | Write-Host "No multiple $searchName defined." 83 | } 84 | 85 | return $searchName 86 | } 87 | catch { 88 | Write-Host "Error parsing JSON: $_" 89 | return -1 90 | } 91 | } 92 | 93 | <# 94 | Sometimes GUID contains underscore (incase of multiple share with teams scenario). 95 | This function trims and validates the GUID. 96 | #> 97 | function Invoke-Validate-And-Clean-Guid { 98 | param ( 99 | [string]$inputGuid 100 | ) 101 | 102 | try { 103 | # Use a regular expression to match the GUID pattern 104 | if ($inputGuid -match '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})') { 105 | return $Matches[1] 106 | } else { 107 | Write-Host "Invalid GUID format: $inputGuid" 108 | return $null 109 | } 110 | } catch { 111 | Write-Host "An error occurred during GUID validation: $_" 112 | return $null 113 | } 114 | } -------------------------------------------------------------------------------- /PowerShell/webhookurl-helper.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This function reads webHookConfiguration from custom deployment settings. 3 | Updates the 'serviceendpoint' record in dataverse. 4 | #> 5 | function Update-WebHookUrls { 6 | param ( 7 | [Parameter()] [String]$dataverseConnectionString, 8 | [Parameter()] [String]$serviceConnection, 9 | [Parameter()] [String]$microsoftXrmDataPowerShellModule, 10 | [Parameter()] [String]$xrmDataPowerShellVersion, 11 | [Parameter()] [String]$webHookConfiguration, 12 | [Parameter()] [String]$token 13 | ) 14 | if($webHookConfiguration -ne ''){ 15 | Write-Host "Importing PowerShell Module: $microsoftXrmDataPowerShellModule - $xrmDataPowerShellVersion" 16 | Import-Module $microsoftXrmDataPowerShellModule -Force -RequiredVersion $xrmDataPowerShellVersion -ArgumentList @{ NonInteractive = $true } 17 | 18 | $conn = Get-CrmConnection -ConnectionString "$dataverseConnectionString" 19 | 20 | . "$env:POWERSHELLPATH/dataverse-webapi-functions.ps1" 21 | $dataverseHost = Get-HostFromUrl "$serviceConnection" 22 | Write-Host "dataverseHost - $dataverseHost" 23 | 24 | Write-Host "webHookConfiguration - $webHookConfiguration" 25 | $webHookConfigCollection = Get-WebHookConfigurations $webHookConfiguration 26 | 27 | foreach ($configuration in $webHookConfigCollection) { 28 | # Retrieve Service End Point 29 | if ($null -ne $configuration.SchemaName) { 30 | Write-Host "Fetching Service endpoint by Name - " $configuration.SchemaName 31 | $serviceEndPointId = Get-Service-Endpoint-By-Name $configuration.SchemaName $token $dataverseHost 32 | 33 | if($serviceEndPointId -ne -1){ 34 | Write-Host "Updating serviceendpoint URL to - "$configuration.Value 35 | Set-CrmRecord -conn $conn -EntityLogicalName serviceendpoint -Id $serviceEndPointId -Fields @{ "url" = $configuration.Value } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | <# 43 | This function gets the 'serviceendpoint' by name. 44 | #> 45 | function Get-Service-Endpoint-By-Name{ 46 | param ( 47 | [Parameter(Mandatory)] [String] [AllowEmptyString()]$endPointName, 48 | [Parameter(Mandatory)] [String] [AllowEmptyString()]$token, 49 | [Parameter(Mandatory)] [String] [AllowEmptyString()]$dataverseHost 50 | ) 51 | $serviceEndPointId = -1 52 | # Check current status of workflow 53 | $queryServiceEndPoint = "serviceendpoints?`$select=url&`$filter=(name eq '$endPointName')" 54 | 55 | try{ 56 | Write-Host "Service End Point Query - $queryServiceEndPoint" 57 | $serviceEndPointResponse = Invoke-DataverseHttpGet $token $dataverseHost $queryServiceEndPoint 58 | } 59 | catch{ 60 | Write-Host "Error $queryServiceEndPoint - $($_.Exception.Message)" 61 | } 62 | 63 | if($null -ne $serviceEndPointResponse.value -and $serviceEndPointResponse.value.count -gt 0){ 64 | $serviceEndPointId = $serviceEndPointResponse.value[0].serviceendpointid 65 | Write-Host "Service End Point Id is $serviceEndPointId" 66 | } 67 | 68 | return $serviceEndPointId 69 | } 70 | 71 | <# 72 | This function reads activateFlowConfiguration from custom deployment settings. 73 | #> 74 | function Get-WebHookConfigurations { 75 | param ( 76 | [Parameter(Mandatory)] [String] [AllowEmptyString()]$activateFlowConfiguration 77 | ) 78 | $activationConfigs = $null 79 | if ($activateFlowConfiguration -ne "") { 80 | $activationConfigs = Get-Content $activateFlowConfiguration | ConvertFrom-Json 81 | } 82 | 83 | return $activationConfigs 84 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Contributing 3 | 4 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 5 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 6 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 7 | 8 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 9 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 10 | provided by the bot. You will only need to do this once across all repos using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 14 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | 16 | ## Trademarks 17 | 18 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 19 | trademarks or logos is subject to and must follow 20 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 21 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 22 | Any use of third-party trademarks or logos are subject to those third-party's policies. 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). 7 | - **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /deployment-settings.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": {}, 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "https://example.com/object1684336995.json", 5 | "title": "Root", 6 | "type": "array", 7 | "default": [], 8 | "items":{ 9 | "$id": "#root/items", 10 | "title": "Items", 11 | "type": "object", 12 | "required": [ 13 | "ApprovalType", 14 | "BuildName", 15 | "BuildTemplate", 16 | "DeploymentEnvironmentName", 17 | "DeploymentEnvironmentUrl", 18 | "EnvironmentUrl", 19 | "ServiceConnectionName", 20 | "StepType", 21 | "UserSettings", 22 | "VariableGroup" 23 | ], 24 | "properties": { 25 | "ApprovalType": { 26 | "$id": "#root/items/ApprovalType", 27 | "title": "Approvaltype", 28 | "type": "integer", 29 | "examples": [ 30 | 809060000 31 | ], 32 | "default": 0 33 | }, 34 | "BuildName": { 35 | "$id": "#root/items/BuildName", 36 | "title": "Buildname", 37 | "type": "string", 38 | "default": "", 39 | "examples": [ 40 | "deploy-validation-ALMAcceleratorSampleSolution" 41 | ], 42 | "pattern": "^.*$" 43 | }, 44 | "BuildTemplate": { 45 | "$id": "#root/items/BuildTemplate", 46 | "title": "Buildtemplate", 47 | "type": "null", 48 | "default": null 49 | }, 50 | "DeploymentEnvironmentName": { 51 | "$id": "#root/items/DeploymentEnvironmentName", 52 | "title": "Deploymentenvironmentname", 53 | "type": "string", 54 | "default": "", 55 | "examples": [ 56 | "Validation" 57 | ], 58 | "pattern": "^.*$" 59 | }, 60 | "DeploymentEnvironmentUrl": { 61 | "$id": "#root/items/DeploymentEnvironmentUrl", 62 | "title": "Deploymentenvironmenturl", 63 | "type": "string", 64 | "default": "", 65 | "examples": [ 66 | "https://contoso-validation.crm3.dynamics.com/" 67 | ], 68 | "pattern": "^.*$" 69 | }, 70 | "EnvironmentUrl": { 71 | "$id": "#root/items/EnvironmentUrl", 72 | "title": "Environmenturl", 73 | "type": "string", 74 | "default": "", 75 | "examples": [ 76 | "https://contoso-dev.crm.dynamics.com/" 77 | ], 78 | "pattern": "^.*$" 79 | }, 80 | "ServiceConnectionName": { 81 | "$id": "#root/items/ServiceConnectionName", 82 | "title": "Serviceconnectionname", 83 | "type": "null", 84 | "default": null 85 | }, 86 | "StepType": { 87 | "$id": "#root/items/StepType", 88 | "title": "Steptype", 89 | "type": "integer", 90 | "examples": [ 91 | 809060001 92 | ], 93 | "default": 0 94 | }, 95 | "UserSettings": { 96 | "$id": "#root/items/UserSettings", 97 | "title": "Usersettings", 98 | "type": "array", 99 | "default": [], 100 | "items":{ 101 | "$id": "#root/items/UserSettings/items", 102 | "title": "Items", 103 | "type": "object", 104 | "required": [ 105 | "Data", 106 | "Name", 107 | "Value" 108 | ], 109 | "properties": { 110 | "Data": { 111 | "$id": "#root/items/UserSettings/items/Data", 112 | "title": "Data", 113 | "type": "string", 114 | "default": "", 115 | "examples": [ 116 | "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps" 117 | ], 118 | "pattern": "^.*$" 119 | }, 120 | "Name": { 121 | "$id": "#root/items/UserSettings/items/Name", 122 | "title": "Name", 123 | "type": "string", 124 | "default": "", 125 | "examples": [ 126 | "connectionreference.cat_CDS_Current" 127 | ], 128 | "pattern": "^.*$" 129 | }, 130 | "Value": { 131 | "$id": "#root/items/UserSettings/items/Value", 132 | "title": "Value", 133 | "type": "string", 134 | "default": "", 135 | "examples": [ 136 | "e34cedbfbb964b4ca7fc7b806121d17c" 137 | ], 138 | "pattern": "^.*$" 139 | } 140 | } 141 | } 142 | 143 | }, 144 | "VariableGroup": { 145 | "$id": "#root/items/VariableGroup", 146 | "title": "Variablegroup", 147 | "type": "null", 148 | "default": null 149 | } 150 | } 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /jeschro-1540-readme.md: -------------------------------------------------------------------------------- 1 | # Pipeline Performance Optimization 2 | 3 | [https://github.com/microsoft/coe-alm-accelerator-templates/tree/jeschro-1540](https://github.com/microsoft/coe-alm-accelerator-templates/tree/jeschro-1540) 4 | 5 | Branch includes performance optimizations by leveraging the [Cache DevOps task](https://docs.microsoft.com/azure/devops/pipelines/release/caching?view=azure-devops). 6 | 7 | ## Caching of Install-PowerShell-Modules.yml 8 | 9 | The Install-PowerShell-Modules.yml template downloads and installs PowerShell modules required in subsequent pipeline templates. This branch include changes to the install-powershell-module.yml that implements the same logic as PP_BT Tools Installer to save PS Modules to a AA4PP specific folder on the agent. By caching this folder the downloaded PS modules can be restored directly from cache rather than have to download them. 10 | The Install-PowerShell-Module.yml has also been modified to download the Microsoft.Xrm.Tooling.ConfigurationMigration which is used by the import-configuration-migration-data.yml. 11 | 12 | ### Cache task for install-powershell-modules.yml 13 | ``` 14 | - task: Cache@2 15 | displayName: Cache Powershell Modules 16 | inputs: 17 | key: restoremodules | "$(powerPlatformToolsSubPath)" | $(Pipeline.Workspace)/PipelineUtils/Pipelines/Templates/install-powershell-modules.yml 18 | path: $(powerPlatformToolsPath) 19 | cacheHitVar: powerPlatformToolsPath_IsCached 20 | ``` 21 | 22 | - $(powerPlatformToolsSubPath) see [Introducing set-tools-paths.yml](#introducing-set-tools-pathsyml) 23 | - $(Pipeline.Workspace)/PipelineUtils/Pipelines/Templates/install-powershell-modules.yml - this is reference to the template file. The cache task will calculate a dynamic cache key based on the hash of the template file 24 | - $(powerPlatformToolsPath) see [Introducting set-tools-paths.yml](#introducing-set-tools-pathsyml) 25 | 26 | ### Invalidating the install-powershell-module.yml cache 27 | 28 | To invalidate the cache for install-powershell-modules.yml users can update the content of the install-powershell-modules.yml file. I.e. if the required version for a powershell module is updated in the template the cache will be invalid and all the ps modules will be downloaded. 29 | 30 | ## Introducing set-tools-paths.yml 31 | 32 | This new template file will set Pipeline Variables to be used in the cache tasks and the install-powershell-modules.yml template 33 | 34 | Following pipeline vars are set 35 | 36 | - powerPlatformToolsSubPath: _coe\ALM_ACC 37 | - powerPlatformToolsPath: will map depending on pipeline agent (usually maps to the pipeline workspace) 38 | - Example: C:\agent\_work\1\$powerPlatformToolsSubPath 39 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "CenterofExcellenceALMAccelerator-March2024", 3 | "published": "2024-03-22", 4 | "content": "coe-alm-accelerator-templates" 5 | } 6 | --------------------------------------------------------------------------------