Trigger Flow when user is added to access team

Hello everyone,

Today's question is around the triggering a flow from an access team.
Seems simple as it is but you need to dig a bit more to perform the full process.

Scenario

My business requirement was to send an email (without code) when user was added to an access team.

Main limitation/problem so far is the fact that a flow using the Trigger "When a record is created, updated or deleted" on the TeamMemberShip entity will never be fired.
Reason ? Unknows so far ...

As I wanted / had to have the process working, after few research I ended up with the solution :

  1. Create your flow with a Http request trigger
  2. Create a WebHook from the CRM/CDS instance linked to your flow
  3. Add a step on the AddUserToRecordTeam message (teamtemplate entity)
  4. Configure the flow

Process

Flow creation + Webhook configuration

It's not needed to rewrite what was nicely explained by other nice people, you can go to Stefan's blog post and check this out

And come back right after ! :)

Step configuration on the Plugin Registration Tool

Our goal was to trigger the workflow when user was added to an Access Team.
Good point is that there is a dedicated message for that : AddUserToRecordTeam but this message can't be linked to the entity where the access team is used (ie : Opportunity or Account).
Nevermind, we will deal with it later !

Configure the Flow

Here is the interesting part !

First step:

As the flow receive the Plugin context as JSON is to properly parse it.
(This is the most tricky part or not).

What I needed in my use case as information were :

  • GUID of added user
  • Record Id where the user was added (for the email content)
  • Entity LogicalName to make sure, the flow was processing a record I was interested in.

As soon as you ran your first run of a flow, you will receive the payload of the Plugin Context you are familiar with, should look like that :

{
  "BusinessUnitId": "cdddddf5-1d81-ea11-a813-000d3a4b2ac6",
  "CorrelationId": "69b8ac32-688c-4c80-9c93-5d6342178db9",
  "Depth": 1,
  "InitiatingUserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
  "InitiatingUserId": "9b781be7-d176-404b-9d2f-1d830dae011f",
  "InputParameters": [
    {
      "key": "Record",
      "value": {
        "__type": "EntityReference:http://schemas.microsoft.com/xrm/2011/Contracts",
        "Id": "c6dd5426-XXXX-XXXX-XXXX-d40c7fac5a8e",
        "KeyAttributes": [],
        "LogicalName": "opportunity",
        "Name": null,
        "RowVersion": null
      }
    },
    {
      "key": "TeamTemplateId",
      "value": "d7c67ce4-XXXX-XXXX-XXXX-000d3a2109c9"
    },
    {
      "key": "SystemUserId",
      "value": "b5a6d14b-XXXX-XXXX-XXXX-000d3a210496"
    }
  ],
  "IsExecutingOffline": false,
  "IsInTransaction": true,
  "IsOfflinePlayback": false,
  "IsolationMode": 1,
  "MessageName": "AddUserToRecordTeam",
  "Mode": 0,
  "OperationCreatedOn": "/Date(1589897514394)/",
  "OperationId": "bf9e6242-XXXX-XXXX-XXXX-e052a4653443",
  "OrganizationId": "8f82a087-XXXX-XXXX-XXXX-dad71fee1361",
  "OrganizationName": "org6b6c11a7",
  "OutputParameters": [
    {
      "key": "AccessTeamId",
      "value": "db4e02ac-XXXX-XXXX-XXXX-000d3a2109c9"
    }
  ],
  "OwningExtension": {
    "Id": "813ac1d5-XXXX-XXXX-XXXX-000d3a2109c9",
    "KeyAttributes": [],
    "LogicalName": "sdkmessageprocessingstep",
    "Name": "Access Team: AddUserToRecordTeam of teamtemplate",
    "RowVersion": null
  },
  "ParentContext": null,
  "PostEntityImages": [],
  "PreEntityImages": [],
  "PrimaryEntityId": "00000000-0000-0000-0000-000000000000",
  "PrimaryEntityName": "teamtemplate",
  "RequestId": "bf9e6242-XXXX-XXXX-XXXX-e052a4653443",
  "SecondaryEntityName": "none",
  "SharedVariables": [
    {
      "key": "IsAutoTransact",
      "value": true
    },
    {
      "key": "ChangedEntityTypes",
      "value": [
        {
          "__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
          "key": "team",
          "value": "Update"
        },
        {
          "__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
          "key": "principalobjectaccess",
          "value": "Update"
        },
        {
          "__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
          "key": "teammembership",
          "value": "Update"
        },
        {
          "__type": "KeyValuePairOfstringstring:#System.Collections.Generic",
          "key": "systemuserprincipals",
          "value": "Create"
        }
      ]
    },
    {
      "key": "POADistributionChanged",
      "value": 10117
    }
  ],
  "Stage": 40,
  "UserAzureActiveDirectoryObjectId": "00000000-0000-0000-0000-000000000000",
  "UserId": "9b781be7-XXXX-XXXX-XXXX-1d830dae011f"
}

And from that JSON schema, I generated the following Parse schema :

{
    "type": "object",
    "properties": {
        "BusinessUnitId": {
            "type": "string"
        },
        "CorrelationId": {
            "type": "string"
        },
        "Depth": {
            "type": "integer"
        },
        "InitiatingUserAzureActiveDirectoryObjectId": {
            "type": "string"
        },
        "InitiatingUserId": {
            "type": "string"
        },
        "InputParameters": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {
                        "type": "object",
                        "properties": {
                            "__type": {
                                "type": "string"
                            },
                            "Id": {
                                "type": "string"
                            },
                            "KeyAttributes": {
                                "type": "array"
                            },
                            "LogicalName": {
                                "type": "string"
                            },
                            "Name": {},
                            "RowVersion": {}
                        }
                    }
                },
                "required": [
                    "key",
                    "value"
                ]
            }
        },
        "IsExecutingOffline": {
            "type": "boolean"
        },
        "IsInTransaction": {
            "type": "boolean"
        },
        "IsOfflinePlayback": {
            "type": "boolean"
        },
        "IsolationMode": {
            "type": "integer"
        },
        "MessageName": {
            "type": "string"
        },
        "Mode": {
            "type": "integer"
        },
        "OperationCreatedOn": {
            "type": "string"
        },
        "OperationId": {
            "type": "string"
        },
        "OrganizationId": {
            "type": "string"
        },
        "OrganizationName": {
            "type": "string"
        },
        "OutputParameters": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {
                        "type": "string"
                    }
                },
                "required": [
                    "key",
                    "value"
                ]
            }
        },
        "OwningExtension": {
            "type": "object",
            "properties": {
                "Id": {
                    "type": "string"
                },
                "KeyAttributes": {
                    "type": "array"
                },
                "LogicalName": {
                    "type": "string"
                },
                "Name": {
                    "type": "string"
                },
                "RowVersion": {}
            }
        },
        "ParentContext": {},
        "PostEntityImages": {
            "type": "array"
        },
        "PreEntityImages": {
            "type": "array"
        },
        "PrimaryEntityId": {
            "type": "string"
        },
        "PrimaryEntityName": {
            "type": "string"
        },
        "RequestId": {
            "type": "string"
        },
        "SecondaryEntityName": {
            "type": "string"
        },
        "SharedVariables": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "key": {
                        "type": "string"
                    },
                    "value": {
                        "type": "boolean"
                    }
                },
                "required": [
                    "key",
                    "value"
                ]
            }
        },
        "Stage": {
            "type": "integer"
        },
        "UserAzureActiveDirectoryObjectId": {
            "type": "string"
        },
        "UserId": {
            "type": "string"
        }
    }
}

This will not work 100% of the time like it is, because based on the data you got when you generated the schema, the value properties of the InputParameters can vary.
And of course, you can clean the properties you will never use to keep only the necessary !

The trick here is just to remove the types of those ones in order to tell the parser that they are not all mandatory and it should accept what he receives. No ideal in other cases but here we know what we receive anyway.

From that standpoint, we do have access to all properties of our PluginContext from the flow. Awesome !

Second step:

Making sure that the flow is triggered for Opportunity entity :

If LogicalName eq 'opportunity' then we continue else we terminate the flow.

This step is not mandatory if you wish to create a webhook per entity for example and save Flow execution !

Third step:

Based on the properties of the InputParameters, grab what you need.

You can see that here the idea is to loop on the InputParameters properties and use a simple switch to process the one you want.
Again in my case, the Added User Id & the Record Id placed in a variable to be used later on.

These are my 2 cents about the :
Triggering a flow when user is added to access team in order to send him an email

Enjoy,
Cheers

Clement

Dynamics 365 CRM & Power Platform addict.