Saturday, January 12, 2019

Dynamics 365 - Copying Notes from a Lead to an Account

We had a requirement to copy notes from a lead onto an account, when the lead is qualified. There is no OOB way to do this, but I found a Workflow library called “msdyncrmWorkflowTools” that has a step that can do this. Here is how:

The step is called “Clone Children”: https://github.com/demianrasko/Dynamics-365-Workflow-Tools/blob/master/docs/Clone%20Children.md

First install the Workflow library by downloading the solution from https://github.com/demianrasko/Dynamics-365-Workflow-Tools/releases and importing the solution into your CRM solution.

  1. Next create a workflow process that is attached to the “Account” entity.

    image
  2. After you save it, open the newly created process.
  3. Set the scope to “Organization” (not a required step)
  4. Set the execute as to “owner of the process” (not a required step)
  5. And set the process to run when the “record is created”
    image
  6. Next add a check condition to see if the account’s Originating Lead has been set, which would signal that the account was being created from a lead.
    image
  7. Next add the Clone Children step from “msdyncrmWorkflowTools (1.0.54.0)
    image
  8. Set the properties to the following values:
    image
    image image
    The relationship name is case-sensitive, so enter it as Lead_Annotation.
    The new parent field, which is used for the old parent field name is objectid.
  9. Save and activate the process and test it!

Friday, January 04, 2019

XrmToolbox–useful build settings

  1. ILMerge
    Used, when you have multiple assemblies and you need to package them together.
    Stage: Post Build Step
    Sample:

  2. ilmerge.exe /target:library /targetplatform:v4,"C:\Windows\Microsoft.NET\Framework\v4.0.30319" "/out:CombinedModuleName.dll" "inputModule1.dll" "inputModule2.dll" "inputModule3.dll"

  3. Copy the plugin to a plugins folder
    This makes it easier to tell XTB to load the dll you are developing

  4. if $(ConfigurationName) == Debug (
      IF NOT EXIST Plugins mkdir Plugins
      move /Y  "CombinedModuleName.dll" Plugins
    )
  5. Debug settings:
    Start external program: path to xrmtoolbox.exe
    Command line arguments:
    /plugin:"NameOfThePlugin" /overridepath:"path to the parent of plugin folder" /connection:"specific connection if you want to use one"

    Note: in step 2, we create a folder called Plugins under the debug output folder (eg: {ProjectName}\bin\Debug\Plugins). The path that OverRidePath should point to is the Debug folder. The reason is that XTB will look for a sub-folder called plugins to locate the plugin.

For more information: https://github.com/MscrmTools/XrmToolBox/wiki/Debug-your-plugins-during-development

Wednesday, January 02, 2019

Xrm.Page is deprecated–what to do in v9+

According to this: https://docs.microsoft.com/en-us/dynamics365/get-started/whats-new/customer-engagement/important-changes-coming, in V9, Xrm.Page is being deprecated. Xrm.Page used to be used in Javascript called by forms as well as the ribbon.

The fix is documented here:

Forms: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/clientapi-form-context#using-the-formcontext-object-instead-of-the-xrmpage-object

Ribbons: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/customize-dev/pass-dynamics-365-data-page-parameter-ribbon-actions#form-and-grid-context-in-ribbon-actions

The gist:

Forms:

  1. Pass the executionContext from the handler: see https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/clientapi-execution-context
  2. Add a parameter on your js function to accept the executionContext
  3. call getFormContext() on your parameter to get the form-context. See: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/clientapi-execution-context

function functionCalledDirectlyByTheForm(executionContext)
{
     var formContext = executionContext.getFormContext(); // get formContext

    // use formContext instead of Xrm.Page 
     var firstName = formContext.getAttribute("firstname").getValue();
     var lastName = formContext.getAttribute("lastname").getValue();
     console.log(firstName + " " + lastName);
}

Ribbon:

  1. Pass the PrimaryControl as a CRM Parameter to your javascript function
    see: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/customize-dev/pass-dynamics-365-data-page-parameter-ribbon-actions#form-and-grid-context-in-ribbon-actions
    In Ribbon Workbench:
    image
  2. Add a parameter on your js function to accept the primaryControl as a parameter
    function OnMyRibbonButtonAction(commandProperties, primaryControl) {
     
    }
  3. use the PrimaryControl parameter as though it were the formContext: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/customize-dev/pass-dynamics-365-data-page-parameter-ribbon-actions#form-and-grid-context-in-ribbon-actions

function functionCalledDirectlyByTheRibbon(primaryControl)
{
    // use primaryControl instead of Xrm.Page 
    var firstName = primaryControl.getAttribute("firstname").getValue();
     var lastName = primaryControl.getAttribute("lastname").getValue();
     console.log(firstName + " " + lastName);
}

Gotchas:

Dont use getFormContext in a function called by the ribbon. “getFormContext” is not available in the UCI, and so even though it may work in the legacy web-client, it will break when you begin using the UCI.

If you want a function to work when called from the form as well as the ribbon and you want it to work with the web-client and UCI, do something like this:

//use this to get an object which you can use like the formContext.
//this code can be called from a function that was either invoked from a form or from the ribbon.
function getFormContext(executionContext) {
     var formContext = null;
     if (executionContext !== null) {
         if (typeof executionContext.getAttribute === 'function') {
             formContext = executionContext; //most likely called from the ribbon.
         } else if (typeof executionContext.getFormContext === 'function'
                 && typeof(executionContext.getFormContext()).getAttribute === 'function') {
             formContext = executionContext.getFormContext(); // most likely called from the form via a handler
         } else {
             throw 'formContext was not found'; //you could do formContext = Xrm.Page; if you like.
         }
     }
     return formContext;
}

Others:

Because Xrm.Page is deprecated, you can no longer use Xrm.Page.context. Instead, you should use Xrm.Utility.getGlobalContext(). This method returns an object that supports most of the methods that were available from Xrm.Page.context.

See: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/reference/xrm-utility/getglobalcontext

This formContext diagram is extremely useful for determining what is available from the formContext and what is not:

formContext object model

from: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/clientapi-form-context

Understand the Client API object model: https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/understand-clientapi-object-model

Thursday, December 20, 2018

GitHub–Steps to merge a fork to its parent

3 simple steps: (this assumes you have setup a remote branch called upstream that points at the original repo)

  1. git fetch upstream
  2. git checkout master
  3. git merge upstream/master

And to setup a remote called upstream that points at the original repo:

git remote add upstream {url to the original repo}

for more info see: https://help.github.com/articles/syncing-a-fork/

Tuesday, December 18, 2018

Adding the Knowledge Base Search to the “Social Pane”

On the Case entity, the KB search is embedded as part of the Social Pane:

image

Now on a custom entity, after you have enabled it for Knowledge Management (done via Settings >> Service Management >> Embedded Knowledge Search), you have to add the KB control on to the form:

image

image

But, what if you wanted to add make it look like the Case entity, where the KB Search is part of the Social Pane? The “KB Search” tab does not show up as part of the properties of the Activities Tab.

image

The only way I have found to get it on the form is to manually edit the FormXML so that it shows up and then edit it via the form editor.

Here are the steps:

  1. Make sure you have the activities/social pane control on your form (if not, add it – its called the Time Line control).
  2. Give it a unique name (i called it “Timeline_1234”)
    image
  3. Create a new solution and add the entity to the solution.
  4. In the next screen, make sure you select only the form in question and uncheck “Include entity metadata” and “add all assets”
    image
  5. Dont include any required components (on the next screen).
  6. Export the solution as an unmanaged solution.
  7. Unzip the solution and open the “customizations.xml” file.
  8. Search for the control name (in this case it was “Timeline_1234”)
  9. It should look like this:

    <parameters>
         <UClientUniqueName>Timeline_1234</UClientUniqueName>
         <UClientModules>Activities,Notes</UClientModules>
         <UClientDefaultModuleForCreateExperience>Notes</UClientDefaultModuleForCreateExperience>
         <UClientShowFilterPane>true</UClientShowFilterPane>
         <UClientExpandFilterPane>false</UClientExpandFilterPane>
         <UClientOrderBy>descending</UClientOrderBy>
         <UClientRecordPerPage>10</UClientRecordPerPage>
         <UClientSortActivitiesByValue>modifiedon</UClientSortActivitiesByValue>
         <UClientDisplayActivityHeaderUsing>defaultformat</UClientDisplayActivityHeaderUsing>
         <UClientCreateActivityUsing>quickcreateform</UClientCreateActivityUsing>
         <UClientDisplayActivityUsing>defaultfields</UClientDisplayActivityUsing>
         <DefaultTabId>ActivitiesTab</DefaultTabId>
         <OrderByActivityWall>descending</OrderByActivityWall>
         <SortActivityWall>modifiedon</SortActivityWall>
         <EmailConversationView>true</EmailConversationView>
         <ShowArticleTab>false</ShowArticleTab>
         <SelectDefaultLanguage>00000000-0000-0000-0000-000000000000</SelectDefaultLanguage>
    </parameters>

  10. Update: ShowArticleTab to true
  11. Add the following:
    <FilterResults>3</FilterResults>
    <AllowChangingFiltersOnUI>false</AllowChangingFiltersOnUI>
    <ShowLanguageFilter>false</ShowLanguageFilter>
    <ShowDepartmentFilter>false</ShowDepartmentFilter>
    <EnableAutoSuggestions>false</EnableAutoSuggestions>
    <NumberOfResults>4</NumberOfResults>
  12. It should look like this:

    <parameters>
         <UClientUniqueName>Timeline_1234</UClientUniqueName>
         <UClientModules>Activities,Notes</UClientModules>
         <UClientDefaultModuleForCreateExperience>Notes</UClientDefaultModuleForCreateExperience>
         <UClientShowFilterPane>true</UClientShowFilterPane>
         <UClientExpandFilterPane>false</UClientExpandFilterPane>
         <UClientOrderBy>descending</UClientOrderBy>
         <UClientRecordPerPage>10</UClientRecordPerPage>
         <UClientSortActivitiesByValue>modifiedon</UClientSortActivitiesByValue>
         <UClientDisplayActivityHeaderUsing>defaultformat</UClientDisplayActivityHeaderUsing>
         <UClientCreateActivityUsing>quickcreateform</UClientCreateActivityUsing>
         <UClientDisplayActivityUsing>defaultfields</UClientDisplayActivityUsing>
         <DefaultTabId>ActivitiesTab</DefaultTabId>
         <OrderByActivityWall>descending</OrderByActivityWall>
         <SortActivityWall>modifiedon</SortActivityWall>
         <EmailConversationView>true</EmailConversationView>
         <ShowArticleTab>true</ShowArticleTab>
         <SelectDefaultLanguage>00000000-0000-0000-0000-000000000000</SelectDefaultLanguage>
         <FilterResults>3</FilterResults>
         <AllowChangingFiltersOnUI>false</AllowChangingFiltersOnUI>
         <ShowLanguageFilter>false</ShowLanguageFilter>
         <ShowDepartmentFilter>false</ShowDepartmentFilter>
         <EnableAutoSuggestions>false</EnableAutoSuggestions>
         <NumberOfResults>4</NumberOfResults>

    </parameters>

  13. Zip the files back up together.
  14. Import the solution and publish it.
  15. Open the form up in Form Editor and open the properties for the activities control.
    You should be able to set the properties for the KB search now:
    image

Thursday, November 15, 2018

Azure Website–Allowing different file types for download

An azure website out of the box will not allow you to download static content files such as JSON, MP4, etc. If you need to enable this feature, you need to add a web.config file in the site/wwwroot folder with the following contents

<?xml version="1.0"?>
<configuration>
     <system.webServer>
         <staticContent>
             <!--following needed only if extensions were defined at a different level -->
             <!-- <remove  fileExtension=".json" /> -->
             <!-- for a good list of other mimetypes see:
https://www.sitepoint.com/mime-types-complete-list/ -->
             <mimeMap fileExtension="json" mimeType="application/json" />
             <mimeMap fileExtension="mp4" mimeType="video/mp4" />
             <mimeMap fileExtension="ogg" mimeType="audio/ogg" />
             <mimeMap fileExtension="m4a" mimeType="audio/mp4" />
             <mimeMap fileExtension="flv" mimeType="video/x-flv" />
             <mimeMap fileExtension="woff" mimeType="application/font-woff" />
             <mimeMap fileExtension="woff2" mimeType="application/font-woff2" />
             <mimeMap fileExtension="ttf" mimeType="application/font-ttf" />
             <mimeMap fileExtension="csv" mimeType="text/plain" />
      </staticContent>
     </system.webServer>
</configuration>

You can pick and choose the values you need for fileextensions.

Wednesday, November 14, 2018

Dynamics CRM Plugin Registration Tool Log File Location

The Plugin Registration Tool stores its log files at: “C:\Users\{user}\AppData\Roaming\Microsoft\Microsoft Dynamics365© Plug-in Registration Tool\”

You can get to it via this shortcut: %appdata%\Microsoft\Microsoft Dynamics365© Plug-in Registration Tool\

And while we are at it, the path to XRM Toolbox’s logs is: %appdata%\MscrmTools\XrmToolBox\Logs\

Thursday, November 01, 2018

Currencies and Strong vs Weak terminology

Note to self:

1. When a currency is labelled as being strong, it means that you get more of the other currency against the currency (eg: US$ is stronger than the Indian Rupee, that would mean you get more rupees per US$).

2. When a currency is labelled as being weak, it means that you get fewer of the other currency against the currency (eg: US$ is weaker than the Indian rupee, that would mean you get fewer rupees per US$).

So in this chart, the US$ has become stronger when compared to the INR (because you getting more INR per $ (INR 63.2/$) today than 52 weeks ago (INR 74.4/$)).

image

When a currency is stronger than its counterpart, different people benefit: The weaker sides exporters are happier as their exports are cheaper and the stronger sides exporters are less happy as their exports




US Exporter

US Importer

US Currency Holder

Indian Exporter

Indian Importer

Indian Currency Holder

US$ stronger/INR weaker

Unhappy
Exports cost more

Happy
Imports cost less

Happy
More INR per $

Happy
Exports cost less

Unhappy
Imports cost less

Unhappy
Fewer $ per INR

US$ weaker/INR stronger

Happy
Exports cost less

Unhappy
Imports cost more

Unhappy
Fewer INR per $

Unhappy
Exports cost more

Happy
Imports cost less

Happy
More $ per INR

Examples

Caterpillar, Boeing, US Software companies

Walmart, World Market

American tourist to India

Indian Software companies, Indian manufacturers

International companies that manufacture their goods elsewhere and import to India, Indian resellers

Indian tourist/student travelling to US

Thursday, October 18, 2018

Cannot convert the literal 'undefined' to the expected type 'Edm.Guid'

If you get this error when working in Javascript with DynamicsCrm (or any system that uses Odata), and you get this error, then you most likely need to setup your object like this:

var request = {

    ObjectId: {guid: "E8C656B7-6AD1-E811-A967-000D3A30D5DB"}

};

Otherwise, the request that will get sent will have undefined value and the response will have an error with the following message:

message=An error occurred while validating input parameters: Microsoft.OData.ODataException: Cannot convert the literal 'undefined' to the expected type 'Edm.Guid'. ---> System.FormatException: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).

Tuesday, October 16, 2018

Dynamics CRM–Business Rules or Workflows?

When should you use a business rule (BR) and when should you use workflows?

First thing we should understand are that business rules are just another type of a process (just like Workflows and Business Process Flows). You can validate this by performing an advanced find and setting the category to Business Rule. Other options for category are:

image

Now when you create a business rule, there are 3 options (2 really), one that applies it to forms (all or specific) and the other that applies it to the entity. When you choose entity and create your BR, and activate it, you will find 2 entries in processes with the same name. One of these handles the javascript part (that gets run client side). The other is a synchronous workflow that runs on create and on update of the fields that are in conditionals of the business rule. You can look at the JavaScript that will run by looking at the ClientData field on Process. Also, the XAML field contains workflow.

Why is this important to know? Here is why:

1. If you have a business rule that needs to be run client side, then Business Rules are your best option. This is especially true if you want to show error messages or perform data validation.

2. But if you are going to have an entity level business rule and it doesn't really contain validation or error messages, then you have a choice: If you use a Workflow, you can decide to run it in the background (asynchronously).

Some other things to consider:

Business Rules have a nicer UI. But, every business rule that fires at the entity level creates a new synchronous workflow that fires on create and on update of a single field. If you use a workflow, you have more choices: you can create a single workflow that runs on Create, that does all the things that need to be done on create, this in my opinion is more efficient, then having multiple workflows that are all firing one after the other.

Also, look at the limitations of BR as defined in microsoft docs: https://docs.microsoft.com/en-us/previous-versions/dynamicscrm-2016/admins-customizers-dynamics-365/mt826761(v=crm.8)#limitations

Inner workings of Business Rule javascript:

Finally, if you are curious about the inner workings of business rules, see the code at: https://github.com/rajrao/CRM-Tools/tree/master/JavaScript/BusinessRules

But basically here are the main things to know:

The javascript for the business rule is injected into the formscript for the form.
The function itself is setup to run on change of the fields that are in the conditionals.
The function is run once at form startup.