Auto Generate PDF Documents with Power Automate

Power Automate is a very powerful tool if you know how to wield it correctly.  Over the course of my career, I’ve run across many different business requirements, and in most required custom development in the past.

A common requirement I’ve seen is to auto-generate documentation based on data within D365.  Before having this auto-generation in place, users would manually create the documents by copying and pasting data which is error-prone on top of being time consuming.

Over the years Microsoft has extended the Power Platform and D365 to allow power users to be able to extend the product on their own without developers. In this post, we’ll walk through leveraging those tools to be able to configure the system to automatically generate a PDF document and attach it to a CRM email (spoiler alert – I used a developer’s assistance for some of the actions in the Power Automate which I’ll walk through below).

Overview + Getting Started

The first thing you’ll need is to have a Word Template created for the document you want to generate.  I won’t be walking through setting up a Word Template in this post.  However, you can navigate to Microsoft’s documentation on Word Templates to get started.

This video below outlines the actual process to accomplish this requirement within D365.  The user will navigate to an Invoice record, set it to Ready for Invoicing, and then Power Automate takes it from there and generates the PDF and email.

 

Here are the high-level Power Automate steps that created the records needed in the video above.  We’ll dive deeper into more detail on each of these steps later.

Leveraging the "Filter Rows" functionality on the trigger

The first thing to note is that when I was creating this solution, I ran into an issue leveraging the “Filter Rows” functionality on the trigger.  I wanted my Power Automate to only run when my Invoice is updated to a specific status, so I had entered in the following value for “Filter Rows”, but was receiving a message that there was a problem with my flow’s trigger.

Head over to this blog to find out more about leveraging Trigger Conditions instead.  After I updated my trigger step to have the Trigger Condition as below (and removed the Filter Rows text I had entered), my trigger was fixed.

Set-up Power Automate

Next, there are 3 variables you need to set up to use in the Power Automate.  Two String variables and an Array variable that will end up holding our Activity Parties (From, To, CC, BCC) that we’ll use on our email.

Another good practice is to leverage a Scope action for the majority of the Power Automate processing.  You can configure steps after the scope to “Configure run after” and select the options other than “is successful.”  This will allow you to catch any unexpected errors that happen in your Power Automate and take action upon them (such as sending an HTTP response back if you created a custom button to call the Power Automate – the custom button can display the error message sent back).

Now to the heavy lifting of the Power Automate that will generate our PDF document, create a CRM Email, and attach the PDF to the Email record automatically.Two

Find the right Document Template to use

Our next set of steps leverages the Invoice Type field on our Invoice that indicates if the invoice is a “detailed” or “summary” version.  You can also have client specific invoice templates if you’d like, but this section will find the right invoice based on your data in D365.

You’ll notice I also have a default action that returns an HTTP Response in case the your data in D365 has a value on Invoice Type that you’re not accounting for.  This scenario could be if an administrator updated the Choice field and added a new option, without updating the Power Automate.  This response could be surfaced to the end users by adding a little bit of code that initiates the flow, and waits for the response.

Retrieve and Parse the ObjectTypeCode

Next we need to grab the ObjectTypeCode so that when we deploy this Power Automate to other environments, we’re not hardcoding this value.  Unfortunately, when you push a custom table from environment to environment, the ObjectTypeCode does not remain the same. Therefore, we cannot assume our custom majer_invoice entity will have the same code in Dev versus Test versus Prod.

In order to accomplish this, we’ll add an “Invoke an HTTP Request” action followed by a “Parse JSON” action.

In the “Invoke an HTTP Request” action we leverage this expression:

uriHost(body(‘Retrieve_the_Invoice’)?[‘@odata.context’])

This expression will dynamically retrieve the begging part of your environments URI such as “majer.crm.dynamics.com” – again, very important if you’re deploying your Power Automate across environments where you don’t want to hard code the environment URI.

The “Invoke an HTTP Request” action also performs a GET on EntityDefinitions API passing in our entity name, and what we want to select which is the ObjectTypeCode.

The “Parse JSON” step then is used to pick out properties we care about from the results of the “Invoke an HTTP Request” action so that we can leverage them easier in later steps of the Power Automate without having to write expressions. 

You can leverage output of a previous Power Automate run of the HTTP Request step, and paste it into the Parse JSON step when clicking the “Generate from Sample” button to see what properties were returned.  This way you can remove what you don’t need and in our case we just care about the “ObjectTypeCode” property.

Retrieve the D365 Document Template Record

The next step simply retrieves the Document Template record leveraging the documentTemplate variable that was populated in the steps above leveraging our Switch statement. This step is a Dataverse List Rows step, providing some syntax in the Filter Rows field to pull the Word Template we need.

Generate the PDF

Our next set of actions will generate the actual PDF bits and bytes leveraging another “Invoke an HTTP Request” action followed by another “Parse JSON” action.  These actions are similar to the two from above that we used to retrieve the ObjectTypeCode. 

However, in these actions we pass in the ObjectTypeCode, Document Template ID, and Invoice ID from previous steps.  We’re also performing a POST HTTP Request instead of a GET, and leveraging the ExportPdfDocument API method.

Similar to above where we parsed the JSON to retrieve just the ObjectTypeCode property we needed, in these steps we’re parsing the JSON to retrieve the PefFile raw document data.

 After we have that parsed JSON, we put it in our pdfData variable that we’ll use later in our Power Automate.

Begin the Activity Party Array

The next few actions will be to grab the Bill To Account from the Invoice, then the Primary Contact on that Bill To Account, and then finally adding that Contact to the emailActivityParties Array variable we initialized earlier.

You can optionally add in check conditions in case your Bill To Account and Primary Contact fields are not required and blank.  If that’s the case, you’ll get an error when you create your Email later on with this emailActivityParties variable.

For our Array variable, the square brackets [ ] indicate that this is an array, and the curly brackets { } indicate this is an object or item in the array.  Looking at the Activity Party table, you’re able to see there are only 2 fields you need which are the Party member (Primary Contact in this case), and Participation Type (1 = From, 2 = To, 3 = CC, 4 = BCC).  Since this is who the email is going to, set the Participation Type to 2, and set the Party value using odata.bind.  

The next set of steps will fill out the rest of the emailActivityParties array (the From, CC, and BCC).  An email has to have a From and a To on it, but the CC and BCC are optional.  A custom field Invoice Email Type was added to the User entity with values of From, CC, and BCC.  This field is used in FetchXML (which can be downloaded by creating your query in Advanced Find, and clicking the Download FetchXML button) in a List Rows action.

Next, a check is performed to ensure no more than one User is designated as the From User since an email can’t come from multiple email addresses at the same time!  If you have more than one User designated as the user the email comes From, send back an error messages response and terminate the Power Automate.  Otherwise, continue on.

Since you know you don’t have more than one From User, you can simply add that user to the emailActivityParties array variable as you did previously with the Primary Contact who the email is going To.

 

Finalize the Activity Party

Let’s finalize this PARTY!!!

The next four steps use similar actions already performed above to 1) Retrieve the CC Users in a List Rows step but slightly different FetchXML, 2) Add the CC Users to the emailActivityParties array variable, 3) Retrieve the BCC Users in a List Rows step but slightly different FetchXML, and 4) Add the BCC Users to the emailActivityParties array variable.