MS Power Automate : Act on Approvals directly from Dynamics CRM on column value change/Button click

What is Issue?

There are lots of business scenarios where the approval requires on the records before proceeding or pushing it to next levels or complete the process. For these scenarios now we have the Power Automate Approvals. When approvals are triggered, it goes to the approvers mailbox to approve it, then approver can happily act on it without navigating to the other systems. But in some cases, Approver gets lots of the approval requests and he need to check each request by navigating to the system. After checking the approval again he need to switch the screen and approve it from mailbox. Now the frustrated approver says, “I can see all my requested records in system directly, I don’t want to go back to the mailbox again!!” and he/she will ask you “is it possible to approve the request from the system only?”

The Reason-

It is easier to act on approval from email which is very quick and easy process. But how the approval action works in power automate is something not explored. So I can say less exposure to the Approval tables in Dataverse.

Solution-

Power Automate provides Approval actions. Once you add it on your flow, system automatically installs some solutions containing approval tables and components in your selected environment(instance).

First lets have look at these tables-

  1. Approval – contains the Approval details
  2. Approval Request – contains the information of who requested and who is approver
  3. Approval response – contains the information about approval responses
  4. Flow Approval – contains the Approval action details which is used in actual power automate flow

In the case where you have to mark the approvals complete and resume the approval flow, you have to look for Approval table and Flow Approval table.

Lets see an example of flow-

I am using column on Account form to act (approve/reject) the approval for the account without using my mailbox to approve the request.

On change of Approval Status column value my approval action should be tracked and flow should be proceed as per my response. On completion of the request I should receive the mail saying approval succeeded or approval rejected.

This is how my approval flow looks like- I am manually triggering the approval for the demo purpose.

When I ran this create Approval Flow I received below approval request on my mailbox with the account URL in link in it.

Let’s see the another flow which will trigger when I update Approval Status column value.

Next step will be finding the correct approval record based on Account ID. So as I mentioned I have added the Item Link to the approval with record id, so I can query the approval where Item Link contains the account id for which flow is triggered. Just for the information you can see the Approval table column which I am using for this purpose-

So next step on my flow is to list the rows from Approval table where linked item contains account id and pending for approval-

Once I got the response then I can find the Flow approvals associated with the Approval record and from this Flow Approval row “Flow Notification URI” column value is the useful URL for me to send the response to the approval request.

Before sending the response to Approval flow using Flow Notification URL, I am making sure the my Approval is completed, so that it will not keep waiting for my response even we completed the approval flow. So I am setting status reason to Completed, Stage to Complete, Result with the Approval status label value(optional) and Status as inactive.

Before sending response to the Approval using “Flow Notification URI”, we need to prepare some json body. It accepts any key value pair, but I am making sure that I am preparing the response similar to the json structure when we approve the request from email request. Once response body is prepared, I am sending the post request to the “Flow Notification URI” with composed json body.

So my completed flow looks like below-

My flow is ready for testing-

When I am approving/ rejecting the request , My approval flow is resumed and completed, and I received mail as per my action –

When I navigated to the Approval request in my mailbox, It is showing that Others have already completed this request. Yay!!!… it worked.

So that’s it.

You can change the trigger of flows according to your requirement. For button click – change it to HTTP trigger and pass the required information on click of buttons.

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

MS Power Automate : Exception handling with Try-Catch-Finally

What is Issue?

MS providing all new ways to develop the application and moving to the less-code implementations. For implementing quick asynchronous logics, MS introduced Power Automate for more than hundred of the products. With traditional coding, developer knows how to implement exception handling. But what about power automate?

The Reason-

Every complex logic is exception prone and implementing it with traditional coding way, it is easy to handle with try-catch-finally in code. We need to understand the exception handling in Power Automate.

Solution-

Power Automate provides many actions and configurations, which we can use to implement Try-Catch-Finally. Lets see-

Example- I am using Account field to check how many times account name is updated. Every time when account is updated, need to send the email notification, in both the cases when name update is failed or success.

We need 3 control -> Scope, each with name – Try, Catch and Finally

Add business logic into the Try block. Variable initialization is not allowed in the Scope control, so you need to initialize all variables at the top of the Try block only.

Configure Catch block to run after “Try” only if “Try” block Failed, or Timed-out.

Configure Finally block to run after “Catch” if “Catch” block Failed, Successful, Timed-out or Skipped

Add logic in Catch block – how to read the exact errors and send it to any user/Team or stored in any record, if some thing fails in Try block?

Use the result from Try block and filter it with only failed or Timed out actions-so use the Filtered Array and iterate through each item from filtered array-

Some useful expressions –

Filtered array From result of Try :

result('Try')

Filter only failed and timed out results using advanced mode in Filtered array –

@contains(createArray('Failed', 'TimedOut'), item()['status'])

Read action name which is failed-

@{items('Apply_to_each_result')['name']}

Read Status-

@{items('Apply_to_each_result')['status']}

Read exact error Message-

@{items('Apply_to_each_result')['error/message']}

Set the Subject line and body in the Try and Catch both, so that in Finally we can send the success or failure email. Now, my Try-Catch-Finally blocks looking like this->

Now testing Time-

On successful run, it will skip Catch Block but will hit the Finally->

Email received on successful flow run-

On Error in Try, it will hit the Catch block and capture the failure details. It will hit Finally to send email on failure with error details-

Email received on failed flow run-

In failure- You can add the current flow run log url, so that any developer can easily track the log and find out the exact issues for failure.

We can use the below syntax to get current flow run log url-

concat('https://us.flow.microsoft.com/manage/environments/', workflow()?['tags']['environmentName'], '/flows/', workflow()?['name'], '/runs/', workflow()?['run']['name'])

That’s it!! You learnt the Exception handling in MS Power Automate.

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

Download File : Power Automate and JavaScript

What is Issue?

When working on D365 CRM, Some time user wants to download file from SharePoint, OneDrive or CRM Notes itself. So how you will do it in shortest way?

The Reason-

Downloading file from SharePoint or any other cloud location it will be time consuming process if you choose Web service or any WCF service.

Solution-

Now Power Automate provides less code and quick solutions. Power Automate has all the connectors and Actions which help you to connect these cloud locations and process on the files.

Let’s see a scenario, I want to download a file on button click on my CRM Account form. The file is located in my account record Timeline Notes.

Let’s go step by step.

First of all, as this is action on click of button on form, we can choose “When Http Request Received” trigger in Power Automate. And in HTTP request we need record id i.e. accountid to retrieve specific Note attachment i.e. “CustomerInfoDoc”-

Now from above step we will get the Document – File name and Document body (base64) content.

So we need a variable to store files in array and another variable for document content-

Setting the variables from the Notes list which we got from “List rows” action-

We need to append array with below json-

{
“Name”: @{items(‘Apply_to_each’)?[‘filename’]},
“ContentBytes”: @{items(‘Apply_to_each’)?[‘documentbody’]}
}

We will use Name and ContentBytes in JavaScript Code which will be in the response of HttpRequest.

Now once file we got file content we need to send them in response-

We need the only file from Notes, In List Row action we can add row count =1, but still in response we can set formula in body first(variable(‘ContentArray’). As this is document in response add headers “Content-type”:”multipart/form-data”

Now our complete flow is ready and it looks like –

So now remaining part is calling the flow from button to download the file-

Add the below javascript and call “callPowerAutomateFlow” function on button click. Below is complete code for download –

function callPowerAutomateFlow(formContext) { 
    id = formContext.data.entity.getId().replace("{", "").replace("}", ""); 
    var account = '{ "accountid": "' + id + '"'+ '}'; 
    var flowUrl = "https://prod-04.centralindia.logic.azure.com:443/workflows/xxxxxxxxxxxxxxxxxxxxxxxxxx/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=xxxxxxxxxxxx"; 
 
    var req = new XMLHttpRequest(); 
    req.open("POST", flowUrl, true); 
    req.setRequestHeader("Accept", "application/json"); 
    req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 
    req.onreadystatechange = function () { 
        if (this.readyState === 4) { 
            req.onreadystatechange = null; 
            if (this.status === 200) { 
                    var result = JSON.parse(this.response); 
                    var fileName = result.Name; 
                    var fileCont = result.ContentBytes; 
                    var filebyte = base64ToBufferArray(fileCont); 
                    downloadFile(fileName, [filebyte]); 
            } else { 
                Xrm.Utility.alertDialog(this.responseText); 
            } 
        } 
    }; 
    req.send(account); 
} 
 
function base64ToBufferArray(base64content) { 
    var binaryString = window.atob(base64content); 
    var binaryLen = binaryString.length; 
    var bytes = new Uint8Array(binaryLen); 
    for (var i = 0; i < binaryLen; i++) { 
        var ascii = binaryString.charCodeAt(i); 
        bytes[i] = ascii; 
    } 
    return bytes; 
} 
 
function downloadFile(name,data) { 
        var blob = new Blob(data, { type: "octet/stream" }), 
            url = window.URL.createObjectURL(blob); 
        if (window.navigator && window.navigator.msSaveOrOpenBlob) { 
            window.navigator.msSaveOrOpenBlob(blob, name); 
        } else { 
            const url = window.URL.createObjectURL(blob); 
            const a = document.createElement('a'); 
            document.body.appendChild(a); 
            a.href = url; 
            a.download = name; 
            a.click(); 
            window.URL.revokeObjectURL(url); 
            document.body.removeChild(a); 
        } 
}

That’s it!! when I am clicking on Download button in CRM Account record, it is downloading my Attachment file from Notes which is with title “CustomerInfoDoc”.

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

Attaching File from SharePoint Location in Email using Power Automate while working on D365 CRM

What is Issue?

When working on D365 CRM, Some processes needs to send notification as well as approval mails to particular users. In these emails some time users want the files to be attached which is uploaded for any particular record in CRM. Developer faces issue to get the file from SharePoint, because of very few columns available in SharePoint which is created by D365 CRM to SharePoint Integration. So the issues are like how to get the file from SharePoint and attach it email?

The Reason-

Using Workflow or custom plugin it will be much time consuming and also complex coding required with this approach. Developer can choose this approach for sending out simple notifications, emails. But when the requirements comes to file attachment from SharePoint or any other cloud location it will be difficult to handle with this approach. Also if the file is in SharePoint location which is configured using Document management in CRM, location will have limited set of columns, so it becomes difficult to query the SharePoint site using API also.

Solution-

For the first issue, which approach to choose in these scenarios-
Microsoft is already recommending users to use Power Automat instead of CRM Workflows. Power Automate is having multiple connectors and actions available which reduces the coding as well as time efforts. So we can choose Power Automate.

Now second Issue, How to get file from SharePoint location which is configured using Document Management in CRM?
As I already mentioned, Power Automate already having hundreds of connectors and actions, we can use the Get File Action in Power Automate. Now again you will be worried about how you can get particular file from this location as Tittle or any other field will not help here. So we can use FileLeafRef column filter for that.

Lets take scenario –

When user have opportunity where he uploads the template for Opportunity Activity logs in Document section. But before closing opportunity user needs the log template approved by his manager. Suppose there is flag when user submits the approval request.

  1. Trigger action for my Power Automate flow will be “Microsoft Dataverse when a row is added, modified or deleted where I will set row filter as AwaitngApprovalflag eq 1
  2. Now we need to initialize the Array which will work as byte array to store the file retrieved from the SharePoint.
  3. Add new SharePoint action “Get File(Properties Only)” and set SharePoint details with Filter Query – FileLeafRef eq “filename.xlsx” (this will be my log file template name)

4. Now add the SharePoint action “Get File Content” and set site address as SharePoint site, File Identifier as identifier from last action i.e. “Get File (Properties Only)”. This will automatically add Apply to Each loop.
5. Append the Array variable with File Content and file name from the above action “Get File Content”.
Example-
{
“FileName”:”fileName.xlsx”,
“ContentByte”:FileContent
}

7. Add action “Send Email With Option” and set participant and email body, and other details. In attachment section set the Array Variable which we initialized and configured with file content.

8. Add condition action and check the response from the manager in Email like he clicked on Approve or Reject button in email. and based on condition perform the next actions on your D365 CRM Record.

9. The Complete Flow looks like-

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech

Batch operation ($batch) using OData API Dynamics 365

What is Issue?

There are OData API provided by Microsoft to perform operations on D365 Tables like Create, update, delete and retrieve. These operations can be perform individually by building the OData API using some tools or with your knowledge. But some time there are the cases where you need to call the bulk create, bulk update, bulk delete or some bulk create and bulk update/delete simultaneously. If there are more than one records, you need to hit OData API one by one multiple times which takes cost in terms of time and performance of the application which is calling OData API. Is there a way to combine these all requests in one call and execute?

The Reason-

There is way to perform the CRUD operations on D365 Tables using OData, but everyone aware of the single operations. Microsoft provides the way to perform Batch operation on Tables using OData API. Lets see in solution for this.

Solution-

Microsoft D365 provides the OData for batch operation to perform using “$batch” like-

https://your_org.crm8.dynamics.com/api/data/v8.2/$batch

You need to include the all other CRUD (POST, PATCH, DELETE, GET) OData APIs in the body for the above request URL.

This request works like all other OData APIs using OAuth2.0 authentication with proper headers added.

Lets take .Net application where I need to perform the Create, update and some delete operation in one call.

You have to prepare your request as below-

Preparing the Batch Call-

#region prepare Batch Call
        private string BatchQueryCall(string crmRestQuery, string batchName, string requestBody)
        {
            string funResponse = null;
            try
            {
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, crmRestQuery);
                //add header parameters
                request.Headers.Add("Authorization", "Bearer " + oauthToken);
                request.Headers.Add("Accept", "application/json");
                request.Content = new StringContent(requestBody);
                request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/mixed;boundary=" + batchName);

                HttpResponseMessage response = httpClient.SendAsync(request).Result;
                string responseString = response.Content.ReadAsStringAsync().Result;

                if (response.IsSuccessStatusCode)
                {
                    funResponse = responseString;
                }
                else
                    funResponse = responseString;

            }
            catch (Exception ex)
            {
                funResponse = ex.Message;
            }
            return funResponse;
        }
        #endregion

Preparing to Call Batch Operation-

Suppose I have to create one record, update one record and delete 2 records in same call.
Below code helps you to understand preparing the Batch call-

  #region bulk Operations
        public string BulkOperationsAPI(string entityname)
        {
            #region Local Variable
            string crmRestQuery = apiUrl + "$batch";
            string batchName = "batch_bt123";
            string changeSetVar = "changeset_ch123";
            int coRelId = 1;
            string changeSet1 = null;
            string requestBody = "--" + batchName + Environment.NewLine
                             + "Content-Type:multipart/mixed;boundary=" + changeSetVar + Environment.NewLine + Environment.NewLine + Environment.NewLine;
            #endregion

            #region Create
                //Creating one record
                CreateUpdateOpportunity entity = new CreateUpdateOpportunity();

                entity.transactioncurrencyid_odata_bind = "/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)";
                entity.estimatedvalue = 1000;
                entity.estimatedclosedate = "2021-01-01";
                entity.name = "Generated From C# Code";
                entity.parentaccountid_odata_bind = "/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)";

                changeSet1 = PrepareReuqestBody("POST", changeSetVar, coRelId++, entityname, null, entity);

                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;
            
            #endregion

            #region Update
                //updating One record
                CreateUpdateOpportunity entity1 = new CreateUpdateOpportunity();

                entity1.transactioncurrencyid_odata_bind = "/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)";
                entity1.estimatedvalue = 1000;
                entity1.estimatedclosedate = "2021-01-01";
                entity1.name = "Generated From CSharp Code";
                entity1.parentaccountid_odata_bind = "/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)";

                changeSet1 = PrepareReuqestBody("PATCH", changeSetVar, coRelId++, entityname, "20e6bb72-2861-eb11-a812-0022486e93ce", entity1);

                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;
            #endregion

            #region Delete
            //delete 1st record  
            changeSet1 = PrepareReuqestBody("DELETE", changeSetVar, coRelId++, entityname, "29e6bb72-2861-eb11-a812-0022486e93ce", null);
                if (!string.IsNullOrEmpty(changeSet1))
                    requestBody += changeSet1 + Environment.NewLine;

            //delete 2nd record
            changeSet1 = PrepareReuqestBody("DELETE", changeSetVar, coRelId++, entityname, "27e6bb72-2861-eb11-a812-0022486e93ce", null);
            if (!string.IsNullOrEmpty(changeSet1))
                requestBody += changeSet1 + Environment.NewLine;
            #endregion


            requestBody += "--" + changeSetVar + "--" + Environment.NewLine;
            requestBody += "--" + batchName + "--";
            
            //format the body URLs
            requestBody = requestBody.Replace("\\/", "/");  

            //call the batch Operations
            return BatchQueryCall(crmRestQuery, batchName, requestBody);
        }
        #endregion

Preparing the body content of Batch call-

In above code there is call made to the function PrepareRequestBody-
This function will prepare the body content of Batch call.

        #region Prepare Request Body
        private string PrepareReuqestBody(string method, string changeset, int contentId, string entityName, string recordid, object entity)
        {

            string x = null;
            switch (method.ToUpper())
            {
                case "PATCH":
                    using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                    {
                        DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                        ser.WriteObject(streamOpportunitySerialize, entity);
                        streamOpportunitySerialize.Position = 0;
                        StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                        string objectJSON = srOpportunity.ReadToEnd();
                        objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                        x = "--" + changeset + Environment.NewLine
                            + "Content-Type: application/http" + Environment.NewLine
                            + "Content-Transfer-Encoding: binary" + Environment.NewLine
                            + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                            + "PATCH " + apiUrl + entityName + "(" + recordid + ") HTTP/1.1" + Environment.NewLine
                            + "Content-Type: application/json;type=entry" + Environment.NewLine
                            + "Accept:application/json" + Environment.NewLine + Environment.NewLine
                            + objectJSON + Environment.NewLine + Environment.NewLine;
                    }
                    break;
                case "POST":
                    using (MemoryStream streamOpportunitySerialize = new MemoryStream())
                    {
                        DataContractJsonSerializer ser = new DataContractJsonSerializer(entity.GetType());
                        ser.WriteObject(streamOpportunitySerialize, entity);
                        streamOpportunitySerialize.Position = 0;
                        StreamReader srOpportunity = new StreamReader(streamOpportunitySerialize);
                        string objectJSON = srOpportunity.ReadToEnd();
                        objectJSON = objectJSON.Replace("_odata_bind", "@odata.bind");

                        x = "--" + changeset + Environment.NewLine
                            + "Content-Type: application/http" + Environment.NewLine
                            + "Content-Transfer-Encoding: binary" + Environment.NewLine
                            + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                            + "POST " + apiUrl + entityName + " HTTP/1.1" + Environment.NewLine
                            + "Content-Type: application/json;type=entry" + Environment.NewLine
                            + "Accept:application/json" + Environment.NewLine + Environment.NewLine
                            + objectJSON + Environment.NewLine + Environment.NewLine;
                    }

                    break;
                case "DELETE":
                    x = "--" + changeset + Environment.NewLine
                    + "Content-Type: application/http" + Environment.NewLine
                    + "Content-Transfer-Encoding: binary" + Environment.NewLine
                    + "Content-ID: " + contentId + Environment.NewLine + Environment.NewLine
                    + "DELETE " + apiUrl + entityName + "(" + recordid + ") HTTP/1.1" + Environment.NewLine
                    + "Accept:application/json" + Environment.NewLine + Environment.NewLine;
                    break;
            }

            return x;
        }
        #endregion

Request Body content will look like-

--batch_bt123
Content-Type:multipart/mixed;boundary=changeset_ch123


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities HTTP/1.1
Content-Type: application/json;type=entry
Accept:application/json

{"estimatedclosedate":"2021-01-01","estimatedvalue":1000,"name":"Generated From C# Code","parentaccountid@odata.bind":"/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)","transactioncurrencyid@odata.bind":"/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)"}


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

PATCH https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64) HTTP/1.1
Content-Type: application/json;type=entry
Accept:application/json

{"estimatedclosedate":"2021-01-01","estimatedvalue":1000,"name":"Generated From CSharp Code","parentaccountid@odata.bind":"/accounts(3b3b7c71-61d2-ea11-a813-000d3af0205e)","transactioncurrencyid@odata.bind":"/transactioncurrencies(c63ba581-6bc6-e811-a96f-000d3af04fb0)"}


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

DELETE https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(46c9eab1-5ac2-4673-a55a-51cb886db902) HTTP/1.1
Accept:application/json


--changeset_ch123
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 4

DELETE https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64) HTTP/1.1
Accept:application/json


--changeset_ch123--
--batch_bt123--

Response recieved will be like-

  --batchresponse_319a4c10-b7fe-4bf0-b4a6-da016d1c0319
Content-Type: multipart/mixed; boundary=changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4

--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 204 No Content
OData-Version: 4.0
Location: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(3d4916b4-3861-eb11-a812-0022486e93ce)
OData-EntityId: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(3d4916b4-3861-eb11-a812-0022486e93ce)


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

HTTP/1.1 204 No Content
OData-Version: 4.0
Location: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64)
OData-EntityId: https://your_org.crm8.dynamics.com/api/data/v8.2/opportunities(d659b4d6-0f5b-eb11-a812-6045bd727e64)


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3

HTTP/1.1 204 No Content
OData-Version: 4.0


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 4

HTTP/1.1 204 No Content
OData-Version: 4.0


--changesetresponse_ee30dcdb-1094-4c24-8170-262eae9336a4--
--batchresponse_319a4c10-b7fe-4bf0-b4a6-da016d1c0319--

For Token Generation you can refer the code in my last blog – https://mscrm16tech.com/2021/01/21/crud-operations-on-dynamics-365-crm-tables-with-odata-api/

Hope this will help…
Enjoy Ms CRM!!!

Follow on Facebook- FB: MSCRM16Tech