Create a new PCF Button Control

  1. Create a new folder in your directory

2. Open Visual Studio Developer 2019 command prompt and navigate to the NewPcfButton folder, and type the following pcf command:
pac pcf init –namespace CanvasAppClosePcfControl –name CanvasAppClose –template field

You should see a pcf project getting created in the NewPcfButton folder.

3. Open Visual studio code, and open this project by navigating to the NewPcfButton folder

4. Now, we will need to install the project dependencies. Click on Terminal in the visual studio code ribbon and select New Terminal. Type the command in the terminal – npm install

You’ll notice a new folder “node_modules” created in the NewPcfButton folder
5. Next, type npm run build to build the solution

6. Type npm start to watch the pcf control in your local browser. You can debug and test your pcf control here

Now that we have the pcf project created, built, and run successfully, let’s go ahead and customize and add a new PCF button control. In this post, we will have not have either a bound/unbound control. Therefore, in your ControlManifest.Input.xml file, command out the following line:

<!–<property name=”sampleProperty” display-name-key=”Property_Display_Key” description-key=”Property_Desc_Key” of-type=”SingleLine.Text” usage=”bound” required=”true” />–>

Next, In your index.ts file, define the following global variables and an eventSubmitClicked event

private _context:ComponentFramework.Context<IInputs>;
private theNotifyOutputChanged:() => void;
private _container:HTMLDivElement;
//Html Elements - Creating a text box and a button

private _eleMainContainer:HTMLDivElement;
private _eleButton:HTMLButtonElement;
private eventSubmitClicked:EventListenerOrEventListenerObject;

Next, inside the init function, write the following code:

this._container = container;
this._context = context;
this.theNotifyOutputChanged = notifyOutputChanged;
//The assignment of the event listener to the function should be done before creating the UI        

this.eventSubmitClicked = this.submitClicked.bind(this);
//Create UI        

//Main Container
this._eleMainContainer = document.createElement("div");
this._eleMainContainer.className = "mydiv";
//Define a button
this._eleButton = document.createElement("button");
this._eleButton.className = "my-btn my-btn-primary";             
this._eleButton.innerHTML = "New button";
this._eleButton.addEventListener("click", this.eventSubmitClicked);
//Add the button inside the main container, and the main container inside the container

this._eleMainContainer.appendChild(this._eleButton);
this._container.appendChild(this._eleMainContainer);

Now, that the button is created, we bind the button to the submitClicked event. Write the following function below your init function.

//Custom event handler – for the button
 private submitClicked(event: Event): void{  alert(“Button is clicked”);    }

Next, build the project using npm run build, and npm start to see the button in action

Click on the button.

The pcf button control works!!

Deployment:

Let’s work on deploying this pcf control to CRM

The first step is to create a solution folder inside the NewPcfControl project folder

The next step is to create a publisher and publisher prefix. This will bind the control to the given publisher and the publisher prefix. When this is imported into CRM, a new publisher and the publisher prefix is created with the given names and the pcf control is bound to that prefix. You could also make use of your existing publisher and publisher prefix if you don’t want to create a new one. Type the following command in the Developer Command prompt for VS 2019

pac solution init –publisher-name MyCompany –publisher-prefix new

The next step is add the pcf control project solution reference to the solution folder. Type the following command, but make sure you replace the path with your project path

pac solution add-reference –path “C:\Users\HameedHussain\Downloads\NewPcfButton”

Next, build the solution directory, type the following command:

msbuild /t:build /restore

This command will build the CRM zip file, that you will use to import your solution into CRM. Let’s look at the Solution and see where we would find the solution zip file

The CRM zip file is found inside the bin folder

Import the Solution.zip file into CRM, and use the PCF control as you see fit in any entity.

Additional Notes:

By default, the solution imported will have the display name and Name of the solution as “Solution”. The default solution version is 1.0 This is because the solution.xml contains the unique name, and description of the CRM solution as “Solution”. You can change the Unique name and Name of the solution as per your solution naming convention. See below.

If you make changes to the Pcf control code and wish to import the new changes, just run the build command (npm run build) and make directory (msbuild /t:build /restore), and import the solution. It’s a good practice to update the solution version on every build.

‘The ‘version’ attribute is invalid – The value ‘1.0’ is invalid according to its datatype ‘versionType’ – The Pattern constraint failed.’

While implementing the PCF control, after building the solution, if you run into the following issue while importing the custom control solution

String”>An error has occurred. Try this action again. If the problem continues, check the Microsoft Dynamics 365 Community for solutions or contact your organization’s Microsoft Dynamics 365 Administrator. Finally, you can contact Microsoft Support. : Microsoft.Crm.Tools.ImportExportPublish.ImportCustomControlException: CustomControl with name failed to import with error: The import manifest file is invalid. XSD validation failed with the following error: ‘The import manifest file is invalid. XSD validation failed with the following error: ‘The ‘version’ attribute is invalid – The value ‘1.0’ is invalid according to its datatype ‘versionType’ – The Pattern constraint failed.’.”‘.” —> Microsoft.Crm.CrmException: The import manifest file is invalid. XSD validation failed with the following error: ‘The import manifest file is invalid. XSD validation failed with the following error: ‘The ‘version’ attribute is invalid – The value ‘1.0’ is invalid according to its datatype ‘versionType’ – The Pattern constraint failed.’.”‘.”at Microsoft.Crm.Tools.ImportExportPublish.CustomControlsInstaller.ValidateSchema()at Microsoft.Crm.Tools.ImportExportPublish.CustomControlsInstaller.Validate(ExecutionContext context)at Microsoft.Crm.Tools.ImportExportPublish.CustomControlsInstaller.Install() — End of inner exception stack trace — at Microsoft.Crm.Tools.ImportExportPublish.CustomControlsInstaller.Install()at Microsoft.Crm.Tools.ImportExportPublish.ImportCustomControlsHandler.ImportItem()

It is most likely due the version attribute being invalid. Make sure the version is 1.0.0 in your ControlManifest.Input.xml file

Power Automate Flow – Flow client error returned with status code “Bad Request”

In Power Automate Flow, you may encounter the error below when tryin to save the flow

Request to XRM API failed with error: ‘Message: Flow client error returned with status code “BadRequest” and details “{“error”:{“code”:”InvalidOpenApiFlow”,”message”:”Flow save failed with code ‘InvalidTemplate’ and message ‘The template validation failed: ‘The repetition action(s) ‘Apply_to_each’ referenced by ‘inputs’ in action ‘Create_a_new_record’ are not defined in the template.’.’.”}}”. Code: 0x80060467 InnerError: Type: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=9.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

There issue may happen when you’re trying to copy a value (example, lookup) from one flow to the other because the flow may have different collection (list of records for example) names. Therefore, when you’re copying one value from one flow to the other, you’re also copying the collection name.

For example, in one flow, your collection name is

outputs(‘Run_a_Child_Flow_-_Retrieve_Records’)?[‘Body’]?[‘new_personcategoriesid’]

In the other flow, if the collection name is different, and you’re copying the above value and pasting in the flow, you’ll see the “Bad Request” error.

To fix the issue, I deleted the value, selected the lookup field from the existing flow using dynamic content. It let me save the flow. This is a simple issue, often that people may figure it out, but I wanted to put it out there if someone run into this issue.

Azure DevOps Pipeline for CRM – Solution Packager

The first step in implementing the Azure DevOps pipeline process for Dynamics 365 is to extract/decompose the CRM solution into its individual components. This is done through the solution packager provided by Microsoft. You can download and install the solution packager from this link

After you install the the solution packager on your disk, you will see the following list

You can find the SolutionPackager.exe file in the CoreTools

Open windows powershell command prompt and navigate to the SolutionPackager.exe folder – D:\NuGetTools\Tools\CoreTools

Before we go to the next step, in the CRM instance, create a new empty solution and export the solution, this solution file is a zip file containing the customizations and solutions xml

We will try to extract this solution using the solution packager and see what it looks like. Go back to the powershell command, and type the following

.\SolutionPackager.exe /action:Extract /zipfile:”SolutionfolderPath/SolutionName.zip” /folder “OutputFolder”

Below, I specified where my solution is and the output folder where I want the extract the zip folder contents.

.\SolutionPackager.exe /action:Extract /zipfile:”D:\Other\DevOps\NewDemoSolution_1_0_0.zip” /folder “D:\Other\DevOps\devopsfiles”

Let’s see what the output folder looks like:

Because, our solution was empty, we did not find the Enities, Plugins, Webresources folders. Let’s add few components to our Dynamics CRM solution

Export this solution, and run the SolutionPackager.exe from the powershell again, you’ll notice extraction of the different components

After you navigate to the output folder, you’ll see the different folders

Navigating inside the Entities folder, you’ll notice that the entities are split into their own folders and files containing the forms, views, charts etc.

Now, you can upload the root folder to the Git/GitHub/TFS, or any other repo, and take the next step in the integration of Azure Dev Ops pipeline for CRM.

Retain Overridecreatedon and createdby in Dynamics 365 during data import

In Dynamics 365 data import, the data imported will create records with today’s date and the data created will be logged against the user who is currently logged in even though the original createdon and createdby user could be different.

In many cases, when you’re importing an existing or legacy data in your Dynamics 365, you want to retain the original createdon date and the user who created that record. If you want to import your data with the date when the data was created, you can use overridecreatedon to override the data with the createdon date. You can also create this record against the original createdby user through impersonation. There are few ways to achieve this in Dynamics 365. You can use the kingswaysoft SSIS package to achieve this. However, in this blog, you will see how overridecreatedon and created by can achieved through xRM solution.

We have to use the OrganizationServiceProxy class to impersonate the createdby user. The class object has a property called “CallerId” that will be used to impersonate a user.

Here’s the code:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
using System.Net;
using System.ServiceModel.Description;
using Microsoft.Xrm.Sdk.Query;namespace DynamicsProxy
{
  class Program
  {
     static void Main(string[] args)
     {
       try
       {
         string crmUserName = "xxxx@onmicrosoft.com";
         string crmPassword = "xxxxxx";
         string crmOrgHost = "https://orgname.crm.dynamics.com/";            
          ClientCredentials clientCredentials = new ClientCredentials();
          clientCredentials.UserName.UserName = crmUserName; 
                                                             
            clientCredentials.UserName.Password = crmPassword; 
                                                               
            // For Dynamics 365 Customer Engagement V9.X, set Security Protocol as TLS12
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


            string crmApiPath = "XRMServices/2011/Organization.svc";

            Uri crmUri = new Uri(crmOrgHost + crmApiPath);

            OrganizationServiceProxy orServiceProxy = new OrganizationServiceProxy(crmUri,null,clientCredentials,null);
            orServiceProxy.EnableProxyTypes();
            //Caller Id: this is the guid of the user we want to impersonate - 4b591077-9fb8-e911-a86e-000d3a372124
            orServiceProxy.CallerId = new Guid("47d57787-433b-eb11-a813-000d3a31c841");
            Entity ent = new Entity("contact");
            ent["lastname"] = "Hameed";
            ent["overriddencreatedon"] = new DateTime(2021, 1, 19);
            orServiceProxy.Create(ent);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: "+ex.Message);
        }
    }
}
}

During the code execution, you may run into an error:

{“Principal user (Id=xxxx-xxx-xxx-xxx-000d3a31c841, type=8, roleCount=5, privilegeCount=811, accessMode=0), is missing prvOverrideCreatedOnCreatedBy privilege (Id=d48cf22f-f8c2-xxxx-89eb-49f8281dxxxx)

To resolve this issue, you need to enable the override createdon privilege on the security role that the user is currently assigned to

Power Automate – Invalid type. Expected Object but got Array

In Power automate, in the parse json, if the response doesn’t align with the schema of the parse json, you’ll notice different errors, one of them being “Invalid type. Expected Object but got Array”.

The Parse json schema was expecting an object as a response.

But, instead, received an array of objects, which results in the following error

This error happens when you’re passing a list of array oject records from the child workflow or list records from the previous steps.

So the parse json was expecting an object in the format of

{

“Var1″:”var1_value”,

“Var2″:”var2_value”

}

But instead receiving an array of objects:

[

{

“Var1″:”var1_value”,

“Var2″:”var2_value”

}

]

The output value outputs(‘Get_List_record_of_Person_Engagement_Event’)?[‘body/value’] returns an array/list of objects

To solve this problem, you need to return just the first object value from the result. Use the following expression (in the above Response body value) to return the first value from the array

first(outputs(‘Get_List_record_of_Person_Engagement_Event’)? [‘body/value’])

This will return the following object

{

“Var1″:”var1_value”,

“Var2″:”var2_value”

}

Which is now in-line with the format that we were expecting in the parse json

An undeclared property ‘new_bulkadjustment’ which only has property annotations in the payload but no property value was found in the payload.

Recently, I was running into an issue on xrm.webAPI.createRecord, when trying to update the lookup attribute to the entity object.

The oData naming convention for lookup attribute and the documentation around it asks you to use the schema name of the field with @odata.bind:

entity[“new_BulkAdjustment@odata.bind”] = “/new_revcontacts(00000000-0000-0000-a000-000d0a000a00)”;

This did not resolve the issue. I tried few other combinations using the REST builder, but unfortunately, nothing worked.

The way to set the lookup is:

entity[“FieldSchemaName_EntityName@odata.bind”]=”/LookupEntityName(guid)”;

entity[“new_BulkAdjustment_new_revcontacts@odata.bind”]=”/new_revcontacts(00000000-0000-0000-a000-000d0a000a00)”;

new_BulkAdjustmnet is a lookup field on new_revcontact entity.

USD Outbound call – Triggering an UII action from a Session Overview

In USD session overview xaml, you can trigger an event or UII action through a command parameter. You can pass the replacement parameters as parameters to this UII action.

In this example, we will trigger an UII action. Additionally, we will pass the telephone number and agent Id to this UII action. The xaml for the telephone number attribute and the command parameter looks like this:

<Image Style=”{DynamicResource ImageLogo}” Source=”{Binding Source=msdyusd_Phone16, Converter={StaticResource CRMImageLoader}}” />

    <TextBlock  TextWrapping=”Wrap” Padding=”5,0,0,5″ FontSize=”12″ Text=”Mobile: ” Foreground=”#262626″  VerticalAlignment=”Center”>

  <Hyperlink Command=”CCA:ActionCommands.DoActionCommand” CommandParameter=”http://uii/CTIConnector/MakeCall?tel:%5B%5Bcontact.mobilephone%5Du+x%5D%26agentid:%5B%5Bsystemuser.edw_agentid%5Du+x%5D”&nbsp; FontWeight=”Regular” AutomationProperties.Name=”Telephone Number [[contact.mobilephone]+x]”  Foreground=”#FF3B79B7″ FontSize=”12″>[[contact.mobilephone]+x]</Hyperlink>

       </TextBlock>

</StackPanel>  

     </Grid>

</Grid>

NOTE: To pass more than one parameter in the command parameter URL, always use encoded format. In the above example, & is encoded as %26. You can’t pass & but the encoded format of ‘&’ which is %26

MakeCall is the custom UII action. I am overriding this action in the CtiConnector custom code. The code will perform the outbound call through a telephony system.

From the session overview on USD, click on the mobile phone link

Couple of things to note:

In this example, we have one command parameter. If you want to add an additional command parameter in the xaml, you need to add an additional hyperlink element. This is not recommended.

If you want to trigger or perform multiple actions through this command parameter, the best way to do it is adding a new event and fire that event through the command parameter. For example, in the same example, if you want to trigger a phone call and also create a phone call record, you can handle this through the event.

You can fire the event through this command:

CommandParameter=”http://uii/Patient/FireEvent?name=FireOutboundCall

FireOutboundCall is the event that is being triggered. You can create this new event on the hosted control Patient, and add action calls

Azure Virtual Network (AVN) and Subnets

Azure Virtual Network (AVN) and Subnets

Source: https://www.udemy.com/course/microsoft-azure-beginners-guide/

Azure Virtual Network is a home for virtual machines

AVN consists of an IP address range

Subnet is a logical separation of resources that you can have in a virtual network.

Each subnet has an address range and is a subset of address range of the AVN

You can spin up VMs in each of the subnets

Each VM has an IP address which is part of the subnet address range

The VM IP addresses (10.1.1.6 & 10.1.2.10) are private IP addresses and is basically used for the internal communication between the VMs

120.20.1.20 – Public IP address – Users can access the application hosted on the VM from the internet

The other subnet does not have a public IP address. This VM could be used to host the database that should not be exposed to the public/internet

Network Security Groups

  • are used to control the flow of traffic into and out of the virtual machine
  • is a seperate resource defined in the azure platform
  • gets attached to the network interface that is attached to the virtual machine
  • can be attached to the network interface card (network security card – In this case, it just impacts that VM) to one VM or linked to the whole subnet (in this case, it affects the entire VMs on that subnet)
  • NSG consists of Inbound and Outbound security roles
  • When an NSG is created, some default inbound and outbound rules are already created which cannot be removed or changed
  • By default, the virtual machine does not allow traffic from the outside world, therefore you need to implement inbound rules and open the port 80 (http listener).
  • You have to setup rules accordingly to allow traffic on port 80. Source is IP address of your computer or the internet (for all users). Destination is your virtual machine/virtual network.
Source: https://www.udemy.com/course/microsoft-azure-beginners-guide/

If you want to connect to VM using RDP, then add an inbound rule for RD for port 3389

Source of the Inbound traffic rules

Denying the inbound traffic from a certain is controlled by priority – Example.

A request is sent and goes through the rules, if a match is found, then that rule is executed.

Destination depends on network interface (VM specific) and subnet (group of VMs)

If the network security group is attached to the subnet, then specify the IP addresses of the virtual machines that will allow the incoming traffic