User Security Manager – My first XrmToolBox Plugin


Nishant Rana's Weblog

Being one of the biggest fan and the most frequent daily user of XrmToolBox, always had this wish to write a plugin for it.

Thanks to Prashant Maurya  (a dear friend and ex-Microsoft Employee) for making this wish come true. (Who played the major role in developing it).

The tool is called – User Security Manager.

The Plugin make it easy for the administrators to manage all the security related aspects of the system users. The tool also gives 360-degree information of the user in correspondence to business unit, security roles and teams.

In our current project we had around 180 users using the system divided into more than 50 business units, which involved frequent update of their business unit, changes in the security roles assigned, update in team assignment and also during testing we had to assign the same BU and roles to the test users…

View original post 472 more words

SCRIPT1003: Expected ‘:’ Error in IE 11


Just sharing a simple scenario where my code was working in Chrome and Edge but It was throwing error  (SCRIPT1003: Expected ‘:’ ) in IE. Basically I was calling an Actions using Web API which was returning two output parameters then I was returning these two values to calling function.

mc.Ribbon.GetSignature = function () {
var signature;
var referenceno;
var Id = Xrm.Page.data.entity.getId().substring(1, 37);
var data = {
“receiptid”: Id
};
var clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest();
req.open(“POST”, clientURL + “/api/data/v8.2/<entity>(” + Id + “)/Microsoft.Dynamics.CRM.<ActionName>”, false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204 || this.status == 200) {
var receipts = JSON.parse(this.response);
signature = receipts[“signature”];
referenceno = receipts[“referenceno”];
}
else {
var error = JSON.parse(this.response).error;
}
}
};
req.send(JSON.stringify(data));
return {signature,referenceno};
};
Notice the return statement where I was returning two variable as object without key/value pair which was not working in IE 11 but it was working perfectly fine with Chrome and Edge. Then I changed the code to return array after assigning the value. See the correct code below which was working perfectly fine in IE 11 as well.

mc.Ribbon.GetSignature = function () {
var signArray = new Array();
var Id = Xrm.Page.data.entity.getId().substring(1, 37);
var data = {
“receiptid”: Id
};
var clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest();
req.open(“POST”, clientURL + “/api/data/v8.2/<entity>(” + Id + “)/Microsoft.Dynamics.CRM.<ActionName>”, false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 204 || this.status == 200) {
var receipts = JSON.parse(this.response);
signArray[“signature”] = receipts[“signature”];
signArray[“referenceno”] = receipts[“referenceno”];
}
else {
var error = JSON.parse(this.response).error;
}
}
};
req.send(JSON.stringify(data));
return signArray;
};

Hope this helps.

Call Actions using Dynamics CRM Web API with Output Parameter


Let me share one scenario where I was working on payment gateway integration with Dynamics CRM . We were redirecting to payment page once user click on ribbon button and redirect to CRM on successful payment. Since we need to pass secure hash as a part of payment URL which is generated using secret key and some security sequence string. Since secret key is secure information so we should not pass to JavaScript so we decided to create Actions which will take some input and generate the Secure Hash using algorithm(SHA-256 HMAC) and return to output parameters.

So mainly 3 steps involve in complete requirement.
1. Create Actions with Input and Output parameter
2. Create Web API to Call Actions to get Secure Hash which will be used to pass as parameter to payment gateway.
3. Call JavaScript function from ribbon button which internally call Web API.

Here is my Actions with input and output parameters

Below is my Plugin code which I registered on “mc_GetSecureHash” message for custom entity called “mc_receipts”.

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace MC.CRM.PlugIns
{
public class GetSecureHash : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
string vendorName = string.Empty;
string merchantId = string.Empty;
string accessCode = string.Empty;
string secretKey = string.Empty;
string transactionType = string.Empty;
string sequenceStringForHash = string.Empty;
string secureHash = string.Empty;

// Obtain the execution context service from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

// Obtain the tracing service from the service provider.
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

// Obtain the Organization Service factory service from the service provider
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

// Use the factory to generate the Organization Service.
IOrganizationService service = factory.CreateOrganizationService(context.UserId);

try
{
if (context.InputParameters.Contains(“Target”) && context.InputParameters[“Target”] is EntityReference)
{
EntityReference receipts = (EntityReference)context.InputParameters[“Target”];
sequenceStringForHash = (string)context.InputParameters[“sequencestring”];

vendorName = (string)context.InputParameters[“vendorname”];

}

EntityCollection paymentGatewaySettings = GetPaymentGatewayInformtion(service, vendorName);
if (paymentGatewaySettings.Entities.Count > 0)
{
Entity paymentSettings = paymentGatewaySettings.Entities[0];
if (paymentSettings.Attributes.Contains(“mc_merchantid”))
{
merchantId = paymentSettings.Attributes[“mc_merchantid”].ToString();
}
if (paymentSettings.Attributes.Contains(“mc_secretkey”))
{
secretKey = paymentSettings.Attributes[“mc_secretkey”].ToString();
}
if (paymentSettings.Attributes.Contains(“mc_accesscode”))
{
accessCode = paymentSettings.Attributes[“mc_accesscode”].ToString();
}

secureHash = GenerateSHA512String(secretKey, sequenceStringForHash);

context.OutputParameters[“securehash”] = secureHash;
}
}
catch (FaultException<OrganizationServiceFault> e)
{
throw new InvalidPluginExecutionException(e.Message);
}

}

public string GenerateSHA512String(string secretKey, string sequenceString)
{
byte[] hashmessage;
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secretKey);
byte[] messageBytes = encoding.GetBytes(sequenceString);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
hashmessage = hmacsha256.ComputeHash(messageBytes);
}
return Convert.ToBase64String(hashmessage);
}

private EntityCollection GetPaymentGatewayInformtion(IOrganizationService service, string venderName)
{

QueryByAttribute paymentGateway = new QueryByAttribute(“mc_paymentgatewaysettings”);
paymentGateway.ColumnSet = new ColumnSet(“mc_name”, “mc_merchantid”, “mc_secretkey”, “mc_accesscode”);
paymentGateway.Attributes.AddRange(“mc_name”);
paymentGateway.Values.AddRange(venderName);
EntityCollection paymentgatewaysettings = service.RetrieveMultiple(paymentGateway);

return paymentgatewaysettings;

}
}
}

Then finally Web API which calling the above Plugins

function GetSecureHash(requestStringForHash) {
debugger;

try
{
var secureHash;
var vendorName = “<>”;
var Id = Xrm.Page.data.entity.getId().substring(1, 37);
console.log(“Receipt Id:” + Id);
var data = {
“receiptid”: Id,
“vendorname”: vendorName,
“sequencestring”: requestStringForHash
};
var clientURL = Xrm.Page.context.getClientUrl();
var req = new XMLHttpRequest();

req.open(“POST”, clientURL + “/api/data/v8.2/mc_receiptses(” + Id + “)/Microsoft.Dynamics.CRM.mc_GetSecureHash”, false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 200) {
var receipts = JSON.parse(this.response);
secureHash = receipts.securehash;
}
else {
var error = JSON.parse(this.response).error;
console.log(error.message);
}
}
};
req.send(JSON.stringify(data));
console.log(“secureHash: ” + secureHash);

}
catch (e) {
console.error(e.message);
}
return secureHash;

}

Using this Web API, we got secure hash to build the complete payment URL and then redirect to payment gateway.

Some reference URL for Action and Web API for more details
https://community.dynamics.com/crm/b/crminogic/archive/2018/01/04/fixed-executing-action-with-complex-output-parameters-through-web-api-in-dynamics-365-v9-0
Invoke your Custom Action from Dynamics CRM Web API–Dynamics CRM 2016

Hope this help you.

How to Create a, “No Code”, Lead Capture Solution


Donna Edwards

Today I would like to share with you how easy it is to get started using PowerApps and the Common Data Service.  For this example, I created a “Lead Entry” PowerApp for use by Sales to quickly enter a new Lead from any supported device.  The lead will be entered into my company’s Dynamics 365 online application and then moved to the Common Data Service using Microsoft Flow.

This article assumes that you have given your user record the required licenses in your Office 365 account (PowerApps & Flow) and that you have logged into the PowerApps admin area and set the appropriate privileges on the environment.  Here are a couple of articles to help with those steps if you have not completed them.

PowerApp Q & A

CDS Environment Overview

Create CDS database 

This article will walk you through the creation of a Common Data Service database, a PowerApp…

View original post 1,053 more words

Retrieve more than 5000 records in Dynamics 365


We know there is limitation that by default we can get maximum 5000 records in a single web service call but by using paging cookie concept we can get more then 5000 records. Sharing sample code how to use it.

public static List<StudentRecord> ReturnStudentListTest(IOrganizationService service, Int16 pageNumber, Int16 fetchCount)
{
List<StudentRecord> StudentList = new List<StudentRecord>();
string fetchStudent = string.Empty;
string pagingCookie = null;

if (fetchCount == 0)
{
fetchCount = 100;
}
if (pageNumber == 0)
{
pageNumber = 1;
}

fetchStudent = “<fetch version=’1.0′ output-format=’xml-platform’ mapping=’logical’ distinct=’true’>” +
” <entity name=’contact’>” +
” <attribute name=’contactid’ />” +
” <attribute name=’fullname’ />” +
” <order attribute=’fullname’ descending=’false’ />” +
” <filter type=’and’>” +
” <condition attribute=’statecode’ operator=’eq’ value=’0′ />” +
” </filter>” +
” </entity>” +
“</fetch>”;

while (true)
{
string fetchXmlWithPaging = Helper.CreateXml(fetchStudent, pagingCookie, pageNumber, fetchCount);
var fetchExpression = new FetchExpression(fetchXmlWithPaging);
EntityCollection studentCollection = service.RetrieveMultiple(fetchExpression);
if (studentCollection.Entities.Count > 0)
{
foreach (var record in studentCollection.Entities)
{
try
{
StudentRecord studentRecord = new StudentRecord();
studentRecord.studentId = record.Contains(CRMConstants.Contact.studentId) ? ((Guid)record.Attributes[CRMConstants.Contact.studentId]).ToString() : string.Empty;
studentRecord.fullname = record.Attributes.Contains(CRMConstants.Contact.fullname) ? record.Attributes[CRMConstants.Contact.fullname].ToString().ToUpper() : String.Empty;
StudentList.Add(studentRecord);
}
catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> ex)
{
throw new Exception(ex.Detail.Message);
}
catch (Exception ex)
{
throw new InvalidCastException(ex.Message);
}
}
if (studentCollection.MoreRecords)
{
pageNumber++;
pagingCookie = studentCollection.PagingCookie;
}
else
break;
}
}
return StudentList;
}

Please refer the below URL for details.
https://msdn.microsoft.com/en-us/library/mt269606.aspx
https://softchief.com/2016/02/20/retrieve-more-than-5000-records-in-dynamics-crm/

Hope it helps.

Retrieve Data Using Web API and FetchXml


Sharing sample code to retrieve data using Web API and using FetchXml to build the query instead of oData. In this example, I am retrieving contact based on email/mobile no and calling function form an entity’s ribbon button to validate duplicate record.

function ValidateContact() {
var mobilePhone = Xrm.Page.getAttribute(“mc_contactnumber”).getValue();
var email = Xrm.Page.getAttribute(“emailaddress”).getValue();
var clientURL = Xrm.Page.context.getClientUrl();
var fetchContact = “<fetch version=’1.0′ output-format=’xml-platform’ mapping=’logical’ distinct=’false’>” +
” <entity name=’contact’>” +
” <attribute name=’fullname’ />” +
” <attribute name=’emailaddress1′ />” +
” <attribute name=’mc_mobilephone’ />” +
” <attribute name=’createdon’ />” +
” <attribute name=’ownerid’ />” +
” <order attribute=’fullname’ descending=’false’ />” +
” <filter type=’and’>” +
” <filter type=’or’>” +
” <condition attribute=’mc_mobilephone’ operator=’eq’ value='” + mobilePhone + “‘ />” +
” <condition attribute=’emailaddress1′ operator=’eq’ value='” + email + “‘ />” +
” </filter>” +
” <condition attribute=’statecode’ operator=’eq’ value=’0′ />” +
” </filter>” +
” </entity>” +
“</fetch>”;

var encodedFetchXml = encodeURI(fetchContact);
var reqURL = clientURL + “/api/data/v8.2/contacts?fetchXml=” + encodedFetchXml;
var req = new XMLHttpRequest();
req.open(“GET”, reqURL, false);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
//req.setRequestHeader(“Prefer”, “odata.include-annotations=\”*\””);
req.setRequestHeader(“Prefer”, “odata.include-annotations=\”OData.Community.Display.V1.FormattedValue\””);
req.onreadystatechange = function () {
if (this.readyState == 4) {
req.onreadystatechange = null;
if (this.status == 200) {
var data = JSON.parse(this.response);
if (data.value.length == 0) {
alert(“There is no matching contact found in exiting contact list.”);
}
else if (data.value.length == 1) {
alert(“Duplicate Contact Record Found: Another Contact with the same detail already exists in the system which is created on: ” + data.value[0][‘createdon@OData.Community.Display.V1.FormattedValue’] + ” by ” + data.value[0][‘_ownerid_value@OData.Community.Display.V1.FormattedValue’] + “.”);
}
else {
alert(“There are more than one matching contact record found.”);
}
}
else {
var error = JSON.parse(this.response).error;
alert(error.message);
}
}
};
req.send();
}

To use FetchXml, we need to format FetchXml in usable Web API service endpoint format. We do this by storing the FetchXML in a variable and encoding the string with the encodeURI function native to JavaScrip as below.
var encodedFetchXml = encodeURI(fetchContact);

Another point, if you want formatted values to be included, then you need to set request header like below(This is mainly used to get Lookup, OptionSet, DateTime specific formatted value like lookup text value).
req.setRequestHeader(“Prefer”, “odata.include-annotations=\”*\””);
OR
req.setRequestHeader(“Prefer”, “odata.include-annotations=\”OData.Community.Display.V1.FormattedValue\””);

Please refer the below URL for details.
https://community.dynamics.com/crm/b/mscrmcustomization/archive/2016/10/18/ms-crm-2016-web-api-operations-retrieve-single-or-multiple-records

http://himbap.com/blog/?p=2012

Issue with Data Export Service Add-ons


In one of requirement, we wanted to use CRM Data for reporting purpose but due to some limitation with FetchXml (like N-N relationship) we were unable to get desire data so we decided to use Data Export Service Add-ons to push into Azure and make use of data for reporting purpose.

When we tried to install/Enable the Data Export Service from Dynamics Marketplace, the add-on installed successfully without any error but when we tried browse Data Export Service(Settings -> Data Export), it was showing blank page and Data Export Profile page was not displaying at all in IE 11 as below.

We raised ticket to Microsoft and they suggested to add below URL in IE trusted site list and once we added these sites in trusted site, we were able to see Data Export Profile page(Settings->Data Export) as shown below.
https://*.azure.net
https://*.crm.dynamics.com
https://*.microsoftonline.com
https://*.windows.net

Note: In Google Chrome , It was working fine even without adding above URLs in trusted sites.

Hope this will help someone.

Configure Dynamics 365 and Azure Service Bus Integration


As we know, we can connect Dynamics 365 with Azure platform by coupling Dynamics 365 event execution pipeline to the Microsoft Azure Service Bus. Once configured, this connection allows data that’s been processed as current Dynamics 365 operation to be posted to the service. Microsoft Azure Service Bus solutions that are “Dynamics-365 aware” can listen for and read the Dynamics 365 data from the service bus.There are many ways to establish a contract between Dynamics 365 and an Azure solution as below.

  • Queue
  • One-way
  • Two-way
  • Rest
  • Topic
  • Event Hub

In this post, I will use “Queue” contract where a listener doesn’t have to be actively listening for messages on the endpoint.
Let’s implement a basic scenario – When a record is created in CRM then pass the execution context to queue and then prepare a application to read and display the entity information.

Lets first configure Azure to create a Queue listener using Azure Portal(https://portal.azure.com).

  1. Create a Service Bus by click on “Add” button.

    2. Enter mandatory fields as below.

3. Create a Queue in newly created Service Bus(mcstaging in my case)

4. Create SAS Policy for Authorization and copy the Primary Connection String which is required during end point registration in CRM

Now let’s create service endpoint in CRM using plugin registration tool.
Login into plugin registration tool and click on Register ->Register New Service End Point as below


Copy the primary connection string form  step 4 and click on “Next”

All information will be populated automatically and then click on “Save”.
Now, need to register a step for this endpoint(In my case, custom entity Enquiry(mc_feedback)  on create message).


Now create a enquiry record in CRM, once you create a record in CRM a system job will be created and message will be passed to azure queue created earlier.

You can see one message in queue as below


Now, how to read the message from Queue. Lets create a console application to read the message from Queue.
Here the connection string will be the same which we had specified in the plugin registration tool. The message body is of type RemoteExecutionContext.


Output

 

Retrieve data from N-N relationship in CRM


Sharing a sample code to retrieve data from N-N relationship in CRM. We have a trainer profile which have N-N relationship with Centre that means one trainer can visit to multiple centre and one centre can be assigned to multiple trainers. Here we are trying to retrieve list of centres associated with particular trainer.

List<Centre> centreList = new List<Centre>();
QueryExpression query = new QueryExpression(“mc_centre”);
query.ColumnSet = new ColumnSet(new string[] { “mc_centreid”, “mc_name” });

LinkEntity linkEntity1 = new LinkEntity(“mc_centre”, “mc_mc_trainer_mc_centre”, “mc_centreid”, “mc_centreid”, JoinOperator.Inner);
LinkEntity linkEntity2 = new LinkEntity(“mc_mc_trainer_mc_centre”, “mc_trainer”, “mc_trainerid”, “mc_trainerid”, JoinOperator.Inner);

linkEntity1.LinkEntities.Add(linkEntity2);
query.LinkEntities.Add(linkEntity1);

linkEntity2.LinkCriteria = new FilterExpression();
linkEntity2.LinkCriteria.AddCondition(new ConditionExpression(“mc_trainerid”, ConditionOperator.Equal, new Guid(trainerId)));

EntityCollection centreCollection = service.RetrieveMultiple(query);
if (centreCollection.Entities.Count > 0)
{
foreach (var centre in centreCollection.Entities)
{
Centre cr = new Centre();
cr.centreId = centre.Attributes[“mc_centreid”].ToString();
cr.centreName = centre.Attributes[“mc_name”].ToString();
centreList.Add(cr);
}
}

return centreList

Execute Workflow using Web API in Dynamics 365


Sharing sample code to call workflow using Web API from JavaScript.
First, we will create a normal workflow which works on demand and a create task with predefined subject and description and activate the workflow.Workflow

Copy the workflow Id and save it to use in JavaScript function.Call below JavaScript function from ribbon button from Lead entity.

function CallWorkflow() {
var workflowId = “71A6BC35-16D8-4447-8ADE-F040CDAE9524″;
var clientURL = Xrm.Page.context.getClientUrl();
var leadId = Xrm.Page.data.entity.getId().replace(‘{‘, ”).replace(‘}’, ”);
var data = {
“EntityId”: leadId
};
var req = new XMLHttpRequest();
req.open(“POST”, clientURL + “/api/data/v8.2/workflows(“+workflowId+”)/Microsoft.Dynamics.CRM.ExecuteWorkflow”, true);
req.setRequestHeader(“Accept”, “application/json”);
req.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);
req.setRequestHeader(“OData-MaxVersion”, “4.0”);
req.setRequestHeader(“OData-Version”, “4.0”);
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null;
if (this.status == 200) {
var data = JSON.parse(this.response);
} else {
var error = JSON.parse(this.response).error;
alert(error.message);
}
}
};
req.send(JSON.stringify(data));
}
For more details, please refer below URL
http://www.inogic.com/blog/2016/11/execute-workflow-using-web-api-in-dynamics-365-2/
hope this helps.

 

%d bloggers like this: