Skip to content

365 Cloudy Days Posts

Display the plain text version of an email on Windows

Posted in ClickDimensions, Dynamics 365, and Dynamics 365 Marketing

Today I had the challenge to display the plain text version of an email on a Windows device. I gave the Outlook setting “Read all standard mail in plain text” in the Trust Center a try, but it only removes the images and formatting from the the HTML version of the email.

Finally I found in the internet that Thunderbird is able to show the text version of a mailing.
Here is the way to switch the view.

plain text version of an email

And this is of course also helpful for plain text emails with Dynamics 365 Marketing.

Get CRM URL in Power Automate

Posted in Dynamics 365, and Power Automation

Sometimes you need to work with the URL of your CRM within Power Automate, for example:

  • to relate or unrelate two records with the Common Data Service (current environment) connector
    CDS (current) - Relate Records Action
  • or writing an email with a hyperlink to a record.

If you have a multi-staged environment and you build your Flow solution aware, you don’t want to update your static URLs after each transport.

Solution

‘Recycle’ the OData Id from a previous CDS action.

URL in OData Id of a Flow
  1. We need an previous CDS action that has an OData Id within its output (like shown above).
    If you have no action like this, consider to create a “List records” action which is limited to 1 by the “Top count” option.
  2. Create a “Compose” action and give it the OData_Id as input.
  3. Create an other “Compose” action and give it following expression to receive the CRM URL.
    concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
  4. Create an other “Compose” action and give it following expression to receive the OData URL.
    concat(join(take(split(outputs('OData_Id'), '/'), 6), '/'), '/')
Compose URL in Flow

How it works

  • concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
    Contains the OData Id we’ve stored in the first “Compose” action.
    "https://yourcrm.crm4.dynamics.com/api/data/v9.1/cdi_postedform(792189BA-BA04-E711-80F6-C4346BAC4DDC)"

  • concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
    Cuts the URL string in separate substring wherever a slash (‘/’) is.
    0: "https:"
    1: ""
    2: "yourcrm.crm4.dynamics.com"
    3: "api"
    4: "data"
    5: "v9.1"
    6: "cdi_postedform(792189BA-BA04-E711-80F6-C4346BAC4DDC)"

  • concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
    Merge the first 3 entries of the splitted URL to a new array.
    0: "https:"
    1: ""
    2: "yourcrm.crm4.dynamics.com"

  • concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
    Merge the new array into a string and separate the entries by a slash (‘/’).
    "https://yourcrm.crm4.dynamics.com" 

  • concat(join(take(split(outputs('OData_Id'), '/'), 3), '/'), '/')
    Create a new string with the merged URL and put a slash (‘/’) at its end.
    "https://yourcrm.crm4.dynamics.com/" 

In the same way you receive the OData URL when you “take” the first 6 elements instead of the first 3 elements.

ClickDimensions Quick Send from any entity

Posted in ClickDimensions, and Dynamics 365

Last year I had a customer requirement to start a ClickDimensions Quick Send on a custom entity. By default, QuickSend is only available on lead, contact and opportunity. ClickDimensions has already an article on how creating a Quick Send like dialog to send a single email from any entity, but dialogs are deprecated. Therefore I made a deep dive into how the Quick Send button works and will share my findings with you.

The button

I will only focus on the command, I think button labels and button icons should be no problem for customizers.

Rebuild the command and use your own javascript in yellow marked areas.

The javascript

The following is just to give you a starting point. You need to fill it with additional logic to refer it to a contact or lead.

function openQuickSendForm() 
{
   // [CUSTOMIZE
   var type = 2;
   var typeName = "contact";
   var id = "{332514B1-14F7-E711-A95F-000D3AB6ED20}";
   // CUSTOMIZE]

   var accountKey = getAccountKey();
   var region = getRegion();
   var crmVersion = getCrmVersion();
   var orglcid = parent.Xrm.Page.context.getOrgLcid();
   var orgName = Xrm.Page.context.getOrgUniqueName();
   var lcid = Xrm.Page.context.getUserLcid();
   var userId = Xrm.Page.context.getUserId();

   var params = 
      '&id=' + id + 
      '&orglcid=' + orglcid + 
      '&orgname=' + orgName + 
      '&type=' + type + 
      '&typename=' + typeName + 
      '&userlcid=' + lcid + 
      '&userid=' + userId;
   
   var url = 
      'https://app' + region + '.clickdimensions.com' +
	  '/MSCRM/v' + crmVersion + '/pages/quicksendemailtemplateForm.aspx' 
	  + '?accountKey=' + accountKey + params;
   
   _security.CreateSecuritySession(function (sessionId) 
   {
      url += '&sessionId=' + sessionId;
      window.open(
	     url, 
		 'quicksendWindow', 
		 'height=800px,width=730px,status=0,toolbar=0,resizable=1,scrollbars=1'
      );
   });
}

Mask query strings

Posted in ClickDimensions, Dynamics 365, and Dynamics 365 Marketing

Email marketing tools automatic add query strings at the end of urls of your hyperlinks. That’s a common way to pass additional informations to the target website inside of hyperlinks. Mostly these are informations about which channel the visitor came and to which campaign the visit is related to.

Issue

The target website should validate the passed query strings and ignore them if it don’t know what to do with. But sometimes you a have target website that crash painfully with a 404 error.

Solution – Mask query strings

Instead of trying to fix the targets understanding of the query strings, you can mask the query strings when you create the hyperlink in your email marketing tool.
Just put the anchor identifier “#” at the end your link url. By that way the recipients web browser interprets the query string as an anchor within the target website. As there is no anchor text with that name on the target website, it has no effect. That’s in the specification of HTML since version 4.01 from 1999 – yes that was is in the last century.

You see how important it is to test your mailing with real recipients.

Filter freemail provider on ClickDimensions forms

Posted in ClickDimensions, Dynamics 365, and Revive

Currently I have the request to filter freemail provider on ClickDimensions forms, because the customer wants only B2B contacts in his database.

ClickDimensions already provides a setting to filter freemail provider and it would be great first step to met the requirement if the editor would save the setting correctly.

At the moment, everytime you reopen the editor the above setting is unchecked and it doesn’t work in the form.

Apart from that, only four freemail providers are offered to filter.
That’s why I took a look into the Javascripts of ClickDimensions and found and extended the original code for it.

The following code will override the original code to add an additional validation. You can add other or even more freemail providers to met your requirements. Just insert it in the CodeEditor of the forms designer.

// enable or disable the filter functionality
var filterFreemail = "true";

// customize the message provided to the user when a freemail address has been entered
var clickd_MSG_FREE_EMAIL = "We accept only business email addresses.";

// customize the array of blocked freemail providers to met your requirements
var blockedFreeEmail = ["hotmail.com", "gmail.com", "yahoo.com", "aol.com"];

// default ClickDimensions function, extended with our own validation
function FormValid()
{
    var isValid = true,
        isAllowed = true,
        reqFieldList = clickd_jquery("input[name='reqField']", "#clickdimensionsForm");

    for (var i = 0; i < reqFieldList.length; i++)
    {
        // default validation
        isValid = ValidField(clickd_jquery(reqFieldList[i]));
        if (!isValid)
        {
            break;
        }

        // custom validation
        isAllowed = AllowedField(clickd_jquery(reqFieldList[i]));
        if (!isAllowed)
        {
            isValid = false;
            break;
        }
    }
    return isValid;
}

// own validation, copied and modified from ClickDimensions original
function AllowedField(hidden)
{
    var fieldType = hidden.attr("alt");
    var fieldID = hidden.val();
    var fieldString = "";

    var field = clickd_jquery("#" + fieldID);
    if (clickd_jquery("#cont_id_" + fieldID).attr("wasskipped") == "1")
    {
        return true;
    }

    var infoId = "required_info_" + fieldID;
    infoId = infoId.replace("f_upload", "f");
    var info = clickd_jquery("#" + infoId);

    var infoText = clickd_jquery(info).text();
    if (infoText != "")
    {
        Un_SelectNotValidInput(info, field);
    }


    if (fieldType.toLowerCase() == "email")
    {
        fieldString = clickd_jquery(field).val();

        if (fieldString.length > 0)
        {
            fieldString = fieldString.replace(/\s+$/gm, '');
        }
    }
    
    if (fieldType.toLowerCase() == "email" && fieldString.length > 0)
    {
        var domain = fieldString.split("@")[1].toLowerCase();

        // set field invalid for freemail provider
        if (filterFreemail.toLowerCase() == "true")            
        {
            for (var i = 0; i < blockedFreeEmail.length; i++)
            {
                if (domain == blockedFreeEmail[i])
                {
                    SelectNotValidInput(info, field, clickd_MSG_FREE_EMAIL);
                    return false;
                }
            }
        }
    }
    return true;
}

Failed internal Azure AD authentication with Dynamics 365 portal

Posted in Dynamics 365, and PowerApps Portals

If you’ve ever had contact with a Dynamics 365 portal, you’ve probably noticed the “Azure AD” button. This enables authentication with the Azure AD of your Dynamics environment.

But we had an error when rolling out the portal, so I had to restart the process. After successful completion, the portal could be reached at “MYURL. microsoftcrmportals. com”, but the Azure AD registration failed.

I will spare you the execution of my whole Trail&Error orgy and tell you what the problem is.

Solution

During the deployment the reply addresses of the registered Azure AD APP have been missconfiguered. To find it navigate in your browser to portal.azure.com and click:

The reply addresses looked like (notice the “1” at the end of the subdomain)

Disable a section on profile form on Dynamics 365 portal dynamically

Posted in Dynamics 365, PowerApps Portals, and Revive

At the moment I’m setting up a community portal, where it is planned that the users can register themselves.

The portal users should also be able to enter their company name.
To do this, I use the field “adx_organizationname” provided by the portal because with an account lookup everyone could see our customers. The backoffice then checks whether this contact is related to an existing company or a new one. As soon as the contact is then connected to an account record, the organization name should no longer be changeable by the portal user.

What not worked

  • Javascript on CRM form.
  • Business Rule on CRM form (because it is Javascript).
  • Add the field twice to the form to have one editable and one readonly and hide the the not applicable with jQuery in the portal.
    The same fields two times on the profile form let the portal crash.
  • Make the field readonly on the CRM form and enable it in the portal with jQuery did not save the data back to CRM.

Solution

I have created a separate section for the company-related fields

and make it read only when parentcustomer is not empty.

This creates following tag in the HTML structure.

The result looks like this.

Oh wait, happy times, Dynamics portals has arrived my blog for the first time!

Get isActivity from Metadata

Posted in Dynamics 365, and Revive

This week I had to differentiate between normal entities and activities in Javascript. I had a look into the SDK and remembered my previous post “Get EntitySetName from Metadata“. A little modification and I get true or false from the ‘isActivity’ information in the metadata.

Here it is fo you: Get isActivity from Metadata

function isActivity(strEntityLogicalName)
{
    var req = new XMLHttpRequest();
    req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.0/" +
        "EntityDefinitions(LogicalName='" + strEntityLogicalName + "')?$select=IsActivity", false);
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("Accept", "application/json");
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    req.onreadystatechange = function ()
    {
        if (this.readyState === 4)
        {
            req.onreadystatechange = null;
            if (this.status === 200)
            {
                var result = JSON.parse(this.response);
                var isActivity = result.IsActivity;
            }
        }
    };
    req.send();
}

isActivity

CalculateRollupField with WebApi function in Javascript

Posted in Dynamics 365, and Revive

Microsoft added with Service Pack 1 a new function called “CalculateRollupField” in Dynamics CRM (v 8.1) which enables us to recalculate a rollup field on demand.
I will show you here how you can use it in Javascript with a http request against the WebApi.

The CalculateRollupField function inside the webrequest needs a few parameter to know which rollup field you want to to calulate:

  • The EntitySetName of the target record.
    I wrote here how you can get the EntitySetName from the Metadata with an webrequest too.
  • The GUID of the target record.
  • The schema name of the target field.

CalculateRollupField in Javascript:

function calcRollupField(strTargetEntitySetName, strTargetRecordId, strTargetFieldName)
{
    strTargetRecordId = strTargetRecordId.replace("{", "").replace("}", "");
    var req = new XMLHttpRequest();
    req.setRequestHeader("OData-Version", "4.0");
    req.setRequestHeader("OData-MaxVersion", "4.0");
    req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/" +
        "CalculateRollupField(Target=@p1,FieldName=@p2)?" +
        "@p1={'@odata.id':'" + strTargetEntitySetName + "(" + strTargetRecordId + ")'}&" +
        "@p2='" + strTargetFieldName + "'", true);

    req.onreadystatechange = function ()
    {
        if (this.readyState === 4)
        {            
            req.onreadystatechange = null;
            if (this.status === 200)
            {
                var results = JSON.parse(this.response);
            }
            else
            {
                Xrm.Utility.alertDialog(this.statusText);
            }
        }
    };    
    req.send(JSON.stringify({}));
}

The answer of the webservice for the CalculateRollupField function contains the value for the target field, the date of the last calculation and its state.

Missing privilege for editable grids

Posted in Dynamics 365, and Revive

In one of our last projects we used an editable grid from Microsoft, but had a strange issue.
Read here how we detected and identified a missing privilege for editable grids.

Issue

During testing with an user account we found that the editable grid for a custom entity does not render. For admins is all fine.
This is how it should look.

This what it actually was.

Analysis

First thing we’ve checked was whether someone has changed the form rendering mode because editable subgrids don’t work on legacy forms. But it was still correct set.

Also we checked the known editable grids limitations for fields from related entities, the state field, partylists, customer and composite fields. But there was no such field in the view.

Further we’ve checked the users security roles. They have a custom role, but it includes all privileges for the parent and child entity.

Next we proved what happens when we remove the editable grid control and use the read only version instead. As a result, everything was ok in the read only grid.

Now I had a look at the browser console and found an error . . . a few times.

As desperation act we gave the user the default sales person security role and were surprised that now everything worked fine.

Identify the missing privilege for editable grids

We compared the two security roles and identified the “prvReadRole” as origin of the issue.
It was set to “None Selected”. I assume it is needed by the editable grid control the check if the user has all necessary privileges to edit the records in his security roles.