Thursday 18 July 2019

Dynamics - PDF Generator


In 2017, a third party provide a solution that use to generate PDF from CRM templates.














In recently, Dynamics 365 organization I have seen this solution as a part of Microsoft out-of-box solution list. And this solution is free.










Also Microsoft still not taken this feature as they done for Resco field service module.

So we can see all entity, web resource etc. solution component with third party prefix.


















We always need to send PDF to customer for quotation, Sales order or Invoices etc. In On-Premise CRM organization it was easy because we can use register plugin is none Isolation Mode and access other iText like assemblies.








But in Online Dynamics 365 it is not possible.

You can refer below link to get User Guide.

Working:-

Click on “Generate PDF Configuration” button top of solution lists and it will open “hcl_pdf_GeneratePDFConfigurator” web resource for us. We can configure/Enable list of Entity having document templates.
Form here we can remove the generate PDF feature for an entity and template whenever needed.


















After configuration is done, we can see a new button configured entity. However this button also visible on create form, which have no use until record is created.









Click on Generate PDF button it will open “hcl_pdf_GeneratePDF” web resource with some query string information from there we can generate our PDF and download or save in note section for selected document template. I have seen it worked of Word Templates.
























However I have not seen preview as showing in user guide. But download and add to note working fine.







We can also use this solution for sending email with PDF with some customization and configuration.

  1. Create a new workflow named. Ex-Customer Invoice.
  2. Create Email with all create information.
  3. Create custom workflow to get record (Invoice, Quote etc.) Guid.
public class GetRecordIDFromURL : CodeActivity
    {
        #region variable used
        [RequiredArgument]
        [Input("RecordURL")]
        public InArgument<string> RecordURL { get; set; }

        [RequiredArgument]
        [Output("RecordId")]
        public OutArgument<string> RecordId { get; set; }
        string traceMessage = string.Empty;
        #endregion
        protected override void Execute(CodeActivityContext executionContext)
        {
            //Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();
            //Create the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            traceMessage = "Workflow started.";
            tracingService.Trace(traceMessage);
            if (RecordURL.Get<string>(executionContext) != null)
            {
                RecordId.Set(executionContext, RecordURL.Get<string>(executionContext).Substring(RecordURL.Get<string>(executionContext).IndexOf("&id=")+4, 36));

            }
        }
      
    }
4. Call Action “Generate PDF Action”. It will return pdf data in  PDFBinaryData” parameter.
















5. Create custom workflow to add “activitymimeattachment” to email.
   var email = context.InputParameterOrDefault<EntityReference>("Email");
   var output = context.InputParameterOrDefault<String>("output");
  if (email!=null)//CreateEmailAttachment
                    {
                        Microsoft.Xrm.Sdk.Entity ActivityMimeAttachment = new Microsoft.Xrm.Sdk.Entity("activitymimeattachment");
                        ActivityMimeAttachment["subject"] = report.GetAttributeValue<string>("name");
                        ActivityMimeAttachment["filename"] = "Invoice.pdf";
                        ActivityMimeAttachment["body"] = output;
                        ActivityMimeAttachment["attachmentnumber"] = 1;
                        ActivityMimeAttachment["objectid"] = email;
                        ActivityMimeAttachment["objecttypecode"] = "email";
                        service.Create(ActivityMimeAttachment);
                    }

6. Create custom workflow to send that Email.
public class SendEmail: CodeActivity
    {
        private string traceMessage = string.Empty;

        [RequiredArgument]
        [Input("Email")]
        [ReferenceTarget("email")]
        public InArgument<EntityReference> Email
        {
            get;
            set;
        }

        #region Execute  function
        protected override void Execute(CodeActivityContext executionContext)
        {
            //Create the tracing service
            ITracingService tracingService = executionContext.GetExtension<ITracingService>();
            //Create the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            try
            {
                traceMessage = "Workflow started.";
                tracingService.Trace(traceMessage);
                if (Email.Get<EntityReference>(executionContext) != null)
                {
                    SendEmailRequest SendEmailRequest = new SendEmailRequest();
                    SendEmailRequest.EmailId=(Email.Get<EntityReference>(executionContext).Id);
                    SendEmailRequest.TrackingToken="";
                    SendEmailRequest.IssueSend=(true);
                    SendEmailResponse Response =(SendEmailResponse) service.Execute(SendEmailRequest);

                }
                else
                    traceMessage += " No Record found to update.";
            }
            catch (Exception ex)
            {
                tracingService.Trace(traceMessage);
                throw new InvalidPluginExecutionException("error occured in SendEmail workflow: " + ex.Message.ToString());
            }

        }
        #endregion
    }











Update action as per requirement.











Once this workflow will run an email with Invoice PDF will sent to customer.

You can also test this Action from XRMToolBox.

























In result you will see PDF content in binary format that can direct use in email or Note attachment.

Please share your feedback.

Saturday 6 July 2019

Integrate Dynamics CRM 365 with Node Js Application


In recent days Node Js becoming quite popular. Different kind of portals and applications are made on Node Js technology because of it's performance and efficiency.

So it's becoming important to integrated Dynamics CRM 365 with node JS.

In this blog I will try to explain how we can integrate Dynamics 365 to Node Js with help of Azure Apps.

After creating azure app we can integrate Dynamics 365 with our other products also.

There are two kind of Azure App which can be used for integration.

1. Native App
2. Web App/ API Application

We can use Both type of application in integration. But the major difference is:-
1. We need to use User name and Password while integrate via Native APP.

2.  We need to use secret key and application user while integrate via Web App /API APP.

Native APP Integration:-

1. Login in Azure with admin permission

2. Click on “Azure Active Directory”

























3. You will find two App registration
      a) App Registrations.
      b) App Registrations(Legacy)


















4.       Click on App registration not Legacy.

5.       Here we will find “Owned applications” and “All applications”, where we can see all registered Apps.












6. Click on “New registration” and select below options.




























Click on “Register” button.

7. Copy and Save Application (client) ID for further use.
















8. Click on Authentication and made below changes and save.












9. Click on API permission and add Dynamics CRM Online.





























































10. Grant admin consent






















11. Go back to App registration and select Endpoints.
and copy Token endpoint(v1)

































If you still want to create Legacy APP, select App registration(Legacy)

1. Provide information and select Application Type as “Native”.




















2. Copy Application ID to use as Client Id while integration.
3.  Click on settings and select “Required Permissions”.



























4. Add Dynamics CRM Online in permission.












We can also use Web App /API APP for Integration


1. With new App you can use same App for Native and Web API as both.

2.  Go to Authentication and provide Type as Web.












3. Add secret Key and create Application User .













Copy and Save secret key for further use.






























1. (Web App with Legacy App Registration) Provide information and select Application Type “Web App/ API”.



















2. Click on settings and select “Key”.

























We can choose one of four (Legacy Native App, Legacy Web App, New Native App, New Web App)
for integration.

Now our App ready to integrate for other product like Node.Js

1. To test application Node.js online, open













and paste below code. Update organization and application information in Code.

//Use Below Sample Code started For Native App Integration Testing
'use strict';
var https = require('https');

//set these values to retrieve the oauth token
var crmorg = 'https://CRMORG.crm8.dynamics.com';
//Application ID
var clientid = 'Application ID';
var username = 'User Name';
var userpassword = 'Password';
var tokenendpoint = 'Pasted copied Endpoint';
var crmwebapihost = ' CRMORG.api.crm8.dynamics.com';
var crmwebapipath = '/api/data/v9.1/contacts'; //basic query to select contacts
//remove https from tokenendpoint url
tokenendpoint = tokenendpoint.toLowerCase().replace('https://', '');
//get the authorization endpoint host name
var authhost = tokenendpoint.split('/')[0];
//get the authorization endpoint path
var authpath = '/' + tokenendpoint.split('/').slice(1).join('/');
//build the authorization request
var reqstring = 'client_id=' + clientid;
reqstring += '&resource=' + encodeURIComponent(crmorg);
reqstring += '&username=' + encodeURIComponent(username);
reqstring += '&password=' + encodeURIComponent(userpassword);
reqstring += '&grant_type=password';
//set the token request parameters
var tokenrequestoptions = {
    host: authhost,
    path: authpath,
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(reqstring)
    }
};
//make the token request
var tokenrequest = https.request(tokenrequestoptions, function (response) {
    //make an array to hold the response parts if we get multiple parts
    var responseparts = [];
    response.setEncoding('utf8');
    response.on('data', function (chunk) {
        //add each response chunk to the responseparts array for later
        responseparts.push(chunk);
    });
    response.on('end', function () {
        //once we have all the response parts, concatenate the parts into a single string
        var completeresponse = responseparts.join('');
        //console.log('Response: ' + completeresponse);
        console.log('Token response retrieved . . . ');
        //parse the response JSON
        var tokenresponse = JSON.parse(completeresponse);
        //extract the token
        var token = tokenresponse.access_token;
        //pass the token to our data retrieval function
        getData(token);
    });
});
tokenrequest.on('error', function (e) {
    console.error(e);
});
//post the token request data
tokenrequest.write(reqstring);
//close the token request
tokenrequest.end();
function getData(token) {
    //set the web api request headers
    var requestheaders = {
        'Authorization': 'Bearer ' + token,
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json; odata.metadata=minimal',
        'Prefer': 'odata.maxpagesize=500',
         'Prefer': 'odata.include-annotations=*'
    };
    //set the crm request parameters
    var crmrequestoptions = {
        host: crmwebapihost,
        path: crmwebapipath,
        method: 'Get',
        headers: requestheaders,
    };
    //make the web api request
    var crmrequest = https.request(crmrequestoptions, function (response) {
        //make an array to hold the response parts if we get multiple parts
        var responseparts = [];
        response.setEncoding('utf8');
        response.on('data', function (chunk) {
            //add each response chunk to the responseparts array for later
            responseparts.push(chunk);
        });
        response.on('end', function () {
            //once we have all the response parts, concatenate the parts into a single string
            var completeresponse = responseparts.join('');
           // console.log(completeresponse);
            //parse the response JSON
            var collection = JSON.parse(completeresponse).value;
collection.forEach(function (row, i) {
  console.log(row['fullname']);
  });
            //loop through the results and write out the fullname
        });
    });
    crmrequest.on('error', function (e) {
        console.error(e);
    });
    //close the web api request
    crmrequest.end();
}
//Use Below Sample Code started For WEB App Integration Testing
'use strict';
var https = require('https');

//set these values to retrieve the oauth token
var crmorg = 'https://CRMORG.crm8.dynamics.com';
//Application ID
var clientid = 'Application/Client ID';
var Client_secret = 'Copied Key';

var tokenendpoint = 'Copied endpoint';

//set these values to query your crm data
var crmwebapihost = 'CRMorg.api.crm8.dynamics.com';
var crmwebapipath = '/api/data/v9.1/contacts'; //basic query to select contacts

//remove https from tokenendpoint url
tokenendpoint = tokenendpoint.toLowerCase().replace('https://', '');

//get the authorization endpoint host name
var authhost = tokenendpoint.split('/')[0];

//get the authorization endpoint path
var authpath = '/' + tokenendpoint.split('/').slice(1).join('/');

//build the authorization request
//if you want to learn more about how tokens work, see IETF RFC 6749 - https://tools.ietf.org/html/rfc6749
var reqstring = 'client_id=' + clientid;
reqstring += '&resource=' + encodeURIComponent(crmorg);
reqstring += '&client_secret=' + Client_secret;
reqstring += '&grant_type=client_credentials';


//set the token request parameters
var tokenrequestoptions = {
    host: authhost,
    path: authpath,
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(reqstring)
    }
};

//make the token request
var tokenrequest = https.request(tokenrequestoptions, function (response) {
    //make an array to hold the response parts if we get multiple parts
    var responseparts = [];
    response.setEncoding('utf8');
    response.on('data', function (chunk) {
        //add each response chunk to the responseparts array for later
        responseparts.push(chunk);
    });
    response.on('end', function () {
        //once we have all the response parts, concatenate the parts into a single string
        var completeresponse = responseparts.join('');
        //console.log('Response: ' + completeresponse);
        console.log('Token response retrieved . . . ');

        //parse the response JSON
        var tokenresponse = JSON.parse(completeresponse);

        //extract the token
        var token = tokenresponse.access_token;
console.log(token);
        //pass the token to our data retrieval function
        getData(token);
    });
});
tokenrequest.on('error', function (e) {
    console.error(e);
});

//post the token request data
tokenrequest.write(reqstring);

//close the token request
tokenrequest.end();


function getData(token) {
 

 
    //set the web api request headers
    var requestheaders = {
        'Authorization': 'Bearer ' + token,
        'OData-MaxVersion': '4.0',
        'OData-Version': '4.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json; odata.metadata=minimal',
      
        'Prefer': 'odata.maxpagesize=500',
       
         'Prefer': 'odata.include-annotations=*'
      
    };

    //set the crm request parameters
    var crmrequestoptions = {
        host: crmwebapihost,
        path: crmwebapipath,
        method: 'Get',
        headers: requestheaders,
      

    };

    //make the web api request
    var crmrequest = https.request(crmrequestoptions, function (response) {
        //make an array to hold the response parts if we get multiple parts
        var responseparts = [];
        response.setEncoding('utf8');
        response.on('data', function (chunk) {
            //add each response chunk to the responseparts array for later
            responseparts.push(chunk);
        });
        response.on('end', function () {
            //once we have all the response parts, concatenate the parts into a single string
            var completeresponse = responseparts.join('');
           // console.log(completeresponse);
            //parse the response JSON
            var collection = JSON.parse(completeresponse).value;
collection.forEach(function (row, i) {
  console.log(row['fullname']);
  });

 
            //loop through the results and write out the fullname

        });
    });
    crmrequest.on('error', function (e) {
        console.error(e);
    });
    //close the web api request
    crmrequest.end();
}



For Fetch query use below code
var fetchQuery = "encoded fetch query";
var crmwebapipath = '/api/data/v9.1/dynamicproperties?fetchXml=' + fetchQuery;

Field Security Profile - Based on Owner

 Recently received requirement related to Field security profile. Expectation : - 1.       Set to users need access of secure attributes. 2....

Test