Scripts/SolutionDeploy.ps1

#
# SolutionDeploy.ps1
#

function Write-PPDOMessage {
    [CmdletBinding()]
    param (
        [Parameter()] 
        [string] $Message,
        [Parameter()]
        [ValidateSet('group', 'warning', 'error', 'section', 'debug', 'command', 'endgroup')]
        [string] $Type,
        [Parameter()] 
        [bool] $LogError = $false,
        [Parameter()] 
        [bool] $LogWarning = $false,
        [Parameter()] 
        [bool] $RunLocally = $false
    )
    switch ($Type) {
        'group' { 
            if ($RunLocally) {
                Write-Host $Message -BackgroundColor Magenta
            }
            else {
                Write-Host "##[group]$Message"
            }
        } 'warning' {
            if ($RunLocally) {
                Write-Host $Message -ForegroundColor DarkYellow
            }
            else {
                if ($LogWarning) {
                    Write-Host "##vso[task.logissue type=warning]$Message"
                }
                else {
                    Write-Host "##[warning]$Message"
                }                
            }
        } 'error' {
            if ($RunLocally) {
                Write-Host $Message -ForegroundColor Red
            }
            else {
                if ($LogError) {
                    Write-Host "##vso[task.logissue type=error]$Message"
                }
                else {
                    Write-Host "##[error]$Message"
                }                
            }
        } 'section' {
            if ($RunLocally) {
                Write-Host $Message -ForegroundColor Green
            }
            else {
                Write-Host "##[section]$Message"
            }
        } 'debug' {
            if ($RunLocally) {
                Write-Host $Message -ForegroundColor Magenta
            }
            else {
                Write-Host "##[debug]$Message"
            }
        } 'command' {
            if ($RunLocally) {
                Write-Host $Message -ForegroundColor Blue
            }
            else {
                Write-Host "##[command]$Message"
            }
        } 'endgroup' {
            if ($RunLocally) {
                Write-Host ":END:" -BackgroundColor Magenta
            }
            else {
                Write-Host "##[endgroup]"
            }
        }
        Default {
            Write-Host $Message
        }
    }
}

function Start-DeploySolution {
    Param(
        [string] [Parameter(Mandatory = $true)] $DeployServerUrl,
        [string] [Parameter(Mandatory = $true)] $UserName,
        [string] [Parameter(Mandatory = $false)] $Password = "",
        [string] [Parameter(Mandatory = $true)] $PipelinePath,
        [bool] [Parameter(Mandatory = $false)] $UseClientSecret = $false,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false,
        [string] [Parameter(Mandatory = $false)] $EnvironmentName = $env:ENVIRONMENT_NAME
    )

    ######################## SETUP
    . "$PSScriptRoot\..\Private\_SetupTools.ps1"

    Write-Host "Using Microsoft.PowerPlatform.DevOps version :" (Get-Module -Name Microsoft.PowerPlatform.DevOps -ListAvailable).Version
    #region "Dependencies"
    Write-PPDOMessage -Message "Installing Dependencies" -Type group -RunLocally $RunLocally
    Install-PAC

    if (!$RunLocally) {
        Install-ConfigMigrationModule
        Install-XrmModule
        Install-PowerAppsCheckerModule
        Install-PowerAppsAdmin
    }
    else {
        Write-Host "Preparing local run"
    }

    Write-PPDOMessage -Type endgroup -RunLocally $RunLocally
    #endregion
    function Import-Package {
        if ($UseClientSecret) {
            [string]$CrmConnectionString = "AuthType=ClientSecret;Url=$DeployServerUrl;ClientId=$UserName;ClientSecret=$Password"
        }
        else {
            [string]$CrmConnectionString = "AuthType=OAuth;Username=$UserName;Password=$Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;LoginPrompt=never"
        }
        $Packages = Get-Content "$PipelinePath\deployPackages.json" -Encoding UTF8 | ConvertFrom-Json

        #handle Environments file should it be missing. Same for DeployPackages and SolutionChecker files!

        $Environments = Get-Content "$PipelinePath\Environments.json" | ConvertFrom-Json
        $EnvConfig = $Environments | Where-Object { $_.EnvironmentName -eq $EnvironmentName }

        Write-PPDOMessage "Creating CRM connection" -Type section -RunLocally $RunLocally
        $CRMConn = Get-CrmConnection -ConnectionString $CrmConnectionString -Verbose #-MaxCrmConnectionTimeOutMinutes 10
        #Set-CrmConnectionTimeout -conn $CRMConn -TimeoutInSeconds 600

        if ($false -eq $CRMConn.IsReady) {
            Write-Host "An error occurred: " $CRMConn.LastCrmError
            Write-Host $CRMConn.LastCrmException.Message
            Write-Host $CRMConn.LastCrmException.Source
            Write-Host $CRMConn.LastCrmException.StackTrace
            throw "Could not establish connection with server"
        }

        #ENVIRONMENT PRE-ACTION
        if ($null -ne $EnvConfig -and $EnvConfig.PreAction -eq $true) {
    
            Write-PPDOMessage "Execute Environment Pre Action" -Type section -RunLocally $RunLocally
            . "$PipelinePath\Common\Environments\Scripts\PreAction.ps1" -Conn $CRMConn -PipelinePath $PipelinePath -EnvironmentName $EnvConfig.EnvironmentName -EnvironmentUrl $DeployServerUrl
            $EnvConfig.PreFunctions | ForEach-Object {
                & $_ -Conn $CRMConn
            }
            Write-PPDOMessage "Environment Pre Action Complete" -Type command -RunLocally $RunLocally
        }
        else {
            Write-PPDOMessage "Environment Pre Action not registered to execute" -Type warning -RunLocally $RunLocally
        }   

        foreach ($package in $Packages) {
            $Deploy = $package.DeployTo | Where-Object { $_.EnvironmentName -eq $EnvironmentName }
            if ($null -ne $Deploy) {  
                $PSolution = $package.SolutionName             
                $SolutionFolder = $package.SolutionName
                $versionFile = "$($PSolution).version"
                Write-PPDOMessage "Preparing Deployment for $PSolution" -Type group -RunLocally $RunLocally
                Write-Host "Deployment step manifest - $Deploy"
                Write-Host ""

                Write-Host "Getting Solutions & Versions to be Deployed..."
                try {
                    $solutionsToDeploy = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile | ConvertFrom-Json
                }
                catch {
                    #Legacy Solution Packaging Support
                    $solutionVersion = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile
                    $solutionsToDeploy = @([ordered]@{SolutionName = $package.SolutionName; Version = $solutionVersion ; })
                }
                $skipPatch = $false
                
                $solutionsToDeploy | ForEach-Object {
                    $deployAsNew = $false
                    $skipDeploy = $false 
                    $anyFailuresInImport = $false; 
        
                    $patchDeploy = $false  
                      
                    $PSolution = $_.SolutionName
                    $deployingVersion = $_.Version
                    $packageFolder = "dataverse_$($PSolution)"
                    if ($PSolution.contains("_Patch")) {
                        $patchDeploy = $true
                    }
                    #region 'Preparing Deployment'

                    Write-PPDOMessage "Preparing $PSolution Solution as $($Deploy.DeploymentType)" -Type section -RunLocally $RunLocally

                    $fileToPack = "$($Deploy.EnvironmentName)_$($PSolution)_$($Deploy.DeploymentType).zip"               

                    Write-PPDOMessage "Packing Solution $PSolution" -Type command -RunLocally $RunLocally
                    #Checking for Canvas App
                    $canvasApps = Get-ChildItem -Path $PipelinePath\$SolutionFolder\$packageFolder\CanvasApps\ -Directory -ErrorAction SilentlyContinue 
                    # pack canvas apps
                    $canvasApps | ForEach-Object {
                        Write-Host "Packing Canvas App $($_.name)";
                        & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe canvas pack --sources $_.FullName --msapp "$($_.FullName).msapp"
                        Remove-Item $_.FullName -Recurse -ErrorAction SilentlyContinue
                    }
                    if ($Deploy.DeploymentType.ToLower() -eq "unmanaged") {
                        & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Unmanaged
                    }
                    else {
                        & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Managed -same
                    }         
               
                    Write-PPDOMessage "Importing package" -Type section -RunLocally $RunLocally
                
                    try {
                        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                        $error.Clear()
                        Write-PPDOMessage "Deploying $PSolution as $($Deploy.DeploymentType) to - $EnvironmentName" -Type section -RunLocally $RunLocally

                        if (!$patchDeploy) {
                            Write-PPDOMessage "Checking to make sure there is no existing $($package.SolutionName)_Upgrade solution" -Type command -RunLocally $RunLocally
                            $ugSolution = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator like -FilterValue "$($package.SolutionName)_Upgrade" -Fields uniquename
                            if ($ugSolution.CrmRecords.Count -gt 0) {
                                Write-PPDOMessage "Found holding solution $($ugSolution.CrmRecords[0].uniquename), removing it" -Type warning -RunLocally $RunLocally

                                Write-Host "Applying Upgrade to Solution"
                                $promoteRequestId = $CRMConn.DeleteAndPromoteSolutionAsync($PSolution);                          

                                if (($null -eq $promoteRequestId) -or ($promoteRequestId -eq [Guid]::Empty)) {
                                    Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                    $anyFailuresInImport = $true;
                                }
                                else { 
                                    Write-Host Async Operation ID: $promoteRequestId 
                                    do {
                                        try {
                                            Start-Sleep -Seconds 5
                                            $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($promoteRequestId) -Fields name, statuscode, friendlymessage, completedon, errorcode
                                            [int]$statuscode = $operation.statuscode_Property.value.Value;
                                                                    
                                            if ($statuscode -le 30) {
                                                Write-Host "Polling Promotion status for Solution: $($PSolution) : $($operation.statuscode)"
                                                $anyFailuresInImport = $false;
                                            }
                                            elseif ($statuscode -eq 31 -or $statuscode -eq 32) {
                                                if ($operation.friendlymessage -match "have an upgrade that is ready to be applied") {
                                                    Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally 
                                                    $anyFailuresInImport = $false;   
                                                }
                                                else {
                                                    Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                                    Write-PPDOMessage "Delete and Promote Failed: #($operation.statuscode)" -Type warning -RunLocally $RunLocally
                                                    Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally
                                                    Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally
                                                    exit 1
                                                    $anyFailuresInImport = $true;    
                                                }
                                            }
                                        }
                                        catch {
                                            Write-Host "Retrying Polling Upgrade status"
                                            $Retrycount = $Retrycount + 1
                                            if ($Retrycount -gt 3) {
                                                $statuscode = 32;
                                                $anyFailuresInImport = $true;
                                                Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                                Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                                                exit 1
                                                break;
                                            }
                                        }
                                    }until($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32)
                                }
                            }
                        }                        

                        #Get Currently Deployed Solution Version
                        Write-Host "Getting Current Solution Version from Target"
                        $SolutionQuery = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -Fields 'solutionid', 'friendlyname', 'version', 'uniquename' -FilterAttribute uniquename -FilterOperator eq -FilterValue $PSolution
                        $Solution = $SolutionQuery.CrmRecords[0]
                        Write-Host $Solution.uniquename " - " $Solution.version

                        if (!$Solution) { 
                            $deployAsHolding = $false;
                            $deployAsNew = $true;
                            if (!$skipPatch) {
                                Write-Host "Solution not found in Target, Importing as New"  
                            }                          
                            $SolutionVersion = [version]"0.0.0.0"
                        }
                        else {
                            $SolutionVersion = $Solution.version
                            Write-Host "Found: $SolutionVersion in $EnvironmentName"

                            if ($null -ne $Deploy.DeployAsHolding -and !$patchDeploy) {
                                [bool]$deployAsHolding = [System.Convert]::ToBoolean($Deploy.DeployAsHolding)
                            }
                            else {
                                $deployAsHolding = $false
                            }
                        }
                        
                        Write-PPDOMessage "Version to be deployed : $deployingVersion" -Type command -RunLocally $RunLocally
                        Write-Host "skipPatch : $skipPatch"
                        Write-Host "patchDeploy : $patchDeploy"  
                        [version]$depVersion = $deployingVersion
                        [version]$solVersion = $solutionVersion               
                        if ($depVersion -le $solVersion) { $skipDeploy = $true; Write-PPDOMessage "Skipping Deployment as Target has same or newer" -Type warning -RunLocally $RunLocally }
                        if ($depVersion -ne $solVersion -and !$patchDeploy -and !$deployAsNew) { $skipPatch = $true; Write-PPDOMessage "Setting skipPatch to true as parent solutions don't match" -Type warning -RunLocally $RunLocally }
                        if ($skipPatch -and $patchDeploy) {
                            $skipDeploy = $true
                        }
                        ########################## IMPORT
                        if (!$skipDeploy) {

                            # Powerapps Solution Checker
                            if ($Deploy.PowerAppsChecker -eq $true -and $UseClientSecret -eq $true) {
                                Write-PPDOMessage "Running PowerApps Solution Checker for $PSolution" -Type command -RunLocally $RunLocally
                                Start-SolutionChecker -PipelinePath $PipelinePath -SolutionPath $PipelinePath\$SolutionFolder\$fileToPack -SolutionName $PSolution -ClientId $UserName -ClientSecret $Password -TenantId "$($CRMConn.TenantId)"
                            }
                            else {
                                Write-PPDOMessage "Powerapps Checker not configured. Add PowerAppsChecker: True as a property in the DeployTo section for your Solution" -Type warning -RunLocally $RunLocally -LogWarning $true                           
                            }
            
                            # PRE ACTION
                            if ($Deploy.PreAction -eq $true -and !$patchDeploy) {
                                if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PreAction.ps1) {
                                    Write-PPDOMessage "Execute Pre Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                                    . $PipelinePath\$SolutionFolder\Scripts\PreAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                                }
                                else {
                                    Write-PPDOMessage "Deployment PreAction step not registered to excecute" -Type warning -RunLocally $RunLocally
                                }
                            }                        

                            $activatePlugIns = $true;
                            $overwriteUnManagedCustomizations = $false;
                            $skipDependancyOnProductUpdateCheckOnInstall = $true;
                            $isInternalUpgrade = $false;   
                            
                            if ($Deploy.OverwriteUnmanagedCustomisations -eq $true) {
                                $overwriteUnManagedCustomizations = $true
                            }

                            Write-PPDOMessage "Initiating Import and deployment to $($DeployServerUrl)" -Type section -RunLocally $RunLocally
                            Write-PPDOMessage "Import as Holding solution : $($deployAsHolding)" -Type command -RunLocally $RunLocally
                        
                            $importId = [guid]::Empty 
                            $result = $CRMConn.ImportSolutionToCrmAsync("$PipelinePath\$SolutionFolder\$fileToPack", [ref]$importId,
                                $activatePlugIns,
                                $overwriteUnManagedCustomizations, 
                                $skipDependancyOnProductUpdateCheckOnInstall,
                                $deployAsHolding,
                                $isInternalUpgrade)

                            Write-Host Async Operation ID: $result 
                            Write-Host Import Job ID: $importId 
                        
                            $Retrycount = 0;

                            # IMPORT
                            do {

                                try {

                                    Start-Sleep -Seconds 5

                                    $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($result) -Fields name, statuscode, friendlymessage, completedon, errorcode
                                    [int]$statuscode = $operation.statuscode_Property.value.Value;

                                    if ($statuscode -le 30) {
                                        $job = Get-CrmRecord -conn $CRMConn -EntityLogicalName importjob -Id ($importId) -Fields progress 
                                        Write-Host "Polling Import for Solution: $($PSolution) : $($operation.statuscode) - $($job.progress)%"
                                        $anyFailuresInImport = $false;
                                    }
                                    elseif ($statuscode -eq 31 -or $statuscode -eq 32) {
                                        Write-PPDOMessage "Unable to import solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                        Write-PPDOMessage "Import Failed: $($operation.statuscode)" -Type warning -RunLocally $RunLocally
                                        Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally
                                        Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally
                                        exit 1
                                        $anyFailuresInImport = $true;
                                    }
                                }
                                catch {
                                    Write-Host "Retrying Polling import status"
                                    $Retrycount = $Retrycount + 1
                                    if ($Retrycount -gt 3) {
                                        $statuscode = 32;
                                        $anyFailuresInImport = $true;
                                        Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                        Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                                        exit 1
                                        break;
                                    }
                                }
                            } until ($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32)

                            $Retrycount = 0;
                            # UPGRADE
                            if ($deployAsHolding -eq $true -and $anyFailuresInImport -eq $false) {
                         
                                # PRE UPGRADE
                                if ($Deploy.PreUpgrade -eq $true -and !$patchDeploy) {
                                    if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1) {
                                        Write-PPDOMessage "Execute Pre Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                                        . $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                                    }
                                    else {
                                        Write-Host "Deployment PreUpgrade step not registered to excecute"
                                    }
                                }


                                Write-Host "Applying Upgrade to Solution"
                                $promoteRequestId = $CRMConn.DeleteAndPromoteSolutionAsync($PSolution);                          

                                if (($null -eq $promoteRequestId) -or ($promoteRequestId -eq [Guid]::Empty)) {
                                    Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                    $anyFailuresInImport = $true;
                                }
                                else { 
                                    Write-Host Async Operation ID: $promoteRequestId 
                                    do {
                                        try {
                                            Start-Sleep -Seconds 5
                                            $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($promoteRequestId) -Fields name, statuscode, friendlymessage, completedon, errorcode
                                            [int]$statuscode = $operation.statuscode_Property.value.Value;
                                                                    
                                            if ($statuscode -le 30) {
                                                Write-Host "Polling Promotion status for Solution: $($PSolution) : $($operation.statuscode)"
                                                $anyFailuresInImport = $false;
                                            }
                                            elseif ($statuscode -eq 31 -or $statuscode -eq 32) {
                                                if ($operation.friendlymessage -match "have an upgrade that is ready to be applied") {
                                                    Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally
                                                    $anyFailuresInImport = $false;
                                                }
                                                else {
                                                    Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                                    Write-PPDOMessage "Delete and Promote Failed: #($operation.statuscode)" -Type warning -RunLocally $RunLocally
                                                    Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally
                                                    Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally
                                                    exit 1
                                                    $anyFailuresInImport = $true;
                                                }
                                            }
                                        }
                                        catch {
                                            Write-Host "Retrying Polling Upgrade status"
                                            $Retrycount = $Retrycount + 1
                                            if ($Retrycount -gt 3) {
                                                $statuscode = 32;
                                                $anyFailuresInImport = $true;
                                                Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true
                                                Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                                                exit 1
                                                break;
                                            }
                                        }
                                    }until($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32)
                                }

                                # Post UPGRADE
                                if ($Deploy.PostUpgrade -eq $true -and !$patchDeploy) {
                                    if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1) {
                                        Write-PPDOMessage "Execute Post Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                                        . $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                                    }
                                    else {
                                        Write-Host "Deployment PostUpgrade step not registered to excecute"
                                    }
                                }
                            }
                            #region 'Reference Data'
                            # DATA CONFIGURATION
                            if ($Deploy.DeployData -eq $true -and $anyFailuresInImport -eq $false) {
                                Write-PPDOMessage "Importing reference Data ..." -Type group -RunLocally $RunLocally
                                try {
                                    if (Test-Path -Path $PipelinePath\$SolutionFolder\ReferenceData\data.zip) {
                                        Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$SolutionFolder\ReferenceData\data.zip -ConcurrentThreads 5 -EmitLogToConsole                    
                                    }
                                    else {
                                        Write-Host "Config Data file does not Exist"
                                    }
                                }
                                catch {
                                    Write-PPDOMessage "Unable to import configuration data - please review Pipeline error logs" -Type error -RunLocally $RunLocally -LogError $true
                                    Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                                    exit 1           
                                }
                            
                            }
                            else {
                                Write-PPDOMessage "No Data to Import for $PSolution" -Type section -RunLocally $RunLocally
                            }
                            Write-PPDOMessage -Type endgroup -RunLocally $RunLocally

                            #endregion

                            # POST ACTION
                            if ($Deploy.PostAction -eq $true -and $anyFailuresInImport -eq $false -and !$patchDeploy) {
                                if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1) {
                                    Write-PPDOMessage "Execute Post Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                                    . $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                                }
                                else {
                                    Write-PPDOMessage "Deployment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally
                                }
                            }
               
                            $ProgressPreference = "Continue"
                            [int]$elapsedTime = $stopwatch.Elapsed.TotalMinutes      
                            $stopwatch.Stop()
                            Write-PPDOMessage "Import Complete in $($elapsedTime) minutes" -Type section -RunLocally $RunLocally
                        }
                    }
                    catch {
                        Write-PPDOMessage "Skipping $PSolution due to Solution import error" -Type section -RunLocally $RunLocally
                        Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                    }
                }
                if (!$skipDeploy -or $Deploy.Flows.AlwaysTryActivate -eq $true) {
                    $FlowsToRetry = @()
                    Write-Host "Establishing Connection References and Activating Flows" -ForegroundColor Green
                    $ProgressPreference = "SilentlyContinue"
                    # Activate Flows and Establish Connection References
                    Write-Host "Getting Environment Id"
                    $orgs = Get-CrmRecords -conn $CRMConn -EntityLogicalName organization
                    if ($orgs.Count -gt 0) {
                        $orgId = $orgs.CrmRecords[0].organizationid
                        if ($UseClientSecret) {
                            Add-PowerAppsAccount -ApplicationId $UserName -ClientSecret $Password -TenantID $CRMConn.TenantId
                        }
                        else {
                            Add-PowerAppsAccount -Username $UserName -Password $Password
                        }
                        $Environment = Get-AdminPowerAppEnvironment | Where-Object OrganizationId -eq $orgId.Guid
                        $EnvId = $Environment.EnvironmentName
                        Write-Host "Environment Id - $EnvId"
                        Write-Host "Checking if there are Connections References in the Solution that Need to be Wired Up"
                        $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)"
                        $solutionId = $solutions.CrmRecords[0].solutionid
                        $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords
                        $connRefs | Where-Object { $null -eq $_.connectionid } | ForEach-Object {
                            $connectionType = $_.connectorid.Replace("/providers/Microsoft.PowerApps/apis/", "")
                            Write-Host "Found Connection Reference $($_.connectionreferencelogicalname) without a Connection to $connectionType"
                        
                            Write-Host "Getting Connections in Environment"
                            $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId | Where-Object ConnectorName -eq $connectionType
                            if ($connection) {
                                # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection
                                $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id -Fields domainname
                                if ($systemusers.Count -gt 0) {
                                    Write-Host "Impersonating the Owner of the Connection - $($systemusers.CrmRecords[0].domainname)"
                                    # Impersonate the Dataverse systemuser that created the connection when updating the connection reference
                                    $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid
                                    $impersonationConn = $CRMConn
                                    $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                    Write-PPDOMessage "Setting Connection Reference to use $($connection[0].DisplayName)" -Type command -RunLocally $RunLocally
                                    Set-CrmRecord -conn $impersonationConn -EntityLogicalName $_.logicalname -Id $_.connectionreferenceid -Fields @{"connectionid" = $connection[0].ConnectionName }                                    
                                }
                            }
                            else {
                                Write-PPDOMessage "No Connection has been set up of type $connectionType, some of your Flows may not Activate succesfully" -Type warning -RunLocally $RunLocally -LogWarning $true
                            }
                        
                        }

                        Write-Host "Checking if there are Flows that need to be Activated"
                        if ($Deploy.Flows.ActivateFlows -eq $true) {
                            if ($Deploy.Flows.OverrideFile) {
                                Write-Host "Using $($Deploy.Flows.OverrideFile) for Flow Activation"
                                try {
                                    $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\$($Deploy.Flows.OverrideFile) -ErrorAction SilentlyContinue | ConvertFrom-Json    
                                }
                                catch {
                                    
                                }
                                
                            }
                            else {
                                Write-Host "Using Flows_Default.json for Flow Activation"
                                try {
                                    $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\Flows_Default.json -ErrorAction SilentlyContinue | ConvertFrom-Json 
                                }
                                catch {
                                    $FlowsToActivate = $null
                                }                                
                            }
                            Write-Host "There are $($FlowsToActivate.Count) Flows that need activating"
                            $ErrorCount = 0
                            if ($FlowsToActivate.Count -gt 0) {
                                $FlowsToActivate | ForEach-Object {
                                    $FlowStore = $_
                                    $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name
                                    if ($_.ActivateAsUser) {
                                        Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user"
                                        $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser
                                        if ($systemuserResult.Count -gt 0) {
                                            $systemUserId = $systemuserResult.CrmRecords[0].systemuserid
                                            #Activate the workflow using the owner.
                                            if ($workflow.statecode -ne "Activated") {
                                                $impersonationConn = $CRMConn
                                                $impersonationCallerId = $systemUserId
                                                $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                                Write-PPDOMessage "Enabling Flow $($workflow.name)" -Type command -RunLocally $RunLocally
                                                try {
                                                    Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated    
                                                }
                                                catch {
                                                    Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true
                                                    Write-Host $_
                                                    if ($_.ToString().Contains("ChildFlowNeverPublished")) {
                                                        $FlowsToRetry += $FlowStore
                                                    }
                                                    else {
                                                        $ErrorCount++    
                                                    }
                                                    
                                                }
                                            
                                            }    
                                        }
                                        Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true
                                    }
                                    else {
                                        Write-Host "Checking if $($workflow.name) needs Activating..."
                                        
                                        $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)"
                                        $solutionId = $solutions.CrmRecords[0].solutionid
                                        $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords
                                        $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue
                                        $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId
                                        # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection
                                        $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id
                                        if ($systemusers.Count -gt 0) {
                                            # Impersonate the Dataverse systemuser that created the connection when updating the connection reference
                                            $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid
                                            if ($workflow.statecode -ne "Activated") {
                                                Write-PPDOMessage "Enabling Flow $($workflow.name) as Owner of Connection Reference" -Type command -RunLocally $RunLocally
                                                $impersonationConn = $CRMConn
                                                $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                                try {
                                                    Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated    
                                                }
                                                catch {
                                                    Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true
                                                    Write-Host $_
                                                    if ($_.ToString().Contains("ChildFlowNeverPublished")) {
                                                        $FlowsToRetry += $FlowStore
                                                    }
                                                    else {
                                                        $ErrorCount++    
                                                    }
    
    
                                                }
                                                
                                            }
    
                                        }
                                        
                                    }
                                }  
                            }
                            
                            $FlowsToRetry | ForEach-Object {
                                Write-Host
                                Write-Host "Retrying Flows that failed due to Child Flows" -ForegroundColor Green
                                $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name
                                if ($_.ActivateAsUser) {
                                    Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user"
                                    $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser
                                    if ($systemuserResult.Count -gt 0) {
                                        $systemUserId = $systemuserResult.CrmRecords[0].systemuserid
                                        #Activate the workflow using the owner.
                                        if ($workflow.statecode -ne "Activated") {
                                            $impersonationConn = $CRMConn
                                            $impersonationCallerId = $systemUserId
                                            $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                            Write-PPDOMessage "Enabling Flow $($workflow.name)" -Type command -RunLocally $RunLocally
                                            try {
                                                Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated    
                                            }
                                            catch {
                                                Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true
                                                Write-Host $_
                                                $ErrorCount++    
                                            
                                                
                                            }
                                        
                                        }    
                                    }
                                    Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true
                                }
                                else {
                                    Write-Host "Checking if $($workflow.name) needs Activating..."
                                    
                                    $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)"
                                    $solutionId = $solutions.CrmRecords[0].solutionid
                                    $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords
                                    $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue
                                    $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId
                                    # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection
                                    $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id
                                    if ($systemusers.Count -gt 0) {
                                        # Impersonate the Dataverse systemuser that created the connection when updating the connection reference
                                        $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid
                                        if ($workflow.statecode -ne "Activated") {
                                            Write-PPDOMessage "Enabling Flow $($workflow.name) as Owner of Connection Reference" -Type command -RunLocally $RunLocally
                                            $impersonationConn = $CRMConn
                                            $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                            try {
                                                Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated    
                                            }
                                            catch {
                                                Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true
                                                Write-Host $_
                                                $ErrorCount++    
                                            }


                                    
                                            
                                        }

                                    }
                                    
                                }
                            }
                            if ($Deploy.Flows.FailonError -eq $true -and $ErrorCount -gt 0) {
                                Write-PPDOMessage "There were $ErrorCount Flow activation errors and FailonError is set to True... exiting." -Type error -RunLocally $RunLocally -LogError $true
                                exit 1                     
                            }

                        }
                                
                        else {
                            Write-Host @"
No Flows were specified for activation. If you wish to include flows for activation, please add the following in deployPackages.json
 
"Flows":
{
    "ActivateFlows": "true",
    "OverrideFile" : "",
    "FailonError" : "false"
}
                         
"@

                        }
                    }
                }
                Write-PPDOMessage -Type endgroup -RunLocally $RunLocally
                #endregion
            }
            else {
                Write-PPDOMessage "$($package.SolutionName) is not configured for deployment to $env:ENVIRONMENT_NAME in deployPackages.json" -Type warning -RunLocally $RunLocally -LogWarning $true
            }
        
        }


        #EXECUTE ENVIRONMENT POST ACTION
        if ($null -ne $EnvConfig -and $EnvConfig.PostAction -eq $true) {
            Write-PPDOMessage "Execute Environment Post Action" -Type section -RunLocally $RunLocally
            . "$PipelinePath\Common\Environments\Scripts\PostAction.ps1" -Conn $CRMConn -PipelinePath $PipelinePath -EnvironmentName $EnvConfig.EnvironmentName -EnvironmentUrl $DeployServerUrl
            $EnvConfig.PostFunctions | ForEach-Object {
                & $_ -Conn $CRMConn
            }
            Write-PPDOMessage "Environment Post Action Complete" -Type command -RunLocally $RunLocally
        }
        else {
            Write-PPDOMessage "Environment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally
        }
    }
    Write-Host Environment $EnvironmentName
    Import-Package
}