Power Automate the enforcement of naming convention on flows with CDS(ce) trigger

Posted by

Enforcement of naming convention on workflows can be Power Automated, as described in my previous blog post. This is also possible for Power Automate flows. The trigger parameters from the flow definition can be used to compose the prefix for the flow naming. This blog post will focus on the flows with a trigger from the Common Data Service (current environment) connector.

The trigger of the Common Data Service (current environment) connector has the following options for the condition(s) and those are related to the message numbers listed in the table down below.

Trigger conditionMessage number
Create1
Create or Delete5
Create or Update4
Create or Update or Delete7
Delete2
Update3
Update or Delete6

There are 3 option to get a list of flows in an environment:

  1. ‘List records’ action from the Common Data Service connector on the entity Processes (workflows) and filter with category equals 5
  2. List Flows as Admin‘ action from the Power Automate Management connector
  3. List flows with the Power Automate Web API

The first and third option will give back the flows definition as part of the clientdata property, but its value is a string-encoded JSON of an object and is difficult to parse because the name of the trigger(s and actions) are not static so a JSON schema won’t help here.
The second option will give all the flows in an enverinmont back as output, including the ones that are part of managed solutions and that’s unacceptable.
So I use a combination of the first and second option to get the proper list of unmanaged flows.

Here is the overview of the flow:

First action of the flow after the Recurrence trigger is to initialize a variable to create an array, so I can use the message number as index (see table above) to get the trigger condition(s) of the listed flows:

createArray('Zero', 'Create', 'Delete', 'Update', 'CreateUpdate', 'CreateDelete', 'DeleteUpdate', 'CreateDeleteUpdate')

The second and third action will get the list of flows from the current environment.

Then I use the unmanaged flow records from the CDS action to filter out the managed flows from the ‘List Flows as Admin’ output, where the workflowEntityId of the flow should match the workflowid.
The filter setting is the following expression (from ‘Edit in advanced mode’):

@equals(item()?['properties']?['workflowEntityId'], items('Apply_to_each_Unmanaged_Flow')?['workflowid'])

Since the output of the ‘List Flows as Admin’ action doesn’t contain the complete flow definition, I need the ‘Get Flow’ action to get the details of the flow based on its name (guid):

first(body('Filter_array_workflowEntityId_eq_workflowid'))?['name']

The Switch action is based on the trigger type of the flow:

first(body('Get_Flow')?['properties']?['definitionSummary']?['triggers'])?['type']

The trigger type of the Common Data Service (current environment) trigger is: OpenApiConnectionWebhook
Now I assume that its unique enough to identify the trigger. The flow continues in the branch “Case equals OpenApiConnectionWebhook” only; I didn’t implement cases for other trigger types like Recurrence and Request.

The flow definition contains the trigger parameters that I need to compose the prefix for the naming.

{
  "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "$connections": {
      "defaultValue": {},
      "type": "Object"
    },
    "$authentication": {
      "defaultValue": {},
      "type": "SecureObject"
    }
  },
  "triggers": {
    "When_a_record_is_updated": {
      "type": "OpenApiConnectionWebhook",
      "inputs": {
        "host": {
          "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps",
          "connectionName": "shared_commondataserviceforapps",
          "operationId": "SubscribeWebhookTrigger"
        },
        "parameters": {
          "subscriptionRequest/message": 3,
          "subscriptionRequest/entityname": "account",
          "subscriptionRequest/scope": 4,
          "subscriptionRequest/filteringattributes": "name,accountnumber",
          "subscriptionRequest/name": "d773f2f2-3677-ea11-a811-000d3ad9928e"
        },
        "authentication": "@parameters('$authentication')"
      },
      "conditions": [
        {
          "expression": "@not(empty(triggerOutputs()?['body/name']))"
        }
      ]
    }
  },
  "actions": {},
  "outputs": {}
}

I’m especially interested in the value of the following parameters that are isolated in the compose actions of the same name:

“subscriptionRequest/message“: 3,
“subscriptionRequest/entityname“: “account”,
“subscriptionRequest/filteringattributes“: “name,accountnumber”

I can’t parse the JSON of the flow definition because the trigger name is not static, so I use some String functions to isolate the values.

Expression to get entityname: (where 50 is the presumed maximum length of the entityname)

substring(string(outputs('Get_Flow')?['body/properties/definition']),add(indexOf(string(outputs('Get_Flow')?['body/properties/definition']),'subscriptionRequest/entityname'),33),indexOf(substring(string(outputs('Get_Flow')?['body/properties/definition']),add(indexOf(string(outputs('Get_Flow')?['body/properties/definition']),'subscriptionRequest/entityname'),33),50),'"'))

Expression to get trigger condition(s) based on message number as index:

variables('triggerConditions')[int(substring(string(outputs('Get_Flow')?['body/properties/definition']),add(indexOf(string(outputs('Get_Flow')?['body/properties/definition']),'subscriptionRequest/message'),29),1))]

Expression to get filtering attributes: (where 150 is the presumed maximum length of the filteringattributes)

if(contains(string(body('Get_Flow')['properties']['definition']),'subscriptionRequest/filteringattributes'),concat('(',substring(string(body('Get_Flow')['properties']['definition']), add(indexOf(string(body('Get_Flow')['properties']['definition']), 'subscriptionRequest/filteringattributes'), 42), indexOf(substring(string(body('Get_Flow')['properties']['definition']), add(indexOf(string(body('Get_Flow')['properties']['definition']), 'subscriptionRequest/filteringattributes'), 42), 150), '"')),')'),'')

The prefix is composed of the previous outputs:

concat(outputs('Compose_entityname'), '|', outputs('Compose_Trigger_condition_based_on_message_number'),outputs('Compose_Filtering_attributes'))

In the condition it’s checked if the display name does not start with the prefix already.

If Yes, then the new display name of the flow is composed as follows, keeping the maximum length under 100:

if(greater(length(concat(outputs('Compose_entityname'), '|', outputs('Compose_Trigger_condition_based_on_message_number'), outputs('Compose_Filtering_attributes'),': ', body('Get_Flow')['properties']['displayName'])),100),substring(concat(outputs('Compose_entityname'), '|', outputs('Compose_Trigger_condition_based_on_message_number'), outputs('Compose_Filtering_attributes'),': ', body('Get_Flow')['properties']['displayName']),0,100),concat(outputs('Compose_entityname'), '|', outputs('Compose_Trigger_condition_based_on_message_number'), outputs('Compose_Filtering_attributes'),': ', body('Get_Flow')['properties']['displayName']))

Then the flow is turned off to be able to update it in the last action, where the dynamic content of the Get Flow output is used, its new display name and the flow state is set to Started.

When the flow is run it will rename the flows that don’t match the naming convention. For example:

In this way the naming convention for flows can be enforced with the help of Power Automate flow.
Even the trigger conditions of the CDS(ce) trigger could be added to the description of the flows.

So this flow could be extended for other trigger types like Recurrence and Request based on a Switch action. If flows with the CDS(ce) trigger are the only “target”, there is a simplified flow that can do the job. See my next blog post.