AppHandling/Run-AlPipeline.ps1

<#
 .Synopsis
  Preview script for running simple AL pipeline
 .Description
  Preview script for running simple AL pipeline
#>

function Run-AlPipeline {
Param(
    [string] $pipelineName,
    [string] $baseFolder,
    [string] $licenseFile,
    [string] $containerName = "$pipelinename-bld".ToLowerInvariant(),
    [string] $imageName = 'my',
    [Boolean] $enableTaskScheduler = $false,
    [string] $memoryLimit = "6G",
    [PSCredential] $credential,
    [string] $codeSignCertPfxFile = "",
    [SecureString] $codeSignCertPfxPassword = $null,
    $installApps = @(),
    $appFolders = @("app", "application"),
    $testFolders = @("test", "testapp"),
    [string] $appVersion = "",
    [string] $testResultsFile = "TestResults.xml",
    [string] $packagesFolder = ".packages",
    [string] $outputFolder = ".output",
    [string] $artifact = "bcartifacts/sandbox//us/latest",
    [string] $artifactSasToken = "",
    [string] $buildArtifactFolder = "",
    [switch] $createRuntimePackages,
    [switch] $installTestFramework,
    [switch] $installTestLibraries,
    [switch] $installPerformanceToolkit,
    [switch] $azureDevOps,
    [switch] $useDevEndpoint,
    [switch] $doNotRunTests,
    [switch] $keepContainer,
    [switch] $enableCodeCop,
    [switch] $enableAppSourceCop,
    [switch] $enableUICop,
    [switch] $enablePerTenantExtensionCop
)

function randomchar([string]$str)
{
    $rnd = Get-Random -Maximum $str.length
    [string]$str[$rnd]
}

function Get-RandomPassword {
    $cons = 'bcdfghjklmnpqrstvwxz'
    $voc = 'aeiouy'
    $numbers = '0123456789'

    ((randomchar $cons).ToUpper() + `
     (randomchar $voc) + `
     (randomchar $cons) + `
     (randomchar $voc) + `
     (randomchar $numbers) + `
     (randomchar $numbers) + `
     (randomchar $numbers) + `
     (randomchar $numbers))
}

$appFolders  = @($appFolders  | ForEach-Object { if (!$_.contains(':')) { Join-Path $baseFolder $_ } else { $_ } } | Where-Object { Test-Path $_ } )
$testFolders = @($testFolders | ForEach-Object { if (!$_.contains(':')) { Join-Path $baseFolder $_ } else { $_ } } | Where-Object { Test-Path $_ } )
if (!$testResultsFile.Contains(':')) { $testResultsFile = Join-Path $baseFolder $testResultsFile }
if (!$packagesFolder.Contains(':'))  { $packagesFolder  = Join-Path $baseFolder $packagesFolder }
if (!$outputFolder.Contains(':'))    { $outputFolder    = Join-Path $baseFolder $outputFolder }

if (!($appFolders)) {
    throw "No app folders found"
}

$sortedFolders = Sort-AppFoldersByDependencies -appFolders ($appFolders+$testFolders) -WarningAction SilentlyContinue

if ($artifact -like "https://*") {
    $artifactUrl = $artifact
}
else {
    $segments = "$artifact/////".Split('/')
    $storageAccount = $segments[0]; if ($storageAccount -eq "") { $storageAccount = "bcartifacts" }
    $type = $segments[1]; if ($type -eq "") { $type = "Sandbox" }
    $version = $segments[2]
    $country = $segments[3]; if ($country -eq "") { $country = "us" }
    $select = $segments[4]; if ($select -eq "") { $select = "latest" }
    if (-not ($artifactSasToken)) { $artifactSasToken = $segments[5] }
    
    $artifactUrl = Get-BCArtifactUrl -storageAccount $storageAccount -type $type -version $version -country $country -select $select -sasToken $artifactSasToken | Select-Object -First 1
}

if (!($artifactUrl)) {
    throw "Unable to locate artifacts"
}

if (Test-Path $packagesFolder) {
    Remove-Item $packagesFolder -Recurse -Force
}

if (Test-Path $outputFolder) {
    Remove-Item $outputFolder -Recurse -Force
}

if ($buildArtifactFolder) {
    if (!(Test-Path $buildArtifactFolder)) {
        throw "BuildArtifactFolder must exist"
    }
}

Write-Host -ForegroundColor Yellow @'
  _____ _
 | __ \ | |
 | |__) |_ _ _ __ __ _ _ __ ___ ___| |_ ___ _ __ ___
 | ___/ _` | '__/ _` | '_ ` _ \ / _ \ __/ _ \ '__/ __|
 | | | (_| | | | (_| | | | | | | __/ |_ __/ | \__ \
 |_| \__,_|_| \__,_|_| |_| |_|\___|\__\___|_| |___/
 
'@

Write-Host -NoNewLine -ForegroundColor Yellow "Pipeline name "; Write-Host $pipelineName
Write-Host -NoNewLine -ForegroundColor Yellow "Container name "; Write-Host $containerName
Write-Host -NoNewLine -ForegroundColor Yellow "Image name "; Write-Host $imageName
Write-Host -NoNewLine -ForegroundColor Yellow "ArtifactUrl "; Write-Host $artifactUrl.Split('?')[0]
Write-Host -NoNewLine -ForegroundColor Yellow "ArtifactSasToken "; if ($artifactSasToken) { Write-Host "Specified" } else { Write-Host "Not Specified" }
Write-Host -NoNewLine -ForegroundColor Yellow "Credential ";
if ($credential) {
    Write-Host "Specified"
}
else {
    $password = Get-RandomPassword
    Write-Host "admin/$password"
    $credential= (New-Object pscredential 'admin', (ConvertTo-SecureString -String $password -AsPlainText -Force))
}
Write-Host -NoNewLine -ForegroundColor Yellow "MemoryLimit "; Write-Host $memoryLimit
Write-Host -NoNewLine -ForegroundColor Yellow "Enable Task Scheduler "; Write-Host $enableTaskScheduler
Write-Host -NoNewLine -ForegroundColor Yellow "Install Test Framework "; Write-Host $installTestFramework
Write-Host -NoNewLine -ForegroundColor Yellow "Install Test Libraries "; Write-Host $installTestLibraries
Write-Host -NoNewLine -ForegroundColor Yellow "Install Perf. Toolkit "; Write-Host $installPerformanceToolkit
Write-Host -NoNewLine -ForegroundColor Yellow "azureDevOps "; Write-Host $azureDevOps
Write-Host -NoNewLine -ForegroundColor Yellow "License file "; if ($licenseFile) { Write-Host "Specified" } else { "Not specified" }
Write-Host -NoNewLine -ForegroundColor Yellow "CodeSignCertPfxFile "; if ($codeSignCertPfxFile) { Write-Host "Specified" } else { "Not specified" }
Write-Host -NoNewLine -ForegroundColor Yellow "TestResultsFile "; Write-Host $testResultsFile
Write-Host -NoNewLine -ForegroundColor Yellow "PackagesFolder "; Write-Host $packagesFolder
Write-Host -NoNewLine -ForegroundColor Yellow "OutputFolder "; Write-Host $outputFolder
Write-Host -NoNewLine -ForegroundColor Yellow "BuildArtifactFolder "; Write-Host $buildArtifactFolder
Write-Host -NoNewLine -ForegroundColor Yellow "CreateRuntimePackages "; Write-Host $createRuntimePackages
Write-Host -NoNewLine -ForegroundColor Yellow "AppVersion "; Write-Host $appVersion
Write-Host -ForegroundColor Yellow "Install Apps"
if ($installApps) { $installApps | ForEach-Object { Write-Host "- $_" } } else { Write-Host "- None" }
Write-Host -ForegroundColor Yellow "Application folders"
if ($appFolders) { $appFolders | ForEach-Object { Write-Host "- $_" } }  else { Write-Host "- None" }
Write-Host -ForegroundColor Yellow "Test application folders"
if ($testFolders) { $testFolders | ForEach-Object { Write-Host "- $_" } } else { Write-Host "- None" }

$signApps = ($codeSignCertPfxFile -ne "")

Measure-Command {

Measure-Command {

Write-Host -ForegroundColor Yellow @'
 
  _____ _ _ _ _ _
 | __ \ | | (_) (_) (_)
 | |__) | _| | |_ _ __ __ _ __ _ ___ _ __ ___ _ __ _ ___ _ _ __ ___ __ _ __ _ ___
 | ___/ | | | | | | '_ \ / _` | / _` |/ _ \ '_ \ / _ \ '__| |/ __| | | '_ ` _ \ / _` |/ _` |/ _ \
 | | | |_| | | | | | | | (_| | | (_| | __/ | | | __/ | | | (__ | | | | | | | (_| | (_| | __/
 |_| \__,_|_|_|_|_| |_|\__, | \__, |\___|_| |_|\___|_| |_|\___| |_|_| |_| |_|\__,_|\__, |\___|
                           __/ | __/ | __/ |
                          |___/ |___/ |___/
 
'@


$genericImageName = Get-BestGenericImageName
Write-Host "Pulling $genericImageName"
docker pull $genericImageName

} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nPulling generic image took $([int]$_.TotalSeconds) seconds" }

$error = $null
try {

Write-Host -ForegroundColor Yellow @'
 
   _____ _ _ _ _
  / ____| | | (_) | | (_)
 | | _ __ ___ __ _| |_ _ _ __ __ _ ___ ___ _ __ | |_ __ _ _ _ __ ___ _ __
 | | | '__/ _ \/ _` | __| | '_ \ / _` | / __/ _ \| '_ \| __/ _` | | '_ \ / _ \ '__|
 | |____| | | __/ (_| | |_| | | | | (_| | | (__ (_) | | | | |_ (_| | | | | | __/ |
  \_____|_| \___|\__,_|\__|_|_| |_|\__, | \___\___/|_| |_|\__\__,_|_|_| |_|\___|_|
                                     __/ |
                                    |___/
 
'@


Measure-Command {

    New-BcContainer `
        -accept_eula `
        -containerName $containerName `
        -imageName $imageName `
        -artifactUrl $artifactUrl `
        -Credential $credential `
        -auth UserPassword `
        -updateHosts `
        -licenseFile $licenseFile `
        -EnableTaskScheduler:$enableTaskScheduler `
        -MemoryLimit $memoryLimit `
        -additionalParameters @("--volume $($baseFolder):c:\sources")

} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nCreating container took $([int]$_.TotalSeconds) seconds" }

if (($installApps) -or $installTestFramework -or $installTestLibraries -or $installPerformanceToolkit) {
Write-Host -ForegroundColor Yellow @'
 
  _____ _ _ _ _
 |_ _| | | | | (_)
   | | _ __ ___| |_ __ _| | |_ _ __ __ _ __ _ _ __ _ __ ___
   | | | '_ \/ __| __/ _` | | | | '_ \ / _` | / _` | '_ \| '_ \/ __|
  _| |_| | | \__ \ |_ (_| | | | | | | | (_| | | (_| | |_) | |_) \__ \
 |_____|_| |_|___/\__\__,_|_|_|_|_| |_|\__, | \__,_| .__/| .__/|___/
                                        __/ | | | | |
                                       |___/ |_| |_|
 
'@

Measure-Command {

    if ($installTestLibraries) {
        Import-TestToolkitToBcContainer -containerName $containerName -credential $credential -includeTestLibrariesOnly -includePerformanceToolkit:$installPerformanceToolkit -doNotUseRuntimePackages
    }
    elseif ($installTestFramework -or $installPerformanceToolkit) {
        Import-TestToolkitToBcContainer -containerName $containerName -credential $credential -includeTestFrameworkOnly -includePerformanceToolkit:$installPerformanceToolkit -doNotUseRuntimePackages
    }

    $installApps | ForEach-Object{
        # Install dependency
    }

} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nInstalling apps took $([int]$_.TotalSeconds) seconds" }
}

Write-Host -ForegroundColor Yellow @'
 
   _____ _ _ _
  / ____| (_) (_)
 | | ___ _ __ ___ _ __ _| |_ _ __ __ _ __ _ _ __ _ __ ___
 | | / _ \| '_ ` _ \| '_ \| | | | '_ \ / _` | / _` | '_ \| '_ \/ __|
 | |____ (_) | | | | | | |_) | | | | | | | (_| | | (_| | |_) | |_) \__ \
  \_____\___/|_| |_| |_| .__/|_|_|_|_| |_|\__, | \__,_| .__/| .__/|___/
                       | | __/ | | | | |
                       |_| |___/ |_| |_|
 
'@

Measure-Command {
$appsFolder = @{}
$apps = @()
$testApps = @()
$sortedFolders | ForEach-Object {
    $folder = $_
    $testApp = $testFolders.Contains($folder)
    $compileParams = @{ }
    if (-not $testApp) {
        $compileParams += @{ 
            "EnableCodeCop" = $enableCodeCop
            "EnableAppSourceCop" = $enableAppSourceCop
            "EnableUICop" = $enableUICop
            "EnablePerTenantExtensionCop" = $enablePerTenantExtensionCop
        }
    }

    if ($appVersion) {
        $version = [System.Version]::Parse($appVersion)
        Write-Host "Using Version $version"
        $appJsonFile = Join-Path $folder "app.json"
        $appJson = Get-Content $appJsonFile | ConvertFrom-Json
        if (!($appJson.version.StartsWith("$($version.Major).$($version.Minor)."))) {
            throw "Major and Minor version of app doesn't match with pipeline"
        }
        $appJson.version = "$version"
        $appJson | ConvertTo-Json -Depth 99 | Set-Content $appJsonFile
    }

    $appFile = Compile-AppInBcContainer @compileParams `
        -containerName $containerName `
        -credential $credential `
        -appProjectFolder $folder `
        -appOutputFolder $outputFolder `
        -appSymbolsFolder $packagesFolder `
        -CopyAppToSymbolsFolder `
        -AzureDevOps:$azureDevOps

    if ($testApp) {
        $testApps += $appFile
    }
    else {
        $apps += $appFile
        $appsFolder += @{ "$appFile" = $folder }
    }
}
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nCompiling apps took $([int]$_.TotalSeconds) seconds" }

if ($signApps -and !$useDevEndpoint) {
Write-Host -ForegroundColor Yellow @'
 
   _____ _ _
  / ____(_) (_) /\
 | (___ _ __ _ _ __ _ _ __ __ _ / \ _ __ _ __ ___
  \___ \| |/ _` | '_ \| | '_ \ / _` | / /\ \ | '_ \| '_ \/ __|
  ____) | | (_| | | | | | | | | (_| | / ____ \| |_) | |_) \__ \
 |_____/|_|\__, |_| |_|_|_| |_|\__, | /_/ \_\ .__/| .__/|___/
            __/ | __/ | | | | |
           |___/ |___/ |_| |_|
 
'@

Measure-Command {
$apps | ForEach-Object {
    $appFile = $_
    Sign-BcContainerApp `
        -containerName $containerName `
        -appFile $appFile `
        -pfxFile $codeSignPfxFile `
        -pfxPassword $codeSignPfxPassword
}
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nSigning apps took $([int]$_.TotalSeconds) seconds" }
}

Write-Host -ForegroundColor Yellow @'
 
  _____ _ _ _ _ _
 | __ \ | | | (_) | | (_) /\
 | |__) | _| |__ | |_ ___| |__ _ _ __ __ _ / \ _ __ _ __ ___
 | ___/ | | | '_ \| | / __| '_ \| | '_ \ / _` | / /\ \ | '_ \| '_ \/ __|
 | | | |_| | |_) | | \__ \ | | | | | | | (_| | / ____ \| |_) | |_) \__ \
 |_| \__,_|_.__/|_|_|___/_| |_|_|_| |_|\__, | /_/ \_\ .__/| .__/|___/
                                           __/ | | | | |
                                          |___/ |_| |_|
 
'@

Measure-Command {
$apps+$testApps | ForEach-Object {
    $appFile = $_
    Publish-BcContainerApp `
        -containerName $containerName `
        -credential $credential `
        -appFile $appFile `
        -skipVerification:($testApps.Contains($appFile) -or !$signApps -or $useDevEndpoint) `
        -sync `
        -install `
        -useDevEndpoint:$useDevEndpoint
}
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nPublishing apps took $([int]$_.TotalSeconds) seconds" }

if (!$doNotRunTests) {
Remove-Item -Path $testResultsFile -Force -ErrorAction SilentlyContinue
if ($testFolders) {
Write-Host -ForegroundColor Yellow @'
 
  _____ _ _______ _
 | __ \ (_) |__ __| | |
 | |__) | _ _ __ _ __ _ _ __ __ _ | | ___ ___| |_ ___
 | _ / | | | '_ \| '_ \| | '_ \ / _` | | |/ _ \ __| __/ __|
 | | \ \ |_| | | | | | | | | | | | (_| | | | __\__ \ |_\__ \
 |_| \_\__,_|_| |_|_| |_|_|_| |_|\__, | |_|\___|___/\__|___/
                                   __/ |
                                  |___/
 
'@

Measure-Command {
$testFolders | ForEach-Object {
    $appJson = Get-Content -Path (Join-Path $_ "app.json") | ConvertFrom-Json
    Run-TestsInBcContainer `
        -containerName $containerName `
        -credential $credential `
        -extensionId $appJson.id `
        -AzureDevOps "$(if($azureDevOps){'error'}else{'no'})" `
        -XUnitResultFileName $testResultsFile `
        -AppendToXUnitResultFile
}
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nRunning tests took $([int]$_.TotalSeconds) seconds" }
}
}

if ($buildArtifactFolder) {
Write-Host -ForegroundColor Yellow @'
   _____ _ ____ _ _ _ _ _ __ _
  / ____| | | | _ \ (_) | | | /\ | | (_)/ _| | |
 | | ___ _ __ _ _ | |_ ___ | |_) |_ _ _| | __| | / \ _ __| |_ _| |_ __ _ ___| |_ ___
 | | / _ \| '_ \| | | | | __/ _ \ | _ <| | | | | |/ _` | / /\ \ | '__| __| | _/ _` |/ __| __/ __|
 | |____ (_) | |_) | |_| | | |_ (_) | | |_) | |_| | | | (_| | / ____ \| | | |_| | || (_| | (__| |_\__ \
  \_____\___/| .__/ \__, | \__\___/ |____/ \__,_|_|_|\__,_| /_/ \_\_| \__|_|_| \__,_|\___|\__|___/
             | | __/ |
             |_| |___/
'@


Measure-Command {

$destFolder = Join-Path $buildArtifactFolder "Apps"
if (!(Test-Path $destFolder -PathType Container)) {
    New-Item $destFolder -ItemType Directory | Out-Null
}
$apps | ForEach-Object {
    $appFile = $_
    Write-Host "Copying $([System.IO.Path]::GetFileName($appFile)) to build artifact"
    Copy-Item -Path $appFile -Destination $destFolder -Force
}
$destFolder = Join-Path $buildArtifactFolder "TestApps"
if (!(Test-Path $destFolder -PathType Container)) {
    New-Item $destFolder -ItemType Directory | Out-Null
}
$testApps | ForEach-Object {
    $appFile = $_
    Write-Host "Copying $([System.IO.Path]::GetFileName($appFile)) to build artifact"
    Copy-Item -Path $appFile -Destination $destFolder -Force
}
if ($createRuntimePackages) {
    $destFolder = Join-Path $buildArtifactFolder "RuntimePackages"
    if (!(Test-Path $destFolder -PathType Container)) {
        New-Item $destFolder -ItemType Directory | Out-Null
    }
    $apps | ForEach-Object {
        $appFile = $_
        $tempRuntimeAppFile = "$($appFile.TrimEnd('.app')).runtime.app"
        $runtimeAppFile = Join-Path $destFolder ([System.IO.Path]::GetFileName($appFile))
        $folder = $appsFolder[$appFile]
        $appJson = Get-Content -Path (Join-Path $folder "app.json") | ConvertFrom-Json
        Write-Host "Getting Runtime Package for $([System.IO.Path]::GetFileName($appFile))"
        Get-NavContainerAppRuntimePackage `
            -containerName $containerName `
            -appName $appJson.name `
            -appVersion $appJson.Version `
            -publisher $appJson.Publisher `
            -appFile $tempRuntimeAppFile

        if ($signApps) {
            Write-Host "Signing runtime package"
            Sign-BcContainerApp `
                -containerName $containerName `
                -appFile $tempRuntimeAppFile `
                -pfxFile $codeSignPfxFile `
                -pfxPassword $codeSignPfxPassword
        }

        Write-Host "Copying runtime package to build artifact"
        Copy-Item -Path $tempRuntimeAppFile -Destination $runtimeAppFile -Force
    }
}

} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nCopying to Build Artifacts took $([int]$_.TotalSeconds) seconds" }

}

} catch {
    $error = $_
    $keepContainer = $false
}

if (!$keepContainer) {
Write-Host -ForegroundColor Yellow @'
 
  _____ _ _____ _ _
 | __ \ (_) / ____| | | (_)
 | |__) |___ _ __ ___ _____ ___ _ __ __ _ | | ___ _ __ | |_ __ _ _ _ __ ___ _ __
 | _ // _ \ '_ ` _ \ / _ \ \ / / | '_ \ / _` | | | / _ \| '_ \| __/ _` | | '_ \ / _ \ '__|
 | | \ \ __/ | | | | | (_) \ V /| | | | | (_| | | |____ (_) | | | | |_ (_| | | | | | __/ |
 |_| \_\___|_| |_| |_|\___/ \_/ |_|_| |_|\__, | \_____\___/|_| |_|\__\__,_|_|_| |_|\___|_|
                                           __/ |
                                          |___/
 
'@

Measure-Command {
Remove-BcContainer `
    -containerName $containerName
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nRemoving container took $([int]$_.TotalSeconds) seconds" }

}

if ($error) {
    throw $error
}

} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nAL Pipeline finished in $([int]$_.TotalSeconds) seconds" }

}
Export-ModuleMember -Function Run-AlPipeline