functions/Invoke-PpacD365AppInstall.ps1


<#
    .SYNOPSIS
        Installs D365 applications in the environment.
         
    .DESCRIPTION
        Enables the user to install D365 applications in the environment as it would be done in Power Platform Admin Center (PPAC).
         
    .PARAMETER EnvironmentId
        The id of the environment that you want to work against
         
    .PARAMETER D365App
        The name, unique name or package name of the D365 application you want to install in the environment.
         
        Supports single or multiple application names.
         
    .EXAMPLE
        PS C:\> Invoke-PpacD365AppInstall -EnvironmentId "eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6" -D365App "Invoice Capture for Dynamics 365 Finance"
         
        This will install the "Invoice Capture for Dynamics 365 Finance" application in the environment.
         
    .EXAMPLE
        PS C:\> Invoke-PpacD365AppInstall -EnvironmentId "eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6" -D365App "msdyn_FnoInvoiceCaptureAnchor"
         
        This will install the "Invoice Capture for Dynamics 365 Finance" application in the environment.
        It will use the unique name of the application (msdyn_FnoInvoiceCaptureAnchor) to find the application and install it.
         
    .EXAMPLE
        PS C:\> Invoke-PpacD365AppInstall -EnvironmentId "eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6" -D365App "MicrosoftOperationsVEAnchor","msdyn_FnoInvoiceCaptureAnchor"
         
        This will install the "Microsoft Operations VE" and "Invoice Capture for Dynamics 365 Finance" applications in the environment.
        It will use the unique name of the D365 applications to find the applications and install them.
        It will start the installation of both applications, and then wait for the installation process to finish.
         
    .NOTES
        Author: Mötz Jensen (@Splaxi)
#>

function Invoke-PpacD365AppInstall {
    [CmdletBinding()]
    param (
        [parameter (Mandatory = $true)]
        [string] $EnvironmentId,

        [parameter (Mandatory = $true)]
        [string[]] $D365App
    )
    
    begin {
        # Make sure all *BapEnvironment* cmdlets will validate that the environment exists prior running anything.
        $envObj = Get-BapEnvironment `
            -EnvironmentId $EnvironmentId | `
            Select-Object -First 1

        if ($null -eq $envObj) {
            $messageString = "The supplied EnvironmentId: <c='em'>$EnvironmentId</c> didn't return any matching environment details. Please verify that the EnvironmentId is correct - try running the <c='em'>Get-BapEnvironment</c> cmdlet."
            Write-PSFMessage -Level Important -Message $messageString
            Stop-PSFFunction -Message "Stopping because environment found based on the id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }
        
        if (Test-PSFFunctionInterrupt) { return }

        # First we will fetch ALL available apps for the environment
        $secureTokenPowerApi = (Get-AzAccessToken -ResourceUrl "https://api.powerplatform.com/" -AsSecureString).Token
        $tokenPowerApiValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureTokenPowerApi
        
        $headersPowerApi = @{
            "Authorization" = "Bearer $($tokenPowerApiValue)"
        }

        $appsAvailable = Get-PpacD365App `
            -EnvironmentId $EnvironmentId
    }
    
    process {
        if (Test-PSFFunctionInterrupt) { return }
        
        [System.Collections.Generic.List[System.Object]] $arrInstallStarted = @()
        [System.Collections.Generic.List[System.Object]] $arrStatus = @()
        [System.Collections.Generic.List[System.Object]] $arrFailedStarts = @()

        $headersPowerApi."Content-Type" = "application/json;charset=utf-8"

        foreach ($appName in $D365App) {
            $appToBeInstalled = $appsAvailable | `
                Where-Object { $_.PpacD365AppId -eq $appName -or
                $_.PpacD365AppName -eq $appName -or
                $_.PpacPackageName -eq $appName } | `
                Select-Object -First 1

            if ($null -eq $appToBeInstalled) {
                $messageString = "The combination of the supplied EnvironmentId: <c='em'>$EnvironmentId</c> and PpacD365AppId: <c='em'>$appName</c> didn't return any matching D365App. Please verify that the EnvironmentId & PpacD365AppId is correct - try running the <c='em'>Get-BapEnvironmentD365App</c> cmdlet."
                Write-PSFMessage -Level Important -Message $messageString
                Stop-PSFFunction -Message "Stopping because environment and d365app combination was NOT found based on the supplied parameters." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
            }

            try {
                $localUri = "https://api.powerplatform.com/appmanagement/environments/{0}/applicationPackages/{1}/install?api-version=2022-03-01-preview" `
                    -f $envObj.PpacEnvId `
                    , $appToBeInstalled.PpacPackageName

                $resIntall = Invoke-RestMethod `
                    -Method Post `
                    -Uri $localUri `
                    -Headers $headersPowerApi `
                    -Body $body

                $arrInstallStarted.Add($resIntall)
            }
            catch {
                $arrFailedStarts.Add($appToBeInstalled)
            }
        }

        if ($arrFailedStarts.Count -gt 0) {
            $messageString = "The following packages <c='em'>failed to start</c>:"
            Write-PSFMessage -Level Important -Message $messageString
                
            $arrFailedStarts.ToArray()

            if ($arrFailedStarts.Count -eq $D365App.Count) {
                Stop-PSFFunction -Message "Stopping because all package installations failed to start." -Exception $([System.Exception]::new($messageString))
                return
            }
        }
        
        do {
            $secureTokenPowerApi = (Get-AzAccessToken -ResourceUrl "https://api.powerplatform.com/" -AsSecureString).Token
            $tokenPowerApiValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureTokenPowerApi
        
            $headersPowerApi = @{
                "Authorization" = "Bearer $($tokenPowerApiValue)"
            }

            Start-Sleep -Seconds 60

            $arrStatus = @()

            foreach ($operation in $arrInstallStarted) {
                $localUri = "https://api.powerplatform.com/appmanagement/environments/{0}/operations/{1}?api-version=2022-03-01-preview" `
                    -f $envObj.PpacEnvId `
                    , $operation.lastOperation.operationId

                $resInstallOperation = Invoke-RestMethod `
                    -Method Get `
                    -Uri $localUri `
                    -Headers $headersPowerApi 4> $null

                $arrStatus.Add($resInstallOperation)
            }

            $arrStatus | Format-Table
        } while (
            (-not ("Succeeded" -in $arrStatus.status)) `
                -and
            (-not ("Failed" -in $arrStatus.status))
        )
    }
    
    end {
        
    }
}