<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[demonstrations - Alexander Development]]></title><description><![CDATA[demonstrations - Alexander Development]]></description><link>https://alexanderdevelopment.net/</link><image><url>https://alexanderdevelopment.net/favicon.png</url><title>demonstrations - Alexander Development</title><link>https://alexanderdevelopment.net/</link></image><generator>Ghost 1.20</generator><lastBuildDate>Fri, 24 Apr 2026 14:20:47 GMT</lastBuildDate><atom:link href="https://alexanderdevelopment.net/tag/demonstrations/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Scheduling Dynamics 365 workflows with Azure Functions and C#]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Over the past few days, I've shared two approaches for scheduling Dynamics 365 workflows using Azure Functions and the Dynamics 365 Web API. One uses <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">Node.js</a>, and the other uses <a href="https://alexanderdevelopment.net/post/2016/11/29/scheduling-dynamics-365-workflows-with-azure-functions-and-python/">Python</a>. Because most Dynamics CRM developers are probably more familiar with C# than Node.js or Python, I also</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/29/scheduling-dynamics-365-workflows-with-azure-functions-and-csharp/</link><guid isPermaLink="false">5a5837246636a30001b97878</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Dynamics 365]]></category><category><![CDATA[Azure]]></category><category><![CDATA[demonstrations]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Wed, 30 Nov 2016 02:00:00 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-29_13-07-59.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-29_13-07-59.png" alt="Scheduling Dynamics 365 workflows with Azure Functions and C#"><p>Over the past few days, I've shared two approaches for scheduling Dynamics 365 workflows using Azure Functions and the Dynamics 365 Web API. One uses <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">Node.js</a>, and the other uses <a href="https://alexanderdevelopment.net/post/2016/11/29/scheduling-dynamics-365-workflows-with-azure-functions-and-python/">Python</a>. Because most Dynamics CRM developers are probably more familiar with C# than Node.js or Python, I also created an equivalent C# version. Just like with my previous examples, this version calls the Web API directly instead of using any SDK assemblies.</p>
<p>Here's my code. It does the following:</p>
<ol>
<li>Request an OAuth token using a username and password.</li>
<li>Query the Dynamics 365 Web API for accounts with names that start with the letter &quot;F.&quot;</li>
<li>Execute a workflow for each record that was retrieved in the previous step. The workflow that I am executing is the same workflow I used in my <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">Node.js example</a> to create a note on an account.</li>
</ol>
<pre><code>#r &quot;Newtonsoft.Json&quot;

using System;
using System.Net;
using System.IO;
using Newtonsoft.Json;

//set these values to retrieve the oauth token
static string crmorg = &quot;https://CRMORG.crm.dynamics.com&quot;;
static string clientid = &quot;00000000-0000-0000-0000-000000000000&quot;;
static string username = &quot;xxxxxx@xxxxxxxx&quot;;
static string userpassword = &quot;xxxxxxxx&quot;;
static string tokenendpoint = &quot;https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token&quot;;

//set these values to query your crm data
static string crmwebapihost = &quot;https://CRMORG.api.crm.dynamics.com/api/data/v8.2&quot;;
static string crmwebapipath = &quot;/accounts?$select=name,accountid&amp;$filter=startswith(name,'F')&quot;;

static string workflowid = &quot;DC8519EC-F3CE-4BC9-BB79-DF2AD70217A1&quot;;

public static void Run(TimerInfo myTimer, TraceWriter log)
{
	//build the authorization request
	var reqstring = &quot;client_id=&quot; + clientid;
	reqstring += &quot;&amp;resource=&quot; + Uri.EscapeUriString(crmorg);
	reqstring += &quot;&amp;username=&quot; + Uri.EscapeUriString(username);
	reqstring += &quot;&amp;password=&quot; + Uri.EscapeUriString(userpassword);
	reqstring += &quot;&amp;grant_type=password&quot;;

	WebRequest req = WebRequest.Create(tokenendpoint);
	req.ContentType = &quot;application/x-www-form-urlencoded&quot;;
	req.Method = &quot;POST&quot;;
	byte[] bytes = System.Text.Encoding.ASCII.GetBytes(reqstring);
	req.ContentLength = bytes.Length;
	System.IO.Stream os = req.GetRequestStream();
	os.Write(bytes, 0, bytes.Length);
	os.Close();

	HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
	StreamReader tokenreader = new StreamReader(resp.GetResponseStream());
	string responseBody = tokenreader.ReadToEnd();
	tokenreader.Close();
	var tokenresponse = JsonConvert.DeserializeObject&lt;Newtonsoft.Json.Linq.JObject&gt;(responseBody);
	var token = tokenresponse[&quot;access_token&quot;];
	log.Info(&quot;got token&quot;);

	WebRequest crmreq = WebRequest.Create(crmwebapihost + crmwebapipath);
	crmreq.Headers = new WebHeaderCollection();
	crmreq.Headers.Add(&quot;Authorization&quot;, &quot;Bearer &quot; + token);
	crmreq.Headers.Add(&quot;OData-MaxVersion&quot;, &quot;4.0&quot;);
	crmreq.Headers.Add(&quot;OData-Version&quot;, &quot;4.0&quot;);
	crmreq.Headers.Add(&quot;Prefer&quot;, &quot;odata.maxpagesize=500&quot;);
	crmreq.Headers.Add(&quot;Prefer&quot;, &quot;odata.include-annotations=OData.Community.Display.V1.FormattedValue&quot;);
	crmreq.ContentType = &quot;application/json; charset=utf-8&quot;;
	crmreq.Method = &quot;GET&quot;;

	HttpWebResponse crmresp = (HttpWebResponse)crmreq.GetResponse();
	StreamReader crmreader = new StreamReader(crmresp.GetResponseStream());
	string crmresponseBody = crmreader.ReadToEnd();
	crmreader.Close();
	var crmresponseobj = JsonConvert.DeserializeObject&lt;Newtonsoft.Json.Linq.JObject&gt;(crmresponseBody);
	foreach(var row in crmresponseobj[&quot;value&quot;].Children())
	{
		log.Info(row[&quot;name&quot;].ToString());
		runWorkflow(token.ToString(), new Guid(row[&quot;accountid&quot;].ToString()), log);

	}

	Console.ReadLine();
}

static void runWorkflow(string token, Guid entityid, TraceWriter log)
{
	var crmwebapiworkflowpath = &quot;/workflows(&quot; + workflowid + &quot;)/Microsoft.Dynamics.CRM.ExecuteWorkflow&quot;;
	WebRequest req = WebRequest.Create(crmwebapihost + crmwebapiworkflowpath);

	log.Info(&quot;  calling workflow for &quot; + entityid);

	string reqobject = &quot;{ \&quot;EntityId\&quot;: \&quot;&quot; + entityid + &quot;\&quot;}&quot;;
    
	req.Headers.Add(&quot;Authorization&quot;, &quot;Bearer &quot; + token);
	req.Headers.Add(&quot;OData-MaxVersion&quot;, &quot;4.0&quot;);
	req.Headers.Add(&quot;OData-Version&quot;, &quot;4.0&quot;);
	req.Headers.Add(&quot;Prefer&quot;, &quot;odata.maxpagesize=500&quot;);
	req.Headers.Add(&quot;Prefer&quot;, &quot;odata.include-annotations=OData.Community.Display.V1.FormattedValue&quot;);
	req.ContentType = &quot;application/json; charset=utf-8&quot;;
    req.Method = &quot;POST&quot;;
	
	byte[] bytes = System.Text.Encoding.ASCII.GetBytes(reqobject);
	req.ContentLength = bytes.Length;
	System.IO.Stream os = req.GetRequestStream();
	os.Write(bytes, 0, bytes.Length);
	os.Close();

	HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
	StreamReader reader = new StreamReader(resp.GetResponseStream());
	string responseBody = reader.ReadToEnd();
	reader.Close();
	
    var responseobj = JsonConvert.DeserializeObject&lt;Newtonsoft.Json.Linq.JObject&gt;(responseBody);
	if(resp.StatusCode == HttpStatusCode.OK)
	{
		log.Info(&quot;    success &quot; + entityid.ToString());
	}
	else
	{
		log.Info(&quot;    error &quot; + entityid.ToString());
	}
}
</code></pre>
<p>To set this up in your Azure tenant, set up a Functions App and a new C# timer trigger function like I described in the <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">Node.js example</a>. Copy the C# code from above and paste it into the function editor window. Set any specifics relative to your Dynamics 365 organization and click save. That's all there is to it.</p>
<p>*<em>If you're wondering about that <code>#r &quot;Newtonsoft.Json&quot;</code> at the top of the C# code, take a look <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#referencing-external-assemblies">here</a>.</em></p>
</div>]]></content:encoded></item><item><title><![CDATA[Scheduling Dynamics 365 workflows with Azure Functions and Python]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Last week I shared a <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">solution</a> for Scheduling Dynamics 365 workflows with Azure Functions and Node.js. In this post, I will show how to achieve equivalent functionality using Python. The actual Python code is simpler than my Node.js example, but the Azure Functions configuration is much more complicated.</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/29/scheduling-dynamics-365-workflows-with-azure-functions-and-python/</link><guid isPermaLink="false">5a5837246636a30001b97872</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Dynamics 365]]></category><category><![CDATA[Python]]></category><category><![CDATA[Azure]]></category><category><![CDATA[demonstrations]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Tue, 29 Nov 2016 12:00:00 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/11/06-2.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/11/06-2.png" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"><p>Last week I shared a <a href="https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/">solution</a> for Scheduling Dynamics 365 workflows with Azure Functions and Node.js. In this post, I will show how to achieve equivalent functionality using Python. The actual Python code is simpler than my Node.js example, but the Azure Functions configuration is much more complicated.</p>
<p>First, here's the Python script I am using. It does this following:</p>
<ol>
<li>Request an OAuth token using a username and password.</li>
<li>Query the Dynamics 365 Web API for accounts with names that start with the letter &quot;F.&quot;</li>
<li>Execute a workflow for each record that was retrieved in the previous step. The workflow that I am executing is the same workflow I used in my Node.js example to create a note on an account.</li>
</ol>
<pre><code>import os
import sys
cwd = os.getcwd()
sitepackage = cwd + &quot;\site-packages&quot;
sys.path.append(sitepackage)

import requests
import json

#set these values to retrieve the oauth token
crmorg = 'https://CRMORG.crm.dynamics.com' #base url for crm org  
clientid = '00000000-0000-0000-0000-000000000000' #application client id  
username = 'xxxxxx@xxxxxxxx' #username  
userpassword = 'xxxxxxxx' #password  
tokenendpoint = 'https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/token' #oauth token endpoint

#set these values to query your crm data
crmwebapi = 'https://lucasdemo01.api.crm.dynamics.com/api/data/v8.2'
crmwebapiquery = &quot;/accounts?$select=name,accountid&amp;$filter=startswith(name,'f')&quot;

workflowid = 'DC8519EC-F3CE-4BC9-BB79-DF2AD70217A1'; #guid for the workflow you want to execute

def start():
    #build the authorization request
    tokenpost = {
        'client_id':clientid,
        'resource':crmorg,
        'username':username,
        'password':userpassword,
        'grant_type':'password'
    }

    #make the token request
    print('requesting token . . .')
    tokenres = requests.post(tokenendpoint, data=tokenpost)
    print('token response received. . .')

    accesstoken = ''

    #extract the access token
    try:
        print('parsing token response . . .')
        accesstoken = tokenres.json()['access_token']
        #print('accesstoken is - ' + accesstoken)

    except(KeyError):
        print('Could not get access token')

    if(accesstoken!=''):
        crmrequestheaders = {
            'Authorization': 'Bearer ' + accesstoken,
            'OData-MaxVersion': '4.0',
            'OData-Version': '4.0',
            'Accept': 'application/json',
            'Content-Type': 'application/json; charset=utf-8',
            'Prefer': 'odata.maxpagesize=500',
            'Prefer': 'odata.include-annotations=OData.Community.Display.V1.FormattedValue'
        }

        print('making crm account request . . .')
        crmres = requests.get(crmwebapi+crmwebapiquery, headers=crmrequestheaders)
        print('crm account response received . . .')
        try:
            print('parsing crm account response . . .')
            crmresults = crmres.json()
            for x in crmresults['value']:
                print (x['name'] + ' - ' + x['accountid'])
                runWorkflow(accesstoken, x['accountid'])
        except KeyError:
            print('Could not parse CRM account results')

        
def runWorkflow(token, entityid):
    crmwebapiworkflowpath = &quot;/workflows(&quot;+workflowid+&quot;)/Microsoft.Dynamics.CRM.ExecuteWorkflow&quot;

    #set the web api request headers
    requestheaders = { 
        'Authorization': 'Bearer ' + token,
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=utf-8'
    };
    
    reqobj = {'EntityId': entityid}
    
    print('  calling workflow for ' + entityid)
	
    crmres = requests.post(crmwebapi+crmwebapiworkflowpath, headers=requestheaders, data=json.dumps(reqobj))
    if(crmres.status_code == requests.codes.ok):
        print('    success ' + entityid)
    else:
        print('    error ' + entityid)

start()

</code></pre>
<p>To set it up as an Azure Function, after you have either created a new Function app or opened an existing Function app, do the following:</p>
<ol>
<li>
<p>Create a new function. Select &quot;create your own custom function&quot; at the bottom. <img src="https://alexanderdevelopment.net/content/images/2016/11/01-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Set language to &quot;Python&quot; and scenario to &quot;Experimental.&quot; Currently there is no timer trigger template for Python, but you can work around that by selecting the &quot;QueueTrigger-Python&quot; template in this step and manually changing the trigger later. <img src="https://alexanderdevelopment.net/content/images/2016/11/02-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Give your function a name and click create. Ignore the queue name and storage account connection values. <img src="https://alexanderdevelopment.net/content/images/2016/11/03-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Once the function is created, select the &quot;integrate&quot; tab from the menu on the left and delete the Azure Queue Storage trigger. <img src="https://alexanderdevelopment.net/content/images/2016/11/04-2.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Click &quot;new trigger&quot; at the top, and then select &quot;timer&quot; from the list that appears. <img src="https://alexanderdevelopment.net/content/images/2016/11/05-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Give it a name (or leave the default) and set the schedule. The <code>0 */5 * * * *</code> value here will execute every five minutes, just like in my previous Node.js example. <img src="https://alexanderdevelopment.net/content/images/2016/11/06-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>After the function is configured to run on a timer trigger, you have to upload the Python Requests library that is used to make the web service calls. Open a new browser window to download the Requests library code from GitHub at <a href="https://github.com/kennethreitz/requests/releases/">https://github.com/kennethreitz/requests/releases/</a>. Select the most recent .zip file.</p>
</li>
<li>
<p>After it downloads, open it and extract the entire &quot;requests&quot; directory. It should contain a directory called &quot;packages.&quot;</p>
</li>
<li>
<p>Zip up just the &quot;requests&quot; directory in a separate &quot;requests.zip&quot; file.</p>
</li>
<li>
<p>Back in the Azure portal Function App blade, select &quot;function app settings&quot; from the menu on the left and then click &quot;Go to Kudu.&quot; <img src="https://alexanderdevelopment.net/content/images/2016/11/07-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>In the window that opens, click the &quot;site&quot; link at the top. <img src="https://alexanderdevelopment.net/content/images/2016/11/08-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Then click the &quot;wwwroot&quot; link. <img src="https://alexanderdevelopment.net/content/images/2016/11/09-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Then click the name of your function. In this case it is &quot;CrmWorkflowTimerPython.&quot; <img src="https://alexanderdevelopment.net/content/images/2016/11/10-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Go to the cmd prompt at the bottom of the page and type <code>mkdir site-packages</code>. Click enter. You should see a new &quot;site-packages&quot; directory get created. <img src="https://alexanderdevelopment.net/content/images/2016/11/11-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Click on the new &quot;site-packages&quot; link to enter that directory.</p>
</li>
<li>
<p>Drag the &quot;requests&quot; .zip file you created in step 9 above into the &quot;site-packages&quot; directory like is shown. <img src="https://alexanderdevelopment.net/content/images/2016/11/12.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Once it is uploaded, extract it by typing <code>unzip requests.zip</code> at the cmd prompt and clicking enter. You should see something like this when it's complete. <img src="https://alexanderdevelopment.net/content/images/2016/11/13.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>At this point, you can close the Kudu window.</p>
</li>
<li>
<p>From the Function App blade, select your Python function and click &quot;develop.&quot; Highlight everything and delete it. <img src="https://alexanderdevelopment.net/content/images/2016/11/14.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Paste the Python code from the beginning of this post. The highlighted lines in the image are how the script knows how to find the Requests library you uploaded earlier. Set any specifics relative to your Dynamics 365 organization, and click save. At this point it should just start running on the schedule you set earlier. <img src="https://alexanderdevelopment.net/content/images/2016/11/15-1.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>You can see all the function invocation logs on the monitor tab. <img src="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-28_13-49-37.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
<li>
<p>Here are the notes that the workflow created in my Dynamics 365 online org. <img src="https://alexanderdevelopment.net/content/images/2016/11/chrome_2016-11-28_13-52-30.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Python"></p>
</li>
</ol>
<p>A few notes/caveats:</p>
<ol>
<li>My Python code has hardly any error handling right now. If the workflow execution call returns an error, the Python code will not recognize it as an error.</li>
<li>My CRM record retrieval is set to retrieve a maximum of 500 records. You would need to modify the Web API request logic to handle more.</li>
<li>Per the &quot;Best Practices for Azure Functions&quot; guide:</li>
</ol>
<blockquote>
<p>Assume your function could encounter an exception at any time. Design your functions with the ability to continue from a previous fail point during the next execution.</p>
</blockquote>
<p>This means you should put logic in your workflow to make sure that duplicate executions are avoided (unless that's what you intend to happen).</p>
<p>That's it for today. Until next time, happy coding!</p>
</div>]]></content:encoded></item><item><title><![CDATA[Scheduling Dynamics 365 workflows with Azure Functions and Node.js]]></title><description><![CDATA[<div class="kg-card-markdown"><p>Earlier this week I showed an easy way to <a href="https://alexanderdevelopment.net/post/2016/11/23/dynamics-365-and-node-js-integration-using-the-web-api/">integrate a Node.js application with Dynamics 365 using the Web API</a>. Building on that example, I have created a scheduled workflow runner using Node.js and <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview">Azure Functions</a>. Here's how I did it.</p>
<p>First, I created a workflow in Dynamics</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/11/25/scheduling-dynamics-365-workflows-with-azure-functions/</link><guid isPermaLink="false">5a5837246636a30001b97866</guid><category><![CDATA[Dynamics 365]]></category><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Azure]]></category><category><![CDATA[demonstrations]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Fri, 25 Nov 2016 17:00:03 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/11/04-1.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/11/04-1.png" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"><p>Earlier this week I showed an easy way to <a href="https://alexanderdevelopment.net/post/2016/11/23/dynamics-365-and-node-js-integration-using-the-web-api/">integrate a Node.js application with Dynamics 365 using the Web API</a>. Building on that example, I have created a scheduled workflow runner using Node.js and <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview">Azure Functions</a>. Here's how I did it.</p>
<p>First, I created a workflow in Dynamics 365 that creates a note on an account record. The screenshots below shows what it looks like:</p>
<p><img src="https://alexanderdevelopment.net/content/images/2016/11/workflow01.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></p>
<p><img src="https://alexanderdevelopment.net/content/images/2016/11/workflow02.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></p>
<p>Next, I wrote Node.js code to do the following in an Azure Function.</p>
<ol>
<li>Request an OAuth token using a username and password.</li>
<li>Query the Dynamics 365 Web API for accounts with names that start with the letter &quot;F.&quot;</li>
<li>Execute a workflow for each record that was retrieved in the previous step.</li>
</ol>
<p><em>Most of this is regular Node.js, but there are a couple of nuances specific to Azure Functions. See the<br>
<a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node">&quot;Azure Functions NodeJS developer reference&quot;</a> for more information.</em></p>
<pre><code>var https = require('https');

//set these values to retrieve the oauth token
//see http://alexanderdevelopment.net/post/2016/11/23/dynamics-365-and-node-js-integration-using-the-web-api/ for more details
var _crmorg = 'https://CRMORG...dynamics.crom';  
var _clientid = 'OAUTH CLIENT ID';  
var _username = 'CRM USERNAME';  
var _userpassword = 'CRM PASSWORD';  
var _tokenendpoint = 'OAUTH TOKEN ENDPOINT FROM EARLIER';

//set these values to query your crm data
var _apipath = '/api/data/v8.2'; //web api version
var _workflowid = 'DC8519EC-F3CE-4BC9-BB79-DF2AD70217A1'; //guid for the workflow you want to execute
var _crmwebapihost = 'XXXX.api.crm.dynamics.com'; //crm api url (without https://)
var _crmwebapiquerypath = &quot;/accounts?$select=name,accountid&amp;$filter=startswith(name,'f')&quot;; //web api query

var _counter = 0; //variable to keep track of how many records retrieved and workflows started

module.exports = function (context, myTimer) {
	//remove https from _tokenendpoint url
	_tokenendpoint = _tokenendpoint.toLowerCase().replace('https://','');

	//get the authorization endpoint host name
	var authhost = _tokenendpoint.split('/')[0];

	//get the authorization endpoint path
	var authpath = '/' + _tokenendpoint.split('/').slice(1).join('/');

	//build the authorization request
	var reqstring = 'client_id='+_clientid;
	reqstring+='&amp;resource='+encodeURIComponent(_crmorg);
	reqstring+='&amp;username='+encodeURIComponent(_username);
	reqstring+='&amp;password='+encodeURIComponent(_userpassword);
	reqstring+='&amp;grant_type=password';

	//set the token request parameters
	var tokenrequestoptions = {
		host: authhost,
		path: authpath,
		method: 'POST',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded',
			'Content-Length': Buffer.byteLength(reqstring)
		}
	};

	//make the token request
	context.log('starting token request');
	var tokenrequest = https.request(tokenrequestoptions, function(response) {
		//make an array to hold the response parts if we get multiple parts
		var responseparts = [];
		response.setEncoding('utf8');
		response.on('data', function(chunk) {
			//add each response chunk to the responseparts array for later
			responseparts.push(chunk);		
		});
		response.on('end', function(){
			//once we have all the response parts, concatenate the parts into a single string
			var completeresponse = responseparts.join('');
			//context.log('Response: ' + completeresponse);
			context.log('token response retrieved');
			
			//parse the response JSON
			var tokenresponse = JSON.parse(completeresponse);
			
			//extract the token
			var token = tokenresponse.access_token;
			//context.log(token);
			
			//pass the token to our data retrieval function
			getData(context, token);
		});
	});
	tokenrequest.on('error', function(e) {
		context.error(e);
		context.done();
	});

	//post the token request data
	tokenrequest.write(reqstring);

	//close the token request
	tokenrequest.end();
}

function getData(context, token){
	//set the web api request headers
	var requestheaders = { 
		'Authorization': 'Bearer ' + token,
		'OData-MaxVersion': '4.0',
		'OData-Version': '4.0',
		'Accept': 'application/json',
		'Content-Type': 'application/json; charset=utf-8',
		'Prefer': 'odata.maxpagesize=500',
		'Prefer': 'odata.include-annotations=OData.Community.Display.V1.FormattedValue'
	};
	
	//set the crm request parameters
	var crmrequestoptions = {
		host: _crmwebapihost,
		path: _apipath+_crmwebapiquerypath,
		method: 'GET',
		headers: requestheaders
	};
	
	//make the web api request
	context.log('starting data request');
	var crmrequest = https.request(crmrequestoptions, function(response) {
		//make an array to hold the response parts if we get multiple parts
		var responseparts = [];
		response.setEncoding('utf8');
		response.on('data', function(chunk) {
			//add each response chunk to the responseparts array for later
			responseparts.push(chunk);		
		});
		response.on('end', function(){
			//once we have all the response parts, concatenate the parts into a single string
			var completeresponse = responseparts.join('');
			
			//parse the response JSON
			var collection = JSON.parse(completeresponse).value;
			
			//set counter length = number of records
			_counter = collection.length;

			//loop through the results and call the workflow for each one
			collection.forEach(function (row, i) {
				callWorkflow(context, token, row['accountid']);
			});
		});
	});
	crmrequest.on('error', function(e) {
		context.error(e);
		context.done();
	});
	//close the web api request
	crmrequest.end();
}

function callWorkflow(context, token, entityid){
	var crmwebapiworkflowpath = _apipath + &quot;/workflows(&quot;+_workflowid+&quot;)/Microsoft.Dynamics.CRM.ExecuteWorkflow&quot;;

	//set the web api request headers
	var requestheaders = { 
		'Authorization': 'Bearer ' + token,
		'OData-MaxVersion': '4.0',
		'OData-Version': '4.0',
		'Accept': 'application/json',
		'Content-Type': 'application/json; charset=utf-8'
	};
	
	//set the crm request parameters
	var crmrequestoptions = {
		host: _crmwebapihost,
		path: crmwebapiworkflowpath,
		method: 'POST',
		headers: requestheaders
	};

	//create an object to post to the executeworkflow action
	var reqobj = {};
	reqobj[&quot;EntityId&quot;] = entityid;
	
	//turn it into a string
	var reqjson = JSON.stringify(reqobj);
	
	//calculate the length to set the content-length header
	crmrequestoptions.headers['Content-Length'] = Buffer.byteLength(reqjson);
	
	//make the web api request
	context.log('starting workflow request for ' + entityid);
	var crmrequest = https.request(crmrequestoptions, function(response) {
		//make an array to hold the response parts if we get multiple parts
		var responseparts = [];
		response.setEncoding('utf8');
		response.on('data', function(chunk) {
			//add each response chunk to the responseparts array for later
			responseparts.push(chunk);		
		});
		response.on('end', function(){
			//once we have all the response parts, concatenate the parts into a single string
			var completeresponse = responseparts.join('');
			context.log('success ' + entityid);
			
			//decrement the counter
			_counter = _counter-1;
			
			//if nothing is left to start, we are done
			if(_counter==0){
				context.log('all workflows started');
				context.done();
			}
		});
	});
	crmrequest.on('error', function(e) {
		context.error(e);
		context.done();
	});
	crmrequest.write(reqjson);

	//close the web api request
	crmrequest.end();
}
</code></pre>
<p>Then in the Azure Portal, I configured an Azure Function app to query accounts and execute the workflow every five minutes. Here are the detailed steps to replicate that.</p>
<ol>
<li>Create a new Function app via New-&gt;Compute-&gt;Function App. <img src="https://alexanderdevelopment.net/content/images/2016/11/01.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Set the app name, resource group, etc. <img src="https://alexanderdevelopment.net/content/images/2016/11/02.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Once the new Function app is provisioned, open it. <img src="https://alexanderdevelopment.net/content/images/2016/11/03.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Select &quot;new function&quot; on the left. <img src="https://alexanderdevelopment.net/content/images/2016/11/04.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Set language to &quot;JavaScript&quot; and scenario to &quot;Core.&quot; Find the &quot;TimerTrigger-JavaScript&quot; template and select it. <img src="https://alexanderdevelopment.net/content/images/2016/11/05.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Give your function a name and set the schedule options. The <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer#schedule-examples">schedule value</a> is a CRON expression that includes six fields: {second} {minute} {hour} {day} {month} {day of the week}. You can accept the default value of every five minutes and change it later. Click &quot;create&quot; to create the new function. <img src="https://alexanderdevelopment.net/content/images/2016/11/06.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>Copy the Node.js code from above and paste it into the editor window. Set any specifics relative to your Dynamics 365 organization, and click save. (You can also use Git for deploying your code, but that's beyond the scope of today's post.) <img src="https://alexanderdevelopment.net/content/images/2016/11/07.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>On the &quot;integrate&quot; tab, you can modify the timer schedule. The schedule shown (0 */5 * * * *) will execute the function every five minutes. <img src="https://alexanderdevelopment.net/content/images/2016/11/08.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>The function will automatically execute at the next fifth minute, and the invocation log is available on the monitor tab. Selecting a specific invocation row shows detailed logging output on the right. <img src="https://alexanderdevelopment.net/content/images/2016/11/09.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>This screenshot shows the process sessions for when the workflow was executed in Dynamics 365. <img src="https://alexanderdevelopment.net/content/images/2016/11/10.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
<li>This screenshot shows the note records that were created by the workflow. <img src="https://alexanderdevelopment.net/content/images/2016/11/11.png#img-thumbnail" alt="Scheduling Dynamics 365 workflows with Azure Functions and Node.js"></li>
</ol>
<p>A few notes/caveats:</p>
<ol>
<li>My Node.js code has hardly any error handling right now. If the workflow execution call returns an error, the Node.js code will not recognize it as an error.</li>
<li>My CRM record retrieval is set to retrieve a maximum of 500 records. You would need to modify the Web API request logic to handle more.</li>
<li>Per the <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-best-practices">&quot;Best Practices for Azure Functions&quot;</a> guide:</li>
</ol>
<blockquote>
<p>Assume your function could encounter an exception at any time. Design your functions with the ability to continue from a previous fail point during the next execution.</p>
</blockquote>
<p>This means you should put logic in your workflow to make sure that duplicate executions are avoided (unless that's what you intend to happen).</p>
<p>This sample just scratches the surface of what's possible with Azure Functions and Dynamics 365, and I'm looking forward to working with Azure Functions more in the future. Have you looked at Azure Functions yet? What do you think? Please let me know in the comments.</p>
</div>]]></content:encoded></item><item><title><![CDATA[Webcast: Sentiment Analysis in Microsoft Dynamics CRM using Azure Text Analytics]]></title><description><![CDATA[<div class="kg-card-markdown"><p>On Monday, April 11, at 12 p.m. EDT, I will be presenting a <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">webcast</a> at MSDynamicsWorld.com that will show how a custom integration with Microsoft Azure Machine Learning can be used to perform sentiment analysis on any data stored in Dynamics CRM.</p>
<p>Custom sentiment analysis integrations can enable</p></div>]]></description><link>https://alexanderdevelopment.net/post/2016/04/06/webcast-sentiment-analysis-in-microsoft-dynamics-crm-using-azure-text-analytics/</link><guid isPermaLink="false">5a5837236636a30001b97819</guid><category><![CDATA[Microsoft Dynamics CRM]]></category><category><![CDATA[machine learning]]></category><category><![CDATA[Azure]]></category><category><![CDATA[text analysis]]></category><category><![CDATA[demonstrations]]></category><dc:creator><![CDATA[Lucas Alexander]]></dc:creator><pubDate>Wed, 06 Apr 2016 14:37:20 GMT</pubDate><media:content url="https://alexanderdevelopment.net/content/images/2016/04/sentiment-dialog.png" medium="image"/><content:encoded><![CDATA[<div class="kg-card-markdown"><img src="https://alexanderdevelopment.net/content/images/2016/04/sentiment-dialog.png" alt="Webcast: Sentiment Analysis in Microsoft Dynamics CRM using Azure Text Analytics"><p>On Monday, April 11, at 12 p.m. EDT, I will be presenting a <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">webcast</a> at MSDynamicsWorld.com that will show how a custom integration with Microsoft Azure Machine Learning can be used to perform sentiment analysis on any data stored in Dynamics CRM.</p>
<p>Custom sentiment analysis integrations can enable a number of interesting processes in Dynamics CRM including:</p>
<ul>
<li>Routing emails to queues based on sentiment</li>
<li>Dynamically load agent scripts</li>
<li>Reporting on interactions</li>
</ul>
<p>This webinar will include an introduction to Azure Machine Learning, an overview of a sample solution and a code deep dive. Programming knowledge will be helpful during the code deep dive, but it is not required for most of the discussion.</p>
<p>You can register for the session <a href="https://msdynamicsworld.webex.com/msdynamicsworld/onstage/g.php?MTID=e9c658b74d1caa0e4f1063423421cd47c&amp;SourceId=la">here</a>.</p>
</div>]]></content:encoded></item></channel></rss>