BcSaaS/Install-BcAppFromAppSource.ps1

<#
 .Synopsis
  Function for installing an AppSource App in an online Business Central environment
 .Description
  Function for installing an AppSource App in an online Business Central environment
  Current implementation uses client service to invoke page 2503 and install the app
  WARNING: The implementation of this function will change when admin center API contains functionality for this
 .Parameter containerName
  ContainerName from which the client service connection is created (this parameter will be removed later)
 .Parameter companyName
  Company name in which the client service connection is done (this parameter will be removed later)
 .Parameter profile
  Profile name for the user which is performing the client service connection (this parameter will be removed later)
 .Parameter culture
  Culture of the user performing the client service connection (this parameter will be removed later)
 .Parameter timeZone
  Timezone of the user performing the client service connection (this parameter will be removed later)
 .Parameter debugMode
  Include this switch if you want to enable debugMode for the client Service connection (this parameter will be removed later)
 .Parameter bcAuthContext
  Authorization Context created by New-BcAuthContext.
 .Parameter environment
  Environment in which you want to install an AppSource App
 .Parameter appId
  AppId of the AppSource App you want to install
 .Parameter appId
  Name of the AppSource App you want to install
 .Example
  $authContext = New-BcAuthContext -includeDeviceLogin
  Install-BcAppFromAppSource -containerName proxy -bcAuthContext $authContext -AppId '55ba54a3-90c7-4d3f-bc73-68eaa51fd5f8'
#>

function Install-BcAppFromAppSource {
    Param (
        [string] $containerName = $bcContainerHelperConfig.defaultContainerName,
        [string] $companyName = "",
        [string] $profile = "",
        [timespan] $interactionTimeout = [timespan]::FromHours(24),
        [string] $culture = "en-US",
        [string] $timezone = "",
        [switch] $debugMode,
        [Parameter(Mandatory=$true)]
        [Hashtable] $bcAuthContext,
        [Parameter(Mandatory=$true)]
        [string] $environment,
        [Parameter(Mandatory=$true)]
        [string] $appId,
        [string] $appName = $appId
    )

    $bcAuthContext = Renew-BcAuthContext -bcAuthContext $bcAuthContext
    $bcEnvironment = Get-BcEnvironments -bcAuthContext $bcAuthContext | Where-Object { $_.Name -eq $environment -and $_.Type -eq "Sandbox" }
    if (!$bcEnvironment) {
        throw "Environment $environment doesn't exist in the current context or it is not a Sandbox environment."
    }
    
    Write-Host -ForegroundColor Yellow "NOTE: The implementation of Install-BcAppFromAppSource will be replaced by the Admin Center API implementation when available"

    $appExists = Get-BcPublishedApps -bcAuthContext $bcauthcontext -environment $environment | Where-Object { $_.id -eq $appid -and $_.state -eq "installed" }
    if ($appExists) {
        Write-Host -ForegroundColor Green "App $($appExists.Name) from $($appExists.Publisher) version $($appExists.Version) is already installed"
    }
    else {
        $response = Invoke-RestMethod -Method Get -Uri "https://businesscentral.dynamics.com/$($bcAuthContext.tenantID)/$environment/deployment/url"
        if($response.status -ne 'Ready') {
            throw "environment not ready, status is $($response.status)"
        }
        $useUrl = $response.data.Split('?')[0]
        $tenant = ($response.data.Split('?')[1]).Split('=')[1]
    
        $PsTestToolFolder = Join-Path $extensionsFolder "$containerName\PsConnectionTestTool"
        CreatePsTestToolFolder -containerName $containerName -PsTestToolFolder $PsTestToolFolder
    
        $serviceUrl = "$($useUrl.TrimEnd('/'))/cs?tenant=$tenant"
        $credential = New-Object pscredential 'user', (ConvertTo-SecureString -String $bcAuthContext.AccessToken -AsPlainText -Force)
    
        if ($companyName) { $serviceUrl += "&company=$([Uri]::EscapeDataString($companyName))" }
        if ($profile) { $serviceUrl += "&profile=$([Uri]::EscapeDataString($profile))" }
    
        . (Join-Path $PsTestToolFolder "PsTestFunctions.ps1") -newtonSoftDllPath (Join-Path $PsTestToolFolder "NewtonSoft.json.dll") -clientDllPath (Join-Path $PsTestToolFolder "Microsoft.Dynamics.Framework.UI.Client.dll") -clientContextScriptPath (Join-Path $PsTestToolFolder "ClientContext.ps1")
    
        $clientContext = $null
        try {
            $clientContext = New-ClientContext -serviceUrl $serviceUrl -auth 'AAD' -credential $credential -interactionTimeout $interactionTimeout -culture $culture -timezone $timezone -debugMode:$debugMode
            
            $dialog = $clientContext.OpenFormWithFilter(2503, "ID IS '$appId'")
            $bar = $clientContext.GetControlByName($dialog,"ActionBar")
            $InstallAction = $clientContext.GetActionByName($bar, 'Install')
            Write-Host "Installing $($appName)"
            $page = $clientContext.InvokeActionAndCatchForm($InstallAction)
            if ($page.ControlIdentifier -like "{000009c7-*") {
                Write-Host -NoNewline "Progress."
                $statusPage = $clientContext.OpenForm(2508)
                if (!($statusPage)) {
                    throw "Couldn't open page 2508"
                }
                $repeater = $clientContext.GetControlByType($statusPage, [Microsoft.Dynamics.Framework.UI.Client.ClientRepeaterControl])
                do {
                    Start-Sleep -Seconds 2
                    Write-Host -NoNewline "."
                    $index = 0
                    $clientContext.SelectFirstRow($repeater)
                    $clientContext.Refresh($repeater)
                    $status = "Failed"
                    $row = $null
                    while ($true)
                    {
                        if ($index -ge ($repeater.Offset + $repeater.DefaultViewport.Count))
                        {
                            $clientContext.ScrollRepeater($repeater, 1)
                        }
                        $rowIndex = $index - $repeater.Offset
                        $index++
                        if ($rowIndex -ge $repeater.DefaultViewport.Count)
                        {
                            break
                        }
                        $row = $repeater.DefaultViewport[$rowIndex]
                        $nameControl = $clientContext.GetControlByName($row, "Name")
                        $name = $nameControl.StringValue
                        if ($name -like "*$appId*") {
                            $status = $clientContext.GetControlByName($row, "Status").StringValue
                            break
                        }
                    }
                }
                while ($status -eq "InProgress")
    
                if ($row) {
                    if ($status -eq "Completed") {
                        Write-Host -ForegroundColor Green " $status"
                    }
                    else {
                        Write-Host -ForegroundColor Red " $status"
                        $details = $clientContext.InvokeActionAndCatchForm($row)
                        if ($details) {
                            $summaryControl = $clientContext.GetControlByName($details, "OpDetails")
                            if ($summaryControl) {
                                Write-Host -ForegroundColor Red $summaryControl.StringValue
                            }
                            $viewDetailsControl = $clientContext.GetControlByName($details, "Details")
                            if ($viewDetailsControl) {
                                $clientContext.InvokeSystemAction($viewDetailsControl, "DrillDown")
                                $detailsControl = $clientContext.GetControlByName($details, "Detailed Message box")
                                if ($detailsControl -and $detailsControl.StringValue) {
                                    Write-Host -ForegroundColor Red "Error Details: $($detailsControl.StringValue)"
                                }
                            }
                            $clientContext.CloseForm($details)
                        }
                        throw "Could not install $appName"
                    }
                }
            }
        }
        catch {
            Write-Host $_.ScriptStackTrace
            if ($debugMode -and $clientContext) {
                Dump-ClientContext -clientcontext $clientContext 
            }
            throw
        }
        finally {
            if ($clientContext) {
                Remove-ClientContext -clientContext $clientContext
            }
        }
    }
}
Export-ModuleMember -Function Install-BcAppFromAppSource