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" | 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) {
            $skipDeploy = $false 
            $anyFailuresInImport = $false; 

            $Deploy = $package.DeployTo | Where-Object { $_.EnvironmentName -eq $EnvironmentName }
            if ($null -ne $Deploy) {
                            #region 'Preparing Deployment'
                $PSolution = $package.SolutionName
                Write-PPDOMessage "Preparing Deployment for $PSolution" -Type group -RunLocally $RunLocally
                Write-Host "Deployment step manifest - $Deploy"
                Write-PPDOMessage "Preparing $PSolution Solution as $($Deploy.DeploymentType)" -Type section -RunLocally $RunLocally

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

                Write-PPDOMessage "Packing Solution $PSolution" -Type command -RunLocally $RunLocally
                #Checking for Canvas App
                $canvasApps = Get-ChildItem -Path $PipelinePath\$PSolution\$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\$PSolution\$packageFolder -z $PipelinePath\$PSolution\$fileToPack -p Unmanaged
                }
                else {
                    & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$PSolution\$packageFolder -z $PipelinePath\$PSolution\$fileToPack -p Managed -same
                }         
               
                Write-PPDOMessage "Importing package" -Type section -RunLocally $RunLocally
                
                try {
                    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    $error.Clear()
                    Write-PPDOMessage "Deploying $($package.SolutionName) as $($Deploy.DeploymentType) to - $EnvironmentName" -Type section -RunLocally $RunLocally
                    #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 $($package.SolutionName)
                    $Solution = $SolutionQuery.CrmRecords[0]

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

                        if ($null -ne $Deploy.DeployAsHolding) {
                            [bool]$deployAsHolding = [System.Convert]::ToBoolean($Deploy.DeployAsHolding)
                        }
                        else {
                            $deployAsHolding = $false
                        }
                    }
                        
                    Write-Host "Getting Version to be Deployed..."
                    $deployingVersion = Get-Content -Path $PipelinePath\$PSolution\$PSolution.version
                    Write-PPDOMessage "Version to be deployed : $deployingVersion" -Type command -RunLocally $RunLocally
                    if ($deployingVersion -le $SolutionVersion) { $skipDeploy = $true; Write-PPDOMessage "Skipping Deployment as Target has same or newer" -Type warning -RunLocally $RunLocally }
                        
                    ########################## 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\$PSolution\$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) {
                            if (Test-Path -Path $PipelinePath\$PSolution\Scripts\PreAction.ps1) {
                                Write-PPDOMessage "Execute Pre Action from $PipelinePath\$PSolution\Scripts" -Type section -RunLocally $RunLocally
                                . $PipelinePath\$PSolution\Scripts\PreAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$PSolution\"
                            }
                            else {
                                Write-Host "Deployment PreAction step not registered to excecute"
                            }
                        }

                        $activatePlugIns = $true;
                        $overwriteUnManagedCustomizations = $true;
                        $skipDependancyOnProductUpdateCheckOnInstall = $true;
                        $isInternalUpgrade = $false;                   

                        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\$PSolution\$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) {
                                if (Test-Path -Path $PipelinePath\$PSolution\Scripts\PreUpgrade.ps1) {
                                    Write-PPDOMessage "Execute Pre Upgrade from $PipelinePath\$PSolution\Scripts" -Type section -RunLocally $RunLocally
                                    . $PipelinePath\$PSolution\Scripts\PreUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$PSolution\"
                                }
                                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) {
                                            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) {
                                if (Test-Path -Path $PipelinePath\$PSolution\Scripts\PostUpgrade.ps1) {
                                    Write-PPDOMessage "Execute Post Upgrade from $PipelinePath\$PSolution\Scripts" -Type section -RunLocally $RunLocally
                                    . $PipelinePath\$PSolution\Scripts\PostUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$PSolution\"
                                }
                                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\$PSolution\ReferenceData\data.zip) {
                                    Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$PSolution\ReferenceData\data.zip -EnabledBatchMode -Verbose                    
                                }
                                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) {
                            if (Test-Path -Path $PipelinePath\$PSolution\Scripts\PostAction.ps1) {
                                Write-PPDOMessage "Execute Post Action from $PipelinePath\$PSolution\Scripts" -Type section -RunLocally $RunLocally
                                . $PipelinePath\$PSolution\Scripts\PostAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$PSolution\"
                            }
                            else {
                                Write-Host "Deployment PostAction step not registered to excecute"
                            }
                        }

                        $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 "A - $UserName"
                            Write-Host "C - $Password"
                            Write-Host "T - $($CRMConn.TenantId)"
                            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 "$PSolution"
                            $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 $conn -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"
                                    $FlowsToActivate = Get-Content -Path $PipelinePath\$PSolution\$($Deploy.Flows.OverrideFile) | ConvertFrom-Json
                                }
                                else {
                                    Write-Host "Using Flows_Default.json for Flow Activation"
                                    $FlowsToActivate = Get-Content -Path $PipelinePath\$PSolution\Flows_Default.json | ConvertFrom-Json -ErrorAction SilentlyContinue
                                }
                                Write-Host "There are $($FlowsToActivate.Count) Flows that need activating"
                                $FlowsToActivate | ForEach-Object {
                                    $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 $_
                                                }
                                            
                                            }    
                                        }
                                        Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true
                                    }
                                    else {
                                        Write-Host "Attempting to Activate Flow as Owner of Connection Reference"
                                        
                                        $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$PSolution"
                                        $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 $conn -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)" -Type command -RunLocally $RunLocally
                                                $impersonationConn = $CRMConn
                                                $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId 
                                                Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated
                                            }

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

                            }
                        }
                                      
                        $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
                }
                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-Host "Environment PostAction step not registered to excecute"
        }
    }
    Write-Host Environment $EnvironmentName
    Import-Package
}