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.
Select the users and/or group to apply the policy to
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.
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.
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.
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.
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;
}
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.
When sharing information or sending out a notification on teams I like to use the adaptive card feature. Adaptive Cards are the Teams method of sharing and displaying blocks of information in an easy-to-read manor without the complexity of customizing CSS or HTML to render them. With adaptive cards you can even create polls, show weather information, and create hyperlinks.
Creating a feedback form
Create a Power Automate flow with the required trigger.
Add the Post adaptive card in a chat or channel Teams action.
Select the Group chat (Channel or Group Chat) or provide the chat ID.
Add the following JSON code for an example feedback form with a title, single line, and multi-line text input fields and two types of choice list.
Power Automate has so many options and possibilities by clicking through the user interface. But it can do even more with the help of expressions. I think I use the following two experssion the most, empty() and first(). Thats why I wanted to share them with you.
Get the first returned value
When looking for a record in the Dataverse, but the record id is unknown we use the action List Rows. This action always returns an array of values. This even happens when only one record is returned. If you try to use the returned value to for example update the found record, then Power Automate automatically generates an apply for each loop. This can be confusing when developing the flow, why would it loop through all the values when there is just one? With the first() expression we can prevent Power Automate to automatically generates an apply for each loop.
Add the Dataverse action list rows and configure it to find the unique record.
In my example I look for the unique record based on the unique order id.
When trying to update the found record the following Apply for each will be automatically generated.
The creation of the Apply for each can be prevent by using the expression first().
Remove the Apply for each and the Update a row action.
Add a new Update a row action and open the expression tab.
Add the expression first(body(‘Internal_name_of_the_list_row_action’)?[‘value’])?[‘internal_field_name’].
This expression returns the first specified field value from the value array in the returned body of the list row action.
In my Cloud Flow I needed to check if a List rows action did not return a value. This is not an option in the drop down list, but it can be done using the empty expression.
Add the condition action.
Open the expression tab.
Add the expression empty(output(‘Internal_name_of_the_list_row_action’)?[‘body/value’]).
This checks if the returned body have any values, with other words checks if the value is empty.
When managing multiple Power Automate Cloud Flows it can easily become a big task to figure out what went wrong and where. My client has more than 10 Cloud Flows that run multiple time per day or per hour. The solution is to create a log table (dataverse) or list (SharePoint) where all the runs are stored. The log table contains all the data an administrator needs to maintain the Cloud Flows. In my example I create a Dataverse table.
The Dataverse log table
In the log table we store the following data.
The Cloud Flow
In every Cloud flow a try and catch scope is added. All the main actions are in the try scope, if one of these actions fails than the catch scope will be used.
Add a Scope with the name Try.
Add the action the Dataverse Add a new row action.
Select the log table by Table name.
Select the Log status Processing, I use Processing, Failed and Successful.
Add the Cloud Flow name to Process name.
Add the following code to the Environment field.
workflow()?['tags']?['environmentName']
Add the following code to External URL, use the code snippets for the fx code. This creates a link to the actual flow run.
workflow()?['tags']?['environmentName']
workflow()?['name']
workflow()?['run']?['name']
Set the Status Reason to Active.
Add the end of the try scope add the Dataverse Update a row action.
Select the log table by Table name.
Add the ID of the create log in the Row ID field.
Select Successful in the Log status field.
Add a Scope with the name catch.
Add the Filter Array action.
Add the result of the try scope in the From field.
result('Try')
Add the following filter Status is equal to Failed
item()?['status']
Add the Dataverse Update a row action.
Select the log table by Table name.
Add the ID of the create log in the Row ID field.
Select Failed in the Log status field.
Select Inactive in the Status field.
Add the following text to the Log details: Flow log action(s):
Add the following code to the Log details, to show the error message from the try scope.
body('Filter_results_with_failed')[0]?['outputs']
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish.AcceptRead More
Privacy & Cookies Policy
Privacy Overview
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.