Skip to content

Commit 7a8c4a7

Browse files
freddydkmazhelezaholstrup1andersgMSFT
authored
PowerPlatform integration (microsoft#927)
Fixes microsoft#361 New Settings: - PowerPlatformSolutionFolder - containing the name of the folder containing a PowerPlatform Solution (only one) - companyId and ppEnvironmentUrl added to deploymentSettings for environments. New Actions: - BuildPowerPlatform - to build a PowerPlatform Solution - DeployPowerPlatform - to deploy a PowerPlatform Solution - PullPowerPlatformChanges - to pull changes made in PowerPlatform studio into the repository - ReadPowerPlatformSettings - to read settings and secrets for PowerPlatform deployment - GetArtifactsForDeployment - originally code from deploy.ps1 to retrieve artifacts for releases or builds - now as an action to read apps into a folder. New Workflows: - Pull PowerPlatform Changes - Push PowerPlatform Changes Other changes - Getting artifacts for deployment moved from deploy.ps1 to a seperate action - Test for specific version of containerhelper moved to avoid many warnings - Add scenarios for PowerPlatform - Add PowerPlatformSolution artifact to builds - Add unpack parameter to DownloadArtifact to unpack after download. TO:DO - [x] Fix failing CI tests - [x] Ensure End 2 End test are passing - [x] Increment version number in PowerPlatform project (awaits Increment Version Number PR from @mazhelez) - [x] Unit Tests - [x] End 2 End test - [x] Remove PREVIEW prefix from various docs - [x] Remove usage of private version of BcContainerHelper - [x] Release notes - [x] Document new settings --------- Co-authored-by: freddydk <[email protected]> Co-authored-by: Maria Zhelezova <[email protected]> Co-authored-by: Alexander Holstrup <[email protected]> Co-authored-by: andersgMSFT <[email protected]>
1 parent 654dd54 commit 7a8c4a7

File tree

105 files changed

+3879
-376
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3879
-376
lines changed

.github/workflows/CI.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
run: |
2222
. (Join-Path "." "Tests/runtests.ps1")
2323
24-
- name: Test AL-GO Workflows
24+
- name: Test AL-Go Workflows
2525
if: github.repository_owner == 'microsoft'
2626
run: |
2727
try {

Actions/AL-Go-Helper.ps1

+21-13
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $defaultCICDPullRequestBranches = @( 'main' )
2020
$runningLocal = $local.IsPresent
2121
$defaultBcContainerHelperVersion = "preview" # Must be double quotes. Will be replaced by BcContainerHelperVersion if necessary in the deploy step - ex. "https://github.com/organization/navcontainerhelper/archive/refs/heads/branch.zip"
2222
$microsoftTelemetryConnectionString = "InstrumentationKey=84bd9223-67d4-4378-8590-9e4a46023be2;IngestionEndpoint=https://westeurope-1.in.applicationinsights.azure.com/"
23-
$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl")
23+
$notSecretProperties = @("Scopes","TenantId","BlobName","ContainerName","StorageAccountName","ServerUrl","ppUserName")
2424

2525
$runAlPipelineOverrides = @(
2626
"DockerPull"
@@ -554,6 +554,7 @@ function ReadSettings {
554554
"type" = "PTE"
555555
"unusedALGoSystemFiles" = @()
556556
"projects" = @()
557+
"powerPlatformSolutionFolder" = ""
557558
"country" = "us"
558559
"artifact" = ""
559560
"companyName" = ""
@@ -2301,12 +2302,30 @@ function RetryCommand {
23012302
}
23022303
}
23032304

2305+
function GetMatchingProjects {
2306+
Param(
2307+
[string[]] $projects,
2308+
[string] $selectProjects = ''
2309+
)
2310+
2311+
if ($selectProjects) {
2312+
# Filter the project list based on the projects parameter
2313+
if ($selectProjects.StartsWith('[')) {
2314+
$selectProjects = ($selectProjects | ConvertFrom-Json) -join ","
2315+
}
2316+
$projectArr = $selectProjects.Split(',').Trim()
2317+
$projects = @($projects | Where-Object { $project = $_; if ($projectArr | Where-Object { $project -like $_ }) { $project } })
2318+
}
2319+
return $projects
2320+
}
2321+
23042322
function GetProjectsFromRepository {
23052323
Param(
23062324
[string] $baseFolder,
23072325
[string[]] $projectsFromSettings,
23082326
[string] $selectProjects = ''
23092327
)
2328+
23102329
if ($projectsFromSettings) {
23112330
$projects = $projectsFromSettings
23122331
}
@@ -2318,18 +2337,7 @@ function GetProjectsFromRepository {
23182337
$projects += @(".")
23192338
}
23202339
}
2321-
if ($selectProjects) {
2322-
# Filter the project list based on the projects parameter
2323-
if ($selectProjects.StartsWith('[')) {
2324-
$selectProjects = ($selectProjects | ConvertFrom-Json) -join ","
2325-
}
2326-
$projectArr = $selectProjects.Split(',').Trim()
2327-
$projects = @($projects | Where-Object { $project = $_; if ($projectArr | Where-Object { $project -like $_ }) { $project } })
2328-
if ($projects.Count -eq 0) {
2329-
throw "No projects matches '$selectProjects'"
2330-
}
2331-
}
2332-
return $projects
2340+
return @(GetMatchingProjects -projects $projects -selectProjects $selectProjects)
23332341
}
23342342

23352343
function Get-PackageVersion($PackageName) {

Actions/AL-Go-TestRepoHelper.ps1

+7
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,20 @@ function Test-SettingsJson {
5454
Test-Shell -json $json -settingsDescription $settingsDescription -property 'shell'
5555
Test-Shell -json $json -settingsDescription $settingsDescription -property 'gitHubRunnerShell'
5656

57+
if ($json.Keys -contains 'bcContainerHelperVersion') {
58+
if ($json.bcContainerHelperVersion -ne 'latest' -and $json.bcContainerHelperVersion -ne 'preview') {
59+
OutputWarning -Message "Using a specific version of BcContainerHelper in $settingsDescription is not recommended and will lead to build failures in the future. Consider removing the setting."
60+
}
61+
}
62+
5763
if ($type -eq 'Repo') {
5864
# Test for things that should / should not exist in a repo settings file
5965
Test-Property -settingsDescription $settingsDescription -json $json -key 'templateUrl' -should
6066
}
6167
if ($type -eq 'Project') {
6268
# GitHubRunner should not be in a project settings file (only read from repo or workflow settings)
6369
Test-Property -settingsDescription $settingsDescription -json $json -key 'githubRunner' -shouldnot
70+
Test-Property -settingsDescription $settingsDescription -json $json -key 'bcContainerHelperVersion' -shouldnot
6471
}
6572
if ($type -eq 'Workflow') {
6673
# Test for things that should / should not exist in a workflow settings file
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
[CmdletBinding()]
2+
param(
3+
[Parameter(mandatory = $true)]
4+
[string] $solutionFolder,
5+
[Parameter(mandatory = $false)]
6+
[string] $companyId,
7+
[Parameter(mandatory = $false)]
8+
[string] $environmentName,
9+
[Parameter(mandatory = $false)]
10+
[string] $appBuild,
11+
[Parameter(mandatory = $false)]
12+
[string] $appRevision
13+
)
14+
$ErrorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0
15+
16+
function Update-PowerAppSettings {
17+
[CmdletBinding()]
18+
param(
19+
[Parameter(Mandatory = $true)]
20+
[string] $SolutionFolder,
21+
[Parameter(Mandatory = $true)]
22+
[string] $environmentName,
23+
[Parameter(Mandatory = $true)]
24+
[string] $companyId
25+
)
26+
27+
# There are multiple files that contain the BC connection info for PowerApps with different structures
28+
# So instead of parsing all of them, we simple find the current connection strings and run a replace operation.
29+
# Note: The connection string has a format of: "EnvironmentName,CompanyId" where companyId is a guid. So the
30+
# replace operation should be safe to run a all json and XML files.
31+
Write-Host "Updating PowerApp settings"
32+
$currentPowerAppSettings = Get-CurrentPowerAppSettings -solutionFolder $SolutionFolder
33+
if ($null -eq $currentPowerAppSettings) {
34+
Write-Host "::Notice::No Power Apps found"
35+
return
36+
}
37+
38+
Write-Host "Number of Business Central Power App connections found: "$currentPowerAppSettings.Count
39+
$newSettings = "$environmentName,$companyId"
40+
foreach ($currentSetting in $currentPowerAppSettings) {
41+
if ($currentSetting -eq $newSettings) {
42+
Write-Host "No changes needed for: "$currentSetting
43+
continue
44+
}
45+
Update-PowerAppFiles -oldSetting $currentSetting -newSetting $newSettings -solutionFolder $SolutionFolder
46+
}
47+
}
48+
49+
function Update-PowerAppFiles {
50+
param(
51+
[Parameter(mandatory = $true)]
52+
[string] $solutionFolder,
53+
[Parameter(mandatory = $true)]
54+
[string] $oldSetting,
55+
[Parameter(mandatory = $true)]
56+
[string] $newSetting
57+
)
58+
59+
$powerAppFiles = Get-ChildItem -Recurse -File "$solutionFolder/CanvasApps"
60+
foreach ($file in $powerAppFiles) {
61+
# only check json and xml files
62+
if (($file.Extension -eq ".json") -or ($file.Extension -eq ".xml")) {
63+
$fileContent = Get-Content $file.FullName
64+
if (Select-String -Pattern $oldSetting -InputObject $fileContent) {
65+
$fileContent = $fileContent -creplace $oldSetting, $newSetting
66+
Set-Content -Path $file.FullName -Value $fileContent
67+
Write-Host "Updated: $($file.FullName)"
68+
}
69+
}
70+
}
71+
}
72+
73+
function Get-CurrentPowerAppSettings {
74+
param (
75+
[Parameter(mandatory = $true)]
76+
[string] $solutionFolder
77+
)
78+
79+
if (-not (Test-Path -Path "$solutionFolder/CanvasApps")) {
80+
# No Canvas apps present in the solution
81+
return @()
82+
}
83+
84+
$currentSettingsList = @()
85+
$connectionsFilePaths = Get-ChildItem -Path "$solutionFolder/CanvasApps" -Recurse -File -Include "Connections.json" | Select-Object -ExpandProperty FullName
86+
foreach ($connectionsFilePath in $connectionsFilePaths) {
87+
$jsonFile = Get-Content $connectionsFilePath | ConvertFrom-Json
88+
89+
# We don't know the name of the connector node, so we need to loop through all of them
90+
$ConnectorNodeNames = ($jsonFile | Get-Member -MemberType NoteProperty).Name
91+
92+
foreach ($connectorNodeName in $ConnectorNodeNames) {
93+
$connectorNode = $jsonFile.$connectorNodeName
94+
# Find the Business Central connection node
95+
if ($connectorNode.connectionRef.displayName -eq "Dynamics 365 Business Central") {
96+
$currentEnvironmentAndCompany = ($connectorNode.datasets | Get-Member -MemberType NoteProperty).Name
97+
98+
if ($null -eq $currentEnvironmentAndCompany) {
99+
# Connections sections for Power Automate flow does not have a dataset node
100+
# Note: Flows are handled in a different function
101+
continue
102+
}
103+
104+
if (!$currentSettingsList.Contains($currentEnvironmentAndCompany)) {
105+
$currentSettingsList += $currentEnvironmentAndCompany
106+
107+
# The Business Central environment can be inconsistent - Either starting with a capital letter or all caps.
108+
# Add both variants to ensure we find all connections
109+
$currentSettingsParts = @($currentEnvironmentAndCompany.Split(","))
110+
$currentSettingsList += "$($currentSettingsParts[0].ToUpperInvariant()),$($currentSettingsParts[1])"
111+
}
112+
}
113+
}
114+
}
115+
return $currentSettingsList
116+
}
117+
118+
function Update-FlowSettings {
119+
[CmdletBinding()]
120+
param(
121+
[Parameter(Mandatory = $true)]
122+
[string] $SolutionFolder,
123+
[Parameter(Mandatory = $true)]
124+
[string] $environmentName,
125+
[Parameter(Mandatory = $true)]
126+
[string] $companyId
127+
)
128+
129+
Write-Host "Updating Flow settings"
130+
$flowFilePaths = Get-ChildItem -Path (Join-Path $SolutionFolder 'Workflows') -Recurse -Filter '*.json' | Select-Object -ExpandProperty FullName
131+
132+
if ($null -eq $flowFilePaths) {
133+
Write-Host "::Notice::No Power Automate flows found"
134+
return
135+
}
136+
137+
foreach ($flowFilePath in $flowFilePaths) {
138+
Update-FlowFile -FilePath $flowFilePath -CompanyId $companyId -EnvironmentName $environmentName
139+
}
140+
}
141+
142+
function Update-FlowFile {
143+
[CmdletBinding()]
144+
param(
145+
[Parameter(Mandatory = $true)]
146+
[string] $filePath,
147+
[Parameter(Mandatory = $true)]
148+
[string] $companyId,
149+
[Parameter(Mandatory = $true)]
150+
[string] $environmentName
151+
)
152+
# Read the JSON file
153+
$jsonObject = Get-Content $filePath | ConvertFrom-Json
154+
$shouldUpdate = $false
155+
156+
# Update all flow triggers
157+
$triggersObject = $jsonObject.properties.definition.triggers
158+
$triggers = $triggersObject | Get-Member -MemberType Properties
159+
foreach ($trigger in $triggers) {
160+
$triggerInputs = $triggersObject.($trigger.Name).inputs
161+
162+
if ($triggerInputs | Get-Member -MemberType Properties -name 'parameters') {
163+
# Business Central triggers have connection information in the input parameters
164+
if (Update-ParameterObject -parametersObject $triggerInputs.parameters -CompanyId $companyId -EnvironmentName $environmentName) {
165+
$shouldUpdate = $true
166+
}
167+
}
168+
}
169+
170+
# Update all flow actions
171+
$actionsObject = $jsonObject.properties.definition.actions
172+
$actions = $actionsObject | Get-Member -MemberType Properties
173+
foreach ($action in $actions) {
174+
$actionInput = $actionsObject.($action.Name).inputs
175+
if ($actionInput | Get-Member -MemberType Properties -name 'parameters') {
176+
# Business Central actions have connection information in the input parameters
177+
if (Update-ParameterObject -parametersObject $actionInput.parameters -CompanyId $companyId -EnvironmentName $environmentName) {
178+
$shouldUpdate = $true
179+
}
180+
}
181+
}
182+
if ($shouldUpdate) {
183+
# Save the updated JSON back to the file
184+
$jsonObject | ConvertTo-Json -Depth 100 | Set-Content $filePath
185+
Write-Host "Updated: $filePath"
186+
}
187+
else {
188+
Write-Host "No update needed for: $filePath"
189+
}
190+
}
191+
192+
function Update-ParameterObject {
193+
[CmdletBinding()]
194+
param(
195+
[Parameter(Mandatory = $true)]
196+
[System.Object] $parametersObject,
197+
[Parameter(Mandatory = $true)]
198+
[string] $companyId,
199+
[Parameter(Mandatory = $true)]
200+
[string] $environmentName
201+
)
202+
# Check if paramers are for Business Central
203+
if ((-not $parametersObject.company) -or (-not $parametersObject.bcEnvironment)) {
204+
return $false
205+
}
206+
207+
$oldCompany = $parametersObject.company
208+
$oldBcEnvironment = $parametersObject.bcenvironment
209+
210+
# Check if parameters are already set to the correct values
211+
if (($oldCompany -eq $companyId) -and ($oldBcEnvironment -eq $environmentName)) {
212+
return $false
213+
}
214+
215+
$enviromentVariablePlaceHolder = "@parameters("
216+
217+
# Check if parameters are set using a different approach (e.g. environment variables or passed in parameters)
218+
if ($oldCompany.contains($enviromentVariablePlaceHolder) -or $oldBcEnvironment.contains($enviromentVariablePlaceHolder)) {
219+
return $false
220+
}
221+
222+
$parametersObject.company = $companyId
223+
$parametersObject.bcEnvironment = $environmentName
224+
225+
return $true
226+
}
227+
228+
function Update-SolutionVersionNode {
229+
param(
230+
[Parameter(mandatory = $true)]
231+
[string] $appBuild,
232+
[Parameter(mandatory = $true)]
233+
[string] $appRevision,
234+
[Parameter(mandatory = $true)]
235+
[xml] $xmlFile
236+
)
237+
238+
if ($appBuild -and $appRevision) {
239+
$versionNode = $xmlFile.SelectSingleNode("//Version")
240+
$versionNodeText = $versionNode.'#text'
241+
242+
$versionParts = $versionNodeText.Split('.')
243+
# Only update the last two parts of the version number - major and minor version should be set manually
244+
$newVersionNumber = $versionParts[0] + '.' + $versionParts[1] + '.' + $appBuild + '.' + $appRevision
245+
246+
Write-Host "New version: "$newVersionNumber
247+
$versionNode.'#text' = $newVersionNumber
248+
}
249+
250+
}
251+
252+
if ($appBuild -and $appRevision) {
253+
Write-Host "Updating Power Platform solution file ($solutionFolder)"
254+
$solutionDefinitionFile = Join-Path $solutionFolder 'other/Solution.xml'
255+
if (-not (Test-Path -Path $solutionDefinitionFile)) {
256+
throw "Solution file not found: $solutionDefinitionFile"
257+
}
258+
$xmlFile = [xml](Get-Content -Encoding UTF8 -Path $solutionDefinitionFile)
259+
Update-SolutionVersionNode -appBuild $appBuild -appRevision $appRevision -xmlFile $xmlFile
260+
$xmlFile.Save($solutionDefinitionFile)
261+
}
262+
else {
263+
Write-Host "Skip solution version update since appBuild and appRevision are not set"
264+
}
265+
266+
if ($environmentName -and $companyId) {
267+
Write-Host "Updating the Power Platform solution Business Central connection settings"
268+
Write-Host "New connections settings: $environmentName, $companyId"
269+
Update-PowerAppSettings -SolutionFolder $SolutionFolder -EnvironmentName $environmentName -CompanyId $companyId
270+
Update-FlowSettings -SolutionFolder $SolutionFolder -EnvironmentName $environmentName -CompanyId $companyId
271+
}
272+
else {
273+
Write-Host "Skip Business Central connection settings update since EnvironmentName and CompanyId are not set"
274+
}

Actions/BuildPowerPlatform/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Build Power Platform
2+
Build the Power Platform solution
3+
4+
## INPUT
5+
6+
### ENV variables
7+
none
8+
9+
### Parameters
10+
| Name | Required | Description | Default value |
11+
| :-- | :-: | :-- | :-- |
12+
| shell | | The shell (powershell or pwsh) in which the PowerShell script in this action should run | powershell |
13+
| solutionFolder | Yes | The Power Platform solution path | |
14+
| outputFolder | Yes | Output folder where the zip file will be placed | |
15+
| outputFileName | Yes | The name of the output zip file | |
16+
| companyId | | The Business Central company ID | |
17+
| environmentName | | The Business Central environment name | |
18+
| appBuild | | The app build number | |
19+
| appRevision | | The app revision number | |
20+
21+
## OUTPUT
22+
none

0 commit comments

Comments
 (0)