Unbound Actions: How to share and unshare D365 records using GrantAccess and RevokeAccess
I recently had the need to automatically share and un-share records in Dynamics 365 - records stored in the Microsoft Dataverse. There are many reasons for this and many ways in which you might carry this out. For simple requirements, you might be able to get away with a classic workflow using Demian Raschkovan's Dynamics 365 Workflow Tools. However, this may not suit your needs if you have a more complex scenario. For example, I wanted to share Opportunity records with a custom access team that's related directly to the Owner's Business Unit (different team for every BU). So I turned to Power Automate...
We can accomplish this using a couple of unbound actions available in the Common Data Service (current environment) connector (still yet to be renamed to Dataverse...). I won't go deep in bound and unbound actions in this post, but here's a few key ideas:
- The Common Data Service (current environment) connector is only available within solutions.
- Bound actions are tied to a specific entity.
- Unbound actions are not tied to a specific entity.
- Unbound actions will often require a bit more complex formatting not often used by us non-developers.
- There's very little documentation on any of these actions. If there is any, it's hidden away in the developer areas of the Dynamics 365 documentation.
The unbound actions we're interested in today are GrantAccess and RevokeAccess. Let's walk through the steps to use each of these actions in a flow to share an Opportunity record using Power Automate.
Open the Power Apps Maker Portal, create a solution, and create a flow from within the solution. Select the connector, Common Data Service (current environment).
Select the desired trigger. For my example, I'm using "When a record is created, updated or deleted". I created a field/column on which I want to trigger this flow, so I will add this field/column to the Filtering attributes section.
Add an action, and select "Perform an unbound action" from the available actions in the Common Data Service (current environment) connector. Wait a second for the options to load, and you can now search for GrantAccess.
As I mentioned earlier in this post, we don't get much help with these unbound actions, and they're not at all intuitive. Here we see two arguments being requested: Target and PrincipalAccess.When I got to this step, I was entirely lost. A quick search on Google showed nothing helpful anywhere in Microsoft documentation. However, I was saved by Aaron Richard's post for some help on the formatting needed here.
In the Target parameter, you simply need to reference the record of interest using proper Odata notation. Rather than simply providing a GUID, you must also specify the entity type using the pluralized logical name: pluralizedlogicalname(GUID). An example is opportunities(628cf01a-aed1-e411-80ef-c4346bac7be8), although you will most likely populate the GUID using dynamic content.
Note - I found using the OData Id from dynamic content here will not work for this action as it typically does when setting lookup fields with the CDS (current environment) connector.
For the PrincipalAccess parameter, we must pass the needed data in JSON format, as in the below images. An important thing to note here is that the "@/at" symbol acts as an escape character when typed directly. You can type this symbol twice ("@@"), but it resolves to a single character upon save and you must re-type the second symbol every time you wish to save. Alternatively, you can create a variable for the symbol that you can use whenever needed. I've found the variable makes things simplest when working with JSONs in these unbound actions (yes, you'll need to do creates similar JSONs to use most other unbound actions ).
I'd also encourage you to use dynamic content to populate the team or user GUID to make your flow as dynamic as possible.Compose PrincipalAccess JSON for sharing to a user
You might be wondering how to know what to type as the AccessMask. Well, for simple scenarios you can either type out the access to provide as text or the equivalent numeral. Available masks can be found in this Docs article and are listed below.
Note - Using the AccessMask of "None" will not revoke access; it will simply make no change to the access of that user/team. You must use RevokeAccess (below) in order to remove access for a user.
In order to combine multiple permissions, you can separate the access masks by a comma without a space. The below code snippets will provide all possible access to either a user or a team. Copy and paste to use for your needs by removing the access masks that are not relevant. The first can be used is providing access to a user, or the second can be used if providing access to a team.
Side note - I originally went about providing multiple permissions in a much more complicated way. I tried a few methods including commas but with spaces, which does not work. However, I discovered each combination of AccessMaks has a specific numeric representation that you can find by using the developer tools and reviewing the request payload of a share request. Inside, you can see a value provided in the <accessrights> element. This is entirely unnecessary, though, and I thank EY Kalman for suggesting the use of commas without spaces. This way is far simpler and easier to understand.
Now that you know how to use GrantAccess to share a record with a user, you might run into a scenario where you need to remove access from a user. To do this, we can use a different unbound action, RevokeAccess. This one is quite a bit simpler to use.
Target - use the same values as in GrantAccess
Revokee - specify the user or team to remove access from with proper odata notation, as referenced earlier in the post
The Next Step
One thing I'd like to do now is to know who has access to a record. This would tell me who I need to put in the Revokee field of RevokeAccess. From some research, I've found a function, RetrieveSharedPrincipalsAndAccess, that does just this. This function returns a collection of teams and users who have access to this record. I'm not a developer, though, and this function is only available through the Web API from what I can see.
Perhaps we can get this added as an unbound action in the future? This would take security handling through flows to the next level.
I've spent a large part of my time in unbound actions debugging error messages. I've compiled a few of the most common error messages you might encounter to help you in building a successful flow.
Incorrect syntax in JSON
- "An error occurred while validating input parameters: Microsoft.OData.ODataException: Does not support untyped value in non-open type."
- "The power flow's logic app flow template was invalid. Unable to parse template language expression 'odata.type'..."
- "Fix invalid expression(s) for the input parameter(s)..."
Improper reference of an entity.
Be sure to use the pluralized logical name for the entity when referencing the Target. A simple GUID or misspelled entity reference will result in errors like the below. E.g. "opportunities(GUID)"
- "Resource not found for the segment <segmentname>"