Execute a Recurring Job in Microsoft Dynamics 365 with Flow

I have written about executing recurring jobs in Dynamics 365 few times in the past. Over time, I suggested different scheduling mechanisms such as Microsoft Dynamics Workflow Timeout step or Azure Scheduler, as the pattern I suggested allows changing the scheduling mechanism without impacting other solution parts.

Asynchronous Batch Process Pattern

Flow can be also used as a scheduling mechanism, one that does not require coding like Azure Function,  as it has a built in integration with Microsoft Dynamics 365 Online.
Once invoked on schedule, the executing component query Dynamics 365 for target business records and apply some business logic (Process) to each business record.

In this post I would like to demonstrate a solution for executing a recurring job in Microsoft Dynamics 365 using Flow as the scheduling component.

Sample business requirement

Weekly Leads Evaluation: once a week, disqualify all Leads that are more than 5 days old and are not rated ‘Hot’.
Add a note to each Lead record to indicate that it was disqualified by an automated process.

Walkthrough

  1. Download, Import and publish the ABP unmanaged solution.
  2. Create the Action component: Disqualify Lead Workflow

    This Workflow Rule will be applied to each valid target business record. It simply attaches a Note and changes the Lead record state to Disqualified. It can be a/synchronous, but you may want to start with a asynchronous execution to monitor the process execution.
    Make sure you define the process as an on-demand process with no automated triggers.

    Create the Action component: Disqualify Lead Workflow

  3. Define Target Business Records Query

    Using the Advanced Find, define a new query to retrieve all Lead records where Created On date is older than 5 days and are not rated ‘Hot’.
    Click to Edit Columns button and remove all possible columns to maximize the query efficiency.
    Click the Download Fetch XML button and open the resulting file with some text editor. Copy the FetchXML query text to the clipboard

    Define Target Business Records Query

  4. Create a Batch Process Record

    This record is used to orchestrate by holding a FetchXML query to define the target business records and also the Action process which will be applied on the the target business records.
    If you imported an unmanaged solution, find the Batch Process entity and set it to appear in the Settings area.
    Create a Batch Process record and paste the FetchXML query text into the Target Records textbox.
    Name the record ‘Weekly Leads Evaluation Process’.
    Select the Disqualify Lead Workflow (created in step 2) in the Process Lookup field and save the record.
    Copy the Reference Token value for the next step.

    Create a Batch Process Record

  5. Define Flow Rule

    In your tenant, navigate to the Flow area and create an empty Flow named Weekly Leads Evaluation.
    Define a Recurrence Trigger to trigger the batch process

    Define a Recurrence Trigger to trigger the batch processDefine a Recurrence Trigger to trigger the batch process

    Next, define a List records action.
    In the Filter Query text box, paste the Batch Process Reference Token value and precede it with dyn_referencetoken eq (e.g. dyn_referencetoken eq ‘LOGY8Y1W6’).

    Next, define a List records action

    Define Update record action which will automatically wrap it with Apply to each wrapper. Add a Condition before the Update Action to verify that the Flow operates only if the Batch Process record status is Scheduled. 

    Define Update record action which will automatically wrap it with Apply to each wrapper

  6. Activate

    Change the Batch Process record status to Scheduled and save.
    Activate the Flow Rule.
    You can monitor the Flow activation by navigating to the Run History area.

    You can monitor the Flow activation by navigating to the Run History area

    For each run, you can see the completion status (success/failure) and failure reason

    For each run, you can see completion status (success/failure) and failure reason

Similarly, you can now add additional scheduled batch processes. 

Implementation Details

Advertisements

Custom Code Tracing & Exception Logging in Dynamics 365 v.9

As a Solution Architect I often review Microsoft Dynamics 365 custom server and client side code.
One of the most common rejects regards tracing and exception handling mechanisms, or their absence. Some code constructs may have empty Try/Catch blocks or none at all, other catch exceptions and just re-throw. As for tracing, code often contains debugging ‘aids’ such as alerts and debugger statements or no tracing notifications at all.

Why is this a reject?

  • Unhandled raw exceptions float to UI, confusing and frustrating users while exposing internal implementation details to potential attackers
  • System Administrator is unaware of custom code exceptions unless users decide to report an error message or a bug
  • Tracing mechanism help diagnose faults and sometimes allow resolving without debugging or recompiling code

Microsoft Dynamics 365 contains a Plug-in Trace repository which contains trace and exception entries originating from Plug-in and Custom Workflow Activity components.
While this is a good built in solution, it has some drawbacks:

  • It does not support common types of custom code such as client side JavaScript and external components consuming Microsoft Dynamics API directly
  • There is no straight forward method to filter Plug-in Trace notification to search for specific text in trace/exception messages
  • No option to trigger a notification to system administrator once an exception is logged
  • No option to accurately correlate a user reported error message to a specific exception entry

In this post I would like to suggest an updated implementation of a more wholesome infrastructure for tracing and exceptions logging. Using this  infrastructure, any developer can easily log trace entries and exceptions from any custom code interacting with Microsoft Dynamics 365 application, while admins can easily monitor the application health in that aspect.

WIIFM

Why using this infrastructure?

  • Easily log trace entries and exceptions from any client and server side custom code interacting with Microsoft Dynamics 365 application in a unified manner
  • You can add processes triggered by exception Log record creation to alert system administrator regarding a fault as it occurs
  • Easily search and filter specific exceptions/trace records according to unique Reference Token provided by users/clients using Quick/Advanced Find

In the following How To Use section you can download a un/managed solution and follow setup and usage instructions.  You can view the GitHub project here.

How to use

  1. Download and import solution

    You can download a managed solution here.
    If you want to make any changes to this solution, download the un-managed version. In that case, open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area. Finally, publish the solution.

    open the solution, navigate to the Log entity and check ‘Settings’ to display the entity in the settings area

  2. Using in an entity form JavaScript code

    Add the dyn_utils.js Web Resource to the target entity form libraries collection.
    Call the LogTrace and LogException functions in your custom code.

    Add the dyn_utils.js Web Resource to the target entity form Libraries collection

        //perform some business logic including tracing and exception handling 
        ns.DoSomeBusinessLogic = function () {
            //define verbose trace entry
            var traceEntry = {
                //title
                "title": "loggingSample.DoSomeBusinessLogic",
                //description
                "description": "Start execution",
                //related business record id 
                "relatedBusinessRecordId": "2514FC63-9E58-4D1E-8226-69256D0197E3",
                //related business record URL
                "relatedBusinessRecordURL": "https://xxx.crm.dynamics.com/main.aspx?etc=8&extraqs=&histKey=520872127&id=%7b2514FC63-9E58-4D1E-8226-69256D0197E3%7d&newWindow=true&pagetype=entityrecord#923042",
                //set current user id as related user id 
                "relatedUserId": getContext().userSettings.userId
            };
    
            //trace function execution start asynchronously
            Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);
    
            try {
                //some business logic including some exception 
                NonExistingFunctionCall();
            }
            catch (err) {
                //define verbose exception entry
                var exceptionEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": Constants.MSG_GENERAL_FAILURE,
                    //related business record id 
                    "errorMessage": err.message,
                    //related business record URL
                    "stackTrace": err.stack
                };
    
                //log exception and return reference token
                var referenceToken = Utils.LogException(exceptionEntry, false);
    
                //notify user with a reference token to report back to admin
                alert(Constants.MSG_GENERAL_FAILURE + referenceToken);
            }
            finally {
                //define thin trace entry 
                var traceEntry = {
                    //title
                    "title": "loggingSample.DoSomeBusinessLogic",
                    //description
                    "description": "Start execution",
                };
    
                //trace function exectuion end asynchronously
                Utils.LogTrace(traceEntry, true, SuccessHandler, FailureHandler);    
            }
        }
    
  3. Using in Web Resource JavaScript code

    You can view the code in action by launching the dyn_loggingSample.htm Web Resource (replace organization base address)
    https://YOUR_ORG_NAME.crm.dynamics.com//WebResources/dyn_loggingSample.htm

    You can view the code in action by launching the dyn_loggingSample.htm Web

    Make sure you include the dyn_utils.js as a referenced resource:

    
    
        
        Logging Sample
        
        
        
        
    
    

    The sample code in the dyn_loggingSample.js demonstrates tracing and exception handling, same as in the code sample above.

  4. Using in Plug-in/Custom Workflow Activity/external SDK client

    The following code sample represents usage in Plug-in and is similar to CWA and external SDK client usage:

        public class LogTraceAndException : IPlugin
        {
            public void Execute(IServiceProvider serviceProvider)
            {
                // Obtain the execution context from the service provider
                IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
                // Get a reference to the organization service
                IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService organizationService = factory.CreateOrganizationService(context.UserId);
    
                //define a trace entry, all attributes are optional except title 
                Log traceEntry1 = new Log
                {
                    title = "ServerSideLogging: Execute",
                    description = "Execution end",
                    //replace user id with an valid one
                    relatedUser = new EntityReference("systemuser", 
                        Guid.Parse("37ACCC73-0FCE-474B-A206-19C08163438B")),
                    relatedBusinessRecordURL = "https://YOURORGNAME.crm.dynamics.com/main.aspx?etc=1&extraqs=&histKey=87055256&id=%7b381FA757-60D7-E811-A97E-000D3AB20035%7d",
                };
    
                //log trace entry, get reference token back 
                string refToken1 = GenericLogging.Log(traceEntry1, 
                    GenericLogging.LogType.Trace, organizationService);
    
                try
                {
                    //some exception raising code.
                    //If transaction is rolled back, Log entries will persist
                    throw new Exception("An error occurred, please notify the System Administrator");
                }
                catch (Exception ex)
                {
                    //define an exception entry 
                    Log excptionEntry = new Log
                    {
                        title = "ServerSideLogging: Execute",
                        errorMessage = ex.Message,
                        stackTrace = ex.StackTrace
                    };
    
                    //log exception entry, get reference token back
                    string refToken2 = GenericLogging.Log(excptionEntry,
                        GenericLogging.LogType.Exception,
                        organizationService);
    
                    throw new InvalidPluginExecutionException(
                        string.Format("An error occurred, please notify the System Administrator. Reference token: {0}", 
                        refToken2));
                }
            }
        }
    

    A resulting exception raised from the Plug-in code includes the reference token. Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    A resulting exception includes the reference token

    Once reported to an Admin, he can easily find the relevant Log entry using Quick Find

    View Log details

Implementation Notes

I have written about the subject in the past and the described approach has not changed much. The implementation has been updated to leverage version 9 features (currently available on for Online implementations)

What has changed since last version:

  • Using Action definition to activate, deactivate and control logging level
  • Using built-in auto-numbering attribute to generate reference token, so a designated CWA component is no longer required
  • Optional usage of client side asynchronous logging operation to keep UX uninterrupted

In order to support logging from transactional components (such as Plugin registered to pre/post operation stages), the Log method in GenericLogging.cs file is using the ExecuteMultipleRequest class to execute the dyn_Log request.
As the ExecuteMultipleRequest instance is external to the Plugin transaction, it manages to create a Log record without the Plugin transaction rolling it back.
Using the Execute method directly with the dyn_LogRequest request instead, would have executed the Log Action, but the created Exception record would have been deleted by the Plugin transaction rollback, leaving no trace.

Custom Trace & Exception Logging diagram

 

Drag & Drop File Upload Revisited

Finally found the time for overdue maintenance on the Drag & Drop solution I created two years ago.

Why revisit?

First of all, as the CodePlex platform, previous home of this solution, is being decommissioned, downloading the component got the whole CodePlex project and few visitors commented that they could not find the actual solution.
So now you can download an unmanaged solution from it’s  new home @ Github.

Second, I fixed a major bug related to the plural name of some entities.
For most entities, appending ‘s’ to the entity schema name would result in the matching entity name for Web API. Some entities, like opportunity, do not match this pattern.

Third, I wanted to update the Web API version to 9.0 and to make sure the solution works with Dynamics 365 Online current version.

Drag and Drop demo

Microsoft Portal – Retrieve Dynamics 365 Data Asynchronously – Part 2

In part 1 of this post I demonstrated building and using a service which receives a FetchXML query and returns Dynamics 365 data to any Portal page in an asynchronous manner as a JSON object. This service is similar to the SDK’s RetrieveMultiple message.
In this part 2, I’ll demonstrate a different service, which like the SDK Retrieve message, receives a record type, record id (GUID) and columns set to return the required data as a JSON object.
This is useful when you already have a specific Dynamics 365 record id at hand and you want to retrieve additional data for this record.

  1. Create a Web Template  named RetrieveService

    Set the Mime Type is to application/json.
    create web template

    Paste the following code in to the web template source and save.

    {% comment %} test for required parameters {% endcomment %}
    {% if request.params[‘entityname’] and request.params[‘id’] and request.params[‘columnset’] %}

    {% capture msg_no_data %}No data for specified attribute {% endcapture %}

    {% comment %} extract parameters {% endcomment %}
    {% comment %} extract target entity {% endcomment %}
    {% assign entityname = request.params[‘entityname’] %}
    {% comment %} extract target record id {% endcomment %}
    {% assign id = request.params[‘id’] %}
    {% comment %} parse requested attributes into array {% endcomment %}
    {% assign columnset = request.params[‘columnset’] | split: “,” %}

    {% comment %} query for target entity  {% endcomment %}
    {% assign item = entities[request.params[‘entityname’]][request.params[‘id’]] %} 

    {% comment %} Emit JSON response {% endcomment %}
        {% if item %}{
            {% comment %} Iterate throguh requestd attributes array {% endcomment %}
            {% for att in columnset %}
                {% comment %} Handle optionset attribute {% endcomment %}
                {% if item[att].label %}”{{ att }}”:”{{ item[att].label | default: msg_no_data }}”
                {% comment %} Handle lookup attribute {% endcomment %}
                {% elseif item[att].name %}”{{ att }}”:{“name”:”{{ item[att].name | default: msg_no_data }}”,”id”:”{{ item[att].id | default: msg_no_data }}”}                           
                {% comment %} Handle other attributes {% endcomment %}
                {% else %}”{{ att }}”:”{{ item[att] | default: msg_no_data }}”
                {% endif %}{% unless forloop.last %},{% endunless %}
            {% endfor -%}                   
        }   
        {% endif %}

    {% comment %} handle missing parameters {% endcomment %}
    {% else %}
      { “error”:”Missing one or more required parameter(s): entityname, id, columnset” }
    {% endif %}

  2. Create a Page Template named RetrieveService
    Map the Page Template to the Web Template created on step 1 above.
    Uncheck the Header and Footer checkbox.

    image

  3. Create a Portal Page named RetrieveService
    Set Home as parent page.
    Map the page to the Page Template created on step 2 and copy the partial URL which is used in the next step

    create a portal page

  4. Consume Service

    Use the following code sample anywhere you need asynchronous data retrieval of from Dynamics 365. In my example it is located in the Home page JavaScript portion.
    Note the URL address which maps to the Page (created above) URL.
    Replace the entityname, id and columnset parameters.
    Most important: make sure your users have the required Entity Permissions for the entities queried, otherwise they will receive empty results.

    $(function (){
      //define asynchronous request from any portal page
        $.ajax({
            method: “POST”,       
            url: “/retrieveservice/”,
            data: {
                //define entity name, id and columnset 
              entityname: ‘incident‘,
              id: ‘D2A697CD-D3C7-E811-A965-000D3AB0F1D7‘,
              columnset: ‘ticketnumber,title,createdon,incidentid,statecode,caseorigincode,customerid,ownerid‘  
            }
        }).done(function (msg){
            console.log(msg);
        }).fail(function (jqXHR, textStatus, errorThrown){
            console.log(textStatus);
        });
    });

    As the sample code logs the resulting JSON object to the console, open the browser developers tool (F12) to view it.

    Resulting JSON object

Microsoft Portal – Retrieve Dynamics 365 Data Asynchronously – Part 1

Leveraging Colin Vermander brilliant article on using Liquid Templates to return JSON, I would like to demonstrate creating and using a ‘service’ to asynchronously retrieve Dynamics 365 data into any portal page.

Why is this useful?

Liquid Templates tags are rendered on server side before a response is returned to the browser, so FetchXML tag will return a static result once the page is returned.
But what about responding dynamically to client side events such as option selection, button click or expending an element to view more details?
Sending the page to the server again is no longer an option if you want to maintain a decent UX. Having an endpoint that can be called asynchronously to return data from Dynamics 365 can certainly help here. The next walkthrough explains how to do just that.
In Part 2, I’ll demonstrate a similar service aimed to support Retrieve requests (rather than RetrieveMultiple).

  1. Create a Web Template named FetchXMLServiceMake sure that the Mime Type is set to application/json

    Create Web Template

    Paste the following code in to the web template source and save

    {% comment %} test for required FetchXML query {% endcomment %}
    {% if request.params[‘query’] %}
    {% capture msg_no_data %}No data or attribute was not included in query{% endcapture %}
    {% comment %} get FetchXML query from request params, decode query XML and execute query {% endcomment %}
    {% fetchxml query %}
    {{ request.params[‘query’] | replace: ‘%3C’, ‘<‘ | replace: ‘%20’, ‘ ‘ | replace: ‘%3D’, ‘=’ | replace: ‘%3E’, ‘>’ | replace: ‘%2F’, ‘/’ }}
    {% endfetchxml %}
    {% comment %} parse requested attributes into array {% endcomment %}
    {% assign columnset = request.params[‘columnset’] | split: “,” %}
    {% comment %} Emit JSON response {% endcomment %}
    {
    “totalcount”: {{ query.results.total_record_count }},
    “morerecords”: {{ query.results.more_records }},
    “results”: [
    {% for item in query.results.entities %}
    {
    {% comment %} Iterate throguh requestd attributes array {% endcomment %}
    {% for att in columnset %}
    {% comment %} Handle optionset attribute {% endcomment %}
    {% if item[att].label %}”{{ att }}”:”{{ item[att].label | default: msg_no_data }}”
    {% comment %} Handle lookup attribute {% endcomment %}
    {% elseif item[att].name %}”{{ att }}”:{“name”:”{{ item[att].name | default: msg_no_data }}”,”id”:”{{ item[att].id | default: msg_no_data }}”}
    {% comment %} Handle other attributes {% endcomment %}
    {% else %}”{{ att }}”:”{{ item[att] | default: msg_no_data }}”
    {% endif %}{% unless forloop.last %},{% endunless %}
    {% endfor -%}
    }
    {% unless forloop.last %},{% endunless %}{% endfor -%}
    ]
    }
    {% comment %} handle no FetchXML query {% endcomment %}
    {% else %}
    { “error”:”No FetchXML query” }
    {% endif %}
  2. Create a Page Template named FetchXMLService 

    Map the Page Template to the Web Template created on step 1 above.
    Uncheck the Header and Footer checkbox.Create page template

  3. Create a Portal Page named FetchXMLService 

    Set Home as parent page.
    Map the page to the FetchXMLService Page Template and copy the partial URL which is used in the next stepcreate page

  4. Consume Service 

    Use the following code sample anywhere you need asynchronously retrieval of data from Dynamics 365. In my example it is located in the Home page JavaScript portion.
    Note the URL address which maps to the Page (created above) URL.
    Replace the FetchXML query with your own query and specify the attributes you want to get values.
    Most important: make sure your users have the required Entity Permissions for the entities queried, otherwise they will receive empty results.

    $(function (){
    //define asynchronous request from any portal page
    $.ajax({
    method: “POST”,
    url: “/fetchxmlservice/“,
    data: {
    //define FetchXML query
    query: encodeURIComponent(
    “<fetch count=’10’ returntotalrecordcount=’true’>” +
    “<entity name=’incident’>” +
    “<attribute name=’ticketnumber’/>” +
    “<attribute name=’prioritycode’ />” +
    “<attribute name=’title’ />” +
    “<attribute name=’createdon’/>” +
    “<attribute name=’customerid’/>” +
    “<attribute name=’ownerid’/>” +
    “<attribute name=’statecode’/>” +
    “<attribute name=’incidentid’/>” +
    “<attribute name=’caseorigincode’/>” +
    “<order attribute=’title’ descending=’false’ />” +
    “</entity>” +
    “</fetch>”
    ),
    //define attributes collection to return values from query
    columnset: “ticketnumber,title,createdon,incidentid,statecode,caseorigincode,customerid,ownerid
    }
    }).done(function (msg){
    console.log(msg);
    }).fail(function (jqXHR, textStatus, errorThrown){
    console.log(textStatus);
    });
    });

    As the sample code logs the resulting JSON object to the console, open the browser developers tool (F12) to view it.

returend object

Implementation Notes

  • The service is useful for simple FetchXML queries. For more complex queries including linked entities, you’ll need to upgrade the Web Template code to handle more complex query results
  • Liquid Templates language includes a url_decode function but it seem unavailable in the Portal language variation. Hence the explicit decoding in the Web Template

Animated Tip: Create a Fully Functional Portal Page in 60 Sec. (Or Less)

The Create Portal Content wizard is a nice new feature in Portal version 8.3 that was published recently. This wizard simplifies the process of creating webpages along with entity forms and entity lists by admins.

If you are not familiar with the process of provisioning new pages in Microsoft Dynamics 365 Portal, you can do some reverse engineering of the Portal elements automatically created by the wizard.

In the following demo, I am creating a new Page for authenticated Portal users which displays Lead records with an option to create new record. It assumes that the user has the required Entity Permission for the Lead entity.

Click here to view a larger version of this animated tip

creating a new Page for authenticated Portal users which displays Lead records with an option to create new record

Execute a Recurring Job in Microsoft Dynamics 365 with Azure Scheduler

The requirement for recurring job execution is quite common in Microsoft Dynamics implementations. Here are some of the business requirements I have encountered:

  • Send monthly newsletter to target customers
  • Synchronize MSCRM Users details with Active Directory once a day
  • Once a month, disqualify all leads that have no open activities
  • Once every hour, export Appointments from mail server and import into Dynamics 365

Microsoft Dynamics 365 has no reliable built in scheduling mechanism that can be leveraged for custom solutions. The Asynchronous Batch Process Pattern I have written about in the past can be used with daily recurring jobs but when it comes to hours resolution and less, it becomes unsteady.

Azure Scheduler is a reliable service that can run jobs in or out of Azure on a predefined schedule, multiple times or just once. So why not harness this mechanism to schedule Microsoft Dynamics 365 recurring jobs?

In this post, I’ll demonstrate how to use Azure Scheduler to execute a recurring job in Microsoft Dynamics 365.

Sample business requirement

Each day, automatically email a birthday greeting to contacts whose birthday occurs on that same day.

Implementation Details

Here are the solution main components:

  1. Custom Action dyn_SendBirthdayGreetings: activates a Custom Workflow Activity SendBirthdayGreeting which Retrieve all relevant Contact records by birthdate, creates and sends an email for each contact record.
  2. Azure Function BirthdayGreetingsFunction: invokes the dyn_SendBirthdayGreetings Custom Action via Microsoft Dynamics 365 API.
  3. Azure Scheduler BirthdayGreetingsSchduler: set to execute once a day at 9:00 for unlimited occurrences and invokes the BirthdayGreetingsFunction Azure Function

Architectural Notes

Why using Custom Action? Although it is possible to manage the required business logic in Azure Function, Dynamics related business logic should reside within Dynamics, managed and packaged in a Solution. This way, the Scheduling and Executing components are kept agnostic and isolated as possible and therefore easily maintained.
Having said that, you should be aware of the Sandbox Execution Timeout Limitation and consider using Custom Workflow Activity after assessing the business logic at hand.

Implementation Steps:

  1. Define dyn_SendBirthdayGreetings Custom Action

    Download, import and publish the unmanaged BirthdayGreetingAutomationSample_1_0_0_0 solution.
    It contains a Custom Action called dyn_sendBirthdayGreeting which will be later called from the BirthdayGreetingsFunction Azure Function.
    By default, the Custom Action will create a birthday greeting email but will not send it. To change this, disable the Custom Workflow Activity, edit the only step and change the Send greeting email after creation? property value to true. Note that this may actually send email to your contacts.
    The SendBirthdayGreeting Custom Workflow Activity code can be found here.

  2. Define BirthdayGreetingsFunction Azure Function

    After creating a Function App (follow the first 3 steps here), create a new C# Function of type Generic webhook under your Function App functions collection, name it SendBirthdayGreetingFunction

    Create a new Function of type Generic webhook under your Function App collection, name it SendBirthdayGreetingFunction

    name it SendBirthdayGreetingFunction
    Click the App Service Editor option in the Application Settings tab

    Click the App Service Editor option in the Application Settings tab

    Add a new file under your Function root, name it project.json. Copy the following JSON snippet into the text editor

    Add a new file under your Function root, name it project.json. Copy the following JSON snippet into the text editor

    {
    	"frameworks": 
    	{
    		"net46":
    		{
    			"dependencies": 
    			{
    				"Microsoft.CrmSdk.CoreAssemblies": "9.0.0.7",
    				"Microsoft.CrmSdk.XrmTooling.CoreAssembly": "9.0.0.7"
    			}
    		}
    	}
    }
    

    Close the App Service Editor to get back to your function. Selecting your Function App, click the Application settings tab.

    Close the App Service Editor to get back to your function. Selecting your Function App, click the Application settings tab

    Scroll down to the Connection strings area and add a new Connection string named TargetOrganization. This will be used to connect and authenticate to your Microsoft Dynamics 365 Organization.
    For the connection string value, set your organization details in the following format:

    AuthType=Office365;Username=XXX@ORGNAME.onmicrosoft.com;Password=PASSWORD;Url=https://ORGNAME.crm.dynamics.com

    Note the data center targeted, crm.dynamics.com is targeting an organization in North America data center.

    Click Save to save the newly created Connection String.

    Scroll down to the Connection strings area and add a new Connection string named TargetOrganization
    Navigate back to your SendBirthdayGreetingFunction function and  replace the default Function code with the following code snippet.
    Note that code is executing a Custom Action named dyn_SendBIrthdayGreetings.It also uses the TargetOrganization connection string when accessing Microsoft Dynamics 365 API.

    using System.Net;
    using System.Configuration;
    using Microsoft.Xrm.Sdk;
    using Microsoft.Xrm.Sdk.Client;
    using Microsoft.Xrm.Tooling.Connector;
    
    public static HttpResponseMessage Run(HttpRequestMessage req, TraceWriter log)
    {
        string actionResponse = string.Empty;
    
        //define Dynamics Custom target Action name 
        string actionName = "dyn_SendBirthdayGreetings";
    
        log.Info("Processing new SendBirthdayGreetingFunction request");
    
        //init Dynamics connection, authenticate and get referent to the Organization Service
        IOrganizationService organizationService = InitDynamicsConnection();
    
        //execute Custom Action  
        OrganizationRequest sendBirthdayGreetingsReq = new OrganizationRequest(actionName);
        OrganizationResponse sendBirthdayGreetingsRes = 
            (OrganizationResponse)organizationService.Execute(sendBirthdayGreetingsReq);
    
        //return completion status response 
        return actionResponse == null
            ? req.CreateResponse(HttpStatusCode.BadRequest, "An error occured")
            : req.CreateResponse(HttpStatusCode.OK, "Action completed successfully");
    }
    
    //init Dynamics connection
    private static IOrganizationService InitDynamicsConnection()
    {
        IOrganizationService result = null;
    
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
        CrmServiceClient client = new CrmServiceClient(
            ConfigurationManager.ConnectionStrings["TargetOrganization"].ConnectionString);
    
        result = client.OrganizationServiceProxy;
    
        return result;
    }
    

    Navigate to the Function Integrate area and set the Mode to standard. This will enable the consumption of the function using HTTP GET method.

    Navigate to the Function Integrate area and set the Mode to standard. This will enable the consumption of the function using HTTP GET method

    Back at your Function code editor, expand the collapsed Test pane and set the HTTP method to GET. Click Run to test your function. If all went well, a success message will be returned.

    Back at your Function code editor, open expand the collapsed Test pane and set the HTTP method to GET. Click Run to test your function. If all went well, a success message will be returned.

    Finally, click the </> Get function URL button on top and copy your function endpoint address.

  3. Define BirthdayGreetingSchduler Azure Scheduler

    Click the + New button on the left menu, type in Scheduler and select the Scheduler element returned. Click Create

    Click the + New button on the left menu, type in Scheduler and select the Scheduler element returned

    Define the scheduler name to BirthdayGreetingSchduler.

    Define the scheduler name to BirthdayGreetingSchduler

    Click the Action settings tile. Set the Action to Https, method to Get and paste in the Function URL you copied at the end of the step 2 above. Click OK

    Click the Action settings tile. Set the Action to Https, method to Get and paste in the Function URL you copied at the end of the step 2 above. Click OK

    Click the Schedule tile and set the schedule as Recurring, frequency to Days and End to Never. Click the Advanced schedule pane and set 9 in the Hours text box. Click Ok.
    This will trigger your function every day at 9:00 for unlimited number of times.
    Check Pin to dashboard and click Create.

    This will trigger your function every day at 9:00 for unlimited number of times.

    After a few seconds, you will be navigated to the Scheduler management area.

  4. Test

    To test the complete solution, update a Microsoft Dynamics 365 test Contact record birthdate to match the day and month of the current date.

    Go back to your Scheduler Job and click Run now.

    Go back to your Scheduler Job and click Run now.

    Clicking the History tab, you can monitor the Schedule job completion status

    Clicking the History tab, you can monitor the Schedule job completion status

    Refreshing the Sample Contact activities list, you should be able to view the newly created Birthday greeting email

    Refreshing the Sample Contact activities list, you should be able to view the newly created email