Here is my first post for the “Tips & Tricks” section of my blog, its entire purpose is to share some tricks or patterns that I encountered whilst working/playing with the Power Platform. Hopefully it is useful and practical!
This post is based on a great concept which Alex Shlega posted a few months ago, you can read his article here – “CDS Post Images and CDS Pre Images in PowerAutomate Flows”. In this write up we are going to build on the concept by extracting and consuming the remote execution context in a no-code approach.
Here is what we are going to cover in this post:
- A brief explanation for the reason behind using the CDS service endpoint and Azure Service Bus
- Extract remote execution context from the Azure Service Bus queue message
- Demonstrate an efficient way to use flow variables to hold entity attribute values
- Child flow pattern in Power Automate to make extraction of entity attribute values more reusable
The Reason for Using the CDS Service Endpoint and Azure Service Bus
There are often integration scenarios where an event triggered in the Common Data Service (CDS) must offload the business logic to an external service. Real-world requirements might be:
- Adding and removing users from certain Microsoft 365 Azure AD groups using the Microsoft Graph API
- Offloading auditing to an external store like Azure Storage
- Copying SharePoint documents to a new location when the document location is updated
The CDS connector trigger – “When a record is created, updated or deleted”, does a great job of capturing CDS events, but unfortunately it doesn’t return the execution context of the caused event. Without the context we cannot access information such as the PreEntityImages or PostEntityImages collections.
By configuring the Azure Service Bus for integration with CDS Service Endpoint, the remote execution context is passed to the Azure Service Bus listener for those events raised by the entities that are configured as steps for the service endpoint. A way to read these messages from the Azure Service Bus queue, or via topic subscription, is to configure a flow with an Azure Service Bus connector trigger.
Let’s Get Started! Our Sample Scenario
Let’s build a flow that receives a message from an Azure Service Bus queue whenever a request (e.g. Create, Update, Delete) is executed on an Account record. From the message, we will extract the Pre Image and Post Image from their respective Entity Image Collection and filter through the image for specific attributes to use later in the flow actions.
Extract Execution Context From the Azure Service Bus Queue Message
The contextual information returned can be extracted and saved neatly in variables:
# | Trigger/Action | Description |
---|---|---|
1. | [Azure Service Bus] Trigger | Once the connection for the connector has been set, a list of queues accessible by the connection permission will be made available to select. For our case, the queue is called account . |
2. | [Compose] Convert Base64 Message to JSON | Message from the queue must be 1) converted from base 64 to string, then 2) to JSON to access the message properties easier using the expressions Value/Expression: json(base64ToString(triggerOutputs()?['body/ContentData'])) |
3. | [Initialize Variable] Initialize Context Primary Entity Name | Context Primary Entity Name Value/Expression: outputs('Convert_Base64_Message_to_JSON')?['PrimaryEntityName'] |
4. | [Initialize Variable] Initialize Context Primary Entity Id | Context Primary Entity Id Value/Expression: outputs('Convert_Base64_Message_to_JSON')?['PrimaryEntityId'] |
5. | [Initialize Variable] Initialize Context Message Name | Message Name – e.g. Create, Update or Delete Value/Expression: outputs('Convert_Base64_Message_to_JSON')?['MessageName'] |
Using Flow Variables to Store Attribute Values From the Entity Images
These variables will play a crucial part later in the flow:
# | Action | Description |
---|---|---|
6. | [Initialize Variable] Initialize Filtering Attributes | Filtering Attributes – an array of JSON objects that define the attributes to be extracted from the entity image Schema: [{ Value: e.g. [ |
7. | [Initialize Variable] Initialize Pre Image Object | Pre Image Object – a JSON object to store attribute values from the pre image entity Value: No value set initially |
8. | [Initialize Variable] Initialize Post Image Object | Post Image Object – a JSON object to store attribute values from the post image entity Value: No value set initially |
9. | [Initialize Variable] Initialize Merged Image Object | Merged Image Object – a JSON object to store merged attribute values from the PreImageObject and PostImageObject variables Value: {} |
Extracting Pre Image Attribute Values
We need to include some validation checks to make sure the message received by the queue is for the account
entity and execute different actions depending on the context message:
Check if the execution context is for the Create
message as the PreEntityImages
property doesn’t exist for this event:
# | Action | Description |
---|---|---|
10.1 | [Filter Array] Find PreImage in PreEntityImage | Iterate PreEntityImages array to find the pre image using the name specified at the image registration e.g. PreImage From/Expression: outputs('Convert_Base64_Message_to_JSON')?['PreEntityImages'] Where/Expression – (Switch to edit in advance mode): @equals(item()?['key'], 'PreImage') |
10.2 | [Child Flow] Extract Pre Image Attributes Values | The action of extracting the attribute values from the entity image has been abstracted away into a child flow. This child flow can be reused for any number of images and flows with similar scenarios. The request takes two properties: EntityImage/Expression: first(body('Find_PreImage_in_PreEntityImage')) FilteringAttributes/Expression: variables('FilteringAttributes') |
10.3 | [Set Variable] Set Pre Image Object – Extracted Value | The result of the child flow will be set in PreImageObject variable Value/Expression: body('Extract_Pre_Image_Attributes_Values')?['Body'] |
Child Flow – Extract Attribute Values From the Entity Image
View the full overview of this child flow here.
Before we dive into the main section of this flow, let’s have a quick look at the JSON of an entity image (note: I’ve removed properties and attributes that are not relevant for this post to make it more readable). Highlighted are the values we are going to extract. Don’t these properties look familiar? These are what we’ve defined in the FilteringAttributes
variable:
{ "key": "PreImage", "value": { "Attributes": [ { "key": "statecode", "value": { "__type": "OptionSetValue:http://schemas.microsoft.com/xrm/2011/Contracts", "Value": 0 } }, { "key": "ownerid", "value": { "__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts", "Id": "0c7a4bce-d339-4467-8420-d22e15cbd374", "KeyAttributes": [], "LogicalName": "systemuser", "Name": "Tae Rim Han", "RowVersion": null } }, { "key": "name", "value": "Oscorp" }, { "key": "modifiedby", "value": { "__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts", "Id": "0c7a4bce-d339-4467-8420-d22e15cbd374", "KeyAttributes": [], "LogicalName": "systemuser", "Name": "Tae Rim Han", "RowVersion": null } }, { "key": "modifiedon", "value": "/Date(1603795983000)/" } ], "FormattedValues": [ { "key": "statecode", "value": "Active" }, { "key": "ownerid", "value": "Tae Rim Han" }, { "key": "modifiedby", "value": "Tae Rim Han" }, { "key": "modifiedon", "value": "2020-10-27T21:53:03+11:00" } ], "Id": "f2470e8e-fe5f-ea11-a811-000d3ad20d1d", "LogicalName": "account" } }
The output of this flow is a JSON object with property names that match the filtering attribute names and the value set to the respective attribute values. Example of a resulting payload:
{ "ownerid": "0c7a4bce-d339-4467-8420-d22e15cbd374", "ownerid_formattedvalue": "Tae Rim Han", "modifiedby": "0c7a4bce-d339-4467-8420-d22e15cbd374", "modifiedby_formattedvalue": "Tae Rim Han", "modifiedon": "/Date(1603795983000)/", "modifiedon_formattedvalue": "2020-10-27T21:53:03+11:00", "name": "Ozcorp", "statecode": 0, "statecode_formattedvalue": "Active" }
The resulting value will be set in this flow variable:
Ok, with all that said let’s cover the main section. We iterate each attribute defined in the FilteringAttribute
array:
# | Action | Description |
---|---|---|
A. | [Apply to Each] Apply to Filtering Attributes | Iterate each attribute definition Input/Expression: triggerBody()?['FilteringAttributes'] |
A.1 | [Filter Array] Find Attribute in Image | Find attribute by name e.g. ownerid From/Expression: triggerBody()?['EntityImage']?['value']?['Attributes'] Where/Expression – (Switch to edit in advance mode): @equals(item()?['key'], items('Apply_to_Filtering_Attributes')?['name']) |
A.2 | [Compose] Compose Image Object Property Value | This compose expression may look intimidating at first but all it is doing is adding a new property to the JSON object where the name is the filtering attribute name and the value is the attribute value found in the path specified in the valueSelector property. If no value is found in that path, defaultValue will be used instead.Inputs/Expression: addProperty(variables('AttributeValuesObject'), items('Apply_to_Filtering_Attributes')?['name'], coalesce(first(body('Find_Attribute_in_Image'))?[items('Apply_to_Filtering_Attributes')?['valueSelector']], items('Apply_to_Filtering_Attributes')?['defaultValue'])) Explanation: addProperty("<Flow variable name>", "<Property name>", "<Value>") Where: "<Property name>" = attribute name"<Value>" = coalesce(“JSON attribute object[Path to value]”, “Default value”) |
A.3 | [Set Variable] Set Attribute Values Object – Value | Set AttributeValuesObject with the added property.Value/Expression: outputs('Compose_Image_Object_Property_Value') |
We are going to do something very similar but for formatted values (if they exist). This way we don’t have to make additional requests to CDS to get either the name of an entity reference or option-set metadata:
# | Action | Description |
---|---|---|
A.4 | [Filter Array] Find Formatted Value in Image | Continuing on, in the context of our apply-each loop for filtering attributes, find the formatted value for the attribute by name. From/Expression: triggerBody()?['EntityImage']?['value']?['FormattedValues'] Where/Expression – (Switch to edit in advance mode): @equals(item()?['key'], items('Apply_to_Filtering_Attributes')?['name']) |
A.5 | [Condition] Check Formatted Value Exists in Image | Check if a formatted value exists. Condition Expression: length(body('Find_Formatted_Value_in_Image')) equals 1 |
A.6 | [Compose] Compose Image Object Property Formatted Value | If one exists, compose a new property to add to AttributeValuesObject variable. The property name is in this the format – “<attribute name>_formattedvalue”Inputs/Expression: addProperty(variables('AttributeValuesObject'), concat(items('Apply_to_Filtering_Attributes')?['name'], '_formattedvalue'), first(body('Find_Formatted_Value_in_Image'))?['value']) Explanation: addProperty("<Flow variable name>", "<Property name>", "<Value>") Where: "<Property name>" = concat(“attribute name”, “_formattedvalue”)"<Value>" = “Value if one exists”) |
A.7 | [Set Variable] Set Attribute Values Object – Formatted Value | Set AttributeValuesObject with the added property.Value/Expression: outputs('Compose_Image_Object_Property_Formatted_Value') |
As a flow response, the AttributeValuesObject
variable value will be returned as JSON payload:
Reuse the Child Flow to Extract Attribute Values From the Post Entity Image
We’ve covered how we can extract values from the pre entity image. This same logic is applied when extracting values for the post entity image. Hence child flow will be used again, but this time we will pass the post entity image to the child flow. The resulting value will be stored in the PostImageObject
variable.
Merge Pre and Post Image Objects
We won’t go over this section in detail as the concept is largely similar. The benefit of merging the PreImageObject
and PostImageObject
variable values into one variable is for ease of access in later flow actions. Key expressions would be:
Expression | Description |
---|---|
addProperty(variables('MergedImageObject'), items('Apply_to_Filtering_Attributes')?['name'], coalesce(variables('PostImageObject')?[items('Apply_to_Filtering_Attributes')?['name']], variables('PreImageObject')?[items('Apply_to_Filtering_Attributes')?['name']])) | Add a property to MergedImageObject with the filtering attribute name, checking first the post image value then the pre image value. |
addProperty(variables('MergedImageObject'), outputs('Formatted_Value_Attribute_Name'), coalesce(variables('PostImageObject')?[outputs('Formatted_Value_Attribute_Name')], variables('PreImageObject')?[outputs('Formatted_Value_Attribute_Name')])) | Add a property to MergedImageObject with the filtering attribute name + “_formattedvalue”, checking first the post image value then the pre image value. |
Simple Usage Example
Nothing fancy, but to illustrate the benefit of using these variables:
- Account Name = variables(‘MergedImageObject’)?[‘name’]
- Modified by = variables(‘PostImageObject’)?[‘modifiedby_formattedvalue’]
- Previous Owner = variables(‘PreImageObject’)?[‘ownerid_formattedvalue’]
- Current Owner = variables(‘PostImageObject’)?[‘ownerid_formattedvalue’]
- Modified On = variables(‘PostImageObject’)?[‘modifiedon_formattedvalue’]
- Status = variables(‘MergedImageObject’)?[‘statecode_formattedvalue’]
Having these variables make it super easy to use in the flow actions that follow.
Hope you enjoyed the post, let me know your thoughts in the comments below or any of my contact channels 😊