Power Automate: Dataverse Contact automatic invitation redemption

In my previouse blog post I explained how to automatically create and delete B2C account for Dataverse Contacts. In this post, I will dive deeper into the process by automating the redemption of an invitation code process. By default, new contacts need to redeem an inventation code before they can access the Power Pages. I am using the one-time-password setup for access to the Power Pages and for ease of use I don’t want the contact (external users) to have to redeem an inventation code.

To achieve this, we will automate the process by granting the contact a web role, connecting the B2C account ID, and adding the external identity. This will allow the contact to access the Power Pages without having to redeem an invitation code.

Creating the automated redemption process

  • Add the following actions below the Scope – Create B2C Users.
  • Initialize a variable called Role URL as a string.
  • Open the required web role in the Portal management tool, the ID is in the end of the URL.
https://[environment].crm4.dynamics.com/main.aspx?appid=39b012bd-1234-1234-1234-0022489fd314&pagetype=entityrecord&etn=adx_webrole&id=04ae7aa4-1234-1234-1234-0022489b74da
  • Add the following URL as the value, with your own unique web role id.
https://[environment].crm4.dynamics.com/api/data/v9.1/adx_webroles(04ae7aa4-1234-1234-1234-0022489b74da)
  • Initialize a variable called Invitation as a string.
  • Add as a value two guid()
  • Initialize a variable called External Identity URL as a String.
  • Add the following URL (with your environment name).
https://[environment].crm4.dynamics.com/api/data/v9.1/adx_externalidentities
  • Add a scope called Add Role to new contact and add the following actions.
  • Add the Dataverse Relate rows action called Add Role to Contact.
  • Set the Table name as Contacts.
  • Set the Row ID to Contact (the contact id of the contact where the flow was triggert on).
  • Set the Relationship to adx_webrole_contact.
  • Set the Relate with to the variable Role URL.
  • We are going to update the existing Dataverse Update a row action called Contact – Add User information.
  • Drag this action into the scope Add Role to new contact.
  • Set the Table name to Contacts.
  • Set the Row ID to Contact (the contact id of the contact where the flow was triggert on).
  • Set the Email Confirmed to Yes.
  • Set the Lockout Enabled to Yes.
  • Set the Login Enabled to Yes.
  • Set the Security Stamp to the guid() expression.
  • Set the Time Zone Rule Version Number to 0.
  • Set the User Name to the ID of the B2C account.
  • Add the Dataverse Add a new row action called External Identities.
  • Set the Table name to External Identities.
  • Set the Contact (Contacts) to Contacts(Contact) (the contact id of the contact where the flow was triggert on).
  • Set the Identity Provider to your Identity Provider URL
  • Set the User Name to the ID of the B2C account.
  • Add the Dataverse Relate rows action called Add External Identity to Contact.
  • Set the Table name to Contacts.
  • Set the Row ID to Contact (the contact id of the contact where the flow was triggert on).
  • Set the Relationship to adx_contact_externalidentity.
  • Set the Relate with the following code, the External Identity is the ID of the newly created External Identity.
https://[environment].crm4.dynamics.com/api/data/v9.1/adx_externalidentities()
  • The flow will now create a new B2C account when a new contact is created, link them together and automatically redeem the invitation.

Create and delete B2C accounts for Dataverse Contact

Today, we’ll be discussing a crucial aspect of B2C account management – the creation and deletion of B2C accounts in response to changes in the Dataverse Contact. This is an important topic for businesses that deal with external Power Pages user (contacts) that want to ensure the security of their records and Power Pages. In this post I will explain how to create or delete B2C accounts that are connected to a Dataverse Contact. So, let’s dive in!

Create a B2C account when a Dataverse Contact is created

Whenever a new contact is created a new B2C account needs to be created automatically that is linked to the contact. This is done thought the email address of the contact and the Azure B2C account id. These automations will limit the amount of manual admin work.

  • Create a new Power Automate flow with the name Create B2C user for Dataverse Contact.
  • Add the Dataverse trigger When a row is added.
  • Set the Change type to Added.
  • Set the Table name to Contacts.
  • Set the Scope to Organization.
  • Create the following 3 variables.
  • You will need to create an Application Registration in the B2C tenant with the following permissions.
Permission typePermissions (from least to most privileged)
Delegated (work or school account)User.ReadWrite.All, Directory.ReadWrite.All
Delegated (personal Microsoft account)
Not supported
ApplicationUser.ReadWrite.All, Directory.ReadWrite.All
  • Store the Client ID, Tenant ID and Secrect in the corresponding variables.
  • Add a HTTP action called HTTP – Delete User to the flow.
  • Set the Method to: Post.
  • Set the URI to the following code.
https://graph.microsoft.com/v1.0/users
  • Set the body to the following code.
  • In my scenario the user will not be using the password but the one-time password from B2C. That’s why I have added a guid twice as the password.
  • This call will create an account of the type email address which allows for any valid email to be used. The email does not have to be part of the B2C domain.
  • Parse the JSON response of the HTTP – Create User call.
  • Add the Dataverse action Update a row.
  • Set the Table name to Contact.
  • Set the Row id to the contact id of the trigger.
  • Add the id that was returned by the HTTP call that created the B2C account.
  • Save the flow.

Delete B2C account when Dataverse Contact is deleted

In my scenario I am maintaining the Power Pages contacts within a canvas app, and when a contact is deleted the associated B2C account needs to be deleted too.

  • Create a new Power Automate flow with a Power Apps (V2) trigger with the name Delete B2C users for deleted Dataverse Contact.
  • Add an input Text field names ActiveDirectoryID.
    • This is the Object ID of the Azure B2C Active Directory User connected to the Contact.
  • Create the following 3 variables.
  • You will need to create an Application Registration in the B2C tenant with the following permissions.
Permission typePermissions (from least to most privileged)
Delegated (work or school account)User.ReadWrite.All
Delegated (personal Microsoft account)
Not supported
ApplicationUser.ReadWrite.All
  • Store the Client ID, Tenant ID and Secret in the corresponding variables.
  • Add a HTTP action called HTTP – Delete User to the flow.
  • Set the Method to: Delete.
  • Set the URI to the following code.
https://graph.microsoft.com/v1.0/users/
  • Add the PowerApp (V2) parameter ActiveDirectoryID to the end of the URI.
  • Set the Tenant, Client ID and Secret fields with their corresponding variables.
  • Set the Authentication to Active Directory OAuth.
  • Set the Audience to the following code.
https://graph.microsoft.com
  • Save the Power Automate Flow.
  • Open or create a canvas app (Power Apps).
  • Open the Power Automate panel in the canvas app.
  • Add the Delete B2C users for deleted Dataverse Contact Power Automate flow.
  • Add a gallery with the source set to the Dataverse Contact table.
  • Add a recycle bin or other delete Icon to the gallery.
  • Add the following code to the recycle bin icon under OnSelect.
    • This will remove the contact record.
    • Starts the Power Automate Flow and sending the User Name (Users B2C object ID).
    • Notifies the users.
Remove(Contacts, ThisItem);
DeleteB2CuserfordeletedDataverseContact.Run(ThisItem.'User Name');
Notify("Record deleted successfully", NotificationType.Success);
  • Save and publish the canvas app.

Power Platform: Custom administrator and developer role

Custom security roles on Power Platform are mostly used for Dynamics and model-driven apps, but they also work for canvas apps. By default an environment (without a Dataverse database) has two default roles (environment maker and environment admin). However if you create and environment with a Dataverse database, you get the ability to create custom security roles and 10 default roles. I strongly advise not to change the default roles.

Custom administrator role

At the moment of writing this blog it is possible to alter the default environment maker role (not system administrator), but I would not recommend it. Microsoft might push changes to the default roles and overwrite the customizations.

Creating a copy of the system administrator role or the environment maker role and making changes to the copied role, is also not an option. In the background Microsoft sets the CanEdit privilege to the administrator/environment maker role, and if you copy the role the CanEdit privilege is lost. The CanEdit privilege can only be set by Microsoft.

This practically means that creating a custom administrator role is not possible.

Note: Granting a user a role that effects the CanEdit privilege will take a non-specified amount of time to take effect. For example, if you switch from a copied role to a default role, it can take 30 minutes for the change to take effect.

Custom developer role

Creating a custom developer role is possible if you are willing to accept the following scenario. The developer gets an custom security role granting the required privileges, for example the ability to work with solutions and canvas apps but no export privileges. With only this security role the developer cannot access the environment and is missing the hidden CanEdit role.

The CanEdit role can also be granted by being an owner or a co-owner of a canvas app in the environment. If an administrator creates a canvas app and makes the developer co-owner of that app then the developer can access the environment and has the hidden CanEdit role.

Conditional access device filtering for canvas apps

Conditional access policies for individual Power Apps will be general available in September 2022 (currently in public preview) and will give us a lot of control on how users can access Power Apps. With the use of Azure Active Directory Conditional Access, we can add extra layers of security to individual Power Apps to contain sensitive data. In my project we needed to create a conditional access policy to prevent a canvass app being opened on any mobile device. With Conditional access policies for individual Power Apps we were able to do this.

Setting up conditional access

First we need to setup the conditional access policies in Azure Active Directory and connect it to an authentication context.

  • Select the authentication context created earlier.
  • Create the conditions and select all device platforms besides windows.
  • Select the Block access under Grant to block all the device platforms besides windows.
  • Click on Save.
  • The policy is now created, but still needs to be connected to the canvas app.

Connect the conditional access to the canvass app

The policy needs to be connected to the canvas app with PowerShell.

  • Open PowerShell as an administrator.
  • Connect PowerShell to the Power Platform with the following command.
Add-PowerAppsAccount
  • The PowerShell command requires the EnvironmentName (ID of the environment), AppName (ID of the canvass app) and the ID of the authentication context. The ID’s in my example are changed for security reasons.
Set-AdminPowerAppConditionalAccessAuthenticationContextIds -EnvironmentName Default-44444444-2222-3338-9b7f-0771d0c3301c -AppName b0111111-5555-4444-a22a-5af2574f1ed7 -AuthenticationContextIds c2
  • The conditional access policy is now connected to the canvas app.

Retrieve Dataverse records with JavaScript

When validations or manipulations in a model-driven app are too complex for a business rule you can use JavaScript instead. With JavaScript you can use the Dynamics API to gather information and/or update records. JavaScript only runs on the interface; this means that the validation or manipulation only happen when a user is interacting with the model-driven app.

retrieveRecord

With retrieveRecord you can retrieve a records form a table if you know the ID.

Xrm.WebApi.retrieveRecord("account", "a8a19cdd-88df-e311-b8e5-6c3be5a8b200", "?$select=name,revenue")

Xrm.WebApi.retrieveRecord("TABLE", "ID", "?$select=COLUMN,COLUMN")

In this example a record from the table accounts is retrieved and the columns name and revenue are returned. If it was successful the results are displayed in the console, if an error occurred then the error message is displayed in the console.

Xrm.WebApi.retrieveRecord("account", "a8a19cdd-88df-e311-b8e5-6c3be5a8b200", "?$select=name,revenue").then(
    function success(result) {
        console.log("Retrieved values: Name: " + result.name + ", Revenue: " + result.revenue);
        // perform operations on record retrieval
    },
    function (error) {
        console.log(error.message);
        // handle error conditions
    }
);

retrieveMultipleRecords

With retrieveMultipleRecords you can retrieve multiple records from a table based on a filtering.

Xrm.WebApi.retrieveMultipleRecords("account", "?$select=name,primarycontactid&$filter=primarycontactid eq a0dbf27c-8efb-e511-80d2-00155db07c77")

Xrm.WebApi.retrieveMultipleRecords("[TABLE]", "?$select=[COLUMN],[COLUMN]&$filter=[COLUMN] eq ID")

In this example three records from the table accounts are retrieved and the columns name is returned. If it was successful the results are displayed in the console, if an error occurred then the error message is displayed in the console.

Xrm.WebApi.retrieveMultipleRecords("account", "?$select=name", 3).then(
    function success(result) {
        for (var i = 0; i < result.entities.length; i++) {
            console.log(result.entities[i]);
        }
        console.log("Next page link: " + result.nextLink);
        // perform additional operations on retrieved records
    },
    function (error) {
        console.log(error.message);
        // handle error conditions
    }
);

Expand query to get related records

With the $expand options we can retrieve related records of the record that was returned, this works for both retrieveRecord and retrieveMultipleRecords. Expand uses navigation columns (relationship/lookup) to retrieve the related records.

Xrm.WebApi.retrieveRecord("account", "a8a19cdd-88df-e311-b8e5-6c3be5a8b200", "?$select=name&$expand=primarycontactid($select=contactid,fullname)")
Xrm.WebApi.retrieveRecord("[TABLE]", "ID", "?$select=[COLUMN]&$expand=[NAVIGATION COLUMN]($select=[COLUMN],[COLUMN])")

Xrm.WebApi.retrieveMultipleRecords("account", "?$select=name&$top=3&$expand=primarycontactid($select=contactid,fullname)", 3)
Xrm.WebApi.retrieveMultipleRecords("[TABLE]", "?$select=[COLUMN]&$top=3&$expand=[NAVIGATION COLUMN]($select=[COLUMN],[COLUMN])", 3)

Asynchronous function to wait on the return

When using retrieveMultipleRecords you might need to use an asynchronous function. The function needs to wait on retrieveMultipleRecords to return the values before continuing with the function. You do this by making two async functions, one with the main logic and the second one which retrieves the records.

async function xseption(formContext) {
    var xseptions = await getXseptions(companyProfileId);
    //Do something with the return
}
async function getXseptions(guid) {
    var query = "?$select=rc_categorytypeid,rc_xseptionsid&$filter=_rc_related_companyprofile_value eq " + guid + "&$expand=rc_categorytypeid($select=rc_value)";
    var result = await Xrm.WebApi.retrieveMultipleRecords("rc_xseptions", query);

    return result;
}

Embed a canvas app in a model-driven app

Did you know that you can embed (add) a canvas app in a model-driven app? With the embedded canvas app, you can fully use the power of the canvas app inside a model-driven app. In my project I used it to provide the user with the capability to search an Oracle database and select a specific company.

It is very easy to add a canvas app, but I recommend to use it only when no other options are viable. The reason for this is that the embedded canvas app needs to be reconnected every time you transfer the solution form one environment to another.

Embed the canvas app

  • Create / add a canvas app in the same environment as the model-driven app.
  • Open the form of the entity where the canvas app needs to be embedded.
  • Click on +Component and select the Canvas app.
  • Fill in the App ID Static value with the unique ID of the canvas app and click on Done.
  • You can find the App ID by right clicking on an app and clicking on Details.

Solution deployments

The canvas app is now part of the model-driven app and needs to be in the same solution. When you transfer the solution from the development environment to the test environment, you will need to update the model-driven form manually. The reason for this is that the model-driven app is still connected to the canvas app on development. You will need to change the reference / GUID to the canvass app on production. And do not forget to share the canvas app with the users.