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.