Sunday, May 29, 2016

CRM and Microsoft Flow

This blog is about the new Microsoft Product named Microsoft Flow. A few days ago my colleague told me about Microsoft Flow and my initial reaction was wow, that is cool.

What is Microsoft Flow?

It is preview product that helps you setup automated workflows between different apps.  It can create records, sync files and get notifications etc.. There are a lot of templates available. It supports a wide collection of applications like CRM online, SharePoint Online, Office 365, OneDrive, Twitter, Facebook, Dropbox and even Salesforce and many more.
Check the following link for more information and templates.
https://flow.microsoft.com/en-us/

Setting up a CRM and Twitter integration workflow using Microsoft Flow

As a CRM guy, I wanted to test some of the CRM templates. There are 7 sample templates available for CRM. I tried the "Create Dynamics Leads based on tweets” template.
Here are all the steps.
  • Go to https://flow.microsoft.com/en-us/
  • Search for CRM templates and choose Create Dynamics Leads based on tweets.

    2016-05-30_10-47-11
  • The template will display the permission(login) screen for Twitter and CRM and add the connection details and click Continue.

    2016-05-30_11-02-26
  • The template will display the details of the workflow. It has 3 main parts as shown in the screenshot.

    2016-05-30_11-06-04
  • In the first part, add the query text to look for in new tweets.I have used CreateCRMLead.

    2016-05-30_13-37-05
  • We don’t have to change anything in the second step. The Get User steps is shown in the following screenshot.

    2016-05-30_11-07-08
  • In the next step, change the organisation name to your organisation name and click Create Flow

    2016-05-30_11-08-34
  • The wizard will display the Done button on next screen.

    2016-05-30_11-09-22
  • You can see your workflow under “My Workflows” as shown in the screenshot.

    2016-05-30_11-09-46

    Testing the workflow

  • Logon to your twitter account and tweet with the word CreateCRMLead (or whatever query text you used above) in it.

    2016-05-30_11-35-33
  • Check in the CRM after a few seconds, there will be a new lead as shown in the screenshot.

    2016-05-30_11-38-44
    It will also add some more information in the description field of the lead.

    2016-05-30_11-39-10


    Monitoring the workflow

    You can check the how your workflow is performing from the Microsoft Flow site as shown in the screenshot by clicking on “I” next to the workflow.
    2016-05-30_14-08-14
  • Thursday, May 26, 2016

    Email Signatures in CRM2016

    CRM2016 Update 1 has introduced email signatures in CRM2016. It is very important if you are using CRM web clients. In the past, CRM users used to copy paste their signatures into CRM emails or used email templates for signatures.

    Now you have an option to create one or more personal email signatures.

    1. Go to personal options as shown in the screenshot.

      2016-05-26_16-21-12_thumb2
    2. Navigate to the Email Signatures tab and press New.

      2016-05-26_16-23-37_thumb3
    3. Enter your email signature and press save.

      2016-05-26_16-25-52_thumb5
    4. You have an option to set the email signature as the default by clicking on the Set as Default button.

    Testing the email signatures

    Create a new email activity and your default email signature will appear automatically.

    2016-05-26_16-36-22_thumb5

    There is also a button available ‘Insert Signature’, which adds the signature manually or replaces the default signature. These buttons are highlighted in yellow in the above screenshot.

    Note: The embedded images in the email signature will not appear in the email unless they are hosted on the internet.

    So here you go. Now you have email signatures in CRM2016.

    Wednesday, May 25, 2016

    Response Routing Rules in Voice of the Customer’s surveys in CRM2016

    In this blog, we will explore the response routings functionality of the CRM VOC surveys. It is a very powerful feature. The response routings rules are equivalent to the business rules in CRM.
    Response routing rule  has 3  parts:
    1. General Tab (contains the name of the rule and name of the survey)
    2. Conditions Tab ( contains the conditions to be met on the survey)
    3. Actions (contains the actions to be taken when the conditions are met)
      Actions can be of 2 types
      • Client: These actions will be triggered on the survey itself on run time. for example hiding or showing a question.
      • Server: These action will be triggered out side a survey. for example creating a follow up or complaint etc..
    Routing rules can be used to show or hide the questions, skip to a question and end the survey etc.  based on the responses of the user.
    For this blog, I have created a sample survey with the following 3 questions.

    I have set the visibility field of the 3rd question to “Do not display”.If you preview this survey, it will look like the following screen shot.


    Requirement

    What I want to do is that if the users selects the rating of less than 3 for the second question, the survey should display the 3rd question to the user and also create a follow up task.

    Solution

    We will create a new response routing rule to fulfil the requirement.
    1. Navigate to the the survey and select Response Routing from the drop down menu as shown in the screen shot below


    2. The system will display the list of Response routings associated with the survey.
    3. The system will display the “New Response Routings” form.
    4. Enter the name for the routing rule and press same
    5. Navigate t the  “Conditions” Tab and a new condition.



    6. The system will display the condition form as shown in the screen shot below



      The condition in the above screen should is checking if the rating selected in the question 2 is less than 3.
      Note: The options for “Operator” and  “Comparison Value” will change based on the type of question selected. Save the condition.
    7. Navigate to the Actions tab and add a new action.

    8. The first action will be triggered on the client side. Select Client from the scope. The action form will look like the following screen.



      The Client tab displaying the actions details. In short, it is saying to display the question 3.
    9. The second action will be triggered on the server side.The action form will look like the following screen.



      The Server tab displaying the actions details. The action will create a response outcome record of type follow up add it to <Amreek Singh> queue.
    10. Make sure the response routing rule is activated and test the action.

    Test Results

    Client Side Results

    The following screen shot display the survey screen before responding to the 2nd question.


    The following screen shot display the survey screen after the rating of 2 is selected for the 2nd question.

    It is displaying the question 3 as setup in the client action.

    Server Side Results

    The following screen shot displays the response outcome record created by the server side action.

    Sunday, April 10, 2016

    Key values in CRM Lookup fields

    I have been working with CRM for nearly 9 years but I have never been aware of the “Key Values” available in the lookup control. Generally, when we retrieve the lookup control in JavaScript, we use code similar to the following :
     lookupFieldObject = Xrm.Page.data.entity.attributes.get('parentaccountid');   
     if (lookupFieldObject.getValue() != null) {   
     entityId = lookupFieldObject.getValue()[0].id;   
     entityName = lookupFieldObject.getValue()[0].entityType;   
     entityLabel = lookupFieldObject.getValue()[0].name;   
     }   
    
     What are the key values for lookup control?
    If you select the lookup entity on a form, the lookup control also contains a collection of key values that have other fields from the lookup entity. The screenshot below displays the key values available for an account lookup, when I select a parent account from a contact form.
     
    The screenshot displays the collection of other account attributes such as abn, address1state, address1_city, primarycontactid, telephone 1, etc. I did not know that these values were also available for a lookup control. Sometimes we need to retrieve the other attributes of the lookup entity and these key values can be used to retrieve them, instead of making an additional call.
    Where are these values coming from?
    After doing a bit of research, we have found that these attributes/values are coming from the lookup view of the entity.
    Here is the lookup view of the account entity in my system.
    These are the same fields as shown in the Key Values collection in the screen shot above.
     
    Gotchas
    Now to the bad news. These key values are not available all the time. These values are only available if you select the value manually from the form. These values won’t be available if the lookup value is set via code or a workflow. My colleague, Mr. Davey has an explanation, that these values get cached when we select the lookup control on the form. That sounds like a good explanation.
     

    Tuesday, April 5, 2016

    A bit more about SDK “Merge” message in CRM plugins

    One of our clients is using auto numbering on contact records. A few days ago, they reported to have duplicates in the system. So I started the investigation, I could not believe that this could happen in our solution. But what I had found out was that those duplicates were generated as a part of the "Merge" functionality available in CRM. Every time users "Merge" the customer and choose the subordinate's customer number to come across, it will override the master record's customer number with the subordinate record's number.

    So we decided to write a plugin that will stop the users from overriding the master record's customer number.
    The  context.InputParameters  collection of the merge message contains 3 of the following objects
    1. Target ( entity reference of the master record)
    2. SubordinateId ( guid of the subordinate record)
    3. UpdateContent ( temporary entity that contains the attribute that will be passed to the master record).

    The following example does not use the SubordinateId object. We are only using Target and UpdateContent entity. Like any other plugin entity, the "UpdateContent" entity only contains attributes that will be updated in the master record.
    Code
     protected void ExecutePreCustomerMerge(LocalPluginContext localContext)  
      {  
           if (localContext == null)  
           {  
             throw new ArgumentNullException("localContext");  
           }  
           IPluginExecutionContext context = localContext.PluginExecutionContext;  
           ITracingService trace = localContext.TracingService;  
           IOrganizationService service = localContext.OrganizationService;  
           // if the sdk message is merge  
           if (context.MessageName.Equals("merge", StringComparison.InvariantCultureIgnoreCase))  
           {  
             //get the merged entity  
             Entity updateContentData = context.InputParameters["UpdateContent"] as Entity;  
             //if the merged record is null then do nothing  
             if (updateContentData == null) { return; }  
               //get the customer number of merged record  
               string mergedCustNumber = updateContentData.GetAttributeValue<string>("custNumber");  
               if (mergedCustNumber == null) return;  
               else  
               {  
                 // if the merged CustNumber is not null retrieve the CustNumber of the master record to display in the message  
                 // get the master entity reference  
                 EntityReference targetReference = (EntityReference)context.InputParameters["Target"];  
                 //trace.Trace("entity reference " + targetReference.LogicalName.ToString());  
                 //retrieve the CustNumber number of the master entity  
                 Entity target = service.Retrieve(targetReference.LogicalName, targetReference.Id, new ColumnSet("custNumber"));  
                 string masterCustNumber = target.GetAttributeValue<string>("custNumber");  
                 throw new InvalidPluginExecutionException("You cannot update the CustNumber of the Master record. Master CustNumber :" + masterCustNumber + " and New CustNumber :" + mergedCustNumber);  
               }  
             }  
      }  
    

    Some Other Observations
    When I tested this plugin in CRM2013 roll up 2, I did not receive the message thrown using InvalidPuginExecutionException. Instead, I received the following message:
    I think it is a bug in CRM2013, that is now fixed in CRM2013 SP1.

    When I tested the same plugin on CRM2013 SP1 UR3, It displays the proper message, as thrown by the InvalidPuginExecutionException.




    Thursday, February 4, 2016

    Keypress methods in CRM2016

    CRM2016 introduced the new keypress methods for text and numeric fields to support keypress events on CRM forms. These methods will be useful to provide immediate feedback on key press. Traditionally, all the validations and actions were executed via the “on Change” event. These methods include
    • addOnKeyPress
    Use this method to attach an event handler to the keypress event
    • removeOnKeyPress
    Use this method to remove an event handler to the keypress event
    • removeOnKeyPress
    Use this method to manually fire an event handler to the keypress event
    For this blog, I have created sample code that will stop the input of numbers in the name field of the account record.
    Here are the steps
    1. Create a new JScript web resource and add the following code to it.

       // JavaScript source code  
       function keyPress()  
       {  
         //attach the validateInput function to the keypress event of the name attribute  
         Xrm.Page.getControl("name").addOnKeyPress(validateInput);    
       }  
       function validateInput() {  
         //get the value of the name field  
         var input = Xrm.Page.getControl("name").getValue();  
         if (input != "") {  
           //check if the last key pressed is a number  
           if (isNaN(input.substr(input.length - 1)) == false) {  
             //display the message  
             alert("Numeric values are not allowed in the account name");  
           }  
         }  
       }  
      

    2. Save and publish the web resource.
    3. Open the account form in customization mode.
    4. Add the JScript web resource to the form and call the keypress() method from load.
    5. Save and publish the account form.
    6. Test the code. If you try to press a number key on the name field you will get an error message as shown in the following screenshot.


    Sunday, January 31, 2016

    CRM Web API and CORS Support

    With the introduction of WEB APIs in CRM2016, you can do all the things that you used to do using organization services (SOAP endpoints). It is good news but the greatest news for me is the addition of CORS support.


    Remember the days when you could not use CRM REST endpoints for standalone applications because of CORS, now you can. According to the SDK, the new WEB API supports cross-origin web resource request when called using OAuth token authentication.This means that we can now create standalone and mobile applications using WEB API. The SDK contains a sample page to demonstrate this functionality. In this blog, I will follow that sample with some additional screenshots.


    Here is the step by step guide to create a html page using CRM Web API. I am using CRM Online for this post.

    Signup up CRM2016 online

    Setup a 30 day free trial CRM2016 organization from the following link:

    Creating the web application

    1. Using Visual Studio 2015, create a new ASP.Net web application project.
    2. Choose the “Empty” template.
    3. Add a new HTML page to the project and name it “CreateAccounts”. Copy the following code and paste it into the HTML page.

       <!DOCTYPE html>  
       <html>  
       <head>  
         <title>Simple SPA</title>  
         <meta charset="utf-8" />  
         <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/adal.min.js"></script>  
         <script type="text/javascript">  
        "use strict";  
        //Set these variables to match your environment  
                      var organizationURI = "https://hpeaustralia.crm6.dynamics.com"; //The URL to connect to CRM Online  
        var tenant = "hpeaustralia.onmicrosoft.com"; //The name of the Azure AD organization you use  
        var clientId = "ef9166ae-8d3f-48c9-b260-243d29f15355"; //The ClientId you got when you registered the application  
        var pageUrl = "http://localhost:39812/CRMAccounts.html"; //The URL of this page in your development environment when debugging.  
        var user, authContext, message, errorMessage, loginButton, logoutButton, getAccountsButton, accountsTable, accountsTableBody;  
        //Configuration data for AuthenticationContext  
        var endpoints = {  
         orgUri: organizationURI  
        };  
        window.config = {  
         tenant: tenant,  
         clientId: clientId,  
         postLogoutRedirectUri: pageUrl,  
         endpoints: endpoints,  
         cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.  
        };  
        document.onreadystatechange = function () {  
         if (document.readyState == "complete") {  
         //Set DOM elements referenced by scripts  
         message = document.getElementById("message");  
         errorMessage = document.getElementById("errorMessage");  
         loginButton = document.getElementById("login");  
         logoutButton = document.getElementById("logout");  
         getAccountsButton = document.getElementById("getAccounts");  
         accountsTable = document.getElementById("accountsTable");  
         accountsTableBody = document.getElementById("accountsTableBody");  
         //Event handlers on DOM elements  
         loginButton.addEventListener("click", login);  
         logoutButton.addEventListener("click", logout);  
         getAccountsButton.addEventListener("click", getAccounts);  
         //call authentication function  
         authenticate();  
         if (user) {  
          loginButton.style.display = "none";  
          logoutButton.style.display = "block";  
          getAccountsButton.style.display = "block";  
          var helloMessage = document.createElement("p");  
          helloMessage.textContent = "Hello " + user.profile.name;  
          message.appendChild(helloMessage)  
         }  
         else {  
          loginButton.style.display = "block";  
          logoutButton.style.display = "none";  
          getAccountsButton.style.display = "none";  
         }  
         }  
        }  
        // Function that manages authentication  
        function authenticate() {  
         //OAuth context  
         authContext = new AuthenticationContext(config);  
         // Check For & Handle Redirect From AAD After Login  
         var isCallback = authContext.isCallback(window.location.hash);  
         if (isCallback) {  
         authContext.handleWindowCallback();  
         }  
         var loginError = authContext.getLoginError();  
         if (isCallback && !loginError) {  
         window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);  
         }  
         else {  
         errorMessage.textContent = loginError;  
         }  
         user = authContext.getCachedUser();  
        }  
        //function that logs in the user  
        function login() {  
         authContext.login();  
        }  
        //function that logs out the user  
        function logout() {  
         authContext.logOut();  
         accountsTable.style.display = "none";  
         accountsTableBody.innerHTML = "";  
        }  
       //function that initiates retrieval of accounts  
        function getAccounts() {  
         getAccountsButton.disabled = true;  
         var retrievingAccountsMessage = document.createElement("p");  
         retrievingAccountsMessage.textContent = "Retrieving 10 accounts from " + organizationURI + "/api/data/v8.0/accounts";  
         message.appendChild(retrievingAccountsMessage)  
         // Function to perform operation is passed as a parameter to the aquireToken method  
         authContext.acquireToken(organizationURI, retrieveAccounts)  
        }  
       //Function that actually retrieves the accounts  
        function retrieveAccounts(error, token) {  
         // Handle ADAL Errors.  
         if (error || !token) {  
         errorMessage.textContent = 'ADAL error occurred: ' + error;  
         return;  
         }  
         var req = new XMLHttpRequest()  
         req.open("GET", encodeURI(organizationURI + "/api/data/v8.0/accounts?$select=name,address1_city&$top=10"), true);  
         //Set Bearer token  
         req.setRequestHeader("Authorization", "Bearer " + token);  
         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 accounts = JSON.parse(this.response).value;  
          renderAccounts(accounts);  
          }  
          else {  
          var error = JSON.parse(this.response).error;  
          console.log(error.message);  
          errorMessage.textContent = error.message;  
          }  
         }  
         };  
         req.send();  
        }  
        //Function that writes account data to the accountsTable  
        function renderAccounts(accounts) {  
         accounts.forEach(function (account) {  
         var name = account.name;  
         var city = account.address1_city;  
         var nameCell = document.createElement("td");  
         nameCell.textContent = name;  
         var cityCell = document.createElement("td");  
         cityCell.textContent = city;  
         var row = document.createElement("tr");  
         row.appendChild(nameCell);  
         row.appendChild(cityCell);  
         accountsTableBody.appendChild(row);  
         });  
         accountsTable.style.display = "block";  
        }  
         </script>  
         <style>  
           body {  
             font-family: 'Segoe UI';  
           }  
           table {  
             border-collapse: collapse;  
           }  
           td, th {  
             border: 1px solid black;  
           }  
           #errorMessage {  
             color: red;  
           }  
           #message {  
             color: green;  
           }  
         </style>  
       </head>  
       <body>  
         <button id="login">Login</button>  
         <button id="logout" style="display:none;">Logout</button>  
         <button id="getAccounts" style="display:none;">Get Accounts</button>  
         <div id="errorMessage"></div>  
         <div id="message"></div>  
         <table id="accountsTable" style="display:none;">  
           <thead><tr><th>Name</th><th>City</th></tr></thead>  
           <tbody id="accountsTableBody"></tbody>  
         </table>  
       </body>  
       </html>  
      
    4. The highlighted part in the above code are the variables that needs to be set to make this code work properly. I have left my settings for those variables in the code. Please change the values of these variables to match your system.
    5. Run the code. It will open a new browser window. Check the URL of that page. It will look something like http://localhost:3764/CrerateAccounts.html
    6. Replace the value of the variable “pageUrl” with the your URL from step 5 above.
    Note:  The next heading “Register the application with windows Azure” will provide the value for the “clientId” variable of the code.

    Register the application with windows Azure

    1. If you don’t have a Windows Azure account then you can create a trial account attached to CRM Online domain by logging into Office 365. Go to step 10.
    2. If you already have a Windows Azure subscription then you need to associate your Office 365 subscription (CRM online account) to the Windows Azure subscription. I already have an Window Azure subscription.
    3. Logon to Azure portal account.
    4. Click on “Active Directory” as shown in the following screenshot and click “New” to associate your Office 365 domain to Windows Azure.
    5. Select “Custom Create” from the following screen
    6. Select “Use existing directory” and tick “I am ready to be signed out”.
    7. Login as the admin of your Office 365 (CRM Online) account. You will see the following message.
    8. Click “Sign out now” on the following screen
    9. Login to the Windows Azure portal again using your Windows Azure account. You will now see 2 active directories.
    10. Click on the CRM directory, It will display the following screen.
    11. Click on the "Applications" tab and select add, as highlighted in the following screenshot.
    12. Select “Add an application my organization is developing”.


    13. Enter the following information:
      • Sign-on URL
        This is the URL which the user should be redirected to after they sign in. For debugging purposes in Visual Studio it should be http://localhost:####/CRMAccounts.html where #### represents the port number you got from step 4 of the Create a web application project procedure.  These are the same values as step 5 of the “Creating the web application”  section of the blog.
      • APP ID URI
        This must be a unique identifier for the application. Use https://XXXX.onmicrosoft.com/CRMAccounts . Where  XXXX is the Active Directory tenant.
    14. With the tab of the newly registered app selected, click "Configure" and locate the Client id. Copy the id and update the “clientId”  variable  of the web application.
    15. Select “Add application” and choose “Dynamics CRM Online”.
    16. Under permissions to other applications, you will find a row for "Dynamics CRM Online" and "Delegated Permissions: 0". Select this and add "Access CRM Online as organization users".
    17. Save the application registration.
    18. Click on “Manage Manifest” and select “Download Manifest”.
    19. Manifest is a json file. Open the file and locate the line: "oauth2AllowImplicitFlow": false, and change false to true and save the file.
    20. Upload the manifest file.


    Run the web application

    1. Now go back to your web application and press F5 to run the page. It will open the page in the browser.
    2. Click on login. The system will take you to the login page. The page will display the name of the logged in user.
    3. Click on “Get Accounts”. The browser will display the following page.

    That is it. Happy coding..