I Wish I Would Have Known #23: Could Flow Run Succeeded after Showing Timeout Message.

It’s known behavior that, if you test a cloud flow that runs for longer than 10 minutes, you may get a timeout message in Power Automate, even though the flow continues to run in the background. If this happens, reopen the view to receive the current status.

I Wish I Would Have Known #22: Environment Type and Backup Retention Period

Changing an environment type to sandbox will immediately reduce backup retention to 7 days. If you do not need backups (restore points) older than 7 days, then you can safely switch the type. If you think you may need restore points older than 7 days, it is recommended that you keep the environment as production and consider restoring to a different environment of type sandbox. Read more on the official docs here.

I Wish I Would Have Known #20: Get the list of new apps and flows in your tenant.

Sometimes, the most useful things may go unnoticed, this FLOW template can  provide a list of new apps, flows, and connectors that have been introduced into your tenant within a configurable window. This is very useful when you are an admin and have to do some house keeping activities in the tenant.

I Wish I Would Have Known #19: Maker Portal: User Cannot See an Environment

Sharing an app with the user and, asking the user to run the app for one time will resolve this issue for most cases. I have had few instances where, new users with maker roles were unable to see the environment in the maker portal even after sharing the app. However, sharing a sample App with Co-owner permission resolved this issue.

I Wish I Would Have Known #18: Delete Permission for Model-Driven Apps

The underlying structure of Model-Driven apps are totally different than Canvas apps. Model-Driven apps are created at environment level and owned by the organization so, if you need to give delete permission for model-driven apps, it should be given at the environment level. Means, this will give the permission to delete apps created by other users as well. This is the main reason why Model-Driven app delete permission is restricted to admin roles.

I Wish I Would Have Known #17: Environment Backup Retention Periods

Backups for production environments that have been created with a database and have one or more Dynamics 365 applications installed are retained up to 28 days. Backups for production environments which do not have Dynamics 365 applications deployed in them will be retained for 7 days. Sometimes, this can be a bit confusing but, very important to understand the differences, read more from official docs here.

Authenticate Dynamics 365 in Azure Functions Version 3

I consider Azure functions as a powerful weapon in the armory in numerous scenarios, but not limited to the following:

  • Expose Dynamics 365 APIs to third-part apps in a well-wrapped manner.
  • Delegate some of the computation load from plugins to outside D365.
  • Create scheduled custom code to run on specific intervals.
  • Insufficient infrastructure to host custom services or APIs for integration.

Read more about Azure Functions here.

One challenge we face when creating a new azure function with Dynamics 365 is that the current runtime version which is 3, it uses .Net core(runtime version 2 also). It was much easier with version 1 as it uses the .Net framework and we can directly use D365 SDK for authentication and consuming 365 services. The power apps .Net core SDK is still under the alpha version and cannot be utilized for production purposes. One easy option here is to use ADAL and WEB API. In this post, let’s see how we can authenticate an application user in Azure functions V3.

Before we start, if you are not familiar with the following, please have a glance at the links provided,

  1. Create Application users in Dynamics
  2. Creating an Azure function
  3. Creating Azure functions from visual Studio / VS Code

Create your Azure function with the help of links added in point 3, and add following NuGet packages if they are not there in your function app.

Now use the following code to authenticate your Dynamics 365 application user.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace SampleFunctionApp
{
    public static class D365
    {
        [FunctionName("ConnecttoD365")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;
            #region Auth Code
            //
            string cloud = "https://login.microsoftonline.com";

            //This is the Domain!

            string tenantId = "**********";

            string authority = $"{cloud}/{tenantId}";

            // ApplicationID in the new UI

            string clientId = "*********";

            //Azure App secret Key

            string clientsecret = "******";

            ClientCredential clientcred = new ClientCredential(clientId, clientsecret);

            // Application ID of the Resource (could also be the Resource URI)

            string resource = "https://******.crm.dynamics.com/";

            AuthenticationContext ac = new AuthenticationContext(authority);
            AuthenticationResult result = null;
            var bearerToken = string.Empty;
            string ErrorMessege = string.Empty;
            try

            {
                //already having token
                result = await ac.AcquireTokenSilentAsync(resource, clientId);
                if (result != null)
                {
                    bearerToken = result.AccessToken;
                    log.LogInformation("Token Acquired:"+ bearerToken);
                }
            }

            catch (AdalException adalException)

            {//Acquire token
                if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently

                || adalException.ErrorCode == AdalError.InteractionRequired)

                {

                    result = await ac.AcquireTokenAsync(resource, clientcred);
                    if (result != null)
                    {
                        bearerToken = result.AccessToken;
                        log.LogInformation("Token Acquired="+ bearerToken);

                    }
                }
                else
                {
                    log.LogWarning("Failed to acquire Bearer Token :-" + adalException.Message);
                    var AdalException = new { adalexception = "Failed to acquire Bearer Token :-" + adalException.Message };
                    return new BadRequestObjectResult(JsonConvert.SerializeObject(AdalException));
                    throw adalException;

                }

            }

            #endregion

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a better response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

The above function simply authenticates and write bearer to the log. Ensure you do not log this in your actual code.

In the next post, we will see how we can use this token to call D365 Web API from Azure Function App.

Hope this helps.