Private/PowerAutomateFlows.ps1
function Invoke-FlowAction { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn, [bool] [Parameter(Mandatory = $true)] $AnyImportSuccessful, [bool] [Parameter(Mandatory = $true)] $AlwaysTryActivate, [bool] [Parameter(Mandatory = $true)] $FailonError, [string] [Parameter(Mandatory = $true)] $SolutionName, [string] [Parameter(Mandatory = $true)] $SolutionFolder, [string] [Parameter(Mandatory = $true)] $PipelinePath, [bool] [Parameter(Mandatory = $false)] $RunLocally = $false, [System.Collections.Hashtable] [Parameter(Mandatory = $true)] $Deploy ) if ($AnyImportSuccessful -or $AlwaysTryActivate -eq $true) { $ProgressPreference = "SilentlyContinue" Write-Host "Fetching details for Power Automate flows" -ForegroundColor Green # set up connection for manipulating flows Write-Host "Getting Environment Id" $orgs = Get-CrmRecords -conn $CRMConn -EntityLogicalName organization -TopCount 1 -Fields "organizationid" if ($orgs.Count -gt 0) { $orgId = $orgs.CrmRecords[0].organizationid $Environment = Get-AdminPowerAppEnvironment | Where-Object OrganizationId -eq $orgId.Guid if ($null -eq $Environment) { # ppdo cannot access Power Apps Admin to get the environment details, notify the user $PermissionsErrorMessage = "To use this function, the service principal needs registered as a Management Application in Azure. You can do so when running PPDO locally." if ($FailonError -eq $true) { Write-PPDOMessage -Message $PermissionsErrorMessage -Type error -RunLocally $RunLocally -LogError $true } else { Write-PPDOMessage -Message $PermissionsErrorMessage -Type warning -RunLocally $RunLocally -LogWarning $true } } else { $EnvId = $Environment.EnvironmentName Write-Host "Environment Id - $EnvId" #check whether setting connection references is enabled if ($Deploy.ConnectionReferences.SetConnections -eq $true) { Write-Host "Configuring Connections for Power Automate flows" -ForegroundColor Green Invoke-SetConnectionReferences -CRMConn $CRMConn -SolutionName $SolutionName -EnvId $EnvId -FailOnError $Deploy.ConnectionReferences.FailOnError -RunLocally $RunLocally } else { Write-PPDOMessage -Message "Skipping setting Connection References per configuration in deployPackages.json" -type "warning" -LogWarning $true -RunLocally $RunLocally } # check whether activate flows is enabled if ($Deploy.Flows.ActivateFlows -eq $true) { Write-Host "Activating Power Automate flows" -ForegroundColor Green Invoke-FlowActivation -CRMConn $CRMConn -PipelinePath $PipelinePath -RunLocally $RunLocally -Deploy $Deploy -SolutionFolder $SolutionFolder -EnvId $EnvId -SolutionName $SolutionName } else { Write-PPDOMessage -Message "Skipping activating Flows per configuration in deployPackages.json" -type "warning" -LogWarning $true -RunLocally $RunLocally } } } else { $NoOrganizationsError = "There are no organization records in CRM, unable to set Connection References or activate flows." if ($FailonError -eq $true) { Write-PPDOMessage -Message $NoOrganizationsError -Type error -RunLocally $RunLocally -LogError $true } else { Write-PPDOMessage -Message $NoOrganizationsError -Type warning -RunLocally $RunLocally -LogWarning $true } } } } function Invoke-FlowActivation { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] [Parameter(Mandatory = $true)] $CRMConn, [string] [Parameter(Mandatory = $true)] $PipelinePath, [string] [Parameter(Mandatory = $true)] $SolutionFolder, [bool] [Parameter(Mandatory = $false)] $RunLocally = $false, [System.Collections.Hashtable] [Parameter(Mandatory = $true)] $Deploy, [string] [Parameter(Mandatory = $true)] $EnvId, [string] [Parameter(Mandatory = $true)] $SolutionName ) function Invoke-Activate { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] [Parameter(Mandatory = $true)] $CRMConn, [System.Object] [Parameter(Mandatory = $true)] $FlowsToActivate, [ref] [Parameter(Mandatory = $true)] $ErrorCount, [switch] $CatchChildFlowActivationErrors ) $FlowsToRetry = @() $FlowsToActivate | ForEach-Object { $FlowStore = $_ try { $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName "workflow" -Id $_.FlowId -Fields "statecode", "name" if ($workflow.statecode -ne "Activated") { Write-Host "Flow status is $($workflow.statecode), attempting to activate flow." $activationConnection = $CRMConn if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to: $($_.ActivateAsUser), attempting to activate flow as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName "systemuser" -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser -TopCount 1 -Fields "systemuserid" if ($systemuserResult.Count -gt 0) { $activationConnection.OrganizationWebProxyClient.CallerId = $systemuserResult.CrmRecords[0].systemuserid } else { Write-PPDOMessage -Message "$($_.Exception.Message)" -Type "error" -RunLocally $RunLocally Throw "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName), unable to impersonate them to activate flow $($workflow.name)" } } Write-PPDOMessage "Enabling Flow '$($workflow.name)'" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $activationConnection -EntityLogicalName "workflow" -Id $_.FlowId -StateCode "Activated" -StatusCode "Activated" Write-Host "...Activated" -ForegroundColor Green } catch { if ($_.ToString().Contains("ChildFlowNeverPublished") -and $CatchChildFlowActivationErrors) { $FlowsToRetry += $FlowStore } else { Throw $_ } } } } catch { Write-PPDOMessage "$($_.Exception.Message)" -Type "error" -RunLocally $RunLocally $ErrorCount.Value++ } } return $FlowsToRetry } # Use Override File if it is set if ($Deploy.Flows.OverrideFile) { $FlowsJSONFilePath = "$PipelinePath\$SolutionFolder\$($Deploy.Flows.OverrideFile)" } else { $FlowsJSONFilePath = "$PipelinePath\$SolutionFolder\Flows_Default.json" } Write-Host "Checking if there are Flows that need to be activated" if ($Deploy.Flows.ActivateFlows -ne $true) { Write-PPDOMessage -Message "Skipping flow activation, per 'ActivateFlows' flag being false in deployPackages.json." -RunLocally $RunLocally return } if (!(Test-Path -Path $FlowsJSONFilePath)) { Write-PPDOMessage -Message "Flow activation is enabled in deployPackages.json, but unable to find the Flows to activate at $FlowsJSONFilePath. Please add the file, or change deployPackages.json to skip activating flows." -RunLocally $RunLocally return } Write-PPDOMessage -Message "Using list of flows to activate at location '$FlowsJSONFilePath'." -RunLocally $RunLocally # Get list of flows to activate from JSON try { $FlowsToActivate = Get-Content -Path $FlowsJSONFilePath | ConvertFrom-Json } catch { Write-PPDOMessage -Message "$($_.Exception.Message)" -Type "error" -LogError $true -RunLocally $RunLocally Throw "An error occurred when trying to get list of flows to activate at location '$FlowsJSONFilePath'." } Write-PPDOMessage -Message "There are $($FlowsToActivate.Count) Flows to activate." -RunLocally $RunLocally if ($FlowsToActivate.Count -le 0) { return } $ErrorCount = 0 $FlowsToRetry = Invoke-Activate -CRMConn $CRMConn -ErrorCount ([ref]$ErrorCount) -FlowsToActivate $FlowsToActivate -CatchChildFlowActivationErrors if ($FlowsToRetry.Count -gt 0) { Invoke-Activate -CRMConn $CRMConn -ErrorCount ([ref]$ErrorCount) -FlowsToActivate $FlowsToRetry } 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 } } function Invoke-ProcessAction { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] $CRMConn, [bool] [Parameter(Mandatory = $true)] $AnyImportSuccessful, [bool] [Parameter(Mandatory = $true)] $AlwaysTryActivate, [bool] [Parameter(Mandatory = $true)] $FailonError, [string] [Parameter(Mandatory = $true)] $SolutionName, [string] [Parameter(Mandatory = $true)] $SolutionFolder, [string] [Parameter(Mandatory = $true)] $PipelinePath, [bool] [Parameter(Mandatory = $false)] $RunLocally = $false, [System.Collections.Hashtable] [Parameter(Mandatory = $true)] $Deploy ) if ($AnyImportSuccessful -or $AlwaysTryActivate -eq $true) { $ProgressPreference = "SilentlyContinue" # check whether activate processes is enabled if ($Deploy.Processes.Activate -eq $true) { Write-Host "Activating Processes" -ForegroundColor Green Invoke-ProcessActivation -CRMConn $CRMConn -PipelinePath $PipelinePath -RunLocally $RunLocally -Deploy $Deploy -SolutionFolder $SolutionFolder -SolutionName $SolutionName } else { Write-PPDOMessage -Message "Skipping activating Processes per configuration in deployPackages.json" -type "warning" -LogWarning $true -RunLocally $RunLocally } } } function Invoke-ProcessActivation { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] [Parameter(Mandatory = $true)] $CRMConn, [string] [Parameter(Mandatory = $true)] $PipelinePath, [string] [Parameter(Mandatory = $true)] $SolutionFolder, [bool] [Parameter(Mandatory = $false)] $RunLocally = $false, [System.Collections.Hashtable] [Parameter(Mandatory = $true)] $Deploy, [string] [Parameter(Mandatory = $true)] $SolutionName ) # Use Override File if it is set if ($Deploy.Processes.OverrideFile) { $ProcessesJSONFilePath = "$PipelinePath\$SolutionFolder\$($Deploy.Processes.OverrideFile)" } else { $ProcessesJSONFilePath = "$PipelinePath\$SolutionFolder\Processes_Default.json" } Write-Host "Checking if there are Processes that need to be activated" if ($Deploy.Processes.Activate -ne $true) { Write-PPDOMessage -Message "Skipping workflow activation, per 'Activate' flag being false in deployPackages.json." -RunLocally $RunLocally return } if (!(Test-Path -Path $ProcessesJSONFilePath)) { Write-PPDOMessage -Message "Process activation is enabled in deployPackages.json, but unable to find the Processes to activate at $ProcessesJSONFilePath. Please add the file, or change deployPackages.json to skip activating workflows." -RunLocally $RunLocally return } Write-PPDOMessage -Message "Using list of Processes to activate at location '$ProcessesJSONFilePath'." -RunLocally $RunLocally # Get list of flows to activate from JSON try { $ProcessesToActivate = Get-Content -Path $ProcessesJSONFilePath | ConvertFrom-Json } catch { Write-PPDOMessage -Message "$($_.Exception.Message)" -Type "error" -LogError $true -RunLocally $RunLocally Throw "An error occurred when trying to get list of Processes to activate at location '$ProcessesJSONFilePath'." } Write-PPDOMessage -Message "There are $($ProcessesToActivate.Count) Processes to activate." -RunLocally $RunLocally if ($ProcessesToActivate.Count -le 0) { return } $ErrorCount = 0 $ProcessesToRetry = Invoke-ProcessActivate -CRMConn $CRMConn -ErrorCount ([ref]$ErrorCount) -ProcessesToActivate $ProcessesToActivate -CatchChildProcessActivationErrors if ($ProcessesToRetry.Count -gt 0) { Invoke-ProcessActivate -CRMConn $CRMConn -ErrorCount ([ref]$ErrorCount) -ProcessesToActivate $ProcessesToRetry } if ($Deploy.Processes.FailOnError -eq $true -and $ErrorCount -gt 0) { Write-PPDOMessage "There were $ErrorCount Process activation errors and FailOnError is set to True... exiting." -Type error -RunLocally $RunLocally -LogError $true exit 1 } } function Invoke-ProcessActivate { Param( [Microsoft.Xrm.Tooling.Connector.CrmServiceClient] [Parameter(Mandatory = $true)] $CRMConn, [System.Object] [Parameter(Mandatory = $true)] $ProcessesToActivate, [ref] [Parameter(Mandatory = $true)] $ErrorCount, [switch] $CatchChildProcessActivationErrors ) $ProcessesToRetry = @() $who = Invoke-CrmWhoAmI $CRMConn [Guid]$assignToId = $who.UserId $ProcessesToActivate | ForEach-Object { $ProcessStore = $_ try { $workflow = Get-CrmRecord -conn $CRMConn -EntityLogicalName "workflow" -Id $_.ProcessId -Fields "statecode", "name", "owninguser" Write-PPDOMessage "Checking Process '$($workflow.name)' - $($_.ProcessId) - Category $($_.Category)" -Type command -RunLocally $RunLocally $activateProcess = $false $assignProcess = $false if ($workflow.statecode -ne "Activated") { $activateProcess = $true } if (($_.Category -eq 0) -or ($_.Category -eq 3)) { if ($workflow.owninguser -ne $who.UserId) { $activateProcess = $true $assignProcess = $true } } if ($assignProcess -eq $true) { if ($workflow.statecode -eq "Activated") { Write-Host "Process status is $($workflow.statecode), attempting to deactivate process." try { Set-CrmRecordState -conn $CRMConn -EntityLogicalName "workflow" -Id $_.ProcessId -StateCode "Draft" -StatusCode "Draft" Write-Host "...Deactivated" -ForegroundColor Green } catch { Write-Host "Error Deactivating process: " $_ -ForegroundColor Red } } try { Write-Host "Process '$($workflow.name)' assigned to $($workflow.owninguser). Assigning to $assignToId" -ForegroundColor Green Set-CrmRecordOwner -conn $CRMConn -EntityLogicalName "workflow" -Id $_.ProcessId -PrincipalId $assignToId } catch { Write-Host "Error Assigning process: " $_ -ForegroundColor Red } } if ($activateProcess) { Write-Host "Process status is $($workflow.statecode), attempting to activate process." $activationConnection = $CRMConn if ($_.ActivateAsUser) { Write-Host "ActivateAsUser defined and set to: $($_.ActivateAsUser), attempting to activate process as this user" $systemuserResult = Get-CrmRecords -conn $CRMConn -EntityLogicalName "systemuser" -FilterAttribute "domainname" -FilterOperator "eq" -FilterValue $_.ActivateAsUser -TopCount 1 -Fields "systemuserid" if ($systemuserResult.Count -gt 0) { $activationConnection.OrganizationWebProxyClient.CallerId = $systemuserResult.CrmRecords[0].systemuserid } else { Write-PPDOMessage -Message "$($_.Exception.Message)" -Type "error" -RunLocally $RunLocally Throw "User $($_.ActivateAsUser) was not found in $($Deploy.EnvironmentName), unable to impersonate them to activate process $($workflow.name)" } } Write-PPDOMessage "Enabling Process '$($workflow.name)'" -Type command -RunLocally $RunLocally try { Set-CrmRecordState -conn $activationConnection -EntityLogicalName "workflow" -Id $_.ProcessId -StateCode "Activated" -StatusCode "Activated" Write-Host "...Activated" -ForegroundColor Green } catch { if ($_.ToString().Contains("ChildProcessNeverPublished") -and $CatchChildProcessActivationErrors) { $ProcessesToRetry += $ProcessStore } else { Throw $_ } } } } catch { Write-PPDOMessage "$($_.Exception.Message)" -Type "error" -RunLocally $RunLocally $ErrorCount.Value++ } } return $ProcessesToRetry } function Get-FlowsToBeDeployed { Param( [string] [Parameter(Mandatory = $true)] $StartPath, [array] [Parameter(Mandatory = $false)] $patchSolutionNames ) try { Write-Host "Generating Flows_Default.json for activating post-deploy" $FlowLocations = @(Join-Path $StartPath "src\Workflows") $FlowJSON = @() $AddedFlows = @() # If there are patches if ($patchSolutionNames.Count -gt 0) { foreach ($PatchName in $patchSolutionNames) { $FlowLocations += (Join-Path $StartPath "Patches\$PatchName\Workflows") } } foreach ($FlowLocation in $FlowLocations) { $Flows = Get-ChildItem -Path $FlowLocation -Filter *.json -ErrorAction SilentlyContinue if ($Flows) { $Flows | ForEach-Object { $FlowName = $_.BaseName.SubString(0, $_.BaseName.Length - 36) $FlowID = $_.BaseName.Replace($FlowName, '') if ($AddedFlows -notcontains $FlowID) { $FlowJSON += @([ordered]@{FlowId = $FlowID; FlowName = $FlowName; ActivateAsUser = ""; }) $AddedFlows += $FlowID } } } } if ($FlowJSON.Count -gt 0) { ConvertTo-Json -Depth 3 $FlowJSON | Format-Json | Out-FileUtf8NoBom $StartPath\Flows_Default.json } } catch { Write-Host $_ pause } } function Get-ProcessesToBeDeployed { Param( [string] [Parameter(Mandatory = $true)] $StartPath, [array] [Parameter(Mandatory = $false)] $patchSolutionNames ) try { Write-Host "Generating Processes_Default.json for activating post-deploy" $ProcessesLocations = @(Join-Path $StartPath "src\Workflows") $ProcessesJSON = @() $AddedProcessess = @() # If there are patches if ($patchSolutionNames.Count -gt 0) { foreach ($PatchName in $patchSolutionNames) { $ProcessesLocations += (Join-Path $StartPath "Patches\$PatchName\Workflows") } } foreach ($ProcessesLocation in $ProcessesLocations) { $Processes = Get-ChildItem -Path $ProcessesLocation -Filter "*xaml.data.xml" -ErrorAction SilentlyContinue if ($Processes) { $Processes | ForEach-Object { $ProcessName = $_.BaseName.SubString(0, $_.BaseName.Length - 47) $ProcessId = $_.BaseName.SubString($ProcessName.Length + 1, 36) [xml]$ProcessXml = Get-Content $_.FullName $category = [int]$ProcessXml.Workflow.Category if ($AddedProcessess -notcontains $ProcessId) { $ProcessesJSON += @([ordered]@{ProcessId = $ProcessId; ProcessName = $ProcessName; Category = $category; ActivateAsUser = ""; }) $AddedProcessess += $ProcessId } } } } if ($ProcessesJSON.Count -gt 0) { ConvertTo-Json -Depth 3 $ProcessesJSON | Format-Json | Out-FileUtf8NoBom $StartPath\Processes_Default.json } } catch { Write-Host $_ pause } } |