AppHandling/Run-AlValidation.ps1

<#
 .Synopsis
  Run AL Validation
 .Description
  Run AL Validation
 .Parameter containerName
  Name of the validation container. Default is bcserver.
 .Parameter imageName
  If imageName is specified it will be used to build an image, which serves as a cache for faster container generation.
  Only speficy imagename if you are going to create multiple containers from the same artifacts.
 .Parameter credential
  These are the credentials used for the container. If not provided, the Run-AlValidation function will generate a random password and use that.
 .Parameter licenseFile
  License file to use for AL Validation
 .Parameter memoryLimit
  MemoryLimit is default set to 8Gb. This is fine for compiling small and medium size apps, but if your have a number of apps or your apps are large and complex, you might need to assign more memory.
 .Parameter installApps
  Array or comma separated list of 3rd party apps to install before validating apps
 .Parameter previousApps
  Array or comma separated list of previous version of apps to use for AppSourceCop validation and upgrade test
 .Parameter apps
  Array or comma separated list of apps to validate
 .Parameter ValidateVersion
  Full or partial version number. If specified, apps will also be validated against this version.
 .Parameter ValidateCurrent
  Include this switch if you want to also validate against current version of Business Central
 .Parameter ValidateNextMinor
  Include this switch if you want to also validate against next minor version of Business Central. If you include this switch you need to specify a sasToken for insider builds as well.
 .Parameter ValidateNextMajor
  Include this switch if you want to also validate against next major version of Business Central. If you include this switch you need to specify a sasToken for insider builds as well.
 .Parameter failOnError
  Include this switch if you want to fail on the first error instead of returning all errors to the caller
 .Parameter includeWarnings
  Include this switch if you want to include Warnings
 .Parameter doNotIgnoreInfos
  Include this switch if you don't want to ignore Infos (if you want to include Infos)
 .Parameter sasToken
  Shared Access Service Token for accessing insider artifacts of Business Central. Available on http://aka.ms/collaborate
 .Parameter countries
  Array or comma separated list of country codes to validate against
 .Parameter affixes
  Array or comma separated list of affixes to use for AppSourceCop validation
 .Parameter supportedCountries
  Array or comma separated list of supportedCountries to use for AppSourceCop validation
 .Parameter obsoleteTagMinAllowedMajorMinor
  Objects that are pending obsoletion with an obsolete tag version lower than the minimum set in the AppSourceCop.json file are not allowed. (AS0105)
 .Parameter vsixFile
  Specify a URL or path to a .vsix file in order to override the .vsix file in the image with this.
  Use Get-LatestAlLanguageExtensionUrl to get latest AL Language extension from Marketplace.
  Use Get-AlLanguageExtensionFromArtifacts -artifactUrl (Get-BCArtifactUrl -select NextMajor -sasToken $insiderSasToken) to get latest insider .vsix
  Use . to specify that you want to use the AL Language extension from the container spun up for validation
  Default is to use the latest AL Language extension from Marketplace for non-insider containers and the Language extension in the container for insider containers
 .Parameter skipVerification
  Include this parameter to skip verification of code signing certificate. Note that you cannot request Microsoft to set this parameter when validating for AppSource.
 .Parameter skipUpgrade
  Include this parameter to skip upgrade. You can request Microsoft to set this when your previous app cannot install on the version we are validating for.
 .Parameter skipAppSourceCop
  Include this parameter to skip appSourceCop. You cannot request Microsoft to set this when running validation
 .Parameter skipConnectionTest
  Include this parameter to skip the connection test. If Connection Test fails in validation, Microsoft will execute manual validation.
 .Parameter throwOnError
  Include this switch if you want Run-AlValidation to throw an error with the validation results instead of returning them to the caller
 .Parameter useGenericImage
  Specify a private (or special) generic image to use for the Container OS.
 .Parameter multitenant
  Include this parameter to use a multitenant container for the validation tests. Default is to use single tenant as validation doesn't run tests.
 .Parameter DockerPull
  Override function parameter for docker pull
 .Parameter NewBcContainer
  Override function parameter for New-BcContainer
 .Parameter CompileAppInBcContainer
  Override function parameter for Compile-AppInBcContainer
 .Parameter GetBcContainerAppInfo
  Override function parameter for Get-BcContainerAppInfo
 .Parameter PublishBcContainerApp
  Override function parameter for Publish-BcContainerApp
 .Parameter RemoveBcContainer
  Override function parameter for Remove-BcContainer
#>

function Run-AlValidation {
Param(
    [string] $containerName = $bcContainerHelperConfig.defaultContainerName,
    [string] $imageName = "",
    [PSCredential] $credential,
    [string] $licenseFile,
    [string] $memoryLimit,
    $installApps = @(),
    $previousApps = @(),
    [Parameter(Mandatory=$true)]
    $apps,
    [string] $validateVersion = "",
    [switch] $validateCurrent,
    [switch] $validateNextMinor,
    [switch] $validateNextMajor,
    [switch] $failOnError,
    [switch] $includeWarnings,
    [switch] $doNotIgnoreInfos,
    [string] $sasToken = "",
    [Parameter(Mandatory=$true)]
    $countries,
    $affixes = @(),
    $supportedCountries = @(),
    [string] $obsoleteTagMinAllowedMajorMinor = "",
    [string] $vsixFile = "",
    [switch] $skipVerification,
    [switch] $skipUpgrade,
    [switch] $skipAppSourceCop,
    [switch] $skipConnectionTest = $true,
    [switch] $throwOnError,
    [string] $useGenericImage = "",
    [switch] $multitenant,
    [scriptblock] $DockerPull,
    [scriptblock] $NewBcContainer,
    [scriptblock] $CompileAppInBcContainer,
    [scriptblock] $PublishBcContainerApp,
    [scriptblock] $GetBcContainerAppInfo,
    [scriptblock] $RemoveBcContainer
)

function DetermineArtifactsToUse {
    Param(
        [string] $version = "",
        [string] $select = "Current",
        [string] $sasToken = "",
        [string[]] $countries = @("us"),
        [switch] $throw
    )

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

    
    $minsto = 'bcartifacts'
    $minsel = $select
    $mintok = $sasToken
    if ($countries) {
        $minver = $null
        $countries | ForEach-Object {
            $url = Get-BCArtifactUrl -version $version -country $_ -select $select -sasToken $sasToken | Select-Object -First 1
            if (!($url)) {
                Write-Host -ForegroundColor Yellow "WARNING: NextMajor artifacts doesn't exist for $_"
            }
            else {
                Write-Host "Found $($url.Split('?')[0])"
                if ($url) {
                    $ver = [Version]$url.Split('/')[4]
                    if ($minver -eq $null -or $ver -lt $minver) {
                        $minver = $ver
                        $minsto = $url.Split('/')[2].Split('.')[0]
                        $minsel = "Latest"
                        $mintok = $url.Split('?')[1]; if ($mintok) { $mintok = "?$mintok" }
                    }
                }
            }
        }
        if ($minver -eq $null) {
            if ($throw) {
                throw "Unable to locate artifacts"
            }
            return ""
        }
        else {
            $version = $minver.ToString()
        }
    }
    $artifactUrl = Get-BCArtifactUrl -storageAccount $minsto -version $version -country $countries[0] -select $minsel -sasToken $mintok | Select-Object -First 1
    if (!($artifactUrl)) {
        if ($throw) { throw "Unable to locate artifacts" }
    }
    Write-Host "Using $($artifactUrl.Split('?')[0])"
    $artifactUrl
}

function GetApplicationDependency( [string] $appFile, [string] $minVersion = "0.0" ) {
    $tmpFolder = Join-Path (Get-TempDir) ([Guid]::NewGuid().ToString())
    try {
        Extract-AppFileToFolder -appFilename $appFile -appFolder $tmpFolder -generateAppJson
        $appJsonFile = Join-Path $tmpFolder "app.json"
        $appJson = [System.IO.File]::ReadAllLines($appJsonFile) | ConvertFrom-Json
    }
    catch {
        throw "Cannot unpack app $([System.IO.Path]::GetFileName($appFile)), it might be a runtime package."
    }
    finally {
        if (Test-Path $tmpFolder) {
            Remove-Item $tmpFolder -Recurse -Force
        }
    }
    if ($appJson.PSObject.Properties.Name -eq "Application") {
        $version = $appJson.application
    }
    else {
        $version = $appJson.dependencies | Where-Object { $_.Name -eq "Base Application" -and $_.Publisher -eq "Microsoft" } | % { $_.Version }
        if (!$version) {
            $version = $minVersion
        }
    }
    if ([System.Version]$version -lt [System.Version]$minVersion) {
        $version = $minVersion
    }
    $version
}

function GetFilePath( [string] $path ) {
    if ($path -like "http://*" -or $path -like "https://*") {
        return $path
    }
    if (!(Test-Path $path -PathType Leaf)) {
        throw "Unable to locate app file: $path"
    }
    else {
        return (Get-Item -Path $path).FullName
    }
}

$telemetryScope = InitTelemetryScope -name $MyInvocation.InvocationName -parameterValues $PSBoundParameters -includeParameters @()
try {

'GetBcContainerAppInfo','PublishBcContainerApp','multitenant','SkipConnectionTest','skipUpgrade','ImageName','LicenseFile' | % {
    if ($PSBoundParameters.Keys.Contains($_)) {
        Write-Host -ForegroundColor Red "WARNING: Parameter $_ is no longer used in Run-AlValidation, please remove the parameter"
    }
}

if ($useGenericImage -eq "") {
    $useGenericImage = Get-BestGenericImageName -filesOnly
}

$warningsToShow = @()
$validationResult = @()

if ($memoryLimit -eq "") {
    $memoryLimit = "8G"
}

$assignPremiumPlan = $false
$tenant = "default"

if ($installApps                    -is [String]) { $installApps = @($installApps.Split(',').Trim() | Where-Object { $_ }) }
if ($previousApps                   -is [String]) { $previousApps = @($previousApps.Split(',').Trim() | Where-Object { $_ }) }
if ($apps                           -is [String]) { $apps = @($apps.Split(',').Trim()  | Where-Object { $_ }) }
if ($countries                      -is [String]) { $countries = @($countries.Split(',').Trim() | Where-Object { $_ }) }
if ($affixes                        -is [String]) { $affixes = @($affixes.Split(',').Trim() | Where-Object { $_ }) }
if ($supportedCountries             -is [String]) { $supportedCountries = @($supportedCountries.Split(',').Trim() | Where-Object { $_ }) }

$installApps = @($installApps | ForEach-Object { GetFilePath $_ })
$previousApps = @($previousApps | ForEach-Object { GetFilePath $_ })
$apps = @($apps | ForEach-Object { GetFilePath $_ })

$countries = @($countries | Where-Object { $_ } | ForEach-Object { getCountryCode -countryCode $_ })
$validateCountries = @($countries | ForEach-Object {
    $countryCode = $_
    if ($bcContainerHelperConfig.mapCountryCode.PSObject.Properties.Name -eq $countryCode) { 
        $bcContainerHelperConfig.mapCountryCode."$countryCode"
    }
    else {
        $countryCode
    }
} | Select-Object -Unique)
$supportedCountries = @($supportedCountries | Where-Object { $_ } | ForEach-Object { getCountryCode -countryCode $_ })

$latestAlLanguageUrl = Get-LatestAlLanguageExtensionUrl

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

Write-Host -NoNewLine -ForegroundColor Yellow "Container name "; Write-Host $containerName
Write-Host -NoNewLine -ForegroundColor Yellow "Credential ";
if ($credential) {
    Write-Host "Specified"
}
else {
    $password = GetRandomPassword
    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 "validateVersion "; Write-Host $validateVersion
Write-Host -NoNewLine -ForegroundColor Yellow "validateCurrent "; Write-Host $validateCurrent
Write-Host -NoNewLine -ForegroundColor Yellow "validateNextMinor "; Write-Host $validateNextMinor
Write-Host -NoNewLine -ForegroundColor Yellow "validateNextMajor "; Write-Host $validateNextMajor
Write-Host -NoNewLine -ForegroundColor Yellow "SasToken "; if ($sasToken) { Write-Host "Specified" } else { Write-Host "Not Specified" }
Write-Host -NoNewLine -ForegroundColor Yellow "countries "; Write-Host ([string]::Join(',',$countries))
Write-Host -NoNewLine -ForegroundColor Yellow "validateCountries "; Write-Host ([string]::Join(',',$validateCountries))
Write-Host -NoNewLine -ForegroundColor Yellow "affixes "; Write-Host ([string]::Join(',',$affixes))
Write-Host -NoNewLine -ForegroundColor Yellow "supportedCountries "; Write-Host ([string]::Join(',',$supportedCountries))
Write-Host -NoNewLine -ForegroundColor Yellow "ObsoleteTagMinAllowedMajorMinor "; Write-Host $obsoleteTagMinAllowedMajorMinor
Write-Host -NoNewLine -ForegroundColor Yellow "vsixFile "; Write-Host $vsixFile

Write-Host -ForegroundColor Yellow "Install Apps"
if ($installApps) { $installApps | ForEach-Object { Write-Host "- $_" } } else { Write-Host "- None" }
Write-Host -ForegroundColor Yellow "Previous Apps"
if ($previousApps) { $previousApps | ForEach-Object { Write-Host "- $_" } } else { Write-Host "- None" }
Write-Host -ForegroundColor Yellow "Apps"
if ($apps) { $apps | ForEach-Object { Write-Host "- $_" } }  else { Write-Host "- None" }

if ($DockerPull) {
    Write-Host -ForegroundColor Yellow "DockerPull override"; Write-Host $DockerPull.ToString()
}
else {
    $DockerPull = { Param($imageName) docker pull $imageName }
}
if ($NewBcContainer) {
    Write-Host -ForegroundColor Yellow "NewBccontainer override"; Write-Host $NewBcContainer.ToString()
}
else {
    $NewBcContainer = { Param([Hashtable]$parameters) New-BcContainer @parameters; Invoke-ScriptInBcContainer $parameters.ContainerName -scriptblock { $progressPreference = 'SilentlyContinue' } }
}
if ($CompileAppInBcContainer) {
    Write-Host -ForegroundColor Yellow "CompileAppInBcContainer override"; Write-Host $CompileAppInBcContainer.ToString()
}
if ($RemoveBcContainer) {
    Write-Host -ForegroundColor Yellow "RemoveBcContainer override"; Write-Host $RemoveBcContainer.ToString()
}
else {
    $RemoveBcContainer = { Param([Hashtable]$parameters) Remove-BcContainer @parameters }
}

$currentArtifactUrl = ""

if ("$validateVersion" -eq "" -and !$validateCurrent -and !$validateNextMinor -and !$validateNextMajor) {
$currentArtifactUrl = DetermineArtifactsToUse -countries $validateCountries -select Current
Write-Host -ForegroundColor Yellow @'
  _____ _ _ _ _ _
 | __ \ | | (_) (_) | | | |
 | | | | ___| |_ ___ _ __ _ __ ___ _ _ __ _ _ __ __ _ __| | ___ _ __ ___ _ __ __| | ___ _ __ ___ _ _
 | | | |/ _ \ __/ _ \ '__| '_ ` _ \| | '_ \| | '_ \ / _` | / _` |/ _ \ '_ \ / _ \ '_ \ / _` |/ _ \ '_ \ / __| | | |
 | |__| | __/ |_ __/ | | | | | | | | | | | | | | | (_| | | (_| | __/ |_) | __/ | | | (_| | __/ | | | (__| |_| |
 |_____/ \___|\__\___|_| |_| |_| |_|_|_| |_|_|_| |_|\__, | \__,_|\___| .__/ \___|_| |_|\__,_|\___|_| |_|\___|\__, |
                                                      __/ | | | __/ |
                                                     |___/ |_| |___/
'@


$validateCurrent = $true
$version = [System.Version]::new($currentArtifactUrl.Split('/')[4])
$currentVersion = "$($version.Major).$($version.Minor)"
$validateVersion = "17.0"

$tmpAppsFolder = Join-Path $hosthelperfolder ([Guid]::NewGuid().ToString())
@(CopyAppFilesToFolder -appFiles @($installApps+$apps) -folder $tmpAppsFolder) | % {
    $appFile = $_
    $version = GetApplicationDependency -appFile $appFile -minVersion $validateVersion
    if ([System.Version]$version -gt [System.Version]$validateVersion) {
        $version = [System.Version]::new($version)
        $validateVersion = "$($version.Major).$($version.Minor)"
    }
}
Remove-Item -Path $tmpAppsFolder -Recurse -Force
Write-Host "Validating against Current Version ($currentVersion)"
if ($validateVersion -eq $currentVersion) {
    $validateVersion = ""
}
else {
    Write-Host "Additionally validating against application dependency ($validateVersion)"
}
}

Measure-Command {

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


Write-Host "Pulling $useGenericImage"

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

Measure-Command {

0..3 | ForEach-Object {

$artifactUrl = ""
$useVsix = ""
if ($_ -eq 0 -and $validateCurrent) {
    if ($currentArtifactUrl -eq "") {
        $currentArtifactUrl = DetermineArtifactsToUse -countries $validateCountries -select Current -throw
    }
    $artifactUrl = $currentArtifactUrl
    if ($vsixFile) {
        if ($vsixFile -ne ".") { $useVsix = $vsixFile }
    }
    else {
        $useVsix = $latestAlLanguageUrl
    }
}
elseif ($_ -eq 1 -and $validateVersion) {
    $artifactUrl = DetermineArtifactsToUse -version $validateVersion -countries $validateCountries -select Latest
}
elseif ($_ -eq 2 -and $validateNextMinor) {
    $artifactUrl = DetermineArtifactsToUse -countries $validateCountries -select NextMinor -sasToken $sasToken
}
elseif ($_ -eq 3 -and $validateNextMajor) {
    $artifactUrl = DetermineArtifactsToUse -countries $validateCountries -select NextMajor -sasToken $sasToken
}

if ($artifactUrl) {

$prevProgressPreference = $progressPreference
$progressPreference = 'SilentlyContinue'

$appPackagesFolder = Join-Path $hosthelperfolder ([Guid]::NewGuid().ToString())
New-Item $appPackagesFolder -ItemType Directory | Out-Null

try {

$validateCountries | % {
$validateCountry = $_

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


Measure-Command {

    $artifactSegments = $artifactUrl.Split('?')[0].Split('/')
    $artifactUrl = $artifactUrl.Replace("/$($artifactSegments[4])/$($artifactSegments[5])","/$($artifactSegments[4])/$validateCountry")
    Write-Host -ForegroundColor Yellow "Creating container for country $validateCountry"

    $Parameters = @{
        "accept_eula" = $true
        "containerName" = $containerName
        "artifactUrl" = $artifactUrl
        "useGenericImage" = $useGenericImage
        "Credential" = $credential
        "auth" = 'UserPassword'
        "updateHosts" = $true
        "vsixFile" = $useVsix
        "EnableTaskScheduler" = $true
        "AssignPremiumPlan" = $assignPremiumPlan
        "MemoryLimit" = $memoryLimit
        "FilesOnly" = $true
    }

    Invoke-Command -ScriptBlock $NewBcContainer -ArgumentList $Parameters

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

if ($installApps) {
    CopyAppFilesToFolder -appFiles $installApps -folder $appPackagesFolder | Out-Null
}

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

Measure-Command {
$parameters = @{
    "containerName" = $containerName
    "credential" = $credential
    "previousApps" = @($previousApps)
    "apps" = @($apps)
    "affixes" = $affixes
    "supportedCountries" = $supportedCountries
    "ObsoleteTagMinAllowedMajorMinor" = $ObsoleteTagMinAllowedMajorMinor
    "enableAppSourceCop" = $true
    "failOnError" = $failOnError
    "ignoreWarnings" = !$includeWarnings
    "doNotIgnoreInfos" = $doNotIgnoreInfos
    "appPackagesFolder" = $appPackagesFolder
    "skipVerification" = $skipVerification
}
if ($CompileAppInBcContainer) {
    $parameters += @{
        "CompileAppInBcContainer" = $CompileAppInBcContainer
    }
}
$validationResult += @(Run-AlCops @Parameters)

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

}

}

} catch {
    $err = "Unexpected error while validating app. Error is: $($_.Exception.Message)"
    $validationResult += $err
    Write-Host -ForegroundColor Red $err
    Write-Host -ForegroundColor Red $_.ScriptStackTrace

}
finally {
    $progressPreference = $prevProgressPreference
    Remove-Item -Path $appPackagesFolder -Recurse -Force
}

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

Measure-Command {

    $Parameters = @{
        "containerName" = $containerName
    }
    Invoke-Command -ScriptBlock $RemoveBcContainer -ArgumentList $Parameters
   
} | ForEach-Object { Write-Host -ForegroundColor Yellow "`nRemoving container took $([int]$_.TotalSeconds) seconds" }

}

}

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

if ($validationResult) {

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

$validationResult | Write-Host -ForegroundColor Red
Write-Host -ForegroundColor Red @'
  _____ ___ __ _ _ _ _ _ ______ _ _
 | __ \ /\ | \ \ / / | (_) | | | | (_) | ____| (_) |
 | |__) | _ _ __ ______ / \ | |\ \ / /_ _| |_ __| | __ _| |_ _ ___ _ __ | |__ __ _ _| |_ _ _ __ ___
 | _ / | | | '_ \______/ /\ \ | | \ \/ / _` | | |/ _` |/ _` | __| |/ _ \| '_ \ | __/ _` | | | | | | '__/ _ \
 | | \ \ |_| | | | | / ____ \| | \ / (_| | | | (_| | (_| | |_| | (_) | | | | | | | (_| | | | |_| | | | __/
 |_| \_\__,_|_| |_| /_/ \_\_| \/ \__,_|_|_|\__,_|\__,_|\__|_|\___/|_| |_| |_| \__,_|_|_|\__,_|_| \___|
 
'@


AddTelemetryProperty -telemetryScope $telemetryScope -key "Validation" -value "Failure"
AddTelemetryProperty -telemetryScope $telemetryScope -key "Result" -value ($validationResult -join "`n")
if ($throwOnError) {
    throw ($validationResult -join "`n")
}
else {
    $validationResult
}

}
else {
Write-Host -ForegroundColor Green @'
  _____ ___ __ _ _ _ _ _ _____
 | __ \ /\ | \ \ / / | (_) | | | | (_) / ____|
 | |__) | _ _ __ ______ / \ | |\ \ / /_ _| |_ __| | __ _| |_ _ ___ _ __ | (___ _ _ ___ ___ ___ ___ ___
 | _ / | | | '_ \______/ /\ \ | | \ \/ / _` | | |/ _` |/ _` | __| |/ _ \| '_ \ \___ \| | | |/ __/ __/ _ \ __/ __|
 | | \ \ |_| | | | | / ____ \| | \ / (_| | | | (_| | (_| | |_| | (_) | | | | ____) | |_| | (__ (__ __\__ \__ \
 |_| \_\__,_|_| |_| /_/ \_\_| \/ \__,_|_|_|\__,_|\__,_|\__|_|\___/|_| |_| |_____/ \__,_|\___\___\___|___/___/
                                                                                                   
'@


AddTelemetryProperty -telemetryScope $telemetryScope -key "Validation" -value "Success"
}

if ($warningsToShow) {
    ($warningsToShow -join "`n") | Write-Host -ForegroundColor Yellow
}
}
catch {
    TrackException -telemetryScope $telemetryScope -errorRecord $_
    throw
}
finally {
    TrackTrace -telemetryScope $telemetryScope
}
}
Export-ModuleMember -Function Run-AlValidation