From 24c4c525011951ac779cb585565aac089b654a1a Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:16:57 +0000 Subject: [PATCH 01/71] test --- Actions/RunPipeline/RunPipeline.ps1 | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index a2dfa364bd..0b6a8fed97 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -219,6 +219,34 @@ try { # Analyze app.json version dependencies before launching pipeline # Analyze InstallApps and InstallTestApps before launching pipeline + function LoadDLL { + Param( + [string] $path + ) + $bytes = [System.IO.File]::ReadAllBytes($path) + [System.Reflection.Assembly]::Load($bytes) | Out-Null + } + $allInstallApps = $install.Apps + $install.TestApps + # Create folder in temp directory with a unique name + $tempFolder = Join-Path $ENV:RUNNER_TEMP "DevelopmentTools-$(Get-Random)" + dotnet tool install Microsoft.Dynamics.BusinessCentral.Development.Tools --version "16.0.23.34358-beta" --tool-path $tempFolder | Out-Host + + # Find the Microsoft.Dynamics.Nav.CodeAnalysis.dll + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Filter "Microsoft.Dynamics.Nav.CodeAnalysis.dll" -Recurse | Select-Object -First 1 + LoadDLL -path $codeanalysisdll.FullName + + foreach($app in $allInstallApps) { + # If the app is a URL, skip it + if ($app -like 'http*://*') { + continue + } + $packageStream = [System.IO.File]::OpenRead($app) + $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) + if (($null -eq $package.ReadSourceCodeFilePaths()) -and (-not $package.IsRuntimePackage)) { + # This is likely a symbols package. Write a waning that this app shouldn't be published to a container + OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." + } + } # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { From 0eb0a769fac2c428eb857ee9511e370e9746efc1 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:44:08 +0000 Subject: [PATCH 02/71] ? --- Actions/RunPipeline/RunPipeline.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 0b6a8fed97..45d9aa736d 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -240,7 +240,10 @@ try { if ($app -like 'http*://*') { continue } - $packageStream = [System.IO.File]::OpenRead($app) + Write-Host "Analyzing app $app for version dependencies" + $appFile = Get-ChildItem $app + Write-Host "Analyzing app file $appFile" + $packageStream = [System.IO.File]::OpenRead($appFile) $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) if (($null -eq $package.ReadSourceCodeFilePaths()) -and (-not $package.IsRuntimePackage)) { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container From 9c7f8b49adc784425ca3515d3a3bf8b465e544da Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:17:01 +0000 Subject: [PATCH 03/71] installapps --- Actions/RunPipeline/RunPipeline.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 45d9aa736d..249adbeffd 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -235,7 +235,7 @@ try { $codeanalysisdll = Get-ChildItem -Path $tempFolder -Filter "Microsoft.Dynamics.Nav.CodeAnalysis.dll" -Recurse | Select-Object -First 1 LoadDLL -path $codeanalysisdll.FullName - foreach($app in $allInstallApps) { + <#foreach($app in $allInstallApps) { # If the app is a URL, skip it if ($app -like 'http*://*') { continue @@ -249,7 +249,7 @@ try { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." } - } + }#> # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { From 09e53bc59adc9282cc2174e8aa58b5d8a885dafb Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:05:16 +0200 Subject: [PATCH 04/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 249adbeffd..ba4779cfbd 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -235,13 +235,13 @@ try { $codeanalysisdll = Get-ChildItem -Path $tempFolder -Filter "Microsoft.Dynamics.Nav.CodeAnalysis.dll" -Recurse | Select-Object -First 1 LoadDLL -path $codeanalysisdll.FullName - <#foreach($app in $allInstallApps) { + foreach($app in $allInstallApps) { # If the app is a URL, skip it if ($app -like 'http*://*') { continue } - Write-Host "Analyzing app $app for version dependencies" - $appFile = Get-ChildItem $app + Write-Host "Analyzing app $app for version dependencies in $projectPath" + $appFile = Join-Path $projectPath $app -Resolve Write-Host "Analyzing app file $appFile" $packageStream = [System.IO.File]::OpenRead($appFile) $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) @@ -249,7 +249,7 @@ try { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." } - }#> + } # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { From 687d718d2d08f90ede7152ecb7a277121d6544ac Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:16:36 +0200 Subject: [PATCH 05/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index ba4779cfbd..aa1570fc2d 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -232,7 +232,8 @@ try { dotnet tool install Microsoft.Dynamics.BusinessCentral.Development.Tools --version "16.0.23.34358-beta" --tool-path $tempFolder | Out-Host # Find the Microsoft.Dynamics.Nav.CodeAnalysis.dll - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Filter "Microsoft.Dynamics.Nav.CodeAnalysis.dll" -Recurse | Select-Object -First 1 + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select -Last 1 + Write-Host "Found $codeanalysisdll - Loading dll" LoadDLL -path $codeanalysisdll.FullName foreach($app in $allInstallApps) { From f2cf24d429cbc3cca2cf37552eda154004ab29a6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:39:07 +0200 Subject: [PATCH 06/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index aa1570fc2d..d267566ef9 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -246,6 +246,8 @@ try { Write-Host "Analyzing app file $appFile" $packageStream = [System.IO.File]::OpenRead($appFile) $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) + Write-Host "IsRuntimePackage: $($package.IsRuntimePackage)" + Write-Host "SourceCodeFilePaths: $($package.ReadSourceCodeFilePaths())" if (($null -eq $package.ReadSourceCodeFilePaths()) -and (-not $package.IsRuntimePackage)) { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." From 9faedc2f9e69dba37a99ee422d1f2fd14d564c8f Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:51:54 +0200 Subject: [PATCH 07/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index d267566ef9..98446c1231 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -248,7 +248,7 @@ try { $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) Write-Host "IsRuntimePackage: $($package.IsRuntimePackage)" Write-Host "SourceCodeFilePaths: $($package.ReadSourceCodeFilePaths())" - if (($null -eq $package.ReadSourceCodeFilePaths()) -and (-not $package.IsRuntimePackage)) { + if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ($package.ReadSourceCodeFilePaths() -eq "")) -and (-not $package.IsRuntimePackage)) { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." } From 6df6f34b913939cab4e4f134ef35dc8ec126029d Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:00:42 +0200 Subject: [PATCH 08/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 98446c1231..c90bb8a21c 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -248,7 +248,7 @@ try { $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) Write-Host "IsRuntimePackage: $($package.IsRuntimePackage)" Write-Host "SourceCodeFilePaths: $($package.ReadSourceCodeFilePaths())" - if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ($package.ReadSourceCodeFilePaths() -eq "")) -and (-not $package.IsRuntimePackage)) { + if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { # This is likely a symbols package. Write a waning that this app shouldn't be published to a container OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." } From c82b46a2f75dae19752a133a61f12cf7fc3a9e19 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:12:51 +0200 Subject: [PATCH 09/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 62 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index c90bb8a21c..ab8b78b5e3 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -220,39 +220,45 @@ try { # Analyze InstallApps and InstallTestApps before launching pipeline function LoadDLL { - Param( - [string] $path - ) + Param( + [string] $path + ) $bytes = [System.IO.File]::ReadAllBytes($path) [System.Reflection.Assembly]::Load($bytes) | Out-Null } - $allInstallApps = $install.Apps + $install.TestApps - # Create folder in temp directory with a unique name - $tempFolder = Join-Path $ENV:RUNNER_TEMP "DevelopmentTools-$(Get-Random)" - dotnet tool install Microsoft.Dynamics.BusinessCentral.Development.Tools --version "16.0.23.34358-beta" --tool-path $tempFolder | Out-Host - - # Find the Microsoft.Dynamics.Nav.CodeAnalysis.dll - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select -Last 1 - Write-Host "Found $codeanalysisdll - Loading dll" - LoadDLL -path $codeanalysisdll.FullName - - foreach($app in $allInstallApps) { - # If the app is a URL, skip it - if ($app -like 'http*://*') { - continue - } - Write-Host "Analyzing app $app for version dependencies in $projectPath" - $appFile = Join-Path $projectPath $app -Resolve - Write-Host "Analyzing app file $appFile" - $packageStream = [System.IO.File]::OpenRead($appFile) - $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create($PackageStream, $true) - Write-Host "IsRuntimePackage: $($package.IsRuntimePackage)" - Write-Host "SourceCodeFilePaths: $($package.ReadSourceCodeFilePaths())" - if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { - # This is likely a symbols package. Write a waning that this app shouldn't be published to a container - OutputWarning -message "App $app is a symbols package and should not be published to a container. Skipping." + + function AnalyzeInstallApps() { + Param( + [string[]] $AllInstallApps, + [string] $RunnerTempFolder = $ENV:RUNNER_TEMP + ) + # Create folder in temp directory with a unique name + $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" + dotnet tool install Microsoft.Dynamics.BusinessCentral.Development.Tools --version "16.0.23.34358-beta" --tool-path $tempFolder | Out-Host + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select -Last 1 + LoadDLL -path $codeanalysisdll.FullName + foreach($app in $allInstallApps) { + # If the app is a URL, skip it for now + if ($app -like 'http*://*') { + continue + } + $appFile = Join-Path $projectPath $app -Resolve -ErrorAction SilentlyContinue + if ($appFile) { + Write-Host "Analyzing app file $appFile" + $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) + if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { + # This is likely a symbols package. Write a waning that this app shouldn't be published to a container + OutputWarning -message "App $app is a symbols package and should not be published to a container." + } + } } + } + + + $allInstallApps = $install.Apps + $install.TestApps + + # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { From 7e7b46d1764f432dd9d2abead9e4c4627b36ffa8 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:14:30 +0200 Subject: [PATCH 10/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index ab8b78b5e3..0610b863e1 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -255,10 +255,7 @@ try { } - - $allInstallApps = $install.Apps + $install.TestApps - - + AnalyzeInstallApps -AllInstallApps ($install.Apps + $install.TestApps) # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { From 34911af771134250a749111ecf95203fb6681503 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:21:04 +0200 Subject: [PATCH 11/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 0610b863e1..291bb701bf 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -240,9 +240,12 @@ try { foreach($app in $allInstallApps) { # If the app is a URL, skip it for now if ($app -like 'http*://*') { + $appFile = "something.app" # TODO + Invoke-WebRequest -Uri $app -Method GET -OutFile $appFile continue + } else { + $appFile = Join-Path $projectPath $app -Resolve -ErrorAction SilentlyContinue } - $appFile = Join-Path $projectPath $app -Resolve -ErrorAction SilentlyContinue if ($appFile) { Write-Host "Analyzing app file $appFile" $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) From 10746453ec2c3bff2b75da5639e0ed992f6e6b4a Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:38:38 +0000 Subject: [PATCH 12/71] Refactor --- Actions/Packages.json | 3 +- Actions/RunPipeline/RunPipeline.ps1 | 53 ++++++---------------------- Actions/RunPipeline/RunPipeline.psm1 | 50 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 43 deletions(-) create mode 100644 Actions/RunPipeline/RunPipeline.psm1 diff --git a/Actions/Packages.json b/Actions/Packages.json index 71707e6ead..9c70328f00 100644 --- a/Actions/Packages.json +++ b/Actions/Packages.json @@ -3,5 +3,6 @@ "Microsoft.ApplicationInsights": "2.20.0", "Az.Accounts": "2.15.1", "Az.Storage": "6.1.1", - "Az.KeyVault": "5.2.0" + "Az.KeyVault": "5.2.0", + "Microsoft.Dynamics.BusinessCentral.Development.Tools": "16.0.23.34358-beta" } diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 291bb701bf..cc99acd040 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -25,6 +25,7 @@ try { Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) DownloadAndImportBcContainerHelper Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\DetermineProjectsToBuild\DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking + Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".RunPipeline.psm1" -Resolve) if ($isWindows) { # Pull docker image in the background @@ -193,6 +194,7 @@ try { } # Replace secret names in install.apps and install.testApps + $tempDependenciesLocation = Join-Path $ENV:RUNNER_TEMP "Dependencies-$(Get-Random)" foreach($list in @('Apps','TestApps')) { $install."$list" = @($install."$list" | ForEach-Object { $pattern = '.*(\$\{\{\s*([^}]+?)\s*\}\}).*' @@ -203,62 +205,29 @@ try { else { $finalUrl = $url } - # Check validity of URL + + # Download the app file if the URL is valid if ($finalUrl -like 'http*://*') { try { - Invoke-WebRequest -Method Head -UseBasicParsing -Uri $finalUrl | Out-Null + $appFile = Join-Path $tempDependenciesLocation ([Uri]::UnescapeDataString([System.IO.Path]::GetFileName($finalUrl.Split('?')[0].TrimEnd('/')))) + Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null } catch { throw "Setting: install$($list) contains an inaccessible URL: $($url). Error was: $($_.Exception.Message)" } } - return $finalUrl + return $appFile }) } # Analyze app.json version dependencies before launching pipeline # Analyze InstallApps and InstallTestApps before launching pipeline - function LoadDLL { - Param( - [string] $path - ) - $bytes = [System.IO.File]::ReadAllBytes($path) - [System.Reflection.Assembly]::Load($bytes) | Out-Null - } - - function AnalyzeInstallApps() { - Param( - [string[]] $AllInstallApps, - [string] $RunnerTempFolder = $ENV:RUNNER_TEMP - ) - # Create folder in temp directory with a unique name - $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" - dotnet tool install Microsoft.Dynamics.BusinessCentral.Development.Tools --version "16.0.23.34358-beta" --tool-path $tempFolder | Out-Host - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select -Last 1 - LoadDLL -path $codeanalysisdll.FullName - foreach($app in $allInstallApps) { - # If the app is a URL, skip it for now - if ($app -like 'http*://*') { - $appFile = "something.app" # TODO - Invoke-WebRequest -Uri $app -Method GET -OutFile $appFile - continue - } else { - $appFile = Join-Path $projectPath $app -Resolve -ErrorAction SilentlyContinue - } - if ($appFile) { - Write-Host "Analyzing app file $appFile" - $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) - if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { - # This is likely a symbols package. Write a waning that this app shouldn't be published to a container - OutputWarning -message "App $app is a symbols package and should not be published to a container." - } - } - } - + if ((-not $settings.useCompilerFolder) -and (-not $settings.doNotPublishApps)) { + # Test that InstallApps are not symbols packages + # Skip this check if we are using a compiler folder or doNotPublishApps is set + Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath } - - AnalyzeInstallApps -AllInstallApps ($install.Apps + $install.TestApps) # Check if codeSignCertificateUrl+Password is used (and defined) if (!$settings.doNotSignApps -and $codeSignCertificateUrl -and $codeSignCertificatePassword -and !$settings.keyVaultCodesignCertificateName) { diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 new file mode 100644 index 0000000000..f8ecb2f153 --- /dev/null +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -0,0 +1,50 @@ +Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) +. (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) + +function LoadDLL { + Param( + [string] $path + ) + $bytes = [System.IO.File]::ReadAllBytes($path) + [System.Reflection.Assembly]::Load($bytes) | Out-Null +} + +function Test-InstallApps() { + Param( + [string[]] $AllInstallApps, + [string] $ProjectPath, + [string] $RunnerTempFolder = $ENV:RUNNER_TEMP + ) + try { + # Create folder in temp directory with a unique name + $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" + + # Download the Microsoft.Dynamics.BusinessCentral.Development.Tools package + $version = GetPackageVersion -PackageName "Microsoft.Dynamics.BusinessCentral.Development.Tools" + dotnet tool install "Microsoft.Dynamics.BusinessCentral.Development.Tools" --version $version --tool-path $tempFolder | Out-Host + + # Load the DLL from the temp folder + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select-Object -Last 1 + LoadDLL -path $codeanalysisdll.FullName + + foreach ($app in $allInstallApps) { + $appFile = Join-Path $ProjectPath $app -Resolve -ErrorAction SilentlyContinue + if ($appFile) { + Write-Host "Analyzing app file $appFile" + $appFileName = $appFile.BaseName + $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) + if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { + # If package is not a runtime package and has no source code files, it is likely a symbols package + # Symbols packages are not meant to be published to a container + OutputWarning -message "App $appFileName is a symbols package and should not be published to a container. The workflow may fail if you try to publish it." + } + } + } + } + catch { + Trace-Information -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" + } + +} + +Export-ModuleMember -Function Test-InstallApps From 9944de56a090e0e97d2c3d3cbd6810690a89a7b6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:39:14 +0000 Subject: [PATCH 13/71] Docs --- Actions/RunPipeline/RunPipeline.psm1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index f8ecb2f153..58e560bf3d 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -9,6 +9,19 @@ function LoadDLL { [System.Reflection.Assembly]::Load($bytes) | Out-Null } +<# + .SYNOPSIS + Analyzes the install apps to check if they are symbols packages. + .DESCRIPTION + Analyzes the install apps to check if they are symbols packages. + If an app is a symbols package, it outputs a warning message. + .PARAMETER AllInstallApps + The list of all install apps to analyze. + .PARAMETER ProjectPath + The path to the project where the apps are located. + .PARAMETER RunnerTempFolder + The temporary folder used by the runner (default is $ENV:RUNNER_TEMP). +#> function Test-InstallApps() { Param( [string[]] $AllInstallApps, From 30d9e77174935c40d189cdc678a6b8874b8216ad Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 06:14:58 +0000 Subject: [PATCH 14/71] Minor updates --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- Actions/RunPipeline/RunPipeline.psm1 | 2 +- RELEASENOTES.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index cc99acd040..aaa8f51658 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -25,7 +25,7 @@ try { Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) DownloadAndImportBcContainerHelper Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\DetermineProjectsToBuild\DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".RunPipeline.psm1" -Resolve) + Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) if ($isWindows) { # Pull docker image in the background diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 58e560bf3d..a15e05728d 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -49,7 +49,7 @@ function Test-InstallApps() { if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { # If package is not a runtime package and has no source code files, it is likely a symbols package # Symbols packages are not meant to be published to a container - OutputWarning -message "App $appFileName is a symbols package and should not be published to a container. The workflow may fail if you try to publish it." + OutputWarning -message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } } diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4b1109b17f..69ee4253ff 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,7 @@ ### Issues - Issue 1770: Wrong type of _projects_ setting in settings schema +- Issue 1512: Throw a warning before trying to publish symbols packages ## v7.2 From b15b33973e54fab3b86f22680152cedaf7973574 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 06:23:01 +0000 Subject: [PATCH 15/71] Some updated docs --- Scenarios/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 21429a9b48..7d5f12fb00 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -90,7 +90,7 @@ The repository settings are only read from the repository settings file (.github | licenseFileUrlSecretName | Specify the name (**NOT the secret**) of the LicenseFileUrl secret. Default is LicenseFileUrl. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use as LicenseFileUrl. A LicenseFileUrl is required when building AppSource apps for Business Central prior to version 22. Read [this](SetupCiCdForExistingAppSourceApp.md) for more information. | LicenseFileUrl | | ghTokenWorkflowSecretName | Specifies the name (**NOT the secret**) of the GhTokenWorkflow secret. Default is GhTokenWorkflow. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use as Personal Access Token with permission to modify workflows when running the Update AL-Go System Files workflow. Read [this](UpdateAlGoSystemFiles.md) for more information. | GhTokenWorkflow | | adminCenterApiCredentialsSecretName | Specifies the name (**NOT the secret**) of the adminCenterApiCredentials secret. Default is adminCenterApiCredentials. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use when connecting to the Admin Center API when creating Online Development Environments. Read [this](CreateOnlineDevEnv2.md) for more information. | AdminCenterApiCredentials | -| installApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the CI/CD workflow can find and download the apps. The apps in installApps are downloaded and installed before compiling and installing the apps.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | +| installApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the CI/CD workflow can find and download the apps. The apps in installApps are downloaded and installed before compiling and installing the apps. Before adding an app to the installApps property, please ensure it is not a symbols-only package. Symbols-only packages cannot be published and installed during CI/CD and will throw an error.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | | installTestApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the CI/CD workflow can find and download the apps. The apps in installTestApps are downloaded and installed before compiling and installing the test apps. Adding a parentheses around the setting indicates that the test in this app will NOT be run, only installed.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | | configPackages | An array of configuration packages to be applied to the build container before running tests. Configuration packages can be the relative path within the project or it can be STANDARD, EXTENDED or EVALUATION for the rapidstart packages, which comes with Business Central. | [ ] | | configPackages.country | An array of configuration packages to be applied to the build container for country **country** before running tests. Configuration packages can be the relative path within the project or it can be STANDARD, EXTENDED or EVALUATION for the rapidstart packages, which comes with Business Central. | [ ] | From 072b03e7fc657902c33bf571b99a9b7093d34488 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:48:03 +0000 Subject: [PATCH 16/71] Fixes --- Actions/RunPipeline/RunPipeline.psm1 | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index a15e05728d..d4705f240a 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -37,27 +37,32 @@ function Test-InstallApps() { dotnet tool install "Microsoft.Dynamics.BusinessCentral.Development.Tools" --version $version --tool-path $tempFolder | Out-Host # Load the DLL from the temp folder - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select-Object -Last 1 + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select-Object -Last 1 LoadDLL -path $codeanalysisdll.FullName - foreach ($app in $allInstallApps) { - $appFile = Join-Path $ProjectPath $app -Resolve -ErrorAction SilentlyContinue - if ($appFile) { - Write-Host "Analyzing app file $appFile" - $appFileName = $appFile.BaseName + foreach ($app in $AllInstallApps) { + $appFilePath = Join-Path $ProjectPath $app -Resolve -ErrorAction SilentlyContinue + if ($appFilePath) { + $appFile = Get-Item -Path $appFilePath + $appFileName = $appFile.Name + Write-Host "Analyzing app file $appFileName" $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { # If package is not a runtime package and has no source code files, it is likely a symbols package # Symbols packages are not meant to be published to a container - OutputWarning -message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." + OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } } } catch { - Trace-Information -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" + Trace-Warning -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" + } finally { + # Clean up the temporary folder + if (Test-Path -Path $tempFolder) { + Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue + } } - } Export-ModuleMember -Function Test-InstallApps From e50f8901e9a16e44952020118d05442f8854a918 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:52:35 +0000 Subject: [PATCH 17/71] Test --- Tests/RunPipeline.Action.Test.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index d0c063c179..6f372a3537 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -23,6 +23,27 @@ Describe "RunPipeline Action Tests" { YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -outputs $outputs } + It 'Test warning for symbols packages' { + Import-Module (Join-Path $scriptRoot '.\RunPipeline.psm1' -Resolve) + . (Join-Path $PSScriptRoot '../Actions/AL-Go-Helper.ps1') + Import-Module (Join-Path $PSScriptRoot '../Actions/TelemetryHelper.psm1') + + # Mock the OutputWarning and Trace-Warning functions + Mock -CommandName OutputWarning -MockWith { param($Message) Write-Host "OutputWarning: $Message" } -ModuleName RunPipeline + Mock -CommandName Trace-Warning -MockWith { param($Message) Write-Host "Trace-Information: $Message" } -ModuleName RunPipeline + + # Invoke the function with TestApp1 (a symbols package) and TestApp2 (a full app package) + $tempFolder = [System.IO.Path]::GetTempPath() + Test-InstallApps -AllInstallApps @(".\TestApps\BaseApplicationFull.app", ".\TestApps\BaseApplicationSymbols.app") -ProjectPath $PSScriptRoot -RunnerTempFolder $tempFolder + + # Assert that the warning was output + Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline + Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App BaseApplicationSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } + + # Assert that Trace-Information was not called + Assert-MockCalled Trace-Warning -Exactly 0 -ModuleName RunPipeline + } + # Call action } From e46aafaa46dbe792723b0fadf3a9b8f1180d1964 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:16:02 +0000 Subject: [PATCH 18/71] test apps --- Tests/RunPipeline.Action.Test.ps1 | 2 +- .../EssentialBusinessHeadlinesFull.app | Bin 0 -> 120866 bytes ...rosoftEssentialBusinessHeadlinesSymbols.app | Bin 0 -> 2675 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Tests/TestApps/EssentialBusinessHeadlinesFull.app create mode 100644 Tests/TestApps/MicrosoftEssentialBusinessHeadlinesSymbols.app diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index 6f372a3537..63f2202e45 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -34,7 +34,7 @@ Describe "RunPipeline Action Tests" { # Invoke the function with TestApp1 (a symbols package) and TestApp2 (a full app package) $tempFolder = [System.IO.Path]::GetTempPath() - Test-InstallApps -AllInstallApps @(".\TestApps\BaseApplicationFull.app", ".\TestApps\BaseApplicationSymbols.app") -ProjectPath $PSScriptRoot -RunnerTempFolder $tempFolder + Test-InstallApps -AllInstallApps @(".\TestApps\EssentialBusinessHeadlinesFull.app", ".\TestApps\BaseApplicationSymbols.app") -ProjectPath $PSScriptRoot -RunnerTempFolder $tempFolder # Assert that the warning was output Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline diff --git a/Tests/TestApps/EssentialBusinessHeadlinesFull.app b/Tests/TestApps/EssentialBusinessHeadlinesFull.app new file mode 100644 index 0000000000000000000000000000000000000000..73418c35196c0453c676e3dc5279d1041d145809 GIT binary patch literal 120866 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 literal 0 HcmV?d00001 diff --git a/Tests/TestApps/MicrosoftEssentialBusinessHeadlinesSymbols.app b/Tests/TestApps/MicrosoftEssentialBusinessHeadlinesSymbols.app new file mode 100644 index 0000000000000000000000000000000000000000..d395933a9172e810c3e7012758391235ee5cac33 GIT binary patch literal 2675 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 literal 0 HcmV?d00001 From e21a2c0339dc37f8cfec90596fcdaf93ff438bbc Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:18:07 +0000 Subject: [PATCH 19/71] fixes --- Tests/RunPipeline.Action.Test.ps1 | 4 ++-- ...ls.app => EssentialBusinessHeadlinesSymbols.app} | Bin 2 files changed, 2 insertions(+), 2 deletions(-) rename Tests/TestApps/{MicrosoftEssentialBusinessHeadlinesSymbols.app => EssentialBusinessHeadlinesSymbols.app} (100%) diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index 63f2202e45..5bb442426b 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -34,11 +34,11 @@ Describe "RunPipeline Action Tests" { # Invoke the function with TestApp1 (a symbols package) and TestApp2 (a full app package) $tempFolder = [System.IO.Path]::GetTempPath() - Test-InstallApps -AllInstallApps @(".\TestApps\EssentialBusinessHeadlinesFull.app", ".\TestApps\BaseApplicationSymbols.app") -ProjectPath $PSScriptRoot -RunnerTempFolder $tempFolder + Test-InstallApps -AllInstallApps @(".\TestApps\EssentialBusinessHeadlinesFull.app", ".\TestApps\EssentialBusinessHeadlinesSymbols.app") -ProjectPath $PSScriptRoot -RunnerTempFolder $tempFolder # Assert that the warning was output Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline - Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App BaseApplicationSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } + Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App EssentialBusinessHeadlinesSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } # Assert that Trace-Information was not called Assert-MockCalled Trace-Warning -Exactly 0 -ModuleName RunPipeline diff --git a/Tests/TestApps/MicrosoftEssentialBusinessHeadlinesSymbols.app b/Tests/TestApps/EssentialBusinessHeadlinesSymbols.app similarity index 100% rename from Tests/TestApps/MicrosoftEssentialBusinessHeadlinesSymbols.app rename to Tests/TestApps/EssentialBusinessHeadlinesSymbols.app From 08f136305c19b23adc6ffcbfc61a752b89ac054c Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:18:56 +0200 Subject: [PATCH 20/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index aaa8f51658..a2934c60fa 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -215,6 +215,8 @@ try { catch { throw "Setting: install$($list) contains an inaccessible URL: $($url). Error was: $($_.Exception.Message)" } + } else { + $appFile = $_ } return $appFile }) From e0baefbc7f16e63d22e61477edc77bdd513d03d6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:41:37 +0200 Subject: [PATCH 21/71] Update RunPipeline.psm1 --- Actions/RunPipeline/RunPipeline.psm1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index d4705f240a..88ecd9b952 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -56,7 +56,8 @@ function Test-InstallApps() { } } catch { - Trace-Warning -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" + #Trace-Warning -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" + Write-Host "$($_.Exception.Message)" } finally { # Clean up the temporary folder if (Test-Path -Path $tempFolder) { From c743f37b2fac9294075f76a2ff1c832439e683e5 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:57:12 +0000 Subject: [PATCH 22/71] newer package --- Actions/Packages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/Packages.json b/Actions/Packages.json index 9c70328f00..2a2210110d 100644 --- a/Actions/Packages.json +++ b/Actions/Packages.json @@ -4,5 +4,5 @@ "Az.Accounts": "2.15.1", "Az.Storage": "6.1.1", "Az.KeyVault": "5.2.0", - "Microsoft.Dynamics.BusinessCentral.Development.Tools": "16.0.23.34358-beta" + "Microsoft.Dynamics.BusinessCentral.Development.Tools": "16.0.24.41895-beta" } From 919f05654fbaf9faab4d106063d9f7a6d168cc38 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:57:29 +0000 Subject: [PATCH 23/71] update test --- Tests/RunPipeline.Action.Test.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index 5bb442426b..8d86201a55 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -24,7 +24,7 @@ Describe "RunPipeline Action Tests" { } It 'Test warning for symbols packages' { - Import-Module (Join-Path $scriptRoot '.\RunPipeline.psm1' -Resolve) + Import-Module (Join-Path $scriptRoot '.\RunPipeline.psm1' -Resolve) -Force . (Join-Path $PSScriptRoot '../Actions/AL-Go-Helper.ps1') Import-Module (Join-Path $PSScriptRoot '../Actions/TelemetryHelper.psm1') @@ -40,7 +40,7 @@ Describe "RunPipeline Action Tests" { Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App EssentialBusinessHeadlinesSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } - # Assert that Trace-Information was not called + # Assert that Trace-Warning was not called Assert-MockCalled Trace-Warning -Exactly 0 -ModuleName RunPipeline } From 653d30f8fcda73cad56c2be96dac2f1ad3f85841 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:06:09 +0000 Subject: [PATCH 24/71] Update check --- Actions/RunPipeline/RunPipeline.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index a2934c60fa..2f6469dfc8 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -225,9 +225,9 @@ try { # Analyze app.json version dependencies before launching pipeline # Analyze InstallApps and InstallTestApps before launching pipeline - if ((-not $settings.useCompilerFolder) -and (-not $settings.doNotPublishApps)) { + if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages - # Skip this check if we are using a compiler folder or doNotPublishApps is set + # Skip this check if doNotPublishApps is set Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath } From a9233f8a5adfe36228420152efa6ea46001974d5 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:53:47 +0000 Subject: [PATCH 25/71] Run in pwsh --- Actions/RunPipeline/RunPipeline.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 2f6469dfc8..713af1a6be 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -228,7 +228,19 @@ try { if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages # Skip this check if doNotPublishApps is set - Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath + + $scriptBlock = { + param ( + [string]$ScriptRoot, + [array]$AllInstallApps, + [string]$ProjectPath, + [string]$RunnerTempFolder + ) + + Import-Module (Join-Path -Path $ScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) + Test-InstallApps -AllInstallApps $AllInstallApps -ProjectPath $ProjectPath -RunnerTempFolder $RunnerTempFolder + } + pwsh -NoProfile -Command $scriptBlock -args $PSScriptRoot, ($install.Apps + $install.TestApps), $projectPath, $ENV:RUNNER_TEMP } # Check if codeSignCertificateUrl+Password is used (and defined) From 247cbeebcc5461828fc0f5f9d4e36a2c2fae7184 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:54:04 +0000 Subject: [PATCH 26/71] Comment --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 88ecd9b952..2c5825b59b 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -48,7 +48,7 @@ function Test-InstallApps() { Write-Host "Analyzing app file $appFileName" $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { - # If package is not a runtime package and has no source code files, it is likely a symbols package + # If package is not a runtime package and has no source code files, it is a symbols package # Symbols packages are not meant to be published to a container OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } From 7c835813157b959632277d7b20904f843515e4e4 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:18:31 +0000 Subject: [PATCH 27/71] Create tempDependenciesLocation --- Actions/RunPipeline/RunPipeline.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 713af1a6be..15ca313dab 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -209,6 +209,9 @@ try { # Download the app file if the URL is valid if ($finalUrl -like 'http*://*') { try { + if (-not (Test-Path $tempDependenciesLocation)) { + New-Item -Path $tempDependenciesLocation -ItemType Directory | Out-Null + } $appFile = Join-Path $tempDependenciesLocation ([Uri]::UnescapeDataString([System.IO.Path]::GetFileName($finalUrl.Split('?')[0].TrimEnd('/')))) Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null } From 358c354b240f2c4e06826d88a45ca8cb3cbab832 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:42:34 +0000 Subject: [PATCH 28/71] Do not fail if file fails to download --- Actions/RunPipeline/RunPipeline.ps1 | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 15ca313dab..34e152a930 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -206,8 +206,15 @@ try { $finalUrl = $url } - # Download the app file if the URL is valid if ($finalUrl -like 'http*://*') { + # Check validity of URL + try { + Invoke-WebRequest -Method Head -UseBasicParsing -Uri $finalUrl | Out-Null + } catch { + throw "Setting: install$($list) contains an inaccessible URL: $($url). Error was: $($_.Exception.Message)" + } + + # Try downloading the app file try { if (-not (Test-Path $tempDependenciesLocation)) { New-Item -Path $tempDependenciesLocation -ItemType Directory | Out-Null @@ -216,7 +223,9 @@ try { Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null } catch { - throw "Setting: install$($list) contains an inaccessible URL: $($url). Error was: $($_.Exception.Message)" + # If the app file fails to be downloaded we skip the check + Write-Host "Failed to download app from $finalUrl. Error was: $($_.Exception.Message)" + return $finalUrl } } else { $appFile = $_ @@ -243,7 +252,11 @@ try { Import-Module (Join-Path -Path $ScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) Test-InstallApps -AllInstallApps $AllInstallApps -ProjectPath $ProjectPath -RunnerTempFolder $RunnerTempFolder } - pwsh -NoProfile -Command $scriptBlock -args $PSScriptRoot, ($install.Apps + $install.TestApps), $projectPath, $ENV:RUNNER_TEMP + + # Filter out URLs and non-existing files + $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' -and (Test-Path $_) } + + pwsh -NoProfile -Command $scriptBlock -args $PSScriptRoot, $allInstallApps, $projectPath, $ENV:RUNNER_TEMP } # Check if codeSignCertificateUrl+Password is used (and defined) From c081bd38d9a88d6093e6d9d100f7764a79ed33bb Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 07:11:20 +0000 Subject: [PATCH 29/71] logging --- Actions/RunPipeline/RunPipeline.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 34e152a930..6d10a57e08 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -252,6 +252,7 @@ try { Import-Module (Join-Path -Path $ScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) Test-InstallApps -AllInstallApps $AllInstallApps -ProjectPath $ProjectPath -RunnerTempFolder $RunnerTempFolder } + Write-Host "Found the following apps: $(($install.Apps + $install.TestApps) -join ', ')" # Filter out URLs and non-existing files $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' -and (Test-Path $_) } From ef12a553113d674481ab9caeda3ae17f4daab414 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 07:20:38 +0000 Subject: [PATCH 30/71] only filter out urls --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 6d10a57e08..8ad52b2882 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -255,7 +255,7 @@ try { Write-Host "Found the following apps: $(($install.Apps + $install.TestApps) -join ', ')" # Filter out URLs and non-existing files - $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' -and (Test-Path $_) } + $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' } pwsh -NoProfile -Command $scriptBlock -args $PSScriptRoot, $allInstallApps, $projectPath, $ENV:RUNNER_TEMP } From ad84033a2c0f7a8f4fc60fc84e57fbdce85c823e Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:02:56 +0000 Subject: [PATCH 31/71] Logging --- Actions/RunPipeline/RunPipeline.psm1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 2c5825b59b..72820eea31 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -28,6 +28,12 @@ function Test-InstallApps() { [string] $ProjectPath, [string] $RunnerTempFolder = $ENV:RUNNER_TEMP ) + + if ($AllInstallApps.Count -eq 0) { + Write-Host "No install apps to analyze." + return + } + try { # Create folder in temp directory with a unique name $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" @@ -52,6 +58,8 @@ function Test-InstallApps() { # Symbols packages are not meant to be published to a container OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } + } else { + Write-Host "App file path for $app could not be resolved ($appFilePath)." } } } From 90e44641042e8e5e0345206c15a8a4011cfb8093 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:32:37 +0000 Subject: [PATCH 32/71] test path --- Actions/RunPipeline/RunPipeline.psm1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 72820eea31..96985e2178 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -47,7 +47,12 @@ function Test-InstallApps() { LoadDLL -path $codeanalysisdll.FullName foreach ($app in $AllInstallApps) { - $appFilePath = Join-Path $ProjectPath $app -Resolve -ErrorAction SilentlyContinue + if (Test-Path -Path $app) { + $appFilePath = (Get-Item -Path $app).FullName + } else { + $appFilePath = Join-Path $ProjectPath $app -Resolve -ErrorAction SilentlyContinue + } + if ($appFilePath) { $appFile = Get-Item -Path $appFilePath $appFileName = $appFile.Name From 0d1e268c768c8628db315a4233f0873f8c07df24 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:04:39 +0000 Subject: [PATCH 33/71] Updates --- Actions/RunPipeline/RunPipeline.ps1 | 2 -- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 8ad52b2882..4f431297c8 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -252,8 +252,6 @@ try { Import-Module (Join-Path -Path $ScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) Test-InstallApps -AllInstallApps $AllInstallApps -ProjectPath $ProjectPath -RunnerTempFolder $RunnerTempFolder } - Write-Host "Found the following apps: $(($install.Apps + $install.TestApps) -join ', ')" - # Filter out URLs and non-existing files $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' } diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 96985e2178..bd0f228c84 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -64,7 +64,7 @@ function Test-InstallApps() { OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } else { - Write-Host "App file path for $app could not be resolved ($appFilePath)." + Write-Host "App file path for $app could not be resolved ($appFilePath). Skipping symbols check." } } } From bc4f66ea904e98f5139f3590f353ad947a9121c6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:08:15 +0000 Subject: [PATCH 34/71] docs --- Actions/RunPipeline/RunPipeline.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 4f431297c8..51f7823343 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -223,7 +223,7 @@ try { Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null } catch { - # If the app file fails to be downloaded we skip the check + # If the app file fails to be downloaded we keep it as a URL and let Run-ALPipeline download it Write-Host "Failed to download app from $finalUrl. Error was: $($_.Exception.Message)" return $finalUrl } @@ -239,8 +239,6 @@ try { # Analyze InstallApps and InstallTestApps before launching pipeline if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages - # Skip this check if doNotPublishApps is set - $scriptBlock = { param ( [string]$ScriptRoot, From 399f95f151b8e37678de5d6ff8afdfb1abd9e09f Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:10:10 +0000 Subject: [PATCH 35/71] doc --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index bd0f228c84..ec4bc486b6 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -60,7 +60,7 @@ function Test-InstallApps() { $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { # If package is not a runtime package and has no source code files, it is a symbols package - # Symbols packages are not meant to be published to a container + # Symbols packages are not meant to be published to a BC Environment OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } else { From 2fd11c66bf43f03607c6faff29be32be4b0ec956 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:11:08 +0000 Subject: [PATCH 36/71] Trace warning --- Actions/RunPipeline/RunPipeline.psm1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index ec4bc486b6..0c8b01c775 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -69,8 +69,7 @@ function Test-InstallApps() { } } catch { - #Trace-Warning -Message "Something went wrong while analyzing install apps. Error was: $($_.Exception.Message)" - Write-Host "$($_.Exception.Message)" + Trace-Warning -Message "Something went wrong while analyzing install apps." } finally { # Clean up the temporary folder if (Test-Path -Path $tempFolder) { From 68806020399cc844a849e37b55ca5fd59939eb8f Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:55:27 +0000 Subject: [PATCH 37/71] release note update --- RELEASENOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 285d4fe908..640a819dfd 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,7 @@ +### Issues + +- Issue 1512: Throw a warning before trying to publish symbols packages + ## v7.3 ### Configurable merge method for pull request auto-merge @@ -50,7 +54,6 @@ Please note, that due to certain security limitations, the properties `runs-on`, - Issue 1837 Deployment from PR builds fail if PR branch name includes forward slashes (e.g., `feature/branch-name`). - Issue 1852 Page Scripting Tests are not added to build summary - Issue 1829 Added custom jobs cannot be removed -- Issue 1512: Throw a warning before trying to publish symbols packages - Idea 1856 Include workflow name as input for action ReadSetting ### Additional debug logging functionality From 5c1e835581bebf5341e6ed25b193be05825e1fc8 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 29 Aug 2025 12:59:39 +0000 Subject: [PATCH 38/71] suggestion --- Actions/RunPipeline/RunPipeline.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 51f7823343..e213d79221 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -219,7 +219,10 @@ try { if (-not (Test-Path $tempDependenciesLocation)) { New-Item -Path $tempDependenciesLocation -ItemType Directory | Out-Null } - $appFile = Join-Path $tempDependenciesLocation ([Uri]::UnescapeDataString([System.IO.Path]::GetFileName($finalUrl.Split('?')[0].TrimEnd('/')))) + $urlWithoutQuery = $finalUrl.Split('?')[0].TrimEnd('/') + $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) + $decodedFileName = [Uri]::UnescapeDataString($rawFileName) + $appFile = Join-Path $tempDependenciesLocation $decodedFileName Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null } catch { From 0c039c958fd494fd86dc32874deee4cbd8aa40cc Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 10 Sep 2025 12:22:25 +0000 Subject: [PATCH 39/71] Suggestions --- Actions/RunPipeline/RunPipeline.psm1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 0c8b01c775..b895e1e0f8 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -64,12 +64,13 @@ function Test-InstallApps() { OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } else { - Write-Host "App file path for $app could not be resolved ($appFilePath). Skipping symbols check." + Write-Host "App file path for $app could not be resolved. Skipping symbols check." } } } catch { Trace-Warning -Message "Something went wrong while analyzing install apps." + OutputDebug -message "Error: $_" } finally { # Clean up the temporary folder if (Test-Path -Path $tempFolder) { From 7940041edda3d6406bdfb0a3a5380a7667de0744 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:28:27 +0200 Subject: [PATCH 40/71] Update Scenarios/settings.md Co-authored-by: Maria Zhelezova <43066499+mazhelez@users.noreply.github.com> --- Scenarios/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scenarios/settings.md b/Scenarios/settings.md index ab98306166..4377a94167 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -100,7 +100,7 @@ The repository settings are only read from the repository settings file (.github | licenseFileUrlSecretName | Specify the name (**NOT the secret**) of the LicenseFileUrl secret. Default is LicenseFileUrl. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use as LicenseFileUrl. A LicenseFileUrl is required when building AppSource apps for Business Central prior to version 22. Read [this](SetupCiCdForExistingAppSourceApp.md) for more information. | LicenseFileUrl | | ghTokenWorkflowSecretName | Specifies the name (**NOT the secret**) of the GhTokenWorkflow secret. Default is GhTokenWorkflow. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use as Personal Access Token with permission to modify workflows when running the Update AL-Go System Files workflow. Read [this](UpdateAlGoSystemFiles.md) for more information. | GhTokenWorkflow | | adminCenterApiCredentialsSecretName | Specifies the name (**NOT the secret**) of the adminCenterApiCredentials secret. Default is adminCenterApiCredentials. AL-Go for GitHub will look for a secret with this name in GitHub Secrets or Azure KeyVault to use when connecting to the Admin Center API when creating Online Development Environments. Read [this](CreateOnlineDevEnv2.md) for more information. | AdminCenterApiCredentials | -| installApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the CI/CD workflow can find and download the apps. The apps in installApps are downloaded and installed before compiling and installing the apps. Before adding an app to the installApps property, please ensure it is not a symbols-only package. Symbols-only packages cannot be published and installed during CI/CD and will throw an error.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | +| installApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the build workflow can find and download the apps. The apps in installApps are downloaded and installed before compiling and installing the apps in the current project. Before adding an app to the installApps property, please ensure it is not a symbols-only package. Symbols-only packages are not intended for publishing and installing and using them via installApps might lead to compilation or runtime errors.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | | installTestApps | An array of 3rd party dependency apps, which you do not have access to through the appDependencyProbingPaths. The setting should be an array of either secure URLs or paths to folders or files relative to the project, where the CI/CD workflow can find and download the apps. The apps in installTestApps are downloaded and installed before compiling and installing the test apps. Adding a parentheses around the setting indicates that the test in this app will NOT be run, only installed.
**Note:** If you specify ${{SECRETNAME}} as part of a URL, it will be replaced by the value of the secret SECRETNAME. | [ ] | | configPackages | An array of configuration packages to be applied to the build container before running tests. Configuration packages can be the relative path within the project or it can be STANDARD, EXTENDED or EVALUATION for the rapidstart packages, which comes with Business Central. | [ ] | | configPackages.country | An array of configuration packages to be applied to the build container for country **country** before running tests. Configuration packages can be the relative path within the project or it can be STANDARD, EXTENDED or EVALUATION for the rapidstart packages, which comes with Business Central. | [ ] | From bf4a63671792bbb818bc98ad9e2550636d25ec6a Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 13:07:50 +0200 Subject: [PATCH 41/71] updates --- Actions/Environment.Packages.proj | 2 +- Actions/RunPipeline/RunPipeline.ps1 | 1 - Actions/RunPipeline/RunPipeline.psm1 | 21 ++++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Actions/Environment.Packages.proj b/Actions/Environment.Packages.proj index d0a3950974..72c6767c65 100644 --- a/Actions/Environment.Packages.proj +++ b/Actions/Environment.Packages.proj @@ -12,7 +12,7 @@ - + diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 7779e30c14..ba746d12fb 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -25,7 +25,6 @@ try { Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) DownloadAndImportBcContainerHelper Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "..\DetermineProjectsToBuild\DetermineProjectsToBuild.psm1" -Resolve) -DisableNameChecking - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) if ($isWindows) { # Pull docker image in the background diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index b895e1e0f8..f5bb71151d 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -26,6 +26,7 @@ function Test-InstallApps() { Param( [string[]] $AllInstallApps, [string] $ProjectPath, + [string] $DevelopmentToolsPackage = "Microsoft.Dynamics.BusinessCentral.Development.Tools", [string] $RunnerTempFolder = $ENV:RUNNER_TEMP ) @@ -39,11 +40,13 @@ function Test-InstallApps() { $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" # Download the Microsoft.Dynamics.BusinessCentral.Development.Tools package - $version = GetPackageVersion -PackageName "Microsoft.Dynamics.BusinessCentral.Development.Tools" - dotnet tool install "Microsoft.Dynamics.BusinessCentral.Development.Tools" --version $version --tool-path $tempFolder | Out-Host + dotnet tool install $DevelopmentToolsPackage ` + --version (GetPackageVersion -PackageName $DevelopmentToolsPackage) ` + --tool-path $tempFolder ` + | Out-Host # Load the DLL from the temp folder - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*Microsoft.Dynamics.Nav.CodeAnalysis.dll" } | Select-Object -Last 1 + $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*al.exe" } | Select-Object -First 1 LoadDLL -path $codeanalysisdll.FullName foreach ($app in $AllInstallApps) { @@ -57,8 +60,7 @@ function Test-InstallApps() { $appFile = Get-Item -Path $appFilePath $appFileName = $appFile.Name Write-Host "Analyzing app file $appFileName" - $package = [Microsoft.Dynamics.Nav.CodeAnalysis.Packaging.NavAppPackageReader]::Create([System.IO.File]::OpenRead($appFile), $true) - if ((($null -eq $package.ReadSourceCodeFilePaths()) -or ("" -eq $package.ReadSourceCodeFilePaths())) -and (-not $package.IsRuntimePackage)) { + if (IsSymbolsOnlyPackage -AppFilePath $appFile -AlExePath $alExe) { # If package is not a runtime package and has no source code files, it is a symbols package # Symbols packages are not meant to be published to a BC Environment OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." @@ -79,4 +81,13 @@ function Test-InstallApps() { } } +function IsSymbolsOnlyPackage { + param( + [string] $AppFilePath, + [string] $AlExePath + ) + . $AlExePath IsSymbolOnly + return $LASTEXITCODE -eq 0 +} + Export-ModuleMember -Function Test-InstallApps From 9c98d08324fcfca72fbb6f8a136ca93966348b6c Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 13:10:53 +0200 Subject: [PATCH 42/71] update IsSymbolsOnlyPackage --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index f5bb71151d..580970a3fd 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -86,7 +86,7 @@ function IsSymbolsOnlyPackage { [string] $AppFilePath, [string] $AlExePath ) - . $AlExePath IsSymbolOnly + . $AlExePath IsSymbolOnly $AppFilePath | Out-Null return $LASTEXITCODE -eq 0 } From e7dd1ee2b8cd5cf927fc6cff8a20a52601e8b908 Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 13:20:47 +0200 Subject: [PATCH 43/71] $alExe --- Actions/RunPipeline/RunPipeline.psm1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 580970a3fd..6721bed18e 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -46,8 +46,7 @@ function Test-InstallApps() { | Out-Host # Load the DLL from the temp folder - $codeanalysisdll = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*al.exe" } | Select-Object -First 1 - LoadDLL -path $codeanalysisdll.FullName + $alExe = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*al.exe" } | Select-Object -First 1 foreach ($app in $AllInstallApps) { if (Test-Path -Path $app) { From 640432cac7093e8745bf6ceecd54b528a30a8529 Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 13:25:27 +0200 Subject: [PATCH 44/71] remove powershell 7 call --- Actions/RunPipeline/RunPipeline.ps1 | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index ba746d12fb..caf151d616 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -241,21 +241,8 @@ try { # Analyze InstallApps and InstallTestApps before launching pipeline if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages - $scriptBlock = { - param ( - [string]$ScriptRoot, - [array]$AllInstallApps, - [string]$ProjectPath, - [string]$RunnerTempFolder - ) - - Import-Module (Join-Path -Path $ScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) - Test-InstallApps -AllInstallApps $AllInstallApps -ProjectPath $ProjectPath -RunnerTempFolder $RunnerTempFolder - } - # Filter out URLs and non-existing files - $allInstallApps = ($install.Apps + $install.TestApps) | Where-Object { $_ -notlike 'http*://*' } - - pwsh -NoProfile -Command $scriptBlock -args $PSScriptRoot, $allInstallApps, $projectPath, $ENV:RUNNER_TEMP + Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) + Test-InstallApps -AllInstallApps $allInstallApps -ProjectPath $projectPath -RunnerTempFolder $ENV:RUNNER_TEMP } # Check if codeSignCertificateUrl+Password is used (and defined) From 4142b08a0bfbfd3e4cc31a5b5904f6ea5c001fd5 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:46:27 +0000 Subject: [PATCH 45/71] update Test-InstallApps --- Actions/RunPipeline/RunPipeline.psm1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 6721bed18e..ee5ff185db 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -45,8 +45,11 @@ function Test-InstallApps() { --tool-path $tempFolder ` | Out-Host - # Load the DLL from the temp folder - $alExe = Get-ChildItem -Path $tempFolder -Recurse -Force | Where-Object { $_.FullName -like "*al.exe" } | Select-Object -First 1 + # Load the AL tool from the downloaded package + $alExe = Get-ChildItem -Path $tempFolder -Filter "al" | Select-Object -First 1 + if (-not $alExe) { + throw "Could not find al.exe in the $DevelopmentToolsPackage package." + } foreach ($app in $AllInstallApps) { if (Test-Path -Path $app) { From d00775cda01d4a294544b43fb0545c8517d2ef23 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:53:44 +0000 Subject: [PATCH 46/71] Handle linux / windows differences --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index ee5ff185db..8706528dc2 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -46,7 +46,7 @@ function Test-InstallApps() { | Out-Host # Load the AL tool from the downloaded package - $alExe = Get-ChildItem -Path $tempFolder -Filter "al" | Select-Object -First 1 + $alExe = Get-ChildItem -Path $tempFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 if (-not $alExe) { throw "Could not find al.exe in the $DevelopmentToolsPackage package." } From 912c6feb05fa02082a2588b6f73e74dfa560c03c Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 15:04:22 +0200 Subject: [PATCH 47/71] test --- Actions/RunPipeline/RunPipeline.psm1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 6721bed18e..198b28b0dc 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -70,8 +70,10 @@ function Test-InstallApps() { } } catch { - Trace-Warning -Message "Something went wrong while analyzing install apps." - OutputDebug -message "Error: $_" + # Temporarily throw + #Trace-Warning -Message "Something went wrong while analyzing install apps." + #OutputDebug -message "Error: $_" + throw "$_" } finally { # Clean up the temporary folder if (Test-Path -Path $tempFolder) { From 0dc28234b5284ee2e8ac21d87f3d3a8f84fe1274 Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 15:10:34 +0200 Subject: [PATCH 48/71] -ExpandProperty --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 53d8662af5..442d36d523 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -46,7 +46,7 @@ function Test-InstallApps() { | Out-Host # Load the AL tool from the downloaded package - $alExe = Get-ChildItem -Path $tempFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 + $alExe = Get-ChildItem -Path $tempFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 -ExpandProperty FullName if (-not $alExe) { throw "Could not find al.exe in the $DevelopmentToolsPackage package." } From 3d50f4d5427278922ff7cbd3f30eeb87a604629e Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 15:14:10 +0200 Subject: [PATCH 49/71] revert throw --- Actions/RunPipeline/RunPipeline.psm1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 442d36d523..393a16f215 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -73,10 +73,8 @@ function Test-InstallApps() { } } catch { - # Temporarily throw - #Trace-Warning -Message "Something went wrong while analyzing install apps." - #OutputDebug -message "Error: $_" - throw "$_" + Trace-Warning -Message "Something went wrong while analyzing install apps." + OutputDebug -message "Error: $_" } finally { # Clean up the temporary folder if (Test-Path -Path $tempFolder) { From 00162c04bec6ac935552d0c42367933748a1a4f0 Mon Sep 17 00:00:00 2001 From: aholstrup1 Date: Tue, 14 Oct 2025 15:45:18 +0200 Subject: [PATCH 50/71] throw on failed download --- Actions/RunPipeline/RunPipeline.ps1 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index caf151d616..82b442a326 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -222,12 +222,10 @@ try { $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) $decodedFileName = [Uri]::UnescapeDataString($rawFileName) $appFile = Join-Path $tempDependenciesLocation $decodedFileName - Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile | Out-Null + Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null } catch { - # If the app file fails to be downloaded we keep it as a URL and let Run-ALPipeline download it - Write-Host "Failed to download app from $finalUrl. Error was: $($_.Exception.Message)" - return $finalUrl + throw "Could not download app from URL: $($url). Error was: $($_.Exception.Message)" } } else { $appFile = $_ From 12b68c1b7b884c13906897dfa17d456bd30a0ffe Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:02:38 +0000 Subject: [PATCH 51/71] allinstallapps --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 82b442a326..ae85e4a81c 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -240,7 +240,7 @@ try { if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) - Test-InstallApps -AllInstallApps $allInstallApps -ProjectPath $projectPath -RunnerTempFolder $ENV:RUNNER_TEMP + Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath -RunnerTempFolder $ENV:RUNNER_TEMP } # Check if codeSignCertificateUrl+Password is used (and defined) From f0b1d7ca2d9f674144a44f2152e4eb1dea57aa0c Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 16 Oct 2025 09:52:37 +0000 Subject: [PATCH 52/71] Refactor --- Actions/AL-Go-Helper.ps1 | 53 ++++++++++++++++++++++++++++ Actions/RunPipeline/RunPipeline.psm1 | 45 ++++++++++------------- Actions/Sign/Sign.psm1 | 34 ++---------------- 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index d28a6f3d32..dfb12e3dae 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -2159,6 +2159,59 @@ function ConnectAz { } } +<# + .SYNOPSIS + Checks whether nuget.org is added as a nuget source. +#> +function AssertNugetSourceIsAdded() { + $nugetSource = "https://api.nuget.org/v3/index.json" + $nugetSourceExists = dotnet nuget list source | Select-String -Pattern $nugetSource + if (-not $nugetSourceExists) { + throw "Nuget source $nugetSource is not added. Please add the source using 'dotnet nuget add source $nugetSource' or add another source with nuget.org as an upstream source." + } +} + +<# + .SYNOPSIS + Installs a .NET tool in a temporary folder and returns the path to the folder + .DESCRIPTION + This function installs a .NET tool using 'dotnet tool install' in a temporary folder and returns the path to the folder where the tool is installed. + .PARAMETER PackageName + The name of the package to install + .PARAMETER ToolPath + The path where the tool should be installed. If not specified, the tool is installed in a temporary folder. + .RETURNS + The path to the folder where the tool is installed. +#> +function Install-DotNetTool { + param( + [Parameter(Mandatory = $true)] + [string] $PackageName, + [Parameter(Mandatory = $false)] + [string] $ToolPath = $ENV:RUNNER_TEMP + ) + AssertNugetSourceIsAdded + + # Use GetTempPath if RunnerTemp is not set (e.g. when running locally) + if (-not $ToolPath) { + $ToolPath = [System.IO.Path]::GetTempPath() + } + $installationFolder = Join-Path -Path $ToolPath "$PackageName-$(Get-Random)" + + # Get version of the package + $version = GetPackageVersion -PackageName $PackageName + + # Install the tool in the temp folder + Write-Host "Installing $PackageName ($version) in $installationFolder" + dotnet tool install $PackageName --version $version --tool-path $installationFolder | Out-Host + + if (-not (Test-Path -Path $installationFolder)) { + throw "Failed to install $PackageName. If you are using a self-hosted runner please make sure you've followed all the steps described in https://aka.ms/algosettings#runs-on." + } + + return $installationFolder +} + function OutputMessageAndArray { Param( [string] $message, diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 393a16f215..b2d374171a 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -1,12 +1,23 @@ Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) -function LoadDLL { - Param( - [string] $path - ) - $bytes = [System.IO.File]::ReadAllBytes($path) - [System.Reflection.Assembly]::Load($bytes) | Out-Null +<# + .SYNOPSIS + Installs the AL tool. + .DESCRIPTION + Installs the AL tool from the Microsoft.Dynamics.BusinessCentral.Development.Tools package. + .RETURNS + The path to the al.exe tool. +#> +function Install-ALTool { + $DevelopmentToolsPackage = "Microsoft.Dynamics.BusinessCentral.Development.Tools" + $alToolFolder = Install-DotNetTool -PackageName $DevelopmentToolsPackage + # Load the AL tool from the downloaded package + $alExe = Get-ChildItem -Path $alToolFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 -ExpandProperty FullName + if (-not $alExe) { + throw "Could not find al.exe in the $DevelopmentToolsPackage package." + } + return $alExe } <# @@ -26,7 +37,6 @@ function Test-InstallApps() { Param( [string[]] $AllInstallApps, [string] $ProjectPath, - [string] $DevelopmentToolsPackage = "Microsoft.Dynamics.BusinessCentral.Development.Tools", [string] $RunnerTempFolder = $ENV:RUNNER_TEMP ) @@ -36,20 +46,8 @@ function Test-InstallApps() { } try { - # Create folder in temp directory with a unique name - $tempFolder = Join-Path $RunnerTempFolder "DevelopmentTools-$(Get-Random)" - - # Download the Microsoft.Dynamics.BusinessCentral.Development.Tools package - dotnet tool install $DevelopmentToolsPackage ` - --version (GetPackageVersion -PackageName $DevelopmentToolsPackage) ` - --tool-path $tempFolder ` - | Out-Host - - # Load the AL tool from the downloaded package - $alExe = Get-ChildItem -Path $tempFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 -ExpandProperty FullName - if (-not $alExe) { - throw "Could not find al.exe in the $DevelopmentToolsPackage package." - } + # Install the AL tool and get the path to al.exe + $alExe = Install-ALTool foreach ($app in $AllInstallApps) { if (Test-Path -Path $app) { @@ -75,11 +73,6 @@ function Test-InstallApps() { catch { Trace-Warning -Message "Something went wrong while analyzing install apps." OutputDebug -message "Error: $_" - } finally { - # Clean up the temporary folder - if (Test-Path -Path $tempFolder) { - Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue - } } } diff --git a/Actions/Sign/Sign.psm1 b/Actions/Sign/Sign.psm1 index 439b2c7113..08352db32a 100644 --- a/Actions/Sign/Sign.psm1 +++ b/Actions/Sign/Sign.psm1 @@ -1,15 +1,3 @@ -<# - .SYNOPSIS - Checks whether nuget.org is added as a nuget source. -#> -function AssertNugetSourceIsAdded() { - $nugetSource = "https://api.nuget.org/v3/index.json" - $nugetSourceExists = dotnet nuget list source | Select-String -Pattern $nugetSource - if (-not $nugetSourceExists) { - throw "Nuget source $nugetSource is not added. Please add the source using 'dotnet nuget add source $nugetSource' or add another source with nuget.org as an upstream source." - } -} - <# .SYNOPSIS Installs the dotnet signing tool. @@ -19,26 +7,10 @@ function AssertNugetSourceIsAdded() { function Install-SigningTool() { . (Join-Path -Path $PSScriptRoot -ChildPath "..\AL-Go-Helper.ps1" -Resolve) - # Create folder in temp directory with a unique name - $tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) "SigningTool-$(Get-Random)" - - # Get version of the signing tool - $version = GetPackageVersion -PackageName "sign" - - # Install the signing tool in the temp folder - Write-Host "Installing signing tool version $version in $tempFolder" - New-Item -ItemType Directory -Path $tempFolder | Out-Null - dotnet tool install sign --version $version --tool-path $tempFolder | Out-Host + $signToolFolder = Install-DotNetTool -PackageName sign # Return the path to the signing tool - $signingTool = Join-Path -Path $tempFolder "sign.exe" - if (-not (Test-Path -Path $signingTool)) { - # Check if nuget.org is added as a nuget source - AssertNugetSourceIsAdded - - # If the tool is not found, throw an error - throw "Failed to install signing tool. If you are using a self-hosted runner please make sure you've followed all the steps described in https://aka.ms/algosettings#runs-on." - } + $signingTool = Join-Path -Path $signToolFolder "sign.exe" return $signingTool } @@ -159,4 +131,4 @@ function Invoke-SigningTool() { } } -Export-ModuleMember -Function Invoke-SigningTool +Export-ModuleMember -Function *-* From d3838c9f9dc68e5bb92e743fd6f684c9c7c10c75 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:42:50 +0000 Subject: [PATCH 53/71] Re-use if already installed on the same runner --- Actions/AL-Go-Helper.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index dfb12e3dae..5ff2131284 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -2196,7 +2196,12 @@ function Install-DotNetTool { if (-not $ToolPath) { $ToolPath = [System.IO.Path]::GetTempPath() } - $installationFolder = Join-Path -Path $ToolPath "$PackageName-$(Get-Random)" + $installationFolder = Join-Path -Path $ToolPath $PackageName + if (Test-Path -Path $installationFolder) { + # Tool is already installed + Write-Host "$PackageName is already installed in $installationFolder" + return $installationFolder + } # Get version of the package $version = GetPackageVersion -PackageName $PackageName From 986b0e550b34c6e26220015df059014dbadd4fe0 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:43:58 +0000 Subject: [PATCH 54/71] Remove runner temp folder --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- Actions/RunPipeline/RunPipeline.psm1 | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index c842cbccf8..f0bde3b6dd 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -240,7 +240,7 @@ try { if ((-not $settings.doNotPublishApps)) { # Test that InstallApps are not symbols packages Import-Module (Join-Path -Path $PSScriptRoot -ChildPath ".\RunPipeline.psm1" -Resolve) - Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath -RunnerTempFolder $ENV:RUNNER_TEMP + Test-InstallApps -AllInstallApps ($install.Apps + $install.TestApps) -ProjectPath $projectPath } # Check if codeSignCertificateUrl+Password is used (and defined) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index b2d374171a..47fd23aef3 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -30,14 +30,11 @@ function Install-ALTool { The list of all install apps to analyze. .PARAMETER ProjectPath The path to the project where the apps are located. - .PARAMETER RunnerTempFolder - The temporary folder used by the runner (default is $ENV:RUNNER_TEMP). #> function Test-InstallApps() { Param( [string[]] $AllInstallApps, - [string] $ProjectPath, - [string] $RunnerTempFolder = $ENV:RUNNER_TEMP + [string] $ProjectPath ) if ($AllInstallApps.Count -eq 0) { From c70a319f885c2714ed27e7d2385191a054d1b6de Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:06:52 +0100 Subject: [PATCH 55/71] Update RunPipeline.ps1 --- Actions/RunPipeline/RunPipeline.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 76abf38923..ab8d6cd292 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -186,9 +186,13 @@ try { }) } + $installTestAppDependencies = @($installTestAppsJson | ConvertFrom-Json) + if ($installTestAppDependencies.Count -gt 0) { + $installTestAppDependencies = @($installTestAppDependencies.Trim('()')) + } $install = @{ "Apps" = $settings.installApps + @($installAppsJson | ConvertFrom-Json) - "TestApps" = $settings.installTestApps + @($installTestAppsJson | ConvertFrom-Json) + "TestApps" = $settings.installTestApps + $installTestAppDependencies } # Replace secret names in install.apps and install.testApps From a3ee6141f195d342c7773f25fe491baf259297e7 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:35:45 +0000 Subject: [PATCH 56/71] Fix --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index ab8d6cd292..a119bb0ee0 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -188,7 +188,7 @@ try { $installTestAppDependencies = @($installTestAppsJson | ConvertFrom-Json) if ($installTestAppDependencies.Count -gt 0) { - $installTestAppDependencies = @($installTestAppDependencies.Trim('()')) + $installTestAppDependencies = @($installTestAppDependencies | ForEach-Object { $_.Trim('()') }) } $install = @{ "Apps" = $settings.installApps + @($installAppsJson | ConvertFrom-Json) From 5e11ead0f59062d814d81d55e442068fd4e0a1ff Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:38:26 +0000 Subject: [PATCH 57/71] logging --- Actions/RunPipeline/RunPipeline.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index a119bb0ee0..47bc078464 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -187,8 +187,10 @@ try { } $installTestAppDependencies = @($installTestAppsJson | ConvertFrom-Json) + Write-Host "Install test apps from input: $($installTestAppDependencies -join ',')" if ($installTestAppDependencies.Count -gt 0) { - $installTestAppDependencies = @($installTestAppDependencies | ForEach-Object { $_.Trim('()') }) + Write-Host "Trimming parentheses from test app dependencies" + $installTestAppDependencies = @($installTestAppDependencies | ForEach-Object { Write-Host $_ ; $_.Trim('()') }) } $install = @{ "Apps" = $settings.installApps + @($installAppsJson | ConvertFrom-Json) From 91354b272e4cf6f10aac7432d301e6453e2ed540 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:41:26 +0000 Subject: [PATCH 58/71] $installTestAppsJson --- Actions/RunPipeline/RunPipeline.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 47bc078464..eb702ff54f 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -186,6 +186,7 @@ try { }) } + Write-Host "installTestAppsJson - $installTestAppsJson" $installTestAppDependencies = @($installTestAppsJson | ConvertFrom-Json) Write-Host "Install test apps from input: $($installTestAppDependencies -join ',')" if ($installTestAppDependencies.Count -gt 0) { From 8b5f873085f165600fcf77f0487325ebae7b91ce Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:48:09 +0000 Subject: [PATCH 59/71] fix --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index eb702ff54f..0cc012154e 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -187,7 +187,7 @@ try { } Write-Host "installTestAppsJson - $installTestAppsJson" - $installTestAppDependencies = @($installTestAppsJson | ConvertFrom-Json) + $installTestAppDependencies = $installTestAppsJson | ConvertFrom-Json Write-Host "Install test apps from input: $($installTestAppDependencies -join ',')" if ($installTestAppDependencies.Count -gt 0) { Write-Host "Trimming parentheses from test app dependencies" From 063f4f18c64e56fb8b09a77583aafb2d0c0d2e0b Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:19:24 +0000 Subject: [PATCH 60/71] Update Install-DotNetTool function --- Actions/AL-Go-Helper.ps1 | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index fda90c40d6..80498fcfc2 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -2203,24 +2203,17 @@ function AssertNugetSourceIsAdded() { This function installs a .NET tool using 'dotnet tool install' in a temporary folder and returns the path to the folder where the tool is installed. .PARAMETER PackageName The name of the package to install - .PARAMETER ToolPath - The path where the tool should be installed. If not specified, the tool is installed in a temporary folder. .RETURNS The path to the folder where the tool is installed. #> function Install-DotNetTool { param( [Parameter(Mandatory = $true)] - [string] $PackageName, - [Parameter(Mandatory = $false)] - [string] $ToolPath = $ENV:RUNNER_TEMP + [string] $PackageName ) AssertNugetSourceIsAdded + $ToolPath = GetTemporaryPath - # Use GetTempPath if RunnerTemp is not set (e.g. when running locally) - if (-not $ToolPath) { - $ToolPath = [System.IO.Path]::GetTempPath() - } $installationFolder = Join-Path -Path $ToolPath $PackageName if (Test-Path -Path $installationFolder) { # Tool is already installed From 6d62f546fc4029fe0bafa7f3fb9d04ee9b19b0a5 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:31:00 +0000 Subject: [PATCH 61/71] suggestions --- Actions/RunPipeline/RunPipeline.ps1 | 7 ------- Actions/RunPipeline/RunPipeline.psm1 | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 3bfbd33e75..98533b67ea 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -212,13 +212,6 @@ try { } if ($finalUrl -like 'http*://*') { - # Check validity of URL - try { - Invoke-WebRequest -Method Head -UseBasicParsing -Uri $finalUrl | Out-Null - } catch { - throw "Setting: install$($list) contains an inaccessible URL: $($url). Error was: $($_.Exception.Message)" - } - # Try downloading the app file try { if (-not (Test-Path $tempDependenciesLocation)) { diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 47fd23aef3..f5174d7eb5 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -10,12 +10,11 @@ Import-Module (Join-Path $PSScriptRoot '..\TelemetryHelper.psm1' -Resolve) The path to the al.exe tool. #> function Install-ALTool { - $DevelopmentToolsPackage = "Microsoft.Dynamics.BusinessCentral.Development.Tools" - $alToolFolder = Install-DotNetTool -PackageName $DevelopmentToolsPackage + $alToolFolder = Install-DotNetTool -PackageName "Microsoft.Dynamics.BusinessCentral.Development.Tools" # Load the AL tool from the downloaded package $alExe = Get-ChildItem -Path $alToolFolder -Filter "al*" | Where-Object { $_.Name -eq "al" -or $_.Name -eq "al.exe" } | Select-Object -First 1 -ExpandProperty FullName if (-not $alExe) { - throw "Could not find al.exe in the $DevelopmentToolsPackage package." + throw "Could not find al.exe in the development tools package." } return $alExe } From 250a9559ec540485a1a9dbc44c7a0b316f958145 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:41:49 +0000 Subject: [PATCH 62/71] Use NewTemporaryFolder --- Actions/RunPipeline/RunPipeline.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 98533b67ea..3a2ae626b4 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -199,7 +199,7 @@ try { } # Replace secret names in install.apps and install.testApps - $tempDependenciesLocation = Join-Path $ENV:RUNNER_TEMP "Dependencies-$(Get-Random)" + $tempDependenciesLocation = NewTemporaryFolder foreach($list in @('Apps','TestApps')) { $install."$list" = @($install."$list" | ForEach-Object { $pattern = '.*(\$\{\{\s*([^}]+?)\s*\}\}).*' From 671ee82ca3cf6359e7726c318c47675c0c3111a8 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:51:24 +0000 Subject: [PATCH 63/71] Add telemetry to Test-InstallApps --- Actions/RunPipeline/RunPipeline.psm1 | 6 ++++++ Tests/RunPipeline.Action.Test.ps1 | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index f5174d7eb5..5d9332360d 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -45,6 +45,7 @@ function Test-InstallApps() { # Install the AL tool and get the path to al.exe $alExe = Install-ALTool + $symbolsOnlyCount = 0 foreach ($app in $AllInstallApps) { if (Test-Path -Path $app) { $appFilePath = (Get-Item -Path $app).FullName @@ -59,12 +60,17 @@ function Test-InstallApps() { if (IsSymbolsOnlyPackage -AppFilePath $appFile -AlExePath $alExe) { # If package is not a runtime package and has no source code files, it is a symbols package # Symbols packages are not meant to be published to a BC Environment + $symbolsOnlyCount++ OutputWarning -Message "App $appFileName is a symbols package and should not be published. The workflow may fail if you try to publish it." } } else { Write-Host "App file path for $app could not be resolved. Skipping symbols check." } } + + if ($symbolsOnlyCount -gt 0) { + Trace-Warning -Message "$symbolsOnlyCount symbols-only package(s) detected in install apps. These packages should not be published." + } } catch { Trace-Warning -Message "Something went wrong while analyzing install apps." diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index 8d86201a55..b9c984b264 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -40,8 +40,10 @@ Describe "RunPipeline Action Tests" { Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App EssentialBusinessHeadlinesSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } + # Assert that Trace-Warning was called once with the count + Should -Invoke -CommandName 'Trace-Warning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*1 symbols-only package(s) detected in install apps." } # Assert that Trace-Warning was not called - Assert-MockCalled Trace-Warning -Exactly 0 -ModuleName RunPipeline + Should -Invoke -CommandName 'Trace-Warning' -Times 0 -ModuleName RunPipeline -ParameterFilter { $Message -like "App file path for * could not be resolved." } } # Call action From 6c1d0279538862316ed245cf824c7ff4d9a9be2d Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:58:38 +0000 Subject: [PATCH 64/71] Revert --- Actions/RunPipeline/RunPipeline.ps1 | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 3a2ae626b4..1e54208cf5 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -186,16 +186,9 @@ try { }) } - Write-Host "installTestAppsJson - $installTestAppsJson" - $installTestAppDependencies = $installTestAppsJson | ConvertFrom-Json - Write-Host "Install test apps from input: $($installTestAppDependencies -join ',')" - if ($installTestAppDependencies.Count -gt 0) { - Write-Host "Trimming parentheses from test app dependencies" - $installTestAppDependencies = @($installTestAppDependencies | ForEach-Object { Write-Host $_ ; $_.Trim('()') }) - } $install = @{ "Apps" = $settings.installApps + @($installAppsJson | ConvertFrom-Json) - "TestApps" = $settings.installTestApps + $installTestAppDependencies + "TestApps" = $settings.installTestApps + @($installTestAppsJson | ConvertFrom-Json) } # Replace secret names in install.apps and install.testApps From 48e781a53224c7acd27a66752a2fb8e94881e76c Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:01:24 +0000 Subject: [PATCH 65/71] cleanup --- Actions/RunPipeline/RunPipeline.ps1 | 4 ---- Actions/Sign/Sign.psm1 | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 1e54208cf5..0894935a20 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -203,13 +203,9 @@ try { else { $finalUrl = $url } - if ($finalUrl -like 'http*://*') { # Try downloading the app file try { - if (-not (Test-Path $tempDependenciesLocation)) { - New-Item -Path $tempDependenciesLocation -ItemType Directory | Out-Null - } $urlWithoutQuery = $finalUrl.Split('?')[0].TrimEnd('/') $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) $decodedFileName = [Uri]::UnescapeDataString($rawFileName) diff --git a/Actions/Sign/Sign.psm1 b/Actions/Sign/Sign.psm1 index 08352db32a..30a3bde5a3 100644 --- a/Actions/Sign/Sign.psm1 +++ b/Actions/Sign/Sign.psm1 @@ -131,4 +131,4 @@ function Invoke-SigningTool() { } } -Export-ModuleMember -Function *-* +Export-ModuleMember -Function Invoke-SigningTool From 066e7f9b0fd0ea2e6657a541fceb51e7c63d8315 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:23:57 +0000 Subject: [PATCH 66/71] update release notes --- RELEASENOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 09883585b2..d0dec10a8b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -55,6 +55,7 @@ Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefault - Issue 2016 Running Update AL-Go system files with branches wildcard `*` tries to update _origin_ - Issue 1960 Deploy Reference Documentation fails - Discussion 1952 Set default values on workflow_dispatch input +- Issue 1512: Throw a warning before trying to publish symbols packages ### Deprecations @@ -87,7 +88,6 @@ Please note that some automated features are premium and require the use of [Git - Issue 1937 trackALAlertsInGitHub is failing in preview - DeployTo settings from environment-specific AL-Go settings are not applied when deploying - `ReadSettings` action outputs too much information that is mainly used for debugging -- Issue 1512: Throw a warning before trying to publish symbols packages ## v7.3 From a267d44b94e72c1b2d10c78d64cdcf0a2eb714bd Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 18:40:47 +0000 Subject: [PATCH 67/71] Fix test --- Tests/RunPipeline.Action.Test.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/RunPipeline.Action.Test.ps1 b/Tests/RunPipeline.Action.Test.ps1 index b9c984b264..2a83e7b2c5 100644 --- a/Tests/RunPipeline.Action.Test.ps1 +++ b/Tests/RunPipeline.Action.Test.ps1 @@ -41,7 +41,7 @@ Describe "RunPipeline Action Tests" { Should -Invoke -CommandName 'OutputWarning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*App EssentialBusinessHeadlinesSymbols.app is a symbols package and should not be published. The workflow may fail if you try to publish it." } # Assert that Trace-Warning was called once with the count - Should -Invoke -CommandName 'Trace-Warning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*1 symbols-only package(s) detected in install apps." } + Should -Invoke -CommandName 'Trace-Warning' -Times 1 -ModuleName RunPipeline -ParameterFilter { $Message -like "*1 symbols-only package(s) detected in install apps*" } # Assert that Trace-Warning was not called Should -Invoke -CommandName 'Trace-Warning' -Times 0 -ModuleName RunPipeline -ParameterFilter { $Message -like "App file path for * could not be resolved." } } From 16f78e12499689ee654cf0d52e969ddc903641c6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:19:27 +0000 Subject: [PATCH 68/71] Move logic to Get-AppFileFromUrl --- Actions/RunPipeline/RunPipeline.ps1 | 35 ++++++++++-------------- Actions/RunPipeline/RunPipeline.psm1 | 40 +++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index 0894935a20..dddd83ed24 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -195,29 +195,22 @@ try { $tempDependenciesLocation = NewTemporaryFolder foreach($list in @('Apps','TestApps')) { $install."$list" = @($install."$list" | ForEach-Object { - $pattern = '.*(\$\{\{\s*([^}]+?)\s*\}\}).*' - $url = $_ - if ($url -match $pattern) { - $finalUrl = $url.Replace($matches[1],[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($matches[2])"))) - } - else { - $finalUrl = $url + $appFile = $_ + + # If the app file is not a URL, return it as is + if ($appFile -notlike 'http*://*') { + return $appFile } - if ($finalUrl -like 'http*://*') { - # Try downloading the app file - try { - $urlWithoutQuery = $finalUrl.Split('?')[0].TrimEnd('/') - $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) - $decodedFileName = [Uri]::UnescapeDataString($rawFileName) - $appFile = Join-Path $tempDependenciesLocation $decodedFileName - Invoke-WebRequest -Method GET -UseBasicParsing -Uri $finalUrl -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null - } - catch { - throw "Could not download app from URL: $($url). Error was: $($_.Exception.Message)" - } - } else { - $appFile = $_ + + # Else, check for secrets in the URL and replace them + $appFileUrl = $appFile + $pattern = '.*(\$\{\{\s*([^}]+?)\s*\}\}).*' + if ($appFile -match $pattern) { + $appFileUrl = $appFileUrl.Replace($matches[1],[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($matches[2])"))) } + + # Download the app file to a temporary location + $appFile = Get-AppFileFromUrl -Url $appFileUrl -DownloadPath $tempDependenciesLocation return $appFile }) } diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 5d9332360d..29e5d58c30 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -87,4 +87,42 @@ function IsSymbolsOnlyPackage { return $LASTEXITCODE -eq 0 } -Export-ModuleMember -Function Test-InstallApps +<# + .SYNOPSIS + Downloads an app file from a URL to a specified download path. + .DESCRIPTION + Downloads an app file from a URL to a specified download path. + It handles URL decoding and sanitizes the file name. + .PARAMETER Url + The URL of the app file to download. + .PARAMETER DownloadPath + The path where the app file should be downloaded. + .RETURNS + The path to the downloaded app file. +#> +function Get-AppFileFromUrl { + Param( + [string] $Url, + [string] $DownloadPath + ) + try { + # Get the file name from the URL + $urlWithoutQuery = $Url.Split('?')[0].TrimEnd('/') + $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) + $decodedFileName = [Uri]::UnescapeDataString($rawFileName) + $decodedFileName = [System.IO.Path]::GetFileName($decodedFileName) + + # Sanitize file name by removing invalid characters + $sanitizedFileName = $decodedFileName.Split([System.IO.Path]::getInvalidFileNameChars()) -join "" + $sanitizedFileName = $sanitizedFileName.Trim() + + # Get the final app file path + $appFile = Join-Path $DownloadPath $sanitizedFileName + Invoke-WebRequest -Method GET -UseBasicParsing -Uri $Url -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null + } + catch { + throw "Could not download app from URL: $($url). Error was: $($_.Exception.Message)" + } +} + +Export-ModuleMember -Function Test-InstallApps, Get-AppFileFromUrl From 5e4e0300d76e7e135c0bd6cd02c5db88a21dd3d6 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:32:58 +0000 Subject: [PATCH 69/71] More actionable error --- Actions/RunPipeline/RunPipeline.ps1 | 9 +++++++-- Actions/RunPipeline/RunPipeline.psm1 | 27 +++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Actions/RunPipeline/RunPipeline.ps1 b/Actions/RunPipeline/RunPipeline.ps1 index dddd83ed24..d6cddd620d 100644 --- a/Actions/RunPipeline/RunPipeline.ps1 +++ b/Actions/RunPipeline/RunPipeline.ps1 @@ -206,11 +206,16 @@ try { $appFileUrl = $appFile $pattern = '.*(\$\{\{\s*([^}]+?)\s*\}\}).*' if ($appFile -match $pattern) { - $appFileUrl = $appFileUrl.Replace($matches[1],[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($matches[2])"))) + $appFileFinalUrl = $appFileUrl.Replace($matches[1],[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secrets."$($matches[2])"))) } # Download the app file to a temporary location - $appFile = Get-AppFileFromUrl -Url $appFileUrl -DownloadPath $tempDependenciesLocation + try { + $appFile = Get-AppFileFromUrl -Url $appFileFinalUrl -DownloadPath $tempDependenciesLocation + } catch { + OutputError -message "Failed to download app from URL: $($appFileUrl). Please check that the URL is valid. Error was: $($_.Exception.Message)" + } + return $appFile }) } diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 29e5d58c30..5abf33c541 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -105,24 +105,19 @@ function Get-AppFileFromUrl { [string] $Url, [string] $DownloadPath ) - try { - # Get the file name from the URL - $urlWithoutQuery = $Url.Split('?')[0].TrimEnd('/') - $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) - $decodedFileName = [Uri]::UnescapeDataString($rawFileName) - $decodedFileName = [System.IO.Path]::GetFileName($decodedFileName) + # Get the file name from the URL + $urlWithoutQuery = $Url.Split('?')[0].TrimEnd('/') + $rawFileName = [System.IO.Path]::GetFileName($urlWithoutQuery) + $decodedFileName = [Uri]::UnescapeDataString($rawFileName) + $decodedFileName = [System.IO.Path]::GetFileName($decodedFileName) - # Sanitize file name by removing invalid characters - $sanitizedFileName = $decodedFileName.Split([System.IO.Path]::getInvalidFileNameChars()) -join "" - $sanitizedFileName = $sanitizedFileName.Trim() + # Sanitize file name by removing invalid characters + $sanitizedFileName = $decodedFileName.Split([System.IO.Path]::getInvalidFileNameChars()) -join "" + $sanitizedFileName = $sanitizedFileName.Trim() - # Get the final app file path - $appFile = Join-Path $DownloadPath $sanitizedFileName - Invoke-WebRequest -Method GET -UseBasicParsing -Uri $Url -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null - } - catch { - throw "Could not download app from URL: $($url). Error was: $($_.Exception.Message)" - } + # Get the final app file path + $appFile = Join-Path $DownloadPath $sanitizedFileName + Invoke-WebRequest -Method GET -UseBasicParsing -Uri $Url -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null } Export-ModuleMember -Function Test-InstallApps, Get-AppFileFromUrl From d95484b1cdd072a8bd0343ed19b6eaa6c7ecc82f Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:36:13 +0000 Subject: [PATCH 70/71] OUTPUTS --- Actions/RunPipeline/RunPipeline.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 5abf33c541..4a57c1ea4d 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -97,7 +97,7 @@ function IsSymbolsOnlyPackage { The URL of the app file to download. .PARAMETER DownloadPath The path where the app file should be downloaded. - .RETURNS + .OUTPUTS The path to the downloaded app file. #> function Get-AppFileFromUrl { From 61be1a5761fe262d410a62d473c8993935f08e46 Mon Sep 17 00:00:00 2001 From: Alexander Holstrup <117829001+aholstrup1@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:40:25 +0000 Subject: [PATCH 71/71] thanks copilot --- Actions/RunPipeline/RunPipeline.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Actions/RunPipeline/RunPipeline.psm1 b/Actions/RunPipeline/RunPipeline.psm1 index 4a57c1ea4d..4ed5fe9e07 100644 --- a/Actions/RunPipeline/RunPipeline.psm1 +++ b/Actions/RunPipeline/RunPipeline.psm1 @@ -118,6 +118,7 @@ function Get-AppFileFromUrl { # Get the final app file path $appFile = Join-Path $DownloadPath $sanitizedFileName Invoke-WebRequest -Method GET -UseBasicParsing -Uri $Url -OutFile $appFile -MaximumRetryCount 3 -RetryIntervalSec 5 | Out-Null + return $appFile } Export-ModuleMember -Function Test-InstallApps, Get-AppFileFromUrl