Scripts/SolutionDeploy.ps1
# # SolutionDeploy.ps1 # function Write-PPDOMessage { [CmdletBinding()] param ( [Parameter()] [string] $Message, [Parameter()] [ValidateSet('group', 'warning', 'error', 'section', 'debug', 'command', 'endgroup')] [string] $Type, [Parameter()] [bool] $LogError = $false, [Parameter()] [bool] $LogWarning = $false, [Parameter()] [bool] $RunLocally = $false ) switch ($Type) { 'group' { if ($RunLocally) { Write-Host $Message -BackgroundColor Magenta } else { Write-Host "##[group]$Message" } } 'warning' { if ($RunLocally) { Write-Host $Message -ForegroundColor DarkYellow } else { if ($LogWarning) { Write-Host "##vso[task.logissue type=warning]$Message" } else { Write-Host "##[warning]$Message" } } } 'error' { if ($RunLocally) { Write-Host $Message -ForegroundColor Red } else { if ($LogError) { Write-Host "##vso[task.logissue type=error]$Message" } else { Write-Host "##[error]$Message" } } } 'section' { if ($RunLocally) { Write-Host $Message -ForegroundColor Green } else { Write-Host "##[section]$Message" } } 'debug' { if ($RunLocally) { Write-Host $Message -ForegroundColor Magenta } else { Write-Host "##[debug]$Message" } } 'command' { if ($RunLocally) { Write-Host $Message -ForegroundColor Blue } else { Write-Host "##[command]$Message" } } 'endgroup' { if ($RunLocally) { Write-Host ":END:" -BackgroundColor Magenta } else { Write-Host "##[endgroup]" } } Default { Write-Host $Message } } } function Start-DeploySolution { Param( [string] [Parameter(Mandatory = $true)] $DeployServerUrl, [string] [Parameter(Mandatory = $true)] $UserName, [string] [Parameter(Mandatory = $false)] $Password = "", [string] [Parameter(Mandatory = $true)] $PipelinePath, [bool] [Parameter(Mandatory = $false)] $UseClientSecret = $false, [bool] [Parameter(Mandatory = $false)] $RunLocally = $false, [string] [Parameter(Mandatory = $false)] $EnvironmentName = $env:ENVIRONMENT_NAME ) ######################## SETUP . "$PSScriptRoot\..\Private\_SetupTools.ps1" Write-Host "Using Microsoft.PowerPlatform.DevOps version :" (Get-Module -Name Microsoft.PowerPlatform.DevOps -ListAvailable).Version #region "Dependencies" Write-PPDOMessage -Message "Installing Dependencies" -Type group -RunLocally $RunLocally Install-PAC if (!$RunLocally) { Install-ConfigMigrationModule Install-XrmModule Install-PowerAppsCheckerModule Install-PowerAppsAdmin } else { Write-Host "Preparing local run" } Write-PPDOMessage -Type endgroup -RunLocally $RunLocally #endregion function Import-Package { if ($UseClientSecret) { [string]$CrmConnectionString = "AuthType=ClientSecret;Url=$DeployServerUrl;ClientId=$UserName;ClientSecret=$Password" } else { [string]$CrmConnectionString = "AuthType=OAuth;Username=$UserName;Password=$Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;LoginPrompt=never" } $Packages = Get-Content "$PipelinePath\deployPackages.json" -Encoding UTF8 | ConvertFrom-Json #handle Environments file should it be missing. Same for DeployPackages and SolutionChecker files! $Environments = Get-Content "$PipelinePath\Environments.json" | ConvertFrom-Json $EnvConfig = $Environments | Where-Object { $_.EnvironmentName -eq $EnvironmentName } Write-PPDOMessage "Creating CRM connection" -Type section -RunLocally $RunLocally $CRMConn = Get-CrmConnection -ConnectionString $CrmConnectionString -Verbose #-MaxCrmConnectionTimeOutMinutes 10 #Set-CrmConnectionTimeout -conn $CRMConn -TimeoutInSeconds 600 if ($false -eq $CRMConn.IsReady) { Write-Host "An error occurred: " $CRMConn.LastCrmError Write-Host $CRMConn.LastCrmException.Message Write-Host $CRMConn.LastCrmException.Source Write-Host $CRMConn.LastCrmException.StackTrace throw "Could not establish connection with server" } #ENVIRONMENT PRE-ACTION if ($null -ne $EnvConfig -and $EnvConfig.PreAction -eq $true) { Write-PPDOMessage "Execute Environment Pre Action" -Type section -RunLocally $RunLocally . "$PipelinePath\Common\Environments\Scripts\PreAction.ps1" -Conn $CRMConn -PipelinePath $PipelinePath -EnvironmentName $EnvConfig.EnvironmentName -EnvironmentUrl $DeployServerUrl $EnvConfig.PreFunctions | ForEach-Object { & $_ -Conn $CRMConn } Write-PPDOMessage "Environment Pre Action Complete" -Type command -RunLocally $RunLocally } else { Write-PPDOMessage "Environment Pre Action not registered to execute" -Type warning -RunLocally $RunLocally } foreach ($package in $Packages) { $Deploy = $package.DeployTo | Where-Object { $_.EnvironmentName -eq $EnvironmentName } if ($null -ne $Deploy) { $PSolution = $package.SolutionName $SolutionFolder = $package.SolutionName $versionFile = "$($PSolution).version" Write-PPDOMessage "Preparing Deployment for $PSolution" -Type group -RunLocally $RunLocally Write-Host "Deployment step manifest - $Deploy" Write-Host "" Write-Host "Getting Solutions & Versions to be Deployed..." try { $solutionsToDeploy = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile | ConvertFrom-Json } catch { #Legacy Solution Packaging Support $solutionVersion = Get-Content -Path $PipelinePath\$SolutionFolder\$versionFile $solutionsToDeploy = @([ordered]@{SolutionName = $package.SolutionName; Version = $solutionVersion ; }) } $skipPatch = $false $solutionsToDeploy | ForEach-Object { $deployAsNew = $false $skipDeploy = $false $anyFailuresInImport = $false; $patchDeploy = $false $PSolution = $_.SolutionName $deployingVersion = $_.Version $packageFolder = "dataverse_$($PSolution)" if ($PSolution.contains("_Patch")) { $patchDeploy = $true } #region 'Preparing Deployment' Write-PPDOMessage "Preparing $PSolution Solution as $($Deploy.DeploymentType)" -Type section -RunLocally $RunLocally $fileToPack = "$($Deploy.EnvironmentName)_$($PSolution)_$($Deploy.DeploymentType).zip" Write-PPDOMessage "Packing Solution $PSolution" -Type command -RunLocally $RunLocally #Checking for Canvas App $canvasApps = Get-ChildItem -Path $PipelinePath\$SolutionFolder\$packageFolder\CanvasApps\ -Directory -ErrorAction SilentlyContinue # pack canvas apps $canvasApps | ForEach-Object { Write-Host "Packing Canvas App $($_.name)"; & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe canvas pack --sources $_.FullName --msapp "$($_.FullName).msapp" Remove-Item $_.FullName -Recurse -ErrorAction SilentlyContinue } if ($Deploy.DeploymentType.ToLower() -eq "unmanaged") { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Unmanaged } else { & $env:APPDATA\Microsoft.PowerPlatform.DevOps\PACTools\tools\pac.exe solution pack -f $PipelinePath\$SolutionFolder\$packageFolder -z $PipelinePath\$SolutionFolder\$fileToPack -p Managed -same } Write-PPDOMessage "Importing package" -Type section -RunLocally $RunLocally try { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $error.Clear() Write-PPDOMessage "Deploying $PSolution as $($Deploy.DeploymentType) to - $EnvironmentName" -Type section -RunLocally $RunLocally if (!$patchDeploy) { Write-PPDOMessage "Checking to make sure there is no existing $($package.SolutionName)_Upgrade solution" -Type command -RunLocally $RunLocally $ugSolution = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator like -FilterValue "$($package.SolutionName)_Upgrade" -Fields uniquename if ($ugSolution.CrmRecords.Count -gt 0) { Write-PPDOMessage "Found holding solution $($ugSolution.CrmRecords[0].uniquename), removing it" -Type warning -RunLocally $RunLocally Write-Host "Applying Upgrade to Solution" $promoteRequestId = $CRMConn.DeleteAndPromoteSolutionAsync($PSolution); if (($null -eq $promoteRequestId) -or ($promoteRequestId -eq [Guid]::Empty)) { Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true $anyFailuresInImport = $true; } else { Write-Host Async Operation ID: $promoteRequestId do { try { Start-Sleep -Seconds 5 $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($promoteRequestId) -Fields name, statuscode, friendlymessage, completedon, errorcode [int]$statuscode = $operation.statuscode_Property.value.Value; if ($statuscode -le 30) { Write-Host "Polling Promotion status for Solution: $($PSolution) : $($operation.statuscode)" $anyFailuresInImport = $false; } elseif ($statuscode -eq 31 -or $statuscode -eq 32) { if ($operation.friendlymessage -match "have an upgrade that is ready to be applied") { Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally } else { Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "Delete and Promote Failed: #($operation.statuscode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally exit 1 $anyFailuresInImport = $true; } } } catch { Write-Host "Retrying Polling Upgrade status" $Retrycount = $Retrycount + 1 if ($Retrycount -gt 3) { $statuscode = 32; $anyFailuresInImport = $true; Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } }until($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32) } } } #Get Currently Deployed Solution Version Write-Host "Getting Current Solution Version from Target" $SolutionQuery = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -Fields 'solutionid', 'friendlyname', 'version', 'uniquename' -FilterAttribute uniquename -FilterOperator eq -FilterValue $PSolution $Solution = $SolutionQuery.CrmRecords[0] Write-Host $Solution.uniquename " - " $Solution.version if (!$Solution) { $deployAsHolding = $false; $deployAsNew = $true; if (!$skipPatch) { Write-Host "Solution not found in Target, Importing as New" } $SolutionVersion = [version]"0.0.0.0" } else { $SolutionVersion = $Solution.version Write-Host "Found: $SolutionVersion in $EnvironmentName" if ($null -ne $Deploy.DeployAsHolding -and !$patchDeploy) { [bool]$deployAsHolding = [System.Convert]::ToBoolean($Deploy.DeployAsHolding) } else { $deployAsHolding = $false } } Write-PPDOMessage "Version to be deployed : $deployingVersion" -Type command -RunLocally $RunLocally Write-Host "skipPatch : $skipPatch" Write-Host "patchDeploy : $patchDeploy" [version]$depVersion = $deployingVersion [version]$solVersion = $solutionVersion if ($depVersion -le $solVersion) { $skipDeploy = $true; Write-PPDOMessage "Skipping Deployment as Target has same or newer" -Type warning -RunLocally $RunLocally } if ($depVersion -ne $solVersion -and !$patchDeploy -and !$deployAsNew) { $skipPatch = $true; Write-PPDOMessage "Setting skipPatch to true as parent solutions don't match" -Type warning -RunLocally $RunLocally } if ($skipPatch -and $patchDeploy) { $skipDeploy = $true } ########################## IMPORT if (!$skipDeploy) { # Powerapps Solution Checker if ($Deploy.PowerAppsChecker -eq $true -and $UseClientSecret -eq $true) { Write-PPDOMessage "Running PowerApps Solution Checker for $PSolution" -Type command -RunLocally $RunLocally Start-SolutionChecker -PipelinePath $PipelinePath -SolutionPath $PipelinePath\$SolutionFolder\$fileToPack -SolutionName $PSolution -ClientId $UserName -ClientSecret $Password -TenantId "$($CRMConn.TenantId)" } else { Write-PPDOMessage "Powerapps Checker not configured. Add PowerAppsChecker: True as a property in the DeployTo section for your Solution" -Type warning -RunLocally $RunLocally -LogWarning $true } # PRE ACTION if ($Deploy.PreAction -eq $true -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PreAction.ps1) { Write-PPDOMessage "Execute Pre Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PreAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-PPDOMessage "Deployment PreAction step not registered to excecute" -Type warning -RunLocally $RunLocally } } $activatePlugIns = $true; $overwriteUnManagedCustomizations = $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\$SolutionFolder\$fileToPack", [ref]$importId, $activatePlugIns, $overwriteUnManagedCustomizations, $skipDependancyOnProductUpdateCheckOnInstall, $deployAsHolding, $isInternalUpgrade) Write-Host Async Operation ID: $result Write-Host Import Job ID: $importId $Retrycount = 0; # IMPORT do { try { Start-Sleep -Seconds 5 $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($result) -Fields name, statuscode, friendlymessage, completedon, errorcode [int]$statuscode = $operation.statuscode_Property.value.Value; if ($statuscode -le 30) { $job = Get-CrmRecord -conn $CRMConn -EntityLogicalName importjob -Id ($importId) -Fields progress Write-Host "Polling Import for Solution: $($PSolution) : $($operation.statuscode) - $($job.progress)%" $anyFailuresInImport = $false; } elseif ($statuscode -eq 31 -or $statuscode -eq 32) { Write-PPDOMessage "Unable to import solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "Import Failed: $($operation.statuscode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally exit 1 $anyFailuresInImport = $true; } } catch { Write-Host "Retrying Polling import status" $Retrycount = $Retrycount + 1 if ($Retrycount -gt 3) { $statuscode = 32; $anyFailuresInImport = $true; Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } } until ($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32) $Retrycount = 0; # UPGRADE if ($deployAsHolding -eq $true -and $anyFailuresInImport -eq $false) { # PRE UPGRADE if ($Deploy.PreUpgrade -eq $true -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1) { Write-PPDOMessage "Execute Pre Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PreUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-Host "Deployment PreUpgrade step not registered to excecute" } } Write-Host "Applying Upgrade to Solution" $promoteRequestId = $CRMConn.DeleteAndPromoteSolutionAsync($PSolution); if (($null -eq $promoteRequestId) -or ($promoteRequestId -eq [Guid]::Empty)) { Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true $anyFailuresInImport = $true; } else { Write-Host Async Operation ID: $promoteRequestId do { try { Start-Sleep -Seconds 5 $operation = Get-CrmRecord -conn $CRMConn -EntityLogicalName asyncoperation -Id ($promoteRequestId) -Fields name, statuscode, friendlymessage, completedon, errorcode [int]$statuscode = $operation.statuscode_Property.value.Value; if ($statuscode -le 30) { Write-Host "Polling Promotion status for Solution: $($PSolution) : $($operation.statuscode)" $anyFailuresInImport = $false; } elseif ($statuscode -eq 31 -or $statuscode -eq 32) { Write-PPDOMessage "Unable to promote or delete solution - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "Delete and Promote Failed: #($operation.statuscode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "Error Code: $($operation.errorcode)" -Type warning -RunLocally $RunLocally Write-PPDOMessage "$($operation.friendlymessage)" -Type warning -RunLocally $RunLocally exit 1 $anyFailuresInImport = $true; } } catch { Write-Host "Retrying Polling Upgrade status" $Retrycount = $Retrycount + 1 if ($Retrycount -gt 3) { $statuscode = 32; $anyFailuresInImport = $true; Write-PPDOMessage "Unable to poll status - please check Solution import history in https://make.powerapps.com/environments" -Type error -RunLocally $RunLocally -LogError $true Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally exit 1 break; } } }until($statuscode -eq 30 -or $statuscode -eq 31 -or $statuscode -eq 32) } # Post UPGRADE if ($Deploy.PostUpgrade -eq $true -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1) { Write-PPDOMessage "Execute Post Upgrade from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PostUpgrade.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-Host "Deployment PostUpgrade step not registered to excecute" } } } #region 'Reference Data' # DATA CONFIGURATION if ($Deploy.DeployData -eq $true -and $anyFailuresInImport -eq $false) { Write-PPDOMessage "Importing reference Data ..." -Type group -RunLocally $RunLocally try { if (Test-Path -Path $PipelinePath\$SolutionFolder\ReferenceData\data.zip) { Import-CrmDataFile -CrmConnection $CRMConn -DataFile $PipelinePath\$SolutionFolder\ReferenceData\data.zip -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 -and !$patchDeploy) { if (Test-Path -Path $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1) { Write-PPDOMessage "Execute Post Action from $PipelinePath\$SolutionFolder\Scripts" -Type section -RunLocally $RunLocally . $PipelinePath\$SolutionFolder\Scripts\PostAction.ps1 -Conn $CRMConn -EnvironmentName $Deploy.EnvironmentName -Path "$PipelinePath\$SolutionFolder\" } else { Write-PPDOMessage "Deployment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally } } $ProgressPreference = "Continue" [int]$elapsedTime = $stopwatch.Elapsed.TotalMinutes $stopwatch.Stop() Write-PPDOMessage "Import Complete in $($elapsedTime) minutes" -Type section -RunLocally $RunLocally } } catch { Write-PPDOMessage "Skipping $PSolution due to Solution import error" -Type section -RunLocally $RunLocally Write-PPDOMessage "$($_.Exception.Message)" -Type error -RunLocally $RunLocally } } if (!$skipDeploy -or $Deploy.Flows.AlwaysTryActivate -eq $true) { $FlowsToRetry = @() Write-Host "Establishing Connection References and Activating Flows" -ForegroundColor Green $ProgressPreference = "SilentlyContinue" # Activate Flows and Establish Connection References Write-Host "Getting Environment Id" $orgs = Get-CrmRecords -conn $CRMConn -EntityLogicalName organization if ($orgs.Count -gt 0) { $orgId = $orgs.CrmRecords[0].organizationid if ($UseClientSecret) { Add-PowerAppsAccount -ApplicationId $UserName -ClientSecret $Password -TenantID $CRMConn.TenantId } else { Add-PowerAppsAccount -Username $UserName -Password $Password } $Environment = Get-AdminPowerAppEnvironment | Where-Object OrganizationId -eq $orgId.Guid $EnvId = $Environment.EnvironmentName Write-Host "Environment Id - $EnvId" Write-Host "Checking if there are Connections References in the Solution that Need to be Wired Up" $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefs | Where-Object { $null -eq $_.connectionid } | ForEach-Object { $connectionType = $_.connectorid.Replace("/providers/Microsoft.PowerApps/apis/", "") Write-Host "Found Connection Reference $($_.connectionreferencelogicalname) without a Connection to $connectionType" Write-Host "Getting Connections in Environment" $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId | Where-Object ConnectorName -eq $connectionType if ($connection) { # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id -Fields domainname if ($systemusers.Count -gt 0) { Write-Host "Impersonating the Owner of the Connection - $($systemusers.CrmRecords[0].domainname)" # Impersonate the Dataverse systemuser that created the connection when updating the connection reference $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid $impersonationConn = $CRMConn $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId Write-PPDOMessage "Setting Connection Reference to use $($connection[0].DisplayName)" -Type command -RunLocally $RunLocally Set-CrmRecord -conn $impersonationConn -EntityLogicalName $_.logicalname -Id $_.connectionreferenceid -Fields @{"connectionid" = $connection[0].ConnectionName } } } else { Write-PPDOMessage "No Connection has been set up of type $connectionType, some of your Flows may not Activate succesfully" -Type warning -RunLocally $RunLocally -LogWarning $true } } Write-Host "Checking if there are Flows that need to be Activated" if ($Deploy.Flows.ActivateFlows -eq $true) { if ($Deploy.Flows.OverrideFile) { Write-Host "Using $($Deploy.Flows.OverrideFile) for Flow Activation" try { $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\$($Deploy.Flows.OverrideFile) -ErrorAction SilentlyContinue | ConvertFrom-Json } catch { } } else { Write-Host "Using Flows_Default.json for Flow Activation" try { $FlowsToActivate = Get-Content -Path $PipelinePath\$SolutionFolder\Flows_Default.json -ErrorAction SilentlyContinue | ConvertFrom-Json } catch { $FlowsToActivate = $null } } Write-Host "There are $($FlowsToActivate.Count) Flows that need activating" $ErrorCount = 0 if ($FlowsToActivate.Count -gt 0) { $FlowsToActivate | ForEach-Object { $FlowStore = $_ $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser if ($systemuserResult.Count -gt 0) { $systemUserId = $systemuserResult.CrmRecords[0].systemuserid #Activate the workflow using the owner. if ($workflow.statecode -ne "Activated") { $impersonationConn = $CRMConn $impersonationCallerId = $systemUserId $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId Write-PPDOMessage "Enabling Flow $($workflow.name)" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ if ($_.ToString().Contains("ChildFlowNeverPublished")) { $FlowsToRetry += $FlowStore } else { $ErrorCount++ } } } } Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true } else { Write-Host "Checking if $($workflow.name) needs Activating..." $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id if ($systemusers.Count -gt 0) { # Impersonate the Dataverse systemuser that created the connection when updating the connection reference $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid if ($workflow.statecode -ne "Activated") { Write-PPDOMessage "Enabling Flow $($workflow.name) as Owner of Connection Reference" -Type command -RunLocally $RunLocally $impersonationConn = $CRMConn $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ if ($_.ToString().Contains("ChildFlowNeverPublished")) { $FlowsToRetry += $FlowStore } else { $ErrorCount++ } } } } } } } $FlowsToRetry | ForEach-Object { Write-Host Write-Host "Retrying Flows that failed due to Child Flows" -ForegroundColor Green $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName workflow -Id $_.FlowId -Fields clientdata, category, statecode, name if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to : $($_.ActivateAsUser), attempting to Active Flow as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser if ($systemuserResult.Count -gt 0) { $systemUserId = $systemuserResult.CrmRecords[0].systemuserid #Activate the workflow using the owner. if ($workflow.statecode -ne "Activated") { $impersonationConn = $CRMConn $impersonationCallerId = $systemUserId $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId Write-PPDOMessage "Enabling Flow $($workflow.name)" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ $ErrorCount++ } } } Write-PPDOMessage "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName)" -Type warning -RunLocally $RunLocally -LogWarning $true } else { Write-Host "Checking if $($workflow.name) needs Activating..." $solutions = Get-CrmRecords -conn $CRMConn -EntityLogicalName solution -FilterAttribute "uniquename" -FilterOperator "eq" -FilterValue "$($package.SolutionName)" $solutionId = $solutions.CrmRecords[0].solutionid $connRefs = (Get-CrmRecords -conn $CRMConn -EntityLogicalName connectionreference -FilterAttribute "solutionid" -FilterOperator eq -FilterValue $solutionid -Fields connectionreferencelogicalname, connectionid, connectorid, connectionreferenceid).CrmRecords $connRefToUse = $connRefs | Where-Object { $null -ne $_.connectionid } | Select-Object -First 1 -ErrorAction SilentlyContinue $connection = Get-AdminPowerAppConnection -EnvironmentName $EnvId -Filter $connRefToUse.ConnectionId # Get Dataverse systemuserid for the system user that maps to the aad user guid that created the connection $systemusers = Get-CrmRecords -conn $CRMConn -EntityLogicalName systemuser -FilterAttribute "azureactivedirectoryobjectid" -FilterOperator "eq" -FilterValue $connection[0].CreatedBy.id if ($systemusers.Count -gt 0) { # Impersonate the Dataverse systemuser that created the connection when updating the connection reference $impersonationCallerId = $systemusers.CrmRecords[0].systemuserid if ($workflow.statecode -ne "Activated") { Write-PPDOMessage "Enabling Flow $($workflow.name) as Owner of Connection Reference" -Type command -RunLocally $RunLocally $impersonationConn = $CRMConn $impersonationConn.OrganizationWebProxyClient.CallerId = $impersonationCallerId try { Set-CrmRecordState -conn $impersonationConn -EntityLogicalName workflow -Id $_.FlowId -StateCode Activated -StatusCode Activated } catch { Write-PPDOMessage "There was an error activating the Flow, please confirm that the user exists and that the appropriate connections have been created as this user in the Environment" -Type warning -RunLocally $RunLocally -LogWarning $true Write-Host $_ $ErrorCount++ } } } } } if ($Deploy.Flows.FailonError -eq $true -and $ErrorCount -gt 0) { Write-PPDOMessage "There were $ErrorCount Flow activation errors and FailonError is set to True... exiting." -Type error -RunLocally $RunLocally -LogError $true exit 1 } } else { Write-Host @" No Flows were specified for activation. If you wish to include flows for activation, please add the following in deployPackages.json "Flows": { "ActivateFlows": "true", "OverrideFile" : "", "FailonError" : "false" } "@ } } } Write-PPDOMessage -Type endgroup -RunLocally $RunLocally #endregion } else { Write-PPDOMessage "$($package.SolutionName) is not configured for deployment to $env:ENVIRONMENT_NAME in deployPackages.json" -Type warning -RunLocally $RunLocally -LogWarning $true } } #EXECUTE ENVIRONMENT POST ACTION if ($null -ne $EnvConfig -and $EnvConfig.PostAction -eq $true) { Write-PPDOMessage "Execute Environment Post Action" -Type section -RunLocally $RunLocally . "$PipelinePath\Common\Environments\Scripts\PostAction.ps1" -Conn $CRMConn -PipelinePath $PipelinePath -EnvironmentName $EnvConfig.EnvironmentName -EnvironmentUrl $DeployServerUrl $EnvConfig.PostFunctions | ForEach-Object { & $_ -Conn $CRMConn } Write-PPDOMessage "Environment Post Action Complete" -Type command -RunLocally $RunLocally } else { Write-PPDOMessage "Environment PostAction step not registered to excecute" -Type warning -RunLocally $RunLocally } } Write-Host Environment $EnvironmentName Import-Package } |