Tuesday, October 23, 2018

Extracting attachments from an incoming email, create case, and add attachments to as notes

Problem

One of my client's using Dynamics CRM Online 2016 Update had a requirements to create Case (renamed to Enquiry) records when an email arrives to their info@*****.com mailbox. Using OOTB Automatic Record Creation and Update Rules feature a Case is created and the email is visible under Activities.
Our client felt that the user experience wasn't great and the original Email was hard to find with all the other activities got created afterwards.

Solution

The solution was to extract the attachments from the email message and create notes against the Case record during the case creation process. Below are the steps used to implement the solution.
  1. Create a custom workflow activity to extract the attachments and create notes
  2. Create a workflow which includes the above custom workflow activity as a Step
  3. Create an Automatic Record Creation and Update Rule
  4. Specify Record Creation and Update Details which calls the workflow created in step 2

Implementation

Create a custom workflow activity to extract the attachments and create notes

Below is the source code of the custom workflow activity.
using System;
using System.Activities;
using System.Linq;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Workflow;

namespace Dyn365Apps.CRM.Workflow
{
    public class ExtractAttachmentsFromEmailAndCreateNotes : CodeActivity
    {
        [RequiredArgument]
        [Input("Email")]
        [ReferenceTarget("email")]
        public InArgument<EntityReference> receivedEmail { get; set; }

        [RequiredArgument]
        [Input("Enquiry")]
        [ReferenceTarget("incident")]
        public InArgument<EntityReference> enquiry { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var trace = context.GetExtension<ITracingService>();

            try
            {
                var serviceFactory = context.GetExtension<IOrganizationServiceFactory>();
                var service = serviceFactory.CreateOrganizationService(Guid.Empty); //Use current user's IDif (service != null)
                {
                    trace.Trace("Organization Service Created");
                }

                // Get Attachment Count
                trace.Trace("Get Attachment Count");
                var rem = receivedEmail.Get(context);
                Entity email = service.Retrieve(rem.LogicalName, rem.Id, new ColumnSet("attachmentcount"));
                int atc = (int)email["attachmentcount"];
                trace.Trace("Attachment count = " + atc.ToString());

                if (atc > 0)
                {
                    // Get all attachments
                    QueryExpression queryAtt = new QueryExpression("activitymimeattachment");
                    queryAtt.ColumnSet = new ColumnSet(newstring[] { "activityid", "attachmentid", "filename", "body", "mimetype", "subject" });
                    queryAtt.Criteria = new FilterExpression();
                    queryAtt.Criteria.FilterOperator = LogicalOperator.And;
                    queryAtt.Criteria.AddCondition(new ConditionExpression("activityid", ConditionOperator.Equal, email.Id));
                    EntityCollection eatt = service.RetrieveMultiple(queryAtt);
                    var entities = eatt.Entities;

                    trace.Trace("Entities count = " + entities.Count());

                    foreach (var ent in entities)
                    {                        
                        trace.Trace("Inside the for loop");
                        trace.Trace("Attributes count = " + ent.Attributes.Count());

                        // Instantiate an Annotation object.
                        Entity annotation = new Entity("annotation");

                        if (ent.Attributes.Contains("subject"))
                        {
                            trace.Trace("subject = " + ent.Attributes["subject"].ToString());
                            annotation["subject"] = ent.Attributes["subject"].ToString();
                        }
                        else
                        {
                            trace.Trace("subject not found");
                            annotation["subject"] = "Undefined";
                        }
                        
                        if(ent.Attributes.Contains("filename"))
                        {
                            trace.Trace("filename = " + ent.Attributes["filename"].ToString());
                            annotation["filename"] = ent.Attributes["filename"].ToString();
                        }
                        else
                        {
                            trace.Trace("filename not found");
                            annotation["filename"] = "Undefined.txt";
                        }

                        if (ent.Attributes.Contains("mimetype"))
                        {
                            trace.Trace("mimetype = " + ent.Attributes["mimetype"].ToString());
                            annotation["mimetype"] = ent.Attributes["mimetype"].ToString();
                        }
                        else
                        {
                            trace.Trace("mimetype not found");
                            annotation["mimetype"] = "plain/text";
                        }

                        if (ent.Attributes.Contains("body"))
                        {
                            annotation["documentbody"] = ent.Attributes["body"];
                        }
                        
                        trace.Trace("objectid = " + enquiry.Get(context).Id.ToString());
                        
                        annotation["objectid"] = enquiry.Get(context);
                        annotation["objecttypecode"] = 112; // Case// Create a Note with the attachment
                        service.Create(annotation);
                    }
                }
            }
            catch (Exception ex)
            {
                trace.Trace("ex.Message = {0}", ex.Message);
                trace.Trace("ex.StackTrace = {0}", ex.StackTrace);
            }
        }
    }
}

Create a workflow which includes the above custom workflow activity as a Step

  • Create a workflow and name it Extract Attachments from Email - Create Notes - Attach to Enquiry
  • Set the Entity as Email
  • Enable workflow to run as a child process.
  • Add the custom workflow activity ExtractAttachmentsFromEmailAndCreateNotes as a step.
  • Custom workflow activity requires two inputs
  1. Email
  2. Case (Enquiry)
  • Activate the workflow.

Create an Automatic Record Creation and Update Rule

  • Navigate to Settings > Service Management and click on Automatic Record Creation and Update Rules.
  • Create a new Record Creation and Update Rule.

Specify Record Creation and Update Details which calls the workflow created in step 2

  • Create a new Record Creation and Update Details record.
  • Configure the conditions and actions.
  • Under Specify Other Actions, add a new step to Start a Child Workflow.
  • Select the Workflow created in Step 1
That's it (Assumption: The mailbox is configured to receive emails).
Now send an email with attachments to the mailbox.
The attachments in the Email Message are now under Case record's Notes section.

Enabling Auditing to monitor User's Security Role changes

I got an call from a client who recently went live with a system we built. Some of their user's security roles were getting removed and they wanted to find out what's going on.
Dynamics 365 (CRM) can record association and disassociation of security roles of a user as part of the OOTB Auditing capability.
Here are the instructions I provided my client to resolve the issue.

Enable Auditing

First step is to make sure Auditing feature is still enabled as we have configured.
Navigate to Settings > Administration > System Settings
Click Auditing Tab
Under Audit Settings, tick the Start Auditing checkbox
If you would like to monitor user access, tick Audit user access checkbox as well.
Click OK.

Enable Auditing for Security Roles

Next step is to make sure Auditing is enabled for Security Roles entity.
Navigate to Settings > Customizations > Customize the System
New Window opens
Expand Entities from the left pane
Click on Security Roles entity (Please wait for few seconds for it to load)
Tick Auditing checkbox.
Click Save.
Click Publish.

Viewing Audit Logs

Finally, to view the audit logs,
navigate to Settings > Auditing > Audit Summary View
You’ll see the list of audit entries
Double click to open the record to see more details.
That's it. 

Sunday, September 9, 2018

Code Snippet for making all form fields read only




function makeFieldsReadOnly() {
    var controls = Xrm.Page.ui.controls.get();
    for (var i in controls) {
        var control = controls[i];
        if (control.getDisabled && control.setDisabled && !control.getDisabled()) {
            control.setDisabled(true);
        }
    }
}

Friday, June 8, 2018

Key Points for Editable Grid in Dynamic 365


Limitations of Editable Grid

Before working with it, we need considering these notes for the limitation.
For field
  • Composite fields will not working in editable grid, such as address, full name fields.
  • State, state code will not be effect to change directly from enable grid.
  • Customer fields.
  • Party list fields (often being used in activity of Crm).
  • Fields from related entities
  • Field Security. Field
For form
Fields are set read-only on the form are not effected to editable grid.

Consideration of Editable Grid

  • Xrm.Page is not worked on editable grids.
  • OnRecordSelect, OnSave, OnChange events are support in editable grid.
  • Nested grid does not work in web and tablet, only work for mobile client.
  • Business rules is support for editable grid:
    • Set field value
    • Set business required
    • Lock/Unclock field
    • Set default value

Tuesday, May 29, 2018

Create a custom Grid in MS Dynamic CRM using Jquery DataTable

Sometime we need to show our data in table format. We should use Jquery DataTable to show the data in sub-grid (Table) format. 

Copy the below code and paste this code in your editor. And change the code as per your need. Here i will create a DataTable of Case Entity.


<html>
<head>
    <title>MS Dynamic CRM</title>
  
    <script src="ClientGlobalContext.js.aspx" type="text/javascript"></script> 
 <link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css">
 <link rel="stylesheet" href="https://cdn.datatables.net/select/1.2.5/css/select.dataTables.min.css">
 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
 <script src="https://code.jquery.com/jquery-1.12.4.js"> </script>
 <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"> </script>
   
<script>

  var dataSet;
  var arrData = [];

$(document).ready(function() { 
 // Get the data in Json format, Change the URL as per your need.
    var entityName ="incident";    // This is the Entity name of Case.
  var  url = window.parent.Xrm.Page.context.getClientUrl() + "/api/data/v8.2/" + entityName +"s?$select=title,ticketnumber,prioritycode";      
  var myData = []; 
  var req = new XMLHttpRequest();
  req.open("GET",url, 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.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
  req.onreadystatechange = function() {
   if (this.readyState === 4) {
    req.onreadystatechange = null;
    if (this.status === 200) {    
       myData = JSON.parse(this.response); 
       dataSet=myData.value;     
    } else {
     Xrm.Utility.alertDialog(this.statusText);
    }
   }
  };
  req.send();
     
   // Convert Json data into 2-d Array
    arrItems = [];    
  $.each(dataSet, function (index, value) {  
   arrItems.push(value.title);
   arrItems.push(value.ticketnumber);
     // arrItems.push(value.prioritycode);   or
   arrItems.push(value["prioritycode@OData.Community.Display.V1.FormattedValue"]) ;  // For OptionSet value    
   arrData.push(arrItems);   // Push The Values Inside the Array to Create 2-D Array
   arrItems = [];          
  });
        
  table(); // Call a table function to create table.  
});

function table() { 
 $('#customdatatable').DataTable( {
        data: arrData,
        columns: [
            { title: "Title" },  // Change the column name as per your need.
   { title: "Ticket Number" },
   { title: "Priority" }          
        ]
    } );
}
   
</script>
</head>

<body style="word-wrap: break-word;">
   
 <table id="customdatatable" class="display" width="100%"></table>

</body>

</html>

 Create a new HTML Web-resource and Upload the code in this web-resource and check the table.









Asynchronous Processes/Wrokflows Stuck in InProgress/Waiting status in MS Dynamic CRM

CRM developer/User once in a life faces the issue regarding Asynchronous Processes stuck in same status i.e. InPrgress/Waiting/Pausing/Canceling.You can see the system job status is not changing.
AsyncWF

The Reason-

The main reason behind this issue  –
  1. Many jobs are in waiting status.
  2. Asyncoperationbase table become full due to many succeeded/canceled jobs occupied space.
  3. Asynchronous processes settings are not proper.
  4. The asynchronous workflows are not configured properly.

Solution-

The solution for this issue –
  1. Very first step is to restart the  Microsoft Dynamics CRM Asynchronous Processing Service. This might work in many cases.
  2. Many Jobs are in waiting status->
    you can update the job status to canceled and completed by creating console application or from database-
    a. Create the console and use the script –
    CancelWFProgramatically
    b. Using Database Query-
    Note- You should create restore(Checkpoint) point first before working on database directly to rollback changes if needed.
    CancelWFDB
    You can use “where statusCode=10 –Waiting”.
  3. Asyncoperationbase table become full due to many succeeded/canceled jobs occupied space –
    You need to cleanup the database by deleting the succeeded and canceled jobs-
    Make sure that only the following Async operation types are deleted if the state code of the types is 3 and the status code of the types is 30 or 32:
    • Workflow Expansion Task (1)
    • Collect SQM data (9)
    • PersistMatchCode (12)
    • FullTextCatalogIndex (25)
    • UpdateContractStates (27)
    • Workflow (10)
    CleanupScript
    If script took very long time then you should stop the script and rebuild the indexes for AsyncOperationBase as well as PrincipalObjectAccess tables. And run the script again.
  4. You can check if the values are optimal-
    • AsyncItemsInMemoryHigh
    • AsyncItemsInMemoryLow
    • AsyncStateStatusUpdateInterval
    • AsyncMaximumThreadsPerCPU
    • AsyncSelectInterval
    • AsyncSelectParallelism
    • AsyncThrottlingConfiguration
    Also you can check the ‘AsyncSdkRootDomain’ setting from  [MSCRM_CONFIG].[dbo].[DeploymentProperties]
    WFsetting1
    Recommended value of ‘AsyncSdkRootDomain’ should be same as ‘ADSdkRootDomain’. Or you can put server name as value.
    WFsetting2
  5. And Finally you can check your asynchronous work flow logic.
    There are many possibilities the workflows are stuck due to internal logic.
You have to restart the  Microsoft Dynamics CRM Asynchronous Processing Service after executing any above step.

Thursday, May 3, 2018

Bad Characters Messing Up Your Migration to Microsoft CRM Dynamics


Our migration process typically consists of moving the source data into a staging SQL Server database prior to the actual migration to CRM. Among other reasons, this gives us a place to do data cleansing prior to the CRM migration.
We run into many common issues such as field length differences and data type mismatches that are often found during the data mapping process with the customer. One less common issue we encounter in testing a migration is that some characters in the source data are not supported in CRM when importing data via the API. There are certain non-printable characters that are supported such as carriage-return and line-feed however others like record separator [char 30] or vertical tab [char 11] often are not accepted when migrating data to CRM.
We've developed a common SQL framework we use to allow us to do some data analysis and clean-up of these invalid characters in our staging tables prior to doing our push of the data to CRM. In most cases we run the data migration without any cleansing and capture any failed rows into an error table where we keep the source system record id and the CRM API error message. From there we can determine if any entities had errors around invalid characters. Here is an example of what that error would look like. In our example we are using the KingswaySoft CRM Adapter for SQL Server Integration Services.
image1
Once we know what entities and fields have invalid characters we can start to build our cleanup routine from our base framework.

So which characters are going to cause us a problem?
In our research we found that the Microsoft Dynamics CRM API does not like asci characters below character number 32 (which is the space " " character). So we start with a list of 1 – 31 to represent potential bad characters. We also know that horizontal tab, carriage return, and line feed (CHAR(9), CHAR(13), and CHAR(10) respectively) are valid in CRM and should not be in this list of bad characters.
For the sake of examples, here is a sample script to spin up 10 ‘note’ records with potentially bad data in the NoteText field.
To create the list of bad characters, we used a Common Table Expression (CTE). The below script gives a numbered list containing the asci character values of known-bad characters called, ‘BadCharacters’.
From there it's a matter of writing a query that will join the known bad characters CTE with your stage table and have it review each character in the field that was reported as having bad characters in your error logging of your data migration. Here the SQL Cross Apply clause comes in handy to make this a simple process. In this example we are migrating notes into the CRM notes entity. I know from my error logging shown above that the notetext field in my stage table has some bad characters that CRM did not like. So I cross apply my BadCharacters table with my notes staging table and have it inspect the notetext field for bad characters (using the above CTE definition).
Here are the results of the above query on my data set. I can see exactly what records, what the raw value is currently in that field, what the bad character was reported and where in the string it exists.
image2
After I do my analysis and confirm that it’s acceptable to replace these characters an update script is run against my stage table. Here is my final script that I can include in my data migration process to swap out any bad characters with a blank string in my stage table prior to sending these records to CRM.
It should be noted that the CTE spins up 256 possible rows to cover every possible ascii character. In this case we know that we only want to do the cross apply on a subset of these potential values. But the Bad Characters CTE could be amended to include/exclude any ascii characters.

Hope it will help You!!.

Get files of last hour in Azure Data Factory

  Case I have a Data Factory pipeline that should run each hour and collect all new files added to the data lake since the last run. What is...