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 Connect-Cli {
    [CmdletBinding()]
    Param(
        [string] [Parameter(Mandatory = $true)] $DeployServerUrl,
        [string] [Parameter(Mandatory = $true)] $UserName,
        [string] [Parameter(Mandatory = $false)] $Password = "",
        [string] [Parameter(Mandatory = $true)] $TenantId,
        [bool] [Parameter(Mandatory = $false)] $UseClientSecret = $false,
        [string] [Parameter(Mandatory = $false)] $EnvironmentName 
    )


    $Env:PAC_CLI_SPN_SECRET = $Password

    if ($UseClientSecret) {
        Write-Host "Using Service Principal"
        Write-Host "Connecting to PAC CLI"

        $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
        # Added escaped quotes required to get secrets working that started with - (https://github.com/dylanhaskins/Capgemini.PowerPlatform.DevOps/issues/150)
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --applicationId $UserName --clientSecret `"$Password`" --environment $DeployServerUrl --tenant $($CRMConn.TenantId) --name ppdo

        Write-Host "Setting Add-PowerAppsAccount"
        Add-PowerAppsAccount -ApplicationId $UserName -ClientSecret $Password -TenantID $CRMConn.TenantId

        Write-Host "Authenticate Azure CLI"
        $checkBroker = az config get | ConvertFrom-Json
        if ($global:devops_FullTool) {
            if (($checkBroker.core.name.IndexOf("allow_broker") -ge 0) -and ($checkBroker.core[$checkBroker.core.name.IndexOf("allow_broker")].value)) {
                az config set core.allow_broker=false
                az login --service-principal -u $UserName -p="$Password" --tenant $CRMConn.TenantId --allow-no-subscriptions
                az config set core.allow_broker=false
            }
        }
        else {
            az login --service-principal -u $UserName -p="$Password" --tenant $CRMConn.TenantId --allow-no-subscriptions
        }
    }
    else {
        Write-Host "Using named account"
        Write-Host "Connecting to PAC CLI"

        if ([string]::IsNullOrEmpty($Password)) {
            $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --name ppdo --environment $DeployServerUrl --tenant $CRMConn.TenantId --username $UserName --password $Password 

            Write-Host "Setting Add-PowerAppsAccount"
            Add-PowerAppsAccount -Username $UserName
        }
        else {
            $ssPassword = ConvertTo-SecureString $Password -AsPlainText -Force
            $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --name ppdo --environment $DeployServerUrl --tenant $CRMConn.TenantId --username $UserName --password $ssPassword

            Write-Host "Setting Add-PowerAppsAccount"
            Add-PowerAppsAccount -Username $UserName -Password $ssPassword
        }
    }        

    & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe org who

}

function Add-7zip([String] $aDirectory, [String] $aZipfile) {
    [string]$pathToZipExe = "$($Env:ProgramFiles)\7-Zip\7z.exe";
    [Array]$arguments = "a", "-tzip", "$aZipfile", "$aDirectory", "-r";
    Remove-Item $aZipfile -ErrorAction SilentlyContinue
    & $pathToZipExe $arguments;
}

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,
        [bool] [Parameter(Mandatory = $false)] $VerboseLogging = $false
    )

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

    Write-Host "Using Capgemini.PowerPlatform.DevOps version :" (Get-Module -Name Capgemini.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" | 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
        If ($VerboseLogging) {
            $CRMConn = Get-CrmConnection -ConnectionString $CrmConnectionString -Verbose     
        }
        else {
            $CRMConn = Get-CrmConnection -ConnectionString $CrmConnectionString
        }

        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"
        }

        #connecting PAC cli - need this git config --global core.longpaths true
        Connect-Cli -DeployServerUrl $DeployServerUrl -UserName $UserName -Password $Password -TenantId $CRMConn.TenantId -UseClientSecret $UseClientSecret -EnvironmentName $EnvironmentName

        #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 }

            $deployStepCheck = $false;
            if ($null -ne $Deploy -and $null -ne $Deploy.Deploy) {  
                $deployStepCheck = $Deploy.Deploy
                Write-PPDOMessage "Using Deploy Flag value - $($Deploy.Deploy)"
            }
            elseif ($null -ne $Deploy -and $null -eq $Deploy.Deploy) {
                $deployStepCheck = $true
                Write-PPDOMessage "Using Deploy Block - True"
            }
            else {
                $deployStepCheck = $false
                Write-PPDOMessage "Deploy Block - False"
            }
          
            if ($deployStepCheck -eq $true) {  
                $PSolution = $package.SolutionName             
                $SolutionFolder = $package.SolutionName
                
                $deployFromZip = $false;
                if ($null -ne $Deploy -and $null -ne $Deploy.DeployFromZip) {
                    [bool]$deployFromZip = [System.Convert]::ToBoolean($Deploy.DeployFromZip)
                    Write-PPDOMessage "Using DeployFromZip Flag value - $($Deploy.DeployFromZip)"
                }
                
                $deployPatchFromZip = $false;
                if ($null -ne $Deploy -and $null -ne $Deploy.DeployPatchFromZip) {
                    [bool]$deployPatchFromZip = [System.Convert]::ToBoolean($Deploy.DeployPatchFromZip)
                    Write-PPDOMessage "Using DeployPatchFromZip Flag value - $($Deploy.DeployPatchFromZip)"
                }

                $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 {
                    $solutionsToDeployJson = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile | ConvertFrom-Json
                    # Sort by version
                    $solutionsToDeploy = $solutionsToDeployJson | Sort-Object { [version]$_.Version }
                }
                catch {
                    #Legacy Solution Packaging Support
                    $solutionVersion = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile
                    $solutionsToDeploy = @([ordered]@{SolutionName = $package.SolutionName; Version = $solutionVersion ; })
                }
                $skipPatch = $false
                $allSolutionsSuccessfullyImported = $true;
                $skipDeploy = $false 
                $patchDeploy = $false  
                
                $solutionsToDeploy | ForEach-Object {
                    $stageForUpgrade = $false
                    $skipDeploy = $false 
                    $anyFailuresInImport = $false; 
                    $patchDeploy = $false  
                      
                    $PSolution = $_.SolutionName
                    $deployingVersion = $_.Version
                    Write-Host "Starting deployment for Solution $PSolution, Version $deployingVersion"
                    
                    if ($PSolution.contains("_Patch")) {
                        $patchDeploy = $true
                        $packageFolder = "Patches\$PSolution"
                    }
                    else{
                        $packageFolder = "src"
                    }
                    Write-Host "packageFolder : $packageFolder"
                    #region 'Preparing Deployment'
                  

                    Write-PPDOMessage "Importing package" -Type section -RunLocally $RunLocally
                
                    try {
                        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                        $error.Clear()

                        #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;
                            $stageForUpgrade = $false;
                            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
                            }
                            if ($null -ne $Deploy.StageForUpgrade -and !$patchDeploy) {
                                [bool]$StageForUpgrade = [System.Convert]::ToBoolean($Deploy.StageForUpgrade)
                            }
                            else {
                                $StageForUpgrade = $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
                        }

                        if (!$skipDeploy) {
                            Write-PPDOMessage "Deploying $PSolution as $($Deploy.DeploymentType) to - $EnvironmentName" -Type section -RunLocally $RunLocally
                            
                            ########################### PACK PRE ACTION
                            if ($Deploy.PackPreAction -eq $true -and !$patchDeploy) {
                                if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PackPreAction.ps1) {
                                    Write-PPDOMessage "Execute Pack Pre Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                                    . $PipelinePath\$SolutionFolder\Scripts\PackPreAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                                }
                                else {
                                    Write-PPDOMessage "Deployment PackPreAction step not registered to excecute" -Type warning -RunLocally $RunLocally
                                }
                            }  

                            Write-PPDOMessage "Preparing $PSolution Solution as $($Deploy.DeploymentType)" -Type section -RunLocally $RunLocally
                            
                            if (($patchDeploy -and ($deployPatchFromZip -eq $true)) -or (!$patchDeploy -and ($deployFromZip -eq $true))) {
                                if ($Deploy.DeploymentType.ToLower() -eq "unmanaged") {
                                    $solutionZipFile = "Deploy\$PSolution.zip"
                                }
                                else {
                                    $solutionZipFile = "Deploy\$($PSolution)_managed.zip"
                                }
                            }
                            else {
                                $solutionZipFile = "$($Deploy.EnvironmentName)_$($PSolution)_$($Deploy.DeploymentType).zip"
                                Execute-PackSolution -anyFailuresInImport ([ref]$anyFailuresInImport) -CRMConn $CRMConn -fileToPack $solutionZipFile `
                                                        -SolutionFolder $SolutionFolder -SolutionName $PSolution -packageFolder $packageFolder `
                                                        -DeploymentType $Deploy.DeploymentType -RunLocally $RunLocally
                                if (!$patchDeploy) {
                                    $upgradeSolutionName = "$($package.SolutionName)_Upgrade"
                                    Execute-PreUpgrade -anyFailuresInImport ([ref]$anyFailuresInImport) -CRMConn $CRMConn -upgradeSolutionName $upgradeSolutionName `
                                                       -solutionName $PSolution -RunLocally $RunLocally
                                }   
                            } 
                            
                            $solutionZipFilePath = "$PipelinePath\$SolutionFolder\$solutionZipFile"
                            Write-Host "solutionZipFilePath : $solutionZipFilePath"  

                            if (!$deployAsHolding -and !$patchDeploy) {
                                Write-PPDOMessage "Checking to make sure there is no existing $($package.SolutionName)_Patch solution" -Type command -RunLocally $RunLocally
                                $patchSolution = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator like -FilterValue "$($package.SolutionName)_Patch%" -Fields uniquename
                                if ($patchSolution.CrmRecords.Count -gt 0) {
                                    Write-PPDOMessage "Setting DeployAsHolding to True as there is a patch in the target environment" -Type command -RunLocally $RunLocally
                                    $deployAsHolding = $true
                                }
                            }

                            ########################## IMPORT
                        
                            #-and !$RunLocally

                            ########################## Powerapps Solution Checker
                            Execute-PowerAppsChecker $solutionZipFilePath $RunLocally
            
                            ########################### 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
                                }
                            }                        
                            
                            ########################### IMPORT
                            $overwriteUnManagedCustomizations = $false;                            
                            if ($Deploy.OverwriteUnmanagedCustomisations -eq $true) {
                                $overwriteUnManagedCustomizations = $true
                            }

                            if ($Deploy.PreUpgrade -eq $true){
                                $PreUpgrade = $true
                            }
                            else{
                                $PreUpgrade = $false
                            }

                            if ($Deploy.PostUpgrade -eq $true){
                                $PostUpgrade = $true
                            }
                            else{
                                $PostUpgrade = $false
                            }

                            Execute-SolutionImport -anyFailuresInImport ([ref]$anyFailuresInImport) -CRMConn $CRMConn -DeployServerUrl $DeployServerUrl `
                                                   -solutionZipFilePath $solutionZipFilePath -SolutionFolder $SolutionFolder -EnvironmentName $Deploy.EnvironmentName `
                                                   -deployAsHolding $deployAsHolding -stageForUpgrade $stageForUpgrade -patchDeploy $patchDeploy -PreUpgrade $PreUpgrade `
                                                   -overwriteUnManagedCustomizations $overwriteUnManagedCustomizations -PostUpgrade $PostUpgrade -RunLocally $RunLocally
                        }

                        if ($anyFailuresInImport -eq $true) {
                            $allSolutionsSuccessfullyImported = $false
                        }
                                       
                        $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
                    }
                }
                
                #region 'Reference Data'
                ########################### DATA CONFIGURATION
                $skipDeployData = $skipDeploy
                if ($skipDeployData -eq $true -and $Deploy.AlwaysDeployData -eq $true -and $Deploy.DeployData -eq $true) { $skipDeployData = $false }

                Execute-ImportReferenceData $CRMConn $PipelinePath $SolutionFolder $RunLocally $allSolutionsSuccessfullyImported $skipDeployData
                #endregion
                
                Execute-SolutionPostAction $CRMConn $PipelinePath $SolutionFolder $allSolutionsSuccessfullyImported $skipDeploy $patchDeploy $RunLocally


                ########################## Establish Connection References and Activate Flows
                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

                        $Environment = Get-AdminPowerAppEnvironment | Where-Object OrganizationId -eq $orgId.Guid
                        $EnvId = $Environment.EnvironmentName
                        Write-Host "Environment Id - $EnvId"

                        Execute-FixConnectionReferences $CRMConn "$($package.SolutionName)" $EnvId $RunLocally

                        Execute-FlowActivation $CRMConn $PipelinePath $RunLocally
                    }
                }
                #endregion
                
                
                ###########################region 'Cleanup action - runs regardless'
                
                if ($Deploy.CleanupAction -eq $true -and $Deploy.Deploy -eq $true) {
                    if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\CleanupAction.ps1) {
                        Write-PPDOMessage "**************************************************** CLEANUP START"
                        Write-PPDOMessage "Execute Solution CleanupAction from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally
                        . $PipelinePath\$SolutionFolder\Scripts\CleanupAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
                        Write-PPDOMessage "**************************************************** CLEANUP END"
                    }
                }
                else {
                    Write-PPDOMessage "Deployment CleanupAction for $($Deploy.EnvironmentName) step not registered to excecute" -Type warning -RunLocally $RunLocally
                }
          
                #endregion


                Write-PPDOMessage -Type endgroup -RunLocally $RunLocally
                
            }
            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
        Execute-EnvPostAction $CRMConn $DeployServerUrl $PipelinePath $RunLocally
    }
    Write-Host Environment $EnvironmentName
    try {
        Import-Package
    }
    catch {
        Write-Host $_
    }
    finally {
        if ($RunLocally) {
            az account clear
            $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
        }
    }    
}

function Execute-PackSolution{
    Param(
        [ref]$anyFailuresInImport,
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $fileToPack,
        [string] [Parameter(Mandatory = $true)] $SolutionFolder,
        [string] [Parameter(Mandatory = $true)] $SolutionName,
        [string] [Parameter(Mandatory = $true)] $packageFolder,
        [string] [Parameter(Mandatory = $true)] $DeploymentType,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    #Checking for Canvas App
    $canvasApps = Get-ChildItem -Path $PipelinePath\$SolutionName\$packageFolder\CanvasApps\src -Directory -ErrorAction SilentlyContinue 
    # pack canvas apps
    $canvasApps | ForEach-Object {
        Write-Host "Packing Canvas App $($_.name)";
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe canvas pack --sources $_.FullName --msapp "$($_.Parent.FullName)\..\$($_.Name)_DocumentUri.msapp"
        Remove-Item $_.FullName -Recurse -ErrorAction SilentlyContinue
    }
    Write-PPDOMessage "Packing Solution $SolutionName" -Type command -RunLocally $RunLocally

    if ($Deploy.DeploymentType.ToLower() -eq "unmanaged") {
        try {
            Write-Host "Unpacking solution $PSolution";
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Unmanaged 3>&1 | Tee-Object -Variable pacOutput
            if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
        }
        catch {
            $anyFailuresInImport.Value = $true;
            Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
            exit 1
            break;
        }
    }
    else {
        try {
            Write-Host "Unpacking solution $($PSolution)_managed";
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Managed --useUnmanagedFileForMissingManaged | Tee-Object -Variable pacOutput
            if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
        }
        catch {
            $anyFailuresInImport.Value = $true;
            Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
            exit 1
            break;
        }
    }
}

function Execute-PowerAppsChecker {
    Param(
        [string] [Parameter(Mandatory = $true)] $solutionZipFilePath,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    Write-PPDOMessage "Running PowerApps Solution Checker for $PSolution" -Type command -RunLocally $RunLocally
    try {
        #if no SolutionChecker file then ignore running?
        if (Test-Path -Path $PipelinePath\SolutionChecker.json) {
            $checkSettings = Get-Content $PipelinePath\SolutionChecker.json | ConvertFrom-Json
            $checkSettings.Geo
                                                           
            if ($checkSettings.ExcludedFileNamePattern.Length -gt 0) {
                $filePattern = $checkSettings.ExcludedFileNamePattern -Join ", " 
            }
            else {
                $filePattern = "*json*" #need to find a default file if none exist
            }

            #need to make all these nested blocks cleaner
            #Check rules collection exist
            if (Test-Path -Path $PipelinePath\SolutionCheckRules.json) {   
                & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution check --path $solutionZipFilePath --geo $checkSettings.Geo --excludedFiles $"$filePattern" --ruleLevelOverride $PipelinePath\SolutionCheckRules.json  3>&1 | Tee-Object -Variable pacOutput
            }
            else {
                & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution check --path $solutionZipFilePath --geo $checkSettings.Geo --excludedFiles $"$filePattern" 3>&1 | Tee-Object -Variable pacOutput
            }
            if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
        }
    }
    catch {
        Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
    }
    #need to add support for Rule overide
}

function Execute-SolutionImport {
    Param(
        [ref]$anyFailuresInImport,
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $DeployServerUrl,
        [string] [Parameter(Mandatory = $true)] $solutionZipFilePath,
        [string] [Parameter(Mandatory = $true)] $SolutionFolder,
        [string] [Parameter(Mandatory = $true)] $EnvironmentName,
        [bool] [Parameter(Mandatory = $true)] $deployAsHolding,
        [bool] [Parameter(Mandatory = $true)] $stageForUpgrade,
        [bool] [Parameter(Mandatory = $true)] $overwriteUnManagedCustomizations,
        [bool] [Parameter(Mandatory = $true)] $PreUpgrade,
        [bool] [Parameter(Mandatory = $true)] $PostUpgrade,
        [bool] [Parameter(Mandatory = $true)] $patchDeploy,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    $activatePlugIns = $true;
    $skipDependancyOnProductUpdateCheckOnInstall = $true;

    Write-PPDOMessage "Initiating Import and deployment to $($DeployServerUrl)" -Type section -RunLocally $RunLocally
    Write-PPDOMessage "Import as Holding solution : $($deployAsHolding)" -Type command -RunLocally $RunLocally
    Write-PPDOMessage "Stage for Upgrade : $($stageForUpgrade)" -Type command -RunLocally $RunLocally
    Write-PPDOMessage "Activate Plugins : $($activatePlugIns)" -Type command -RunLocally $RunLocally
    Write-PPDOMessage "Overwrite Unmanaged Customisations : $($overwriteUnManagedCustomizations)" -Type command -RunLocally $RunLocally
    Write-PPDOMessage "Skip Dependancy Checks : $($skipDependancyOnProductUpdateCheckOnInstall)" -Type command -RunLocally $RunLocally
                        
    $Retrycount = 0;
    $importSuccess = $false;
    do {
        try {
            $pacCLI = "$env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe"
            [Array]$PACarguments = "solution", "import", "--path", $solutionZipFilePath, "--async", "--max-async-wait-time", "240"
            if ($activatePlugIns) { $PACarguments += "--activate-plugins" }
            if ($overwriteUnManagedCustomizations) { $PACarguments += "--force-overwrite" }
            if ($skipDependancyOnProductUpdateCheckOnInstall) { $PACarguments += "--skip-dependency-check" }
            if ($deployAsHolding -and !$stageForUpgrade) { $PACarguments += "--stage-and-upgrade" }
            if ($stageForUpgrade) { $PACarguments += "--import-as-holding" }
            & $pacCLI $PACarguments 3>&1 | Tee-Object -Variable pacOutput

                                    
            if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
            if ($pacOutput -match "FAILURE:") { Throw $pacOutput -match "FAILURE:" }
            $importSuccess = $true;
        }
        catch {
            if (($pacOutput -match "Uninstall") -or ($pacOutput -match "PublishAll")) {
                Write-PPDOMessage "Waiting for another Solution Operation to complete" -Type group -RunLocally $RunLocally
                Start-Sleep -Seconds 30   
            }    
            else {
                $Retrycount = 50
            }                                                          
            $Retrycount = $Retrycount + 1
            if ($Retrycount -gt 50) {
                $anyFailuresInImport.Value = $true;
                Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                exit 1
                break;
            }
        }
    } until ($importSuccess -eq $true)

    ########################### UPGRADE
    if ($stageForUpgrade -eq $true -and $anyFailuresInImport -eq $false) {
        Execute-Upgrade -anyFailuresInImport ([ref]$anyFailuresInImport) -CRMConn $CRMConn -SolutionFolder $SolutionFolder -EnvironmentName $EnvironmentName `
                        -PreUpgrade $PreUpgrade -PostUpgrade $PostUpgrade -patchDeploy $patchDeploy -RunLocally $RunLocally
    }
}

function Execute-PreUpgrade{
    Param(
        [ref]$anyFailuresInImport,
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $upgradeSolutionName,
        [string] [Parameter(Mandatory = $true)] $solutionName,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    Write-PPDOMessage "Checking to make sure there is no existing $upgradeSolutionName solution" -Type command -RunLocally $RunLocally
    $ugSolution = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator like -FilterValue "$upgradeSolutionName" -Fields uniquename
    if ($ugSolution.CrmRecords.Count -gt 0) {
        Write-PPDOMessage "Found holding solution $($ugSolution.CrmRecords[0].uniquename), applying upgrade" -Type warning -RunLocally $RunLocally

        Write-Host "Applying Upgrade to Solution $solutionName"
        $Retrycount = 0;
        $upgradeSuccess = $false;
        do {
            try {
                & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution upgrade --solution-name $solutionName --async 3>&1 | Tee-Object -Variable pacOutput
                if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
                if ($pacOutput -match "FAILURE:") { Throw $pacOutput -match "FAILURE:" }
                $upgradeSuccess = $true;
            }
            catch {
                if (($pacOutput -match "Uninstall") -or ($pacOutput -match "PublishAll")) {
                    Write-PPDOMessage "Waiting for another Solution Operation to complete" -Type group -RunLocally $RunLocally
                    Start-Sleep -Seconds 30   
                }    
                else {
                    $Retrycount = 50
                }                                                          
                $Retrycount = $Retrycount + 1
                if ($Retrycount -gt 50) {
                    $anyFailuresInImport.Value = $true;
                    Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                    exit 1
                    break;
                }                                 
            }
        } until ($upgradeSuccess -eq $true)
    }
}

function Execute-Upgrade {
    Param(
        [ref]$anyFailuresInImport,
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $SolutionFolder,
        [string] [Parameter(Mandatory = $true)] $EnvironmentName,
        [bool] [Parameter(Mandatory = $true)] $PreUpgrade,
        [bool] [Parameter(Mandatory = $true)] $PostUpgrade,
        [bool] [Parameter(Mandatory = $true)] $patchDeploy,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    # PRE UPGRADE
    if ($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 $EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
        }
        else {
            Write-Host "Deployment PreUpgrade step not registered to excecute"
        }
    }

    Write-Host "Applying Upgrade to Solution $PSolution"
    $Retrycount = 0;
    $upgradeSuccess = $false;
    do {
        try {
            & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe solution upgrade --solution-name $PSolution --async --max-async-wait-time 120 3>&1 | Tee-Object -Variable pacOutput
            if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" }
            if ($pacOutput -match "FAILURE:") { Throw $pacOutput -match "FAILURE:" }
            $upgradeSuccess = $true;
        }
        catch {
            if (($pacOutput -match "_Upgrade' is not found in the target Dataverse organization")) {
                Write-PPDOMessage "Upgrade completed outside of this operation" -Type group -RunLocally $RunLocally
                $upgradeSuccess = $true;
            } 
            if (($pacOutput -match "Uninstall") -or ($pacOutput -match "PublishAll")) {
                Write-PPDOMessage "Waiting for another Solution Operation to complete" -Type group -RunLocally $RunLocally
                Start-Sleep -Seconds 30   
            }     
            else {
                $Retrycount = 50
            }                                                          
            $Retrycount = $Retrycount + 1
            if ($Retrycount -gt 50) {
                $anyFailuresInImport.Value = $true;
                Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                exit 1
                break;
            }                                 
        }
    } until ($upgradeSuccess -eq $true)

    # Post UPGRADE
    if ($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 $EnvironmentName -Path "$PipelinePath\$SolutionFolder\"
        }
        else {
            Write-Host "Deployment PostUpgrade step not registered to excecute"
        }
    }
}

function Execute-FixConnectionReferences {
    Param(
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $SolutionName,
        [string] [Parameter(Mandatory = $true)] $EnvId,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    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 $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 |  ForEach-Object {
        #Where-Object { $null -eq $_.connectionid } |
        $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 | Select-Object -ExpandProperty Statuses -Property ConnectionName, DisplayName, ConnectorName, CreatedBy, CreatedTime | Where-Object { ($_.status -eq "Connected") -and ($_.ConnectorName -eq $connectionType) } | Sort-Object -Property CreatedTime
        #| 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
        }
                        
    }
}

function Execute-FlowActivation {
    Param(
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $PipelinePath,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )

    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 {
                Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally 
            }
                                
        }
        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 = $_
                try {    
                    $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
                        if ($null -ne $connRefToUse) {
                            Write-Host "---- Connection Ref Details ----"
                            Write-Host "Connectiod ID : " $connRefToUse.ConnectionId 
                            Write-Host $connRefToUse
                            Write-Host "--------------------------------"
                            $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId    
                        } 
                        else {
                            Write-PPDOMessage "There was an error getting a Connection to use, 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
                            $ErrorCount++
                        }                                           
                        # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection
                        if ($null -ne $connection) {
                            Write-Host "---- Connection Details -----"
                            Write-Host "Connection UserID : " $connection[0].CreatedBy.id
                            Write-Host $connection[0]
                            Write-Host "-----------------------------"
                            $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id
                        }                
                        else {
                            Write-PPDOMessage "There was an error getting the owner of the Connection to use, 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
                            $ErrorCount++
                        }                            
                        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    
                                    Write-Host "...Activated" -ForegroundColor Green
                                }
                                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++    
                                    }
        
        
                                }
                                                    
                            }
        
                        }                                          
                    }
                }
                catch {
                    #add catch incase the Get-CrmRecord returns null exception
                    Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally
                }
            }  
        }
        ########################## - Retry Flow
        $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""
            }"

    }
}

function Execute-ImportReferenceData {
    Param(
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $PipelinePath,
        [string] [Parameter(Mandatory = $true)] $SolutionFolder,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false,
        [bool] [Parameter(Mandatory = $false)] $allSolutionsSuccessfullyImported = $true,
        [bool] [Parameter(Mandatory = $false)] $skipDeployData = $false
    )

    if ($Deploy.DeployData -eq $true -and $allSolutionsSuccessfullyImported -eq $true -and $skipDeployData -eq $false) {                            
        #Connect-Cli -DeployServerUrl $DeployServerUrl -UserName $UserName -Password $Password -TenantId $CRMConn.TenantId -UseClientSecret $UseClientSecret -EnvironmentName $EnvironmentName

        Write-PPDOMessage "Importing reference Data for $PSolution..." -Type group -RunLocally $RunLocally
        try {
            if (Test-Path -Path $PipelinePath\$SolutionFolder\ReferenceData\Extracted) {
                Write-PPDOMessage "Extracted Data Found... packaging for Import"
                Add-7zip $PipelinePath\$SolutionFolder\ReferenceData\Extracted\*.* $PipelinePath\$SolutionFolder\ReferenceData\data.zip
                #Compress-Archive -Path $PipelinePath\$SolutionFolder\ReferenceData\Extracted\*.* -DestinationPath $PipelinePath\$SolutionFolder\ReferenceData\data.zip -Force
            }
            if (Test-Path -Path $PipelinePath\$SolutionFolder\ReferenceData\data.zip) {
                Write-PPDOMessage "Config data.zip found, importing now."
                if ($Deploy.LegacyDataTool) {
                    Write-Host "Importing Data using Legacy Data Tool"
                    If ($VerboseLogging) {
                        Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$SolutionFolder\ReferenceData\data.zip -ConcurrentThreads 5 -Verbose    
                    }
                    else {
                        Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$SolutionFolder\ReferenceData\data.zip -ConcurrentThreads 5 
                    }
                }
                else {
                    Write-Host "Importing Data using PAC Data"
                    & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe data import --data $PipelinePath\$SolutionFolder\ReferenceData\data.zip 3>&1 | Tee-Object -Variable pacOutput
                }
            }
            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
}

function Execute-SolutionPostAction {
    Param(
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $PipelinePath,
        [string] [Parameter(Mandatory = $true)] $SolutionFolder,
        [bool] [Parameter(Mandatory = $false)] $allSolutionsSuccessfullyImported = $true,
        [bool] [Parameter(Mandatory = $false)] $skipDeploy = $false,
        [bool] [Parameter(Mandatory = $false)] $patchDeploy = $false,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )
    
    if (!$patchDeploy -and !$skipDeploy) {

        ########################### POST ACTION
        if ($Deploy.PostAction -eq $true -and $allSolutionsSuccessfullyImported -eq $true -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
            }
        }
    }
}

function Execute-EnvPostAction {
    Param(
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn,
        [string] [Parameter(Mandatory = $true)] $DeployServerUrl,
        [string] [Parameter(Mandatory = $true)] $PipelinePath,
        [bool] [Parameter(Mandatory = $false)] $RunLocally = $false
    )

    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
    }
}