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 ) if ($UseClientSecret) { Write-Host "Using Service Principal" Write-Host "Connecting to PAC CLI" & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --name $EnvironmentName --url $DeployServerUrl --tenant $CRMConn.TenantId --applicationId $UserName --clientSecret $Password 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-PPDOMessage Write-Host "Connecting to PAC CLI" if ([string]::IsNullOrEmpty($Password)) { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --name $EnvironmentName --url $DeployServerUrl --tenant $CRMConn.TenantId --username $UserName --password $Password Write-Host "Setting Add-PowerAppsAccount" Add-PowerAppsAccount -Username $UserName } else { $ssPassword = ConvertTo-SecureString $Password -AsPlainText -Force & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --name $EnvironmentName --url $DeployServerUrl --tenant $CRMConn.TenantId --username $UserName --password $ssPassword Write-Host "Setting Add-PowerAppsAccount" Add-PowerAppsAccount -Username $UserName -Password $ssPassword } } & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe org who } 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 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 Connect-Cli -DeployServerUrl $DeployServerUrl -UserName $UserName -Password $Password -TenantId $CRMConn.TenantId -UseClientSecret $UseClientSecret -EnvironmentName $EnvironmentName #region "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 } #endregion 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 $versionFile = "$($PSolution).version" Write-PPDOMessage "Preparing Deployment for $PSolution" -Type group -RunLocally $RunLocally Write-Host "Deployment step manifest - $Deploy" Write-Host "" Write-Host "Getting Solutions & Versions to be Deployed..." try { $solutionsToDeploy = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile | ConvertFrom-Json } catch { #Legacy Solution Packaging Support $solutionVersion = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile $solutionsToDeploy = @([ordered]@{SolutionName = $package.SolutionName; Version = $solutionVersion ; }) } $skipPatch = $false #region "SolutionLoop" $solutionsToDeploy | ForEach-Object { $deployAsNew = $false $skipDeploy = $false $anyFailuresInImport = $false; $patchDeploy = $false $PSolution = $_.SolutionName $deployingVersion = $_.Version $legacyFolders = $false if (Test-Path $PipelinePath\$SolutionFolder\pac_$($PSolution)) { $packageFolder = "pac_$($PSolution)\$($PSolution)\src" } else { $packageFolder = "dataverse_$($PSolution)" $legacyFolders = $true Write-Host "Using Legacy Folders" } if ($PSolution.contains("_Patch")) { $patchDeploy = $true } #region 'Preparing Deployment' Write-PPDOMessage "Preparing $PSolution Solution as $($Deploy.DeploymentType)" -Type section -RunLocally $RunLocally $fileToPack = "$($Deploy.EnvironmentName)_$($PSolution)_$($Deploy.DeploymentType).zip" Write-PPDOMessage "Packing Solution $PSolution" -Type command -RunLocally $RunLocally if ($Deploy.DeploymentType.ToLower() -eq "unmanaged") { try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Unmanaged --processCanvasApps 3>&1 | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } } catch { $anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } else { try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Managed --useUnmanagedFileForMissingManaged --processCanvasApps | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } } catch { $anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } Write-PPDOMessage "Importing package" -Type section -RunLocally $RunLocally try { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $error.Clear() Write-PPDOMessage "Deploying $PSolution as $($Deploy.DeploymentType) to - $EnvironmentName" -Type section -RunLocally $RunLocally if (!$patchDeploy) { Write-PPDOMessage "Checking to make sure there is no existing $($package.SolutionName)_Upgrade solution" -Type command -RunLocally $RunLocally $ugSolution = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator like -FilterValue "$($package.SolutionName)_Upgrade" -Fields uniquename if ($ugSolution.CrmRecords.Count -gt 0) { Write-PPDOMessage "Found holding solution $($ugSolution.CrmRecords[0].uniquename), removing it" -Type warning -RunLocally $RunLocally Write-Host "Applying Upgrade to Solution" try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution upgrade --solution-name $PSolution --async 3>&1 | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } } catch { #$anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type warning -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 $PSolution $Solution = $SolutionQuery.CrmRecords[0] Write-Host $Solution.uniquename " - " $Solution.version if (!$Solution) { $deployAsHolding = $false; $deployAsNew = $true; if (!$skipPatch) { Write-Host "Solution not found in Target, Importing as New" } $SolutionVersion = [version]"0.0.0.0" } else { $SolutionVersion = $Solution.version Write-Host "Found: $SolutionVersion in $EnvironmentName" if ($null -ne $Deploy.DeployAsHolding -and !$patchDeploy) { [bool]$deployAsHolding = [System.Convert]::ToBoolean($Deploy.DeployAsHolding) } else { $deployAsHolding = $false } } Write-PPDOMessage "Version to be deployed : $deployingVersion" -Type command -RunLocally $RunLocally Write-Host "skipPatch : $skipPatch" Write-Host "patchDeploy : $patchDeploy" [version]$depVersion = $deployingVersion [version]$solVersion = $solutionVersion if ($depVersion -le $solVersion) { $skipDeploy = $true; Write-PPDOMessage "Skipping Deployment as Target has same or newer" -Type warning -RunLocally $RunLocally } if ($depVersion -ne $solVersion -and !$patchDeploy -and !$deployAsNew) { $skipPatch = $true; Write-PPDOMessage "Setting skipPatch to true as parent solutions don't match" -Type warning -RunLocally $RunLocally } if ($skipPatch -and $patchDeploy) { $skipDeploy = $true } ########################## IMPORT if (!$skipDeploy) { # Powerapps Solution Checker if ($Deploy.PowerAppsChecker -eq $true -and $UseClientSecret -eq $true) { Write-PPDOMessage "Running PowerApps Solution Checker for $PSolution" -Type command -RunLocally $RunLocally try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution check --path $PipelinePath\$SolutionFolder\$fileToPack --geo Australia 3>&1 | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } } catch { $anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } #need to add support for Rule overide } 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 } #region "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 } } #endregion $activatePlugIns = $true; $overwriteUnManagedCustomizations = $false; $skipDependancyOnProductUpdateCheckOnInstall = $true; if ($Deploy.OverwriteUnmanagedCustomisations -eq $true) { $overwriteUnManagedCustomizations = $true } Write-PPDOMessage "Initiating Import and deployment to $($DeployServerUrl)" -Type section -RunLocally $RunLocally Write-PPDOMessage "Import as Holding solution : $($deployAsHolding)" -Type command -RunLocally $RunLocally $Retrycount = 0; $importSuccess = $false; do{ try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution import --path $PipelinePath\$SolutionFolder\$fileToPack --activate-plugins $activatePlugIns --force-overwrite $overwriteUnManagedCustomizations --skip-dependency-check $skipDependancyOnProductUpdateCheckOnInstall --async --import-as-holding $deployAsHolding --max-async-wait-time 120 3>&1 | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } $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 } $Retrycount = $Retrycount + 1 if ($Retrycount -gt 50) { $anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } } until ($importSuccess -eq $true) # UPGRADE if ($deployAsHolding -eq $true -and $anyFailuresInImport -eq $false) { #region "PRE UPGRADE" if ($Deploy.PreUpgrade -eq $true -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1) { Write-PPDOMessage "Execute Pre Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-Host "Deployment PreUpgrade step not registered to excecute" } } #endregion Write-Host "Applying Upgrade to Solution" try { & $env:APPDATA\Microsoft.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:" } } catch { $anyFailuresInImport = $true; Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } #region "Post UPGRADE" if ($Deploy.PostUpgrade -eq $true -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1) { Write-PPDOMessage "Execute Post Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-Host "Deployment PostUpgrade step not registered to excecute" } } #endregion } if (!$patchDeploy) { #region "POST ACTION" if ($Deploy.PostAction -eq $true -and $anyFailuresInImport -eq $false -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1) { Write-PPDOMessage "Execute Post Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-PPDOMessage "Deployment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally } } #endregion } #region 'Portal CLI' if ($null -ne $package.Portal -and $anyFailuresInImport -eq $false) { Connect-Cli -DeployServerUrl $DeployServerUrl -UserName $UserName -Password $Password -TenantId $CRMConn.TenantId -UseClientSecret $UseClientSecret -EnvironmentName $EnvironmentName Write-PPDOMessage "Deploying Portal configuration via PAPORTAL CLI" Write-PPDOMessage "Deployment Profile - $($Deploy.EnvironmentName)" Write-PPDOMessage "Locating Portal Source $($package.Portal) from $PipelinePath\$SolutionFolder\Deployment\" try { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe paportal upload --path "$PipelinePath\$SolutionFolder\Deployment\$($package.Portal)" --deploymentProfile "$($Deploy.EnvironmentName)" 3>&1 | Tee-Object -Variable pacOutput if ($pacOutput -match "Error:") { Throw $pacOutput -match "Error:" } } catch { Write-PPDOMessage "Powerapps PAC PAPORTAL CLI" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally } } #endregion #region 'Reference Data' # DATA CONFIGURATION if ($Deploy.DeployData -eq $true -and $anyFailuresInImport -eq $false) { Connect-Cli -DeployServerUrl $DeployServerUrl -UserName $UserName -Password $Password -TenantId $CRMConn.TenantId -UseClientSecret $UseClientSecret -EnvironmentName $EnvironmentName Write-PPDOMessage "Importing reference Data ..." -Type group -RunLocally $RunLocally try { if (Test-Path -Path $PipelinePath\$SolutionFolder\ReferenceData\data.zip) { Write-PPDOMessage "Config data.zip found, importing now." Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$SolutionFolder\ReferenceData\data.zip -ConcurrentThreads 5 -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 $ProgressPreference = "Continue" [int]$elapsedTime = $stopwatch.Elapsed.TotalMinutes $stopwatch.Stop() Write-PPDOMessage "Import Complete in $($elapsedTime) minutes" -Type section -RunLocally $RunLocally } } catch { Write-PPDOMessage "Skipping $PSolution due to Solution import error" -Type section -RunLocally $RunLocally Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally } if (!$skipDeploy -or $Deploy.Flows.AlwaysTryActivate -eq $true) { $FlowsToRetry = @() Write-Host "Establishing Connection References and Activating Flows" -ForegroundColor Green $ProgressPreference = "SilentlyContinue" # Activate Flows and Establish Connection References Write-Host "Getting Environment Id" $orgs = Get-CrmRecords -conn $CRMConn -EntityLogicalName organization if ($orgs.Count -gt 0) { $orgId = $orgs.CrmRecords[0].organizationid $Environment = Get-AdminPowerAppEnvironment | Where-Object OrganizationId -eq $orgId.Guid $EnvId = $Environment.EnvironmentName Write-Host "Environment Id - $EnvId" Write-Host "Checking if there are Connections References in the Solution that Need to be Wired Up" $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefs | 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 } } Write-Host "Checking if there are Flows that need to be Activated" if ($Deploy.Flows.ActivateFlows -eq $true) { if ($Deploy.Flows.OverrideFile) { Write-Host "Using $($Deploy.Flows.OverrideFile) for Flow Activation" try { $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\$($Deploy.Flows.OverrideFile) -ErrorAction SilentlyContinue | ConvertFrom-Json } catch { } } else { Write-Host "Using Flows_Default.json for Flow Activation" try { $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\Flows_Default.json -ErrorAction SilentlyContinue | ConvertFrom-Json } catch { $FlowsToActivate = $null } } Write-Host "There are $($FlowsToActivate.Count) Flows that need activating" $ErrorCount = 0 if ($FlowsToActivate.Count -gt 0) { $FlowsToActivate | ForEach-Object { $FlowStore = $_ $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser if ($systemuserResult.Count -gt 0) { $systemUserId = $systemuserResult.CrmRecords[0].systemuserid #Activate the workflow using the owner. if ($workflow.statecode -ne "Activated") { $impersonationConn = $CRMConn $impersonationCallerId = $systemUserId $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId Write-PPDOMessage "Enabling Flow '$($workflow.name)'" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ if ($_.ToString().Contains("ChildFlowNeverPublished")) { $FlowsToRetry += $FlowStore } else { $ErrorCount++ } } } } Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true } else { Write-Host "Checking if '$($workflow.name)' needs Activating..." $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue 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++ } } } } } } } $FlowsToRetry | ForEach-Object { Write-Host Write-Host "Retrying Flows that failed due to Child Flows" -ForegroundColor Green $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser if ($systemuserResult.Count -gt 0) { $systemUserId = $systemuserResult.CrmRecords[0].systemuserid #Activate the workflow using the owner. if ($workflow.statecode -ne "Activated") { $impersonationConn = $CRMConn $impersonationCallerId = $systemUserId $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId Write-PPDOMessage "Enabling Flow '$($workflow.name)'" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ $ErrorCount++ } } } Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true } else { Write-Host "Checking if '$($workflow.name)' needs Activating..." $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id if ($systemusers.Count -gt 0) { # Impersonate the Dataverse systemuser that created the connection when updating the connection reference $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid if ($workflow.statecode -ne "Activated") { Write-PPDOMessage "Enabling Flow '$($workflow.name)' as Owner of Connection Reference" -Type command -RunLocally $RunLocally $impersonationConn = $CRMConn $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ $ErrorCount++ } } } } } if ($Deploy.Flows.FailonError -eq $true -and $ErrorCount -gt 0) { Write-PPDOMessage "There were $ErrorCount Flow activation errors and FailonError is set to True... exiting." -Type error -RunLocally $RunLocally -LogError $true exit 1 } } else { Write-Host @" No Flows were specified for activation. If you wish to include flows for activation, please add the following in deployPackages.json "Flows": { "ActivateFlows": "true", "OverrideFile" : "", "FailonError" : "false" } "@ } } } Write-PPDOMessage -Type endgroup -RunLocally $RunLocally #endregion } #endregion } else { Write-PPDOMessage "$($package.SolutionName) is not configured for deployment to $env:ENVIRONMENT_NAME in deployPackages.json" -Type warning -RunLocally $RunLocally -LogWarning $true } } #region "ENVIRONMENT POST ACTION" if ($null -ne $EnvConfig -and $EnvConfig.PostAction -eq $true) { Write-PPDOMessage "Execute Environment Post Action" -Type section -RunLocally $RunLocally . "$PipelinePath\Common\Environments\Scripts\PostAction.ps1" -Conn $CRMConn -PipelinePath $PipelinePath -EnvironmentName $EnvConfig.EnvironmentName -EnvironmentUrl $DeployServerUrl $EnvConfig.PostFunctions | ForEach-Object { & $_ -Conn $CRMConn } Write-PPDOMessage "Environment Post Action Complete" -Type command -RunLocally $RunLocally } else { Write-PPDOMessage "Environment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally } #endregion } Write-Host Environment $EnvironmentName Import-Package if ($RunLocally) { az account clear } } |