<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matthew Collinge</title>
    <description>The latest articles on DEV Community by Matthew Collinge (@mcombp).</description>
    <link>https://dev.to/mcombp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1039590%2Fe69df5d8-ef04-4a6f-bf52-299d36350c0f.jpg</url>
      <title>DEV Community: Matthew Collinge</title>
      <link>https://dev.to/mcombp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mcombp"/>
    <language>en</language>
    <item>
      <title>Send Sales Orders to Zoho Sign from Zoho CRM with a Custom Deluge Function</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Tue, 30 Jun 2026 10:40:39 +0000</pubDate>
      <link>https://dev.to/mcombp/send-sales-orders-to-zoho-sign-from-zoho-crm-with-a-custom-deluge-function-5228</link>
      <guid>https://dev.to/mcombp/send-sales-orders-to-zoho-sign-from-zoho-crm-with-a-custom-deluge-function-5228</guid>
      <description>&lt;p&gt;I had been meaning to share this knowledge wider for a long time. This post was written alongside Claude Opus 4.8 using a custom "My Voice" skill. Every word is checked and verified by me — but without Claude's help it wouldn't have got written, and I think being upfront about that matters. Here goes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Send Sales Orders to Zoho Sign from Zoho CRM with a Custom Deluge Function
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The native &lt;a href="https://help.zoho.com/portal/en/kb/zoho-sign/integrations/zoho-apps/zoho-crm/articles/sign-crm-integration" rel="noopener noreferrer"&gt;Zoho CRM ↔ Zoho Sign integration&lt;/a&gt; is fine for the simplest case: pick a template, pick a recipient, hit send. The moment you want anything beyond that — control the document name, pick the template dynamically, write a linked record back to CRM, or react to API failures — you hit the ceiling.&lt;/p&gt;

&lt;p&gt;This post walks through a custom Deluge function that does all of that. It runs from a button on a Sales Order, generates a PDF from a CRM Inventory Template, posts it to the Zoho Sign API, renames the document on the way in, links the resulting Sign request back to the original Sales Order, and opens the signing UI in a popup. Errors are caught and emailed to support.&lt;/p&gt;

&lt;p&gt;Like me if you're coming from the Power Platform world, this post also explains why this is a Deluge function and not a Zoho Flow — that bit alone is worth reading if you've ever wondered why Zoho's no-code workflow tool gets sidelined for jobs like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use the standard integration (or Zoho Flow)?
&lt;/h2&gt;

&lt;p&gt;In Power Automate, you'd authorise the relevant connectors once, drop them into a cloud flow, and you'd be done. The connector handles auth on your behalf and the flow can run as a service account for everyone in the tenant.&lt;/p&gt;

&lt;p&gt;Zoho doesn't work that way. Two things matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;zohocrm&lt;/code&gt; → &lt;code&gt;zohocrm&lt;/code&gt; calls need a Connection.&lt;/strong&gt; When you call Zoho CRM's REST endpoints from inside Zoho CRM (yes, really — calling itself), you authorise a named &lt;strong&gt;Connection&lt;/strong&gt; at &lt;em&gt;Setup → Developer Space → Connections&lt;/em&gt;. You authorise it once, as a service account, and every Deluge function in the org uses it. This is the connection that pulls the PDF from the Inventory Template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;zoho.sign.createDocument&lt;/code&gt; needs a per-user Connection too&lt;/strong&gt; — but in Zoho Flow, that connection is &lt;strong&gt;per end user&lt;/strong&gt;. Each user of the flow has to authenticate their own Zoho Sign connection. There's no shared, service-account-style auth available in Flow for Zoho Sign. So if 30 sales reps need to send signing requests, you're herding 30 individual OAuth handshakes, with all the breakage and re-auth that implies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In CRM Deluge functions, the &lt;strong&gt;Connection is authorised once&lt;/strong&gt; by a developer/admin and re-used. Same effect as a Power Automate connection authorised on a service principal. That's the entire reason this lives in a Deluge function rather than a Zoho Flow — Flow's per-user auth model for Sign rules it out for any multi-user business process.&lt;/p&gt;

&lt;p&gt;The other limitations of the native CRM ↔ Sign integration that this function works around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document name is hard-coded to the template name, not the record context (you get five PDFs called &lt;em&gt;Sales_Order 01150654064064.pdf&lt;/em&gt; in your Sign inbox).&lt;/li&gt;
&lt;li&gt;No place to inject CRM context into the recipient assignment beyond what the template exposes.&lt;/li&gt;
&lt;li&gt;No structured error handling — if Sign rejects the request, the user sees a generic "something went wrong".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the function does (step by step)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ecnl10wevb7od100hsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ecnl10wevb7od100hsw.png" alt="Technical Diagram" width="799" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Collects field values from the Sales Order&lt;/strong&gt; — order number, subject, contact (recipient name + email). This can also be extended as much as required for you specific process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generates a PDF&lt;/strong&gt; by calling the CRM &lt;code&gt;print_preview&lt;/code&gt; endpoint against a specific Inventory Template, via the &lt;code&gt;zohocrm&lt;/code&gt; Connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Posts the PDF to Zoho Sign&lt;/strong&gt; with &lt;a href="https://www.zoho.com/deluge/help/sign/create-document.html" rel="noopener noreferrer"&gt;&lt;code&gt;zoho.sign.createDocument&lt;/code&gt;&lt;/a&gt;, supplying a name, the SIGN action, and a single ordered recipient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renames the document&lt;/strong&gt; inside the Sign request (the file gets uploaded with whatever name &lt;code&gt;print_preview&lt;/code&gt; gave it — we override it to &lt;code&gt;&amp;lt;Order Number&amp;gt; - &amp;lt;Subject&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates a record&lt;/strong&gt; in the &lt;code&gt;zohosign__ZohoSign_Documents&lt;/code&gt; module linking the Sign request back to the Sales Order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opens the Sign UI&lt;/strong&gt; in a popup window so the user lands directly on the signing setup screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catches errors at every external call&lt;/strong&gt; and emails the IT support desk with the record URL and error details.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key capabilities vs the standard integration
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom document naming&lt;/strong&gt; — &lt;code&gt;&amp;lt;Order Number&amp;gt; - &amp;lt;Subject&amp;gt;&lt;/code&gt; instead of the template's default name. You can restructure this any way you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic recipient resolution&lt;/strong&gt; — pulled from &lt;code&gt;Contact_Name&lt;/code&gt; on the Sales Order, not hard-coded on the template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linked record creation&lt;/strong&gt; — a &lt;code&gt;zohosign__ZohoSign_Documents&lt;/code&gt; row is written immediately, with &lt;code&gt;Module_Record_ID&lt;/code&gt; pointing back at the Sales Order, so the Sign request is visible from the related list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured error notifications&lt;/strong&gt; — Zoho Sign API failures send a formatted email to support with the CRM record link, error code, and message.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sales orders&lt;/strong&gt; where the PDF needs a meaningful filename for the customer's records and your audit trail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract issuance&lt;/strong&gt; workflows where the signer is always derived from a CRM lookup (Contact, Account owner, etc.) rather than typed in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-user signing flows&lt;/strong&gt; where every salesperson sending documents through Zoho Sign would otherwise need their own authorised Connection (the Zoho Flow blocker).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document-pack scenarios&lt;/strong&gt; where you want to build the Sign request server-side and then hand the user a popup to add fields, drag signature blocks, and send.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical deep dive
&lt;/h2&gt;

&lt;p&gt;The function is invoked from a Button in the SalesOrders Module on the Sales Order layout. It receives the record &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;userID&lt;/code&gt; as arguments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lpndyuacpaw1soluwy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lpndyuacpaw1soluwy8.png" alt="Function Button" width="202" height="51"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Collect the record data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;SalesOrderid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecordById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sales_Orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;varSalesOrderNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SalesOrderid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Order_Number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;varSubject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SalesOrderid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;docname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;varSalesOrderNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;varSubject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;varExternal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SalesOrderid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contact_Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;recID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;varExternal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;varExternalRec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecordById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contacts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;varExternalFull_Name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;varExternalRec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Full_Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;varExternalEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;varExternalRec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two &lt;code&gt;getRecordById&lt;/code&gt; calls — one for the Sales Order, one to resolve the Contact lookup into a full name and email address. &lt;code&gt;docname&lt;/code&gt; is the custom filename that Sign will end up displaying.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generate the PDF from the CRM template
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;varTemplateID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOUR_TEMPLATE_ID&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;invokeurl&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://zohoapis.com/crm/v2/settings/inventory_templates/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;varTemplateID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/actions/print_preview?record_id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;print_type=pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;GET&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohocrm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to call out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The template ID is the value in the URL when you edit the template in CRM&lt;/strong&gt; — &lt;code&gt;.../settings/inventory_templates/&amp;lt;this bit&amp;gt;/edit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;connection:"zohocrm"&lt;/code&gt;&lt;/strong&gt; is the named Connection authorised under a service account. If this Connection is missing or revoked, this is the first thing to fail — the response will be empty or contain an error string. The function checks both:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// log + return error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// log + return error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Build the Zoho Sign request payload &lt;a href="https://www.zoho.com/sign/api/document-managment/create-document.html" rel="noopener noreferrer"&gt;Create Document API&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recipient_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;varExternalFull_Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;recipient_email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;varExternalEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;signing_order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;action_type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}};&lt;/span&gt;

&lt;span class="nx"&gt;dummydata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;dummydata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;docname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is_sequential&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;requestMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;requestMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dummydata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;respSign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmtosignconnection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload mirrors the &lt;a href="https://www.zoho.com/sign/api/#create-document" rel="noopener noreferrer"&gt;Zoho Sign create document API&lt;/a&gt; shape: a &lt;code&gt;requests&lt;/code&gt; object containing &lt;code&gt;request_name&lt;/code&gt;, &lt;code&gt;is_sequential&lt;/code&gt;, and an &lt;code&gt;actions&lt;/code&gt; array of recipients. Here there's a single external SIGN recipient — extending this to multi-signer is just adding more entries to &lt;code&gt;actions&lt;/code&gt; with incrementing &lt;code&gt;signing_order&lt;/code&gt; values. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;zoho.sign.createDocument&lt;/code&gt; takes three arguments: the file payload (the PDF we just downloaded), the data map, and the named Sign Connection (&lt;code&gt;crmtosignconnection&lt;/code&gt; in this example, set up at &lt;em&gt;Setup → Developer Space → Connections&lt;/em&gt; with the relevant &lt;code&gt;ZohoSign.documents.CREATE&lt;/code&gt; scope).&lt;/p&gt;

&lt;p&gt;The response is checked before we go any further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;respSign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failure&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;signErrorCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;respSign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;signErrorMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;respSign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// sendmail to support with the CRM record URL and details&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ERROR: Zoho Sign document creation failed...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Rename the document inside the Sign request
&lt;/h3&gt;

&lt;p&gt;The PDF arrives in Sign with whatever name &lt;code&gt;print_preview&lt;/code&gt; produced. We override it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;documents_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_ids&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;documents_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;document_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;documentMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;documentMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;documentMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;document_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;documentMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;document_ids&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;documentMap&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;dataMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;dataMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reqID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dataMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the bit that gives you human-readable filenames in Zoho Sign — &lt;code&gt;SO-12345 - Project Phoenix&lt;/code&gt; rather than &lt;code&gt;Sales_Order 01150654064064.pdf&lt;/code&gt;. It's a separate API call because the create endpoint doesn't accept a per-document &lt;code&gt;document_name&lt;/code&gt; override at upload time.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Write the linked record back to CRM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;varSalesOrderNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__Module_Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sales_Orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__ZohoSign_Document_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;reqID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__Contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesOrderid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contact_Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__Date_Sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__Document_Status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Drafted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__Module_Record_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Id&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;zoho&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zohosign__ZohoSign_Documents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zohosign__ZohoSign_Documents&lt;/code&gt; is the module the Zoho Sign extension installs. Writing a row to it with &lt;code&gt;Module_Name&lt;/code&gt; and &lt;code&gt;Module_Record_ID&lt;/code&gt; is what makes the Sign request appear in the Sales Order's related list. The native integration does this for you; here we do it ourselves because the native integration isn't involved.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Open the Sign UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;varURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://sign.zoho.com/zs/{zohosignorgid}/request/new/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;reqID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;openUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;varURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;popup window&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;height=800px,width=1460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user gets dropped into the Sign setup screen for the request that was just created. From here they drag signature/date/text fields onto the PDF and send.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr7jgwud7uspdqmxm7ox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr7jgwud7uspdqmxm7ox.png" alt="Zoho Sign Document" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set this up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Zoho CRM with Admin access (to install the Zoho Sign extension, create Connections, deploy functions, and add buttons).&lt;/li&gt;
&lt;li&gt;Zoho Sign Org, with an Organisation ID you can read from the URL when logged in.&lt;/li&gt;
&lt;li&gt;A Service Account user with access to CRM — this is who authorises the Connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Install the Zoho Sign extension in CRM
&lt;/h3&gt;

&lt;p&gt;This installs the &lt;code&gt;zohosign__ZohoSign_Documents&lt;/code&gt; module and related fields. &lt;em&gt;Setup → Marketplace → Zoho → Zoho Sign for CRM&lt;/em&gt;. We'll use this to build upon, plus this serves as a bare-bones backup if anything goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create the Inventory Template
&lt;/h3&gt;

&lt;p&gt;Build the document layout you want signed — quote, sales order, contract — as a CRM Inventory Template against the Sales Orders module. Note the &lt;strong&gt;template ID&lt;/strong&gt; from the URL when editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create the two Connections
&lt;/h3&gt;

&lt;p&gt;Both connections live at &lt;em&gt;Setup → Developer Space → Connections&lt;/em&gt;. They serve different purposes and have very different onboarding stories.&lt;/p&gt;




&lt;h4&gt;
  
  
  Connection 1: &lt;code&gt;zohocrm&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This connection calls the Zoho CRM REST API from inside Zoho CRM itself — yes, CRM calling itself — to pull the PDF from the Inventory Template via &lt;code&gt;print_preview&lt;/code&gt;. It is authorised &lt;strong&gt;once&lt;/strong&gt; by the service account and shared with all relevant profiles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important: the connection name must exactly match the string used in the function.&lt;/strong&gt; The &lt;code&gt;invokeurl&lt;/code&gt; block references &lt;code&gt;connection:"zohocrm"&lt;/code&gt; — if you name yours differently, the call fails silently. Name it &lt;code&gt;zohocrm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Setup steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;Setup → Developer Space → Connections → New Connection&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Default Zoho OAuth&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Connection Name&lt;/strong&gt; to &lt;code&gt;zohocrm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add scopes: ZohoCRM.templates.inventory.READ — required by the print_preview endpoint to fetch the PDF from the Inventory Template.
ZohoCRM.modules.salesorders.READ — required to fetch the Sales Order record. ZohoCRM.modules.contacts.READ — required to resolve the Contact lookup for the recipient name and email.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create and Connect&lt;/strong&gt; and authorise as the &lt;strong&gt;service account&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Once created, share the connection with the profiles that need to use the button.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat worth noting: &lt;strong&gt;print_preview&lt;/strong&gt; isn't documented as a distinct endpoint in the v8 docs — it's an undocumented action on the inventory templates path. If you hit a permission error in practice, &lt;strong&gt;ZohoCRM.modules.salesorders.READ&lt;/strong&gt; added to the template scope is the likely fix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabjroz6mfwjb4xjit8ae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabjroz6mfwjb4xjit8ae.png" alt="Zoho CRM Connections" width="765" height="288"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Connection 2: &lt;code&gt;crmtosignconnection&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is where it gets more involved. Unlike the &lt;code&gt;zohocrm&lt;/code&gt; connection above, &lt;strong&gt;&lt;code&gt;crmtosignconnection&lt;/code&gt; must be authorised individually by each user&lt;/strong&gt; who will use the button. This is a Zoho Sign OAuth requirement — Sign issues tokens per-user, not per-org, so there's no shared service-account-style auth available for this one.&lt;/p&gt;

&lt;p&gt;This is also &lt;strong&gt;the exact reason this entire solution is a Deluge function and not a Zoho Flow&lt;/strong&gt;. In Zoho Flow, the per-user Sign connection would need to be set up inside the flow itself, requiring every user to individually authenticate before the flow can run on their behalf. In a CRM Deluge function with a named Connection, you manage the auth once per user through a controlled IT process — a much more supportable pattern.&lt;/p&gt;

&lt;p&gt;IT Support should run the following steps for each new user:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Assign the user to the correct role&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the user record and assign the &lt;strong&gt;'Send to Sign Integration'&lt;/strong&gt; permission set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://crm.zoho.com/crm/org{ZohoCRMorgId}/settings/users/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faouc36wi7d6ovnnv6hmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faouc36wi7d6ovnnv6hmn.png" alt="CRM Connections" width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Temporarily enable the Connection on the user's profile&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the profile and enable access to &lt;code&gt;crmtosignconnection&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://crm.zoho.com/crm/org{ZohoCRMorgId}/settings/profiles/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This permission is enabled temporarily to allow the user to complete the OAuth flow in the next step.&lt;/p&gt;

&lt;p&gt;[Screenshot: Profile settings with crmtosignconnection enabled]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Have the user authenticate the Connection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask the user to navigate to &lt;em&gt;Setup → Developer Space → Connections&lt;/em&gt;, find &lt;strong&gt;&lt;code&gt;crmtosignconnection&lt;/code&gt;&lt;/strong&gt;, and click &lt;strong&gt;Connect&lt;/strong&gt;. They will be prompted to accept three consecutive OAuth permission pop-ups. They must click &lt;strong&gt;Accept&lt;/strong&gt; or &lt;strong&gt;Connect&lt;/strong&gt; on each one. This authenticates them against Zoho Sign under their own account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wx1pnr9rh3c5w0oj0ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3wx1pnr9rh3c5w0oj0ly.png" alt="Zoho Flow" width="473" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddicocsv1w8xs2u0h2ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddicocsv1w8xs2u0h2ho.png" alt="Connection Authorisation 1" width="602" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3e02tnibjt9snhudynaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3e02tnibjt9snhudynaj.png" alt="Connection Authorisation 2" width="525" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Disable the profile permission&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the user has authenticated, go back to the profile and &lt;strong&gt;remove&lt;/strong&gt; the temporary Connection permission. The user's token is now stored and the button will work without them needing direct access to the Connections settings page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Test the button and allow pop-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Have the user open a Sales Order record and click the button. If the Zoho Sign UI does not appear, their browser is likely blocking pop-ups from Zoho CRM — they'll need to allow pop-ups for &lt;code&gt;crm.zoho.com&lt;/code&gt; in their browser settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnije2c8rai0g9bht4ppo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnije2c8rai0g9bht4ppo.png" alt="Un-Block Pop-Ups" width="538" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up for admins&lt;/strong&gt;: this onboarding process needs repeating for every new user added to this workflow. It's worth documenting it as a runbook in your IT support knowledge base so the steps don't get lost.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Deploy the function
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Setup → Developer Space → Functions → New Function&lt;/em&gt; (Standalone). Paste the code, replace &lt;code&gt;{YOUR_TEMPLATE_ID}&lt;/code&gt;, &lt;code&gt;{ZohoCRMorgId}&lt;/code&gt; and &lt;code&gt;{zohosignorgid}&lt;/code&gt; with your real values, and replace the support email with your team's inbox. Save and enable REST API endpoint or attach to a button (next step).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8cbe0vecvdkw7wqrbec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8cbe0vecvdkw7wqrbec.png" alt="Create Function" width="567" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Attach to a button
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Setup → Customisation → Modules → Sales Orders → Links and Buttons → New Button&lt;/em&gt;. Place it on the &lt;em&gt;Detail Page&lt;/em&gt; layout, set the action to &lt;em&gt;Writing Function&lt;/em&gt;, point it at your function, and pass the record &lt;code&gt;Id&lt;/code&gt; and &lt;code&gt;loginuserid&lt;/code&gt; (or equivalent) as arguments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsizileecmdc8bt23xz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsizileecmdc8bt23xz5.png" alt="Sales Orders" width="800" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling strategy
&lt;/h2&gt;

&lt;p&gt;There are three failure points worth being defensive about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;zohocrm&lt;/code&gt; Connection is dead&lt;/strong&gt; — token revoked, scope changed, service account password reset. The &lt;code&gt;download&lt;/code&gt; variable comes back empty or contains an error string. The function bails, logs the connection name and authorising user, and returns a clear message to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zoho Sign rejects the create request&lt;/strong&gt; — usually a scope problem on &lt;code&gt;crmtosignconnection&lt;/code&gt;, an invalid recipient email, or the document binary being malformed. &lt;code&gt;respSign.get("status") == "failure"&lt;/code&gt; catches this. The function emails support with the CRM record URL, Sign error code, and message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything else&lt;/strong&gt; — caught by the top-level &lt;code&gt;try/catch&lt;/code&gt;. Same email pattern.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason this matters: button-triggered functions return a string that's shown to the user as an alert. Without the explicit checks, a half-failed run looks like a success from the user's perspective (no red error, but no Sign document either). The targeted checks turn silent failures into either visible errors or — better — actionable support tickets that include the record URL so the issue can be reproduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Process flow
&lt;/h2&gt;

&lt;p&gt;The flow at a glance: the Sales Order is the starting point, the Deluge function orchestrates the PDF generation and the Sign API call, and the linked record is written back to CRM so the signing request is visible from the Sales Order's related list. See the diagram rendered above the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The native Zoho CRM ↔ Zoho Sign integration is built for the 80% case. The other 20% — custom naming, dynamic recipients, linked records, structured error handling, multi-user auth — is where you reach for a Deluge function and the Zoho Sign API.&lt;/p&gt;

&lt;p&gt;Coming from the Power Platform, the auth model is the bit that takes the most getting used to. There's no Power Automate-style "authorise once, use everywhere" connector for Zoho Sign in Zoho Flow — every user authenticates their own connection, which is why Flow is a non-starter for shared business processes. CRM Deluge functions, with named Connections authorised by a service account, give you the equivalent of a connection reference on a service principal. Once that's in place, the rest is just API plumbing.&lt;/p&gt;

&lt;p&gt;Extend this however you like: multi-signer workflows, conditional templates based on order value, post-signature webhooks to push the signed PDF into SharePoint, attribute-based recipient resolution. The shape stays the same — collect, generate, send, link, handle errors.&lt;/p&gt;

</description>
      <category>zoho</category>
      <category>deluge</category>
      <category>lowcode</category>
      <category>automation</category>
    </item>
    <item>
      <title>When Dataflows Fail in Silence</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:41:36 +0000</pubDate>
      <link>https://dev.to/mcombp/when-dataflows-fail-in-silence-4059</link>
      <guid>https://dev.to/mcombp/when-dataflows-fail-in-silence-4059</guid>
      <description>&lt;p&gt;This post was written alongside Claude Opus 4.8 using a custom "My Voice" skill. Every word is checked and verified by me — but without Claude's help it wouldn't have got written, and I think being upfront about that matters. Here goes.&lt;/p&gt;

&lt;h1&gt;
  
  
  When Dataflows Fail in Silence
&lt;/h1&gt;

&lt;p&gt;Your service account runs dataflows overnight. The refresh finishes, or it doesn't — and either way, nobody knows. There's no inbox, no owner, no alert. The data is stale and the business won't find out until someone runs a report and the numbers look wrong.&lt;/p&gt;

&lt;p&gt;This is a solved problem, but it requires a bit of deliberate plumbing. Here's the Power Automate flow I built to catch failed dataflow refreshes and surface them to the team before anyone notices.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wy5nzxfwdhkr4h4axus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5wy5nzxfwdhkr4h4axus.png" alt=" " width="797" height="1389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow runs on a recurrence trigger — every day of the week at 00:00 GMT. There's no native Power Automate trigger for dataflow refresh failures, so a scheduled poll is the right approach here. Midnight is a deliberate choice: by running at the start of a new day, the flow captures the full previous day's refresh history before anyone starts work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2unp0hbiuwfw5rex7129.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2unp0hbiuwfw5rex7129.png" alt=" " width="800" height="810"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow itself has three logical steps: query Dataverse for yesterday's failures, check if any exist, and if so — send a formatted email.&lt;/p&gt;




&lt;h2&gt;
  
  
  Querying the Right Table
&lt;/h2&gt;

&lt;p&gt;Power Platform stores dataflow refresh history in the &lt;code&gt;msdyn_dataflowrefreshhistories&lt;/code&gt; Dataverse table. It's not one that comes up often, but it has everything you need: the dataflow name, refresh end time, status, and a JSON blob containing the error detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxptmvsde78p9qw1ouf2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxptmvsde78p9qw1ouf2f.png" alt=" " width="800" height="1150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;List rows&lt;/strong&gt; action queries this table using FetchXML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fetch&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;entity&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_dataflowrefreshhistory"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_dataflowrefreshhistoryid"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_endtime"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_refreshstatus"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_errorinfojson"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_dataflowid"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;attribute&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_dataflowname"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"and"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;condition&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_endtime"&lt;/span&gt; &lt;span class="na"&gt;operator=&lt;/span&gt;&lt;span class="s"&gt;"yesterday"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;condition&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"msdyn_refreshstatus"&lt;/span&gt; &lt;span class="na"&gt;operator=&lt;/span&gt;&lt;span class="s"&gt;"eq"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Failed"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/entity&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fetch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FetchXML is the right choice here because date operators like &lt;code&gt;yesterday&lt;/code&gt; aren't available in standard OData filtering — you'd have to calculate and pass a UTC date string, which gets messy. FetchXML handles it cleanly.&lt;/p&gt;

&lt;p&gt;The filter returns only records where the refresh ended yesterday with a &lt;code&gt;Failed&lt;/code&gt; status. Clean days return nothing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Only Send When There's Something to Say
&lt;/h2&gt;

&lt;p&gt;Before building the email, the flow checks whether the query returned any rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;equals(empty(outputs('List_rows_...')?['body/value']), false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx45ktzql2niobuaqf5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhx45ktzql2niobuaqf5g.png" alt=" " width="799" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the array is empty — no failures yesterday — the flow does nothing. No email, no noise. The team only hears about this flow when it matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shaping the Data for Humans
&lt;/h2&gt;

&lt;p&gt;When there are failures, a &lt;strong&gt;Select&lt;/strong&gt; action maps the raw Dataverse columns into something readable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Expression&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item()?['msdyn_dataflowname']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;End Time&lt;/td&gt;
&lt;td&gt;&lt;code&gt;formatDateTime(item()?['msdyn_endtime'], 'MMM dd, yyyy hh:mm tt')&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataflow ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;item()?['msdyn_dataflowid']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Message&lt;/td&gt;
&lt;td&gt;&lt;code&gt;json(item()?['msdyn_errorinfojson'])?['ErrorMessage']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;json(item()?['msdyn_errorinfojson'])?['ErrorCode']&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;msdyn_errorinfojson&lt;/code&gt; field is a raw JSON string. Wrapping it in &lt;code&gt;json()&lt;/code&gt; and extracting &lt;code&gt;ErrorMessage&lt;/code&gt; and &lt;code&gt;ErrorCode&lt;/code&gt; gives the reader the two things they actually need to diagnose what went wrong — without having to go and look it up manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxhmal397me80gz5cehw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxhmal397me80gz5cehw.png" alt=" " width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, &lt;strong&gt;Create HTML table&lt;/strong&gt; converts the selected rows into a table, and a &lt;strong&gt;Compose&lt;/strong&gt; action injects CSS to make it readable in an email client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border-collapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;collapse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="nt"&gt;th&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#AAAAAA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="nd"&gt;:nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;even&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#D9E1F2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="nt"&gt;thead&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4472C4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="nt"&gt;thead&lt;/span&gt; &lt;span class="nt"&gt;th&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#FFFFFF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawz0khw4yqp7ajbbkd10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawz0khw4yqp7ajbbkd10.png" alt=" " width="799" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basic — but it renders a legible, scannable table in Outlook without any additional tooling.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Email Goes to Support Desk, Not an Inbox
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Send an email (V2)&lt;/strong&gt; action sends to &lt;code&gt;support@mycompany.com&lt;/code&gt;. This isn't an accident.&lt;/p&gt;

&lt;p&gt;Sending to a shared inbox means the email sits there until someone acts on it — or doesn't. Sending to the support Desk creates a support ticket automatically, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The failure is tracked&lt;/li&gt;
&lt;li&gt;It gets assigned&lt;/li&gt;
&lt;li&gt;There's an audit trail&lt;/li&gt;
&lt;li&gt;It won't be quietly read and forgotten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The email body includes a direct link to the dataflows list in the production environment and a link back to the flow itself — so the person picking up the ticket can get to the right place in one click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl56tc7fqtbl0bus79ksb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl56tc7fqtbl0bus79ksb.png" alt=" " width="800" height="890"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Known Gaps
&lt;/h2&gt;

&lt;p&gt;This flow does the job, but it's not perfect. Worth being honest about the tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failures are reported the morning after, not the same day.&lt;/strong&gt; Running at midnight with &lt;code&gt;operator="yesterday"&lt;/code&gt; means the team won't see a ticket until they start work the following day. For most reporting and ETL scenarios that's acceptable — but if a dataflow feeds something time-sensitive, you'd want a more frequent check using a rolling &lt;code&gt;last-x-hours&lt;/code&gt; window instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Null error JSON will crash the Select.&lt;/strong&gt; If any refresh history row has a null or malformed &lt;code&gt;msdyn_errorinfojson&lt;/code&gt;, the &lt;code&gt;json()&lt;/code&gt; expression throws and the entire Select action fails — no email goes out. Wrapping it in a null check would make it more resilient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(empty(item()?['msdyn_errorinfojson']), 'N/A', json(item()?['msdyn_errorinfojson'])?['ErrorMessage'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multiple failures per dataflow produce multiple rows.&lt;/strong&gt; If a dataflow is configured to refresh twice a day and fails both times, it appears as two rows. This is arguably correct behaviour, but worth being aware of.&lt;/p&gt;




&lt;p&gt;Visibility is the feature. The dataflows were always going to fail occasionally — that's not the problem. The problem was that nobody would find out. This flow makes the failure loud, routes it to a system that tracks it, and gives the person picking it up everything they need to act.&lt;/p&gt;

&lt;p&gt;The gaps are real, but the alternative — checking manually or finding out from a user — is worse.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>database</category>
      <category>powerautomate</category>
    </item>
    <item>
      <title>Diagnosing `ipCookieBindingError` in a Managed Production Environment</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:38:00 +0000</pubDate>
      <link>https://dev.to/mcombp/diagnosing-ipcookiebindingerror-in-a-managed-production-environment-116j</link>
      <guid>https://dev.to/mcombp/diagnosing-ipcookiebindingerror-in-a-managed-production-environment-116j</guid>
      <description>&lt;p&gt;This post was written alongside Claude Opus 4.8 using a custom "My Voice" skill. Every word is checked and verified by me — but without Claude's help it wouldn't have got written, and I think being upfront about that matters. Here goes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Diagnosing &lt;code&gt;ipCookieBindingError&lt;/code&gt; in a Managed Production Environment
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Users intermittently couldn't open a model-driven app in Production. Some could, some couldn't, and the pattern kept shifting. The root cause was &lt;strong&gt;IP address-based cookie binding&lt;/strong&gt; terminating sessions when a user's egress IP changed mid-session — triggered by new office network infrastructure combined with hybrid working. Fix: disable the rule at the &lt;strong&gt;Environment Group&lt;/strong&gt; level in Power Platform admin center. Environment-level toggle won't work if a group rule is enforcing it. This could be a problem for any rule in an Environment Group if you were to try and change a setting in a child Environment. &lt;/p&gt;

&lt;h2&gt;
  
  
  Symptoms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Error screen: &lt;em&gt;"Your IP address has changed. Sign out and Sign in again to continue"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Error code: &lt;code&gt;ipCookieBindingError&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sign-in loop where the "Sign in to continue" modal reappears immediately after signing in&lt;/li&gt;
&lt;li&gt;Browser console: repeated &lt;code&gt;POST .../oauth2/v2.0/token 400 (Bad Request)&lt;/code&gt; from MSAL silent refresh&lt;/li&gt;
&lt;li&gt;Affected users did &lt;strong&gt;not&lt;/strong&gt; share the same security roles — the pattern was network-related, not permission-related&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftocs1j268kg4dfkkwe9o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftocs1j268kg4dfkkwe9o.png" alt=" " width="792" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it started failing now
&lt;/h2&gt;

&lt;p&gt;The rule was published in July 2025 and worked fine for months. What changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New office network infrastructure, likely introducing multiple egress IPs (dual-WAN, SD-WAN, or a new NAT pool)&lt;/li&gt;
&lt;li&gt;Ongoing hybrid working — users moving between home and office&lt;/li&gt;
&lt;li&gt;Result: a single user session can traverse different public IPs during normal use. Each IP change invalidates the cookie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The feature is doing exactly what it's designed to do. It's just incompatible with a network where a user's IP isn't stable for the duration of a session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnosis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Identify the error
&lt;/h3&gt;

&lt;p&gt;It looks fairly obvious what the culprit is from the error, but let's check the browser console on an affected user's machine. Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ipCookieBindingError&lt;/code&gt; in any visible error screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token 400 (Bad Request)&lt;/code&gt; in the Network tab&lt;/li&gt;
&lt;li&gt;MSAL errors around &lt;code&gt;acquireTokenByRefreshToken&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2 — Try to disable the setting in PPAC
&lt;/h3&gt;

&lt;p&gt;The obvious thing to go and check here is the "Enable IP address based cookie binding" setting for the Environment.&lt;br&gt;
Go to &lt;strong&gt;Environments → [Our Environment] → Settings → Product → Privacy + Security&lt;/strong&gt; and toggle &lt;strong&gt;Enable IP address based cookie binding&lt;/strong&gt; to Off. Save. - Done!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz7x38yp1aataubp8u1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz7x38yp1aataubp8u1a.png" alt=" " width="443" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Except, we go to Toggle it to "Off", click Save, then it goes staight back to "On" again - Frustating! We can't just apply to the stereotypical Dad solution of spraying on a load of WD40 on this UI to fix this problem, oh no.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hteaga67b4abp6f99ay.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hteaga67b4abp6f99ay.jpg" alt=" " width="500" height="381"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 — Confirm via the Dataverse Web API
&lt;/h3&gt;

&lt;p&gt;Useful to get the real error message the UI swallows. PATCH the organisation record directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enableipbasedcookiebinding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-Json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Patch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OrgUrl&lt;/span&gt;&lt;span class="s2"&gt;/api/data/v9.2/organizations(&lt;/span&gt;&lt;span class="nv"&gt;$OrgId&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response body gave the actual cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x80098016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You can't change these settings because this environment is part of a group. If you're a tenant admin, modify the settings in the Environment Group."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we go - we have an answer: the setting is locked by an &lt;strong&gt;Environment Group rule&lt;/strong&gt;, not by a permissions gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Note on property naming
&lt;/h2&gt;

&lt;p&gt;My first attempt used &lt;code&gt;isbasedonipcookiebinding&lt;/code&gt; — wrong name, returned &lt;code&gt;0x80060888 Could not find a property&lt;/code&gt;. The actual property is &lt;strong&gt;&lt;code&gt;enableipbasedcookiebinding&lt;/code&gt;&lt;/strong&gt;. To list the real property names on the organisation table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OrgUrl&lt;/span&gt;&lt;span class="s2"&gt;/api/data/v9.2/organizations(&lt;/span&gt;&lt;span class="nv"&gt;$OrgId&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PSObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Properties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ip|cookie|firewall'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resolution
&lt;/h2&gt;

&lt;p&gt;Environment Group rules, once published, they &lt;strong&gt;lock the corresponding setting&lt;/strong&gt; on every environment in the group. Local System Administrators can't override them. This is deliberate — it's how Microsoft implements tenant-scoped governance for managed environments. Per Microsoft Learn:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"When a rule is published at the Environment Group level, it's enforced across every environment within that group. This means the corresponding setting or policy becomes locked (read-only) within individual environments, ensuring that local system administrators can't modify or override these centrally defined rules."&lt;/em&gt;&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/power-platform/admin/environment-groups#rules" rel="noopener noreferrer"&gt;MS LEarn - Environment Groups - Rules &lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To change the rule you need &lt;strong&gt;Power Platform Administrator&lt;/strong&gt; at tenant level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to &lt;a href="https://admin.powerplatform.microsoft.com/" rel="noopener noreferrer"&gt;Power Platform admin center&lt;/a&gt; as a Power Platform tenant admin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage → Environment Groups&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gtktmo0z0s60oonk0sf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gtktmo0z0s60oonk0sf.png" alt=" " width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the group Production belongs to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules&lt;/strong&gt; tab → find &lt;strong&gt;Enable IP Cookie Binding&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Open the rule → toggle &lt;strong&gt;Enable IP address-based cookie binding&lt;/strong&gt; to Off&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish rules&lt;/strong&gt; — this is the step that actually propagates the change. Unpublished changes don't apply&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptyi39gsabtmr71i348n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptyi39gsabtmr71i348n.png" alt=" " width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wait for propagation&lt;/li&gt;
&lt;li&gt;Confirm on an affected user — the error should stop&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Follow-up — don't leave the security gap open
&lt;/h2&gt;

&lt;p&gt;IP cookie binding was mitigating cookie replay attacks. Disabling it removes that control. Replace it with mechanisms that don't break on IP changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;👮‍♀️Conditional Access in Entra ID&lt;/strong&gt; — sign-in frequency, compliant device requirement, trusted location policies. This is what Microsoft recommends as the primary mitigation for session hijacking in modern deployments. You should &lt;strong&gt;turn this on&lt;/strong&gt; if it is not already.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;👮‍♀️Session timeout and inactivity timeout&lt;/strong&gt; in Privacy + Security — bounded session lifetime without IP sensitivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgpjmd37bsas8kvp2ukq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgpjmd37bsas8kvp2ukq.png" alt=" " width="524" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;👮‍♀️IP firewall&lt;/strong&gt; (different from IP cookie binding) — blocks unknown IPs at the door rather than invalidating mid-session. Requires enumerating all legitimate egress IPs first though, not ideal for hybrid working.&lt;/li&gt;
&lt;li&gt;If IP cookie binding is genuinely required for compliance, revisit it after the network has stable, documented egress IPs, and populate the &lt;strong&gt;👮‍♀️Reverse proxy IP addresses&lt;/strong&gt; field if reverse proxies are in scope for you organisation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silent toggle reversion in PPAC = Environment Group rule in effect.&lt;/strong&gt; No error message; the toggle just flips back. Check group membership first when an environment admin can't save a setting they should be able to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the Dataverse Web API when the UI swallows errors.&lt;/strong&gt; PATCH the &lt;code&gt;organizations&lt;/code&gt; record directly and read the response body — it helps you investigate and it gives you the actual reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;enableipbasedcookiebinding&lt;/code&gt;&lt;/strong&gt; is the property name on the organisation table — not the one implied by the UI label&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Group rules require a tenant-level admin to modify.&lt;/strong&gt; System Administrator on the Dataverse side isn't enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish rules&lt;/strong&gt; is a separate step from Save — rule changes don't propagate until published&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/power-platform/admin/block-cookie-replay-attack" rel="noopener noreferrer"&gt;Safeguarding Dataverse sessions with IP cookie binding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/power-platform/admin/environment-groups" rel="noopener noreferrer"&gt;Environment Groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/power-platform/admin/environment-groups-rules" rel="noopener noreferrer"&gt;Rules for Environment Groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/power-platform/admin/settings-privacy-security" rel="noopener noreferrer"&gt;Manage privacy and security settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powerplatform</category>
      <category>security</category>
    </item>
    <item>
      <title>Catching run-only connection misconfigurations in Power Automate Child Flows</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:31:54 +0000</pubDate>
      <link>https://dev.to/mcombp/catching-run-only-connection-misconfigurations-in-power-automate-child-flows-3nj3</link>
      <guid>https://dev.to/mcombp/catching-run-only-connection-misconfigurations-in-power-automate-child-flows-3nj3</guid>
      <description>&lt;p&gt;This post was written alongside Claude Opus 4.8 using a custom "My Voice" skill. Every word is checked and verified by me — but without Claude's help it wouldn't have got written, and I think being upfront about that matters. Here goes.&lt;/p&gt;

&lt;p&gt;You've just deployed an approval solution to UAT. The pipeline ran clean — export, unpack, solution checker, artifact published. Everything green.&lt;/p&gt;

&lt;p&gt;Then you notice a Child Flow is off 😡.&lt;/p&gt;

&lt;p&gt;Not erroring. Not failing. Just off — sitting there in a turned-off state as if it never wanted to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem hiding in plain sight
&lt;/h2&gt;

&lt;p&gt;The culprit, when you eventually find it, is a Teams connector (or any connector. Sometimes all you need to do is look at the Power Automate Flow Designer on a Child Flow and the default connection setting is changed to &lt;strong&gt;Provided by run-only user&lt;/strong&gt;. This means the Flow expects whoever is invoking it to supply their own connection at runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa23qwe30xj93hhmvi8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa23qwe30xj93hhmvi8v.png" alt=" " width="443" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's fine for Flows a person runs manually. It's a real pain for Child Flows called by a service account.&lt;/p&gt;

&lt;p&gt;When the Child Flow is invoked by a Parent Flow running under a service account — which has no Connection of its own — the Flow can't start. Power Platform's response to this isn't to error loudly. It deploys the Flow in a disabled state and leaves you to figure out why.&lt;/p&gt;

&lt;p&gt;The part that stings is that nothing in your pipeline catches this. The solution exports without complaint. The solution checker raises no warnings. The build passes. The misconfiguration travels all the way from your development environment to UAT before anyone notices.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1meptvowhqljo0w2lcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1meptvowhqljo0w2lcx.png" alt=" " width="794" height="31"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The workflow with id '7a2b9d5c-e4f1-4328-b1a9-d8e7c6b5a413', name [child] - MyFlow cannot be used as a child workflow because child workflows only support embedded connections.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The fix is easy — once you find the problem
&lt;/h2&gt;

&lt;p&gt;In the source environment, open the Child Flow, find the Teams connector, and change the connection setting from &lt;strong&gt;Provided by run-only user&lt;/strong&gt; to &lt;strong&gt;Use this connection&lt;/strong&gt; (the service account's connection). Save the Flow, re-export, and re-run the pipeline.&lt;/p&gt;

&lt;p&gt;Two minutes of work, once you know where to look.&lt;/p&gt;

&lt;p&gt;The issue isn't fixing it. The issue is finding it — and finding it after it's already deployed somewhere means you're fixing a target environment instead of fixing source control. That's the wrong direction for ALM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching it in the Build Pipeline
&lt;/h2&gt;

&lt;p&gt;When Power Platform Build Tools unpacks a solution, the workFlow JSON files land on disk. Each one contains the Flow's full definition — triggers, actions, connection references — readable as plain JSON.&lt;/p&gt;

&lt;p&gt;The connection setting that causes the problem shows up in &lt;code&gt;properties.connectionReferences&lt;/code&gt;. For most connectors it appears as &lt;code&gt;runtimeSource: "invoker"&lt;/code&gt; or &lt;code&gt;providedByRunOnlyUser: true&lt;/code&gt;. The Dataverse connector stores it differently — as &lt;code&gt;impersonation: { source: "invoker" }&lt;/code&gt; with &lt;code&gt;runtimeSource&lt;/code&gt; still set to &lt;code&gt;"embedded"&lt;/code&gt;. Either way, it's right there in the file. We just need something to look for it before the artifact gets published.&lt;/p&gt;

&lt;p&gt;The script below does exactly that. It runs after the Unpack step, scans every workFlow JSON file, and fails the build immediately if it finds a Child Flow with a run-only connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtvu3doe3ga964r3eyy7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtvu3doe3ga964r3eyy7.png" alt=" " width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that together mean trouble
&lt;/h2&gt;

&lt;p&gt;Before looking at the script, it's worth understanding what it's looking for — because not every Flow with a run-only connection is necessarily a problem. It only matters for Child Flows.&lt;/p&gt;

&lt;p&gt;The script identifies Child Flows by looking for two things together: a &lt;code&gt;manual&lt;/code&gt; trigger (the internal name for the &lt;strong&gt;When an HTTP request is received&lt;/strong&gt; trigger that Child Flows use), and a &lt;code&gt;Response&lt;/code&gt; action somewhere in the action tree. A Flow with both is almost certainly designed to be called by a Parent Flow and return a result. That's the Flow where a run-only connection will silently break things at deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ============================================================&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Check-ChildFlowRunOnlyConnections&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Fails the build if any Child Flow in the solution uses&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# run-only / invoker connections.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# ============================================================&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$UnpackRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;Build.SourcesDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;\&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;SolutionName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;\managed"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="n"&gt;Found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$UnpackRoot&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.logissue type=error]Unpack root not found at: &lt;/span&gt;&lt;span class="nv"&gt;$UnpackRoot&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.complete result=Failed;]Cannot locate unpacked solution."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$WorkFlowsRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$UnpackRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'WorkFlows'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkFlowsRoot&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No WorkFlows folder found in solution — nothing to check."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Find-ResponseAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mandatory&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Actions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PSObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Response'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="s1"&gt;'actions'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'else'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Find-ResponseAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$branch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cases&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PSObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$case&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Find-ResponseAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$case&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$wfFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkFlowsRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'*.json'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$wfFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No workFlow JSON files found — nothing to check."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$wfFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$wf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.logissue type=warning]Could not parse: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# CORRECT PATHS — properties.definition and properties.connectionReferences&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;properties&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$triggers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggers&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$actions&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actions&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$hasManual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$triggers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$triggers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PSObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Properties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'manual'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$hasManual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$hasResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Find-ResponseAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$actions&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$hasResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$badRefs&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$connRefs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectionReferences&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$connRefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$connRefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PSObject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;$isRunOnly&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runtimeSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'invoker'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runtimeSource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Invoker'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;providedByRunOnlyUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticationType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Invoker'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt;
                         &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;impersonation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ieq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'invoker'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$isRunOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nv"&gt;$badRefs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSCustomObject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nx"&gt;runtimeSource&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtimeSource&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nx"&gt;providedByRunOnlyUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providedByRunOnlyUser&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nx"&gt;authenticationType&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticationType&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nx"&gt;impersonationSource&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cr&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impersonation&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$badRefs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$displayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;displayName&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$displayName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$displayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

        &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.logissue type=error]Child Flow '&lt;/span&gt;&lt;span class="nv"&gt;$displayName&lt;/span&gt;&lt;span class="s2"&gt;' has run-only/invoker connection(s) — &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$badRefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nv"&gt;$detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"  Connection ref: '&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;' | runtimeSource=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runtimeSource&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | providedByRunOnlyUser=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;providedByRunOnlyUser&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | authenticationType=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authenticationType&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | impersonationSource=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;impersonationSource&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.logissue type=error]&lt;/span&gt;&lt;span class="nv"&gt;$detail&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="n"&gt;Found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$Error&lt;/span&gt;&lt;span class="n"&gt;Found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"##vso[task.complete result=Failed;]One or more Child Flows use run-only/invoker connections. Fix in the source environment before re-running."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check passed — no run-only/invoker connections found in Child Flows."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where it goes
&lt;/h2&gt;

&lt;p&gt;Place it after the &lt;strong&gt;Unpack Solution&lt;/strong&gt; task and before artifact publishing. It needs the workFlow JSON files on disk, and it should block the pipeline before anything gets committed or deployed downstream.&lt;/p&gt;

&lt;p&gt;The script uses ADO inline variable syntax — &lt;code&gt;$(Build.SourcesDirectory)&lt;/code&gt; and &lt;code&gt;$(SolutionName)&lt;/code&gt; — which means it must run as an inline script rather than a file reference. Paste the content directly into your pipeline YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PowerShell@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Child&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Flows&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;run-only/invoker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;connections'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;targetType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inline&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;# paste script content here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SolutionName&lt;/code&gt; needs to be defined as a pipeline variable in your variable group or &lt;code&gt;variables:&lt;/code&gt; block.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the failure looks like
&lt;/h2&gt;

&lt;p&gt;When the script finds a problem, it writes directly to the ADO build summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;#[error]Child Flow 'child-MyFlow-7a2b9d5c-e4f1-4328-b1a9-d8e7c6b5a413' has run-only/invoker connection(s)&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;#[error]  Connection ref: 'shared_teams-1' | runtimeSource=invoker | providedByRunOnlyUser= | authenticationType= | impersonationSource=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a Dataverse connector set to "Provided by run-only user" the output looks slightly different — &lt;code&gt;runtimeSource&lt;/code&gt; stays &lt;code&gt;embedded&lt;/code&gt; but &lt;code&gt;impersonationSource&lt;/code&gt; is populated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;#[error]Child Flow 'child-MyFlow-7a2b9d5c-e4f1-4328-b1a9-d8e7c6b5a413' has run-only/invoker connection(s)&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;#[error]  Connection ref: 'shared_commondataserviceforapps' | runtimeSource=embedded | providedByRunOnlyUser= | authenticationType= | impersonationSource=invoker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdiowf0b7h233egw3zft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdiowf0b7h233egw3zft.png" alt=" " width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It names the Flow and the specific connection reference. No ambiguity about what needs fixing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing it
&lt;/h2&gt;

&lt;p&gt;When the build fails:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://make.powerautomate.com" rel="noopener noreferrer"&gt;make.powerautomate.com&lt;/a&gt; and switch to the &lt;strong&gt;source environment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Open the flagged Child Flow for editing&lt;/li&gt;
&lt;li&gt;Find the connector named in the error — change its connection setting from &lt;strong&gt;Provided by run-only user&lt;/strong&gt; to &lt;strong&gt;Use this connection&lt;/strong&gt;, selecting the service account's connection&lt;/li&gt;
&lt;li&gt;Save the Flow&lt;/li&gt;
&lt;li&gt;Re-export the solution and re-run the pipeline&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix takes two minutes. The important thing is where it happens — in the source environment, in the Flow definition, before anything is deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference it makes
&lt;/h2&gt;

&lt;p&gt;Before: a Teams connector left on the wrong setting ships to UAT or even Production, the Flow is disabled, someone spends time hunting the cause, and the fix happens in a target environment instead of source control.&lt;/p&gt;

&lt;p&gt;After: the pipeline stops at the gate, the error names the Flow and the connector, the developer fixes it in the source environment and re-runs. The misconfiguration never reaches UAT.&lt;/p&gt;

&lt;p&gt;One less thing that only breaks in the wrong place 🙂.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>devops</category>
      <category>powerautomate</category>
      <category>automation</category>
    </item>
    <item>
      <title>Cloning Power Automate Flows using a Power Automate Flow</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Wed, 15 Apr 2026 09:25:54 +0000</pubDate>
      <link>https://dev.to/mcombp/cloning-power-automate-flows-using-a-power-automate-flow-5f1g</link>
      <guid>https://dev.to/mcombp/cloning-power-automate-flows-using-a-power-automate-flow-5f1g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When you build a document trigger on top of SharePoint, you quickly run into a fundamental constraint: &lt;strong&gt;a SharePoint trigger Flow is hardwired to a single Site and Library&lt;/strong&gt;. There is no native way to point one Flow at a dynamic or configurable location — the Site URL and Library GUID are baked into the trigger definition at creation time.&lt;/p&gt;

&lt;p&gt;For a one-off deployment that's fine. But when you're setting up a standardised *&lt;em&gt;Processes &amp;amp; Procedures *&lt;/em&gt; programme across an organisation — where every Team or Business unit gets its own SharePoint Site — you need an approval trigger Flow for &lt;em&gt;each&lt;/em&gt; one. Creating and maintaining these by hand rapidly becomes a maintenance problem: if you fix a bug in one, you have to fix it in all of them.&lt;/p&gt;

&lt;p&gt;This post walks through the solution we built to address that: a &lt;strong&gt;Site-specific Provisioning Flow&lt;/strong&gt; that clones a template approval trigger into a new, Site-specific Flow automatically, using nothing more than Dataverse, Power Automate, and a clean separation between configuration data and Flow logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SPO P&amp;amp;P System at a Glance
&lt;/h2&gt;

&lt;p&gt;Before diving into the provisioning Flow, it's worth establishing the wider architecture of the solution.&lt;/p&gt;

&lt;p&gt;Each Processes and &amp;amp; Procedures Library is in a SharePoint Site. Authors upload documents to the Library. When a document is submitted for approval (its approval status set to &lt;em&gt;Pending Approval&lt;/em&gt;), a Power Automate Flow fires, routes the approval through a standard and centralised process via Microsoft Teams, and on Approval publishes the document to its major version and updates the approval metadata. This process can be maintained and changed whenever required and is all done in one place. &lt;/p&gt;

&lt;p&gt;That last Flow, the one watching the Library for changes, is the one we need to clone. It is specific to a Site and Library. The goal of this project was to make provisioning a new P&amp;amp;P Site a self-service operation: an admin fills in a record, runs a Flow from the Model Driven app, and everything wires itself up automatically 😀.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z5yln8nfnf5a014lwjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9z5yln8nfnf5a014lwjf.png" alt="Screenshot: SPO P&amp;amp;P Admin Model Driven App — Site Registries list view" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Flows
&lt;/h2&gt;

&lt;p&gt;The solution contains three Power Automate Flows that work together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flow&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SPO PP - Provision Site Flow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orchestrator — clones the template and provisions a new Site-specific Flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SPO PP - Approval Trigger - TEMPLATE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Template — the Flow definition that gets cloned for each new Site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Child: PP Approval Standard Process&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Worker — handles the actual approval logic, called by every cloned trigger Flow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The template and child Flows are permanent, shared fixtures. The cloned approval trigger Flows are ephemeral — one per Site, generated at provisioning time. We can have as many as we want of these, but point them all to the same child Power Automate Flow so we can control the centralised process in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug16ax4y3wxsmthetufk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug16ax4y3wxsmthetufk.png" alt="Diagram: Relationship between the three Flows" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Site Registry Table
&lt;/h2&gt;

&lt;p&gt;The hub of the whole system is a custom Dataverse table: &lt;strong&gt;&lt;code&gt;Site_Registry&lt;/code&gt;&lt;/strong&gt; . It stores one row per P&amp;amp;P Site and acts as the source of truth for both provisioning and ongoing operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Columns
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Display Name&lt;/th&gt;
&lt;th&gt;Logical Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Site Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sitename&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Human-readable name; also used as the Primary Name field and in the provisioned Flow's display name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Site URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Siteurl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Full URL of the SharePoint Site (e.g. &lt;code&gt;https://companySite.sharepoint.com/Sites/Finance&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Library GUID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Libraryguid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;GUID of the Document Library within the Site — required by the SharePoint trigger&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Flowid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Written back by the provision Flow after cloning; links this registry entry to its live approval trigger Flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Site Registry ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Site_Registryid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GUID (Primary Key)&lt;/td&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;Auto-generated unique identifier for the row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Flowid&lt;/code&gt; column starts empty. It is the only field the provisioning process writes — everything else is entered by the admin before triggering provisioning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Library GUID, Not Library Name?
&lt;/h3&gt;

&lt;p&gt;The SharePoint &lt;code&gt;When a file is created or modified (properties only)&lt;/code&gt; trigger requires a list/Library GUID rather than its display name. GUIDs are stable across renames and don't break if someone tidies up the Library name. Recording the GUID in the Site Registry means provisioning is reliable regardless of what the Library is called. For our template, we can just use a development SharePoint site and a placeholder document Library to keep things simple and standard. I had tried inserting &lt;code&gt;placeholderName&lt;/code&gt; and &lt;code&gt;Library-guid-name&lt;/code&gt; as placeholders, but the Flow struggled to save. &lt;br&gt;
As a possible improvement in the Model Driven App, we could make selection easier by showing them a list of possible document libraries for the selected sites, so the user does not have to go and find the GUID. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kkprsra65gimikc4ovl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9kkprsra65gimikc4ovl.png" alt="Screenshot: Site Registry form in the Model Driven App — showing all custom columns" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Approval Trigger Template
&lt;/h2&gt;

&lt;p&gt;The template Flow (&lt;code&gt;SPOPP-ApprovalTrigger-TEMPLATE&lt;/code&gt;) is the blueprint for every Site-specific approval trigger. It lives in the solution and is &lt;strong&gt;never activated&lt;/strong&gt; — it exists solely to be cloned.&lt;/p&gt;

&lt;p&gt;Its trigger is a SharePoint &lt;code&gt;When a file is created or modified (properties only)&lt;/code&gt; action polling every 1 minute, pointed at two placeholders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Site URL placeholder:&lt;/strong&gt; &lt;code&gt;https://companysite.sharepoint.com/Sites/ITDevelopment&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Library GUID placeholder:&lt;/strong&gt; &lt;code&gt;a3f7c291-84e6-4d1a-b509-2e763dc18f45&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A trigger condition filters the events down to only relevant ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ApprovalStatus = 3 (Pending Approval)
AND Editor is NOT the service account  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the Flow from firing on system-generated updates and ensures it only acts when a real user has submitted a document for review.&lt;/p&gt;

&lt;p&gt;When the trigger fires, the Flow calls a single action inside a Try scope: &lt;strong&gt;Run a Child Flow — PP Approval Standard Process&lt;/strong&gt;. It passes the item's ID, Site URL, Library GUID, author claims, full path, approval status, and editor claims to the child Flow, then waits for a response.&lt;/p&gt;

&lt;p&gt;The Catch scope sends an error notification email to the support desk if anything fails.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixcz0r9400ubje6h654g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixcz0r9400ubje6h654g.png" alt="Screenshot: Template Flow overview in Power Automate designer" width="191" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;clientdata&lt;/code&gt; Column — the Key to Cloning
&lt;/h3&gt;

&lt;p&gt;When Power Automate stores a Flow in Dataverse, it uses the &lt;code&gt;workFlows&lt;/code&gt; ("Processes") table. The Flow's entire definition — triggers, actions, connections, expressions — is serialised as JSON and stored in a column called &lt;strong&gt;&lt;code&gt;clientdata&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the column we read from the template and write to the new Flow. The string-replace approach works because the placeholders (the hardcoded Site URL and Library GUID) are literal strings embedded in that JSON. Swapping them out produces a valid, Site-specific Flow definition ready to run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47iz52xwmvr78y88siaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F47iz52xwmvr78y88siaz.png" alt="Screenshot: workFlows table row for the template, showing clientdata column in Dataverse" width="323" height="614"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Provision Site Flow — Step by Step
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SPO PP - Provision Site Flow&lt;/strong&gt; is triggered from the Model Driven App by an admin selecting a Site Registry row and running the Flow from the command bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcklzky6h792c3mq1qsl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcklzky6h792c3mq1qsl2.png" alt="Trigger the Flow for a Site and Library" width="800" height="281"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger: Record Selected on Site_registries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trigger uses &lt;code&gt;splitOn&lt;/code&gt;, meaning if multiple rows are selected the Flow runs once per row — each Site is provisioned independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94lcjkdw0i599smlgzca.png" alt="Screenshot: Provision Site Flow — full overview in Power Automate designer" width="780" height="1572"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Initialise Variable (&lt;code&gt;varClientData&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Before entering the Try scope, a string variable &lt;code&gt;varClientData&lt;/code&gt; is initialised empty. This will hold the cloned Flow definition once the placeholders have been replaced.&lt;/p&gt;

&lt;p&gt;This is declared outside the Try scope deliberately: it needs to be initialised before any branching logic, and the scoped Try/Catch pattern requires variables to be declared at the root level to be accessible in both branches.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 — Get Site Registry Row (&lt;code&gt;Get a row by ID — Site Registry from Trigger&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The trigger provides the &lt;code&gt;Site_Registryid&lt;/code&gt; from the selected record, but only the ID and Site name are included in the trigger payload. We need the full row — specifically the &lt;code&gt;Siteurl&lt;/code&gt; and &lt;code&gt;Libraryguid&lt;/code&gt; — so the first action inside the Try scope fetches it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entity: Site_registries
Record ID: triggerBody()?['entity']?['Site_Registryid']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output of this action is referenced throughout the rest of the Flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkmovi5xev2sb892j9hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkmovi5xev2sb892j9hg.png" alt="Screenshot: Get a row by ID action — Site Registry" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 — Retrieve the Template Flow Definition (&lt;code&gt;List rows — Processes&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Next, the Flow queries the Dataverse &lt;code&gt;workFlows&lt;/code&gt; ("Processes") table to retrieve the template's &lt;code&gt;clientdata&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workFlows&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clientdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workFlowid&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workFlowid&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="s1"&gt;'05a1a909-5b33-f111-88b3-6045bddd6b33'&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template's workFlow ID is hardcoded here — it is a known, stable fixture in the solution. Using &lt;code&gt;$top: 1&lt;/code&gt; ensures a single result is returned efficiently.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;clientdata&lt;/code&gt; that comes back is the full JSON definition of the template Flow, including the hardcoded Site URL and Library GUID placeholders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafdgc0inkot5fu7vyjlc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafdgc0inkot5fu7vyjlc.png" alt="Screenshot: List rows action — retrieving template clientdata from workFlows table" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4 — Inject Real Values (&lt;code&gt;Set variable — varClientData&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This is the core of the cloning mechanism. A double-nested &lt;code&gt;replace()&lt;/code&gt; expression swaps out both placeholders in a single operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;List_rows_-_Processes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body/value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])?[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clientdata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://companySite.sharepoint.com/Sites/ITDevelopment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get_a_row_by_ID_-_Site_Registry_from_Trigger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body/Siteurl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a3f7c291-84e6-4d1a-b509-2e763dc18f45&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get_a_row_by_ID_-_Site_Registry_from_Trigger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body/Libraryguid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner &lt;code&gt;replace&lt;/code&gt; substitutes the Site URL. The outer &lt;code&gt;replace&lt;/code&gt; then takes that result and substitutes the Library GUID. The outcome is stored in &lt;code&gt;varClientData&lt;/code&gt;: a complete, valid Flow definition targeting the new Site's Library.&lt;/p&gt;

&lt;p&gt;No parsing, no schema manipulation — just string replacement in JSON.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20oquqth4p8f7m8upm94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20oquqth4p8f7m8upm94.png" alt="Screenshot: Set variable action — showing the double replace expression" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5 — Create the New Flow (&lt;code&gt;Add a new row — Processes&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The modified definition is now written back to the &lt;code&gt;workFlows&lt;/code&gt; ("Processes") table as a new row, creating the cloned Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;workFlows&lt;/span&gt;
&lt;span class="na"&gt;item/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPO&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Approval&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Trigger&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{Sitename}"&lt;/span&gt;
&lt;span class="na"&gt;item/category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5        (Modern Flow)&lt;/span&gt;
&lt;span class="na"&gt;item/type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;1        (Definition)&lt;/span&gt;
&lt;span class="na"&gt;item/primaryentity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
&lt;span class="na"&gt;item/xaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;     &lt;span class="s"&gt;(required field, but not used for modern Flows)&lt;/span&gt;
&lt;span class="na"&gt;item/clientdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="s"&gt;variables('varClientData')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;category: 5&lt;/code&gt; value identifies this as a cloud Flow. The &lt;code&gt;xaml&lt;/code&gt; field is a legacy artefact required by the API but irrelevant for modern Flows apparently — a single space " " keeps it happy.&lt;/p&gt;

&lt;p&gt;The response from this action includes the new Flow's &lt;code&gt;workFlowid&lt;/code&gt;, which is carried forward to the next two steps.&lt;/p&gt;

&lt;p&gt;The new Flow is created in a &lt;strong&gt;disabled/draft state&lt;/strong&gt;. It will not run until it is manually opened, saved (to authenticate connections), and turned on. This is by design — connections must be authorised by a human with the appropriate permissions, not ideal for automations, but we can live with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmx3cztsc3m03jwjs00gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmx3cztsc3m03jwjs00gh.png" alt="Screenshot: Add a new row action — creating the cloned workFlow in Dataverse" width="652" height="534"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 6 — Add to Solution (&lt;code&gt;Perform an unbound action — Add to Solution&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;With the Flow created, it exists in Dataverse but is not yet part of any solution. This step calls the &lt;code&gt;AddSolutionComponent&lt;/code&gt; unbound action to register it under the &lt;code&gt;SPOPPSiteConfig&lt;/code&gt; solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ComponentId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;(new Flow's workFlowid)&lt;/span&gt;
&lt;span class="na"&gt;ComponentType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;29    (Cloud Flow)&lt;/span&gt;
&lt;span class="na"&gt;SolutionUniqueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;SPOPPSiteConfig&lt;/span&gt;
&lt;span class="na"&gt;AddRequiredComponents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;DoNotIncludeSubcomponents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Component type &lt;code&gt;29&lt;/code&gt; maps to cloud Flows in the Dataverse component type enumeration. By adding it to the solution, the provisioned Flow becomes visible in the main solution and is logically grouped with the rest of the P&amp;amp;P components - making management cleaner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3owyxvtdn9ygi2wpnaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3owyxvtdn9ygi2wpnaz.png" alt="Screenshot: Perform unbound action — AddSolutionComponent" width="679" height="794"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 7 — Write the Flow ID Back to Site Registry (&lt;code&gt;Update a row — Site Registry&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The final Dataverse write closes the loop: the new Flow's ID is written back to the Site Registry record that triggered this whole process. This is for record keeping maintenance mainly. As an added extra you could concatenate the full URL for the Flow if you wanted to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entity: Site_registries
Record ID: triggerBody()?['entity']['Site_Registryid']
Flowid: outputs('Add_a_new_row_-_Processes')?['body/workFlowid']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this step, the Site Registry row has a &lt;code&gt;Flow ID&lt;/code&gt; value. This serves as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A human-readable audit trail (the admin can see which Flow was provisioned for which Site)&lt;/li&gt;
&lt;li&gt;A navigation link back to the provisioned Flow&lt;/li&gt;
&lt;li&gt;A signal that provisioning has completed for this Site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea3yr72bn16w4v7k1xzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea3yr72bn16w4v7k1xzo.png" alt="Screenshot: Update a row action — writing the Flow ID back to Site Registry" width="669" height="634"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 8 — Notify via Teams Adaptive Card (&lt;code&gt;Post card — Turn Flow On&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The final action sends an Adaptive Card to the user who triggered the Flow via Teams (the &lt;code&gt;triggerOutputs()?['headers']['x-ms-user-email']&lt;/code&gt; header on the trigger response identifies them):&lt;/p&gt;

&lt;p&gt;![Screenshot: Adaptive Card in Teams — SPO P&amp;amp;P Flow Provisioning notification]&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww990b6ipx6dsfu2p5er.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww990b6ipx6dsfu2p5er.png" alt=" " width="660" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned earlier, I tried to use some Power Automate actions to Turn On the Flow, But couldn't find any that worked, so this is our only workaround and a quick step for the end user, although not ideal. If you know a way to easily do this, please leave a comment. &lt;/p&gt;

&lt;p&gt;The card surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Completed automatically:&lt;/strong&gt; a green checklist of everything the Flow did&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual activation required:&lt;/strong&gt; a warning block explaining the three steps the admin must take&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Flow button:&lt;/strong&gt; a direct link to the new Flow in the Power Automate portal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The three manual steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Flow via the link in the card&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Turn on&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This manual step is a conscious design decision. Power Automate does not permit programmatic activation of a Flow that has connection references requiring user consent — the connections must be authorised interactively. The card makes this as frictionless as possible.&lt;/p&gt;


&lt;h3&gt;
  
  
  Error Handling — Scope Catch
&lt;/h3&gt;

&lt;p&gt;If any action inside the Try scope fails, times out, or is skipped, the Catch scope runs. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filters &lt;code&gt;result('Scope_-_Try')&lt;/code&gt; to find the actions that failed, timed out, or were skipped&lt;/li&gt;
&lt;li&gt;Posts a plain Teams message to the triggering user with a direct link to the failed Flow run
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Provision Site Flow Failed
Site: {Sitename}
View Flow Run: [link]
Once resolved, re-trigger provisioning from the SPO P&amp;amp;P Administration app.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The admin can inspect the failed run, resolve the root cause, and re-trigger provisioning from the app. Because the Site Registry record is only updated in Step 7 (after everything else succeeds), a partial failure leaves the record in its original state — &lt;code&gt;Flow ID&lt;/code&gt; will be blank, signalling that provisioning is incomplete and can safely be re-run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuet0dyeb11dxfm5f97q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuet0dyeb11dxfm5f97q.png" alt="Screenshot: Catch scope — Teams error message action" width="671" height="566"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Child Approval Flow
&lt;/h2&gt;

&lt;p&gt;This is the central Flow that the cloned trigger Flows hand off to. The team can iterate on this particular Flow / Process as much as is needed without having to worry about duplicate actions, etc., as the Process can be amended when needed. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Child: PP Approval Standard Process&lt;/strong&gt; is a manually-triggered child Flow that accepts seven parameters from every approval trigger:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ItemID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SharePoint list item ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SiteURL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;URL of the Site (passed through, not hardcoded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ListGUID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Library GUID (passed through)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AuthorClaims&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claims string of the document author&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FullPath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Server-relative path of the document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ApprovalStatus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Numeric approval status value from SharePoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Editor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claims string of the last editor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On receipt, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks the item isn't already approved (terminates gracefully if it is)&lt;/li&gt;
&lt;li&gt;Builds the server-relative URL via a &lt;code&gt;Compose&lt;/code&gt; expression&lt;/li&gt;
&lt;li&gt;Calls the SharePoint REST API to &lt;strong&gt;publish&lt;/strong&gt; the document (&lt;code&gt;_api/web/GetFileByServerRelativeUrl(...)/Publish&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reads the updated file properties back from SharePoint&lt;/li&gt;
&lt;li&gt;Calls the SharePoint REST API to &lt;strong&gt;set the approval status&lt;/strong&gt;  to Approved (&lt;code&gt;OData__ModerationStatus: 0&lt;/code&gt;) and records the approval date and updates any other column metadata required.&lt;/li&gt;
&lt;li&gt;Posts a Teams message to the document author confirming approval and the new version number&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the Site URL and Library GUID are passed in as parameters (not hardcoded), this single child Flow serves every cloned approval trigger across the entire organisation.&lt;/p&gt;


&lt;h2&gt;
  
  
  How It All Fits Together
&lt;/h2&gt;

&lt;p&gt;Here's the end-to-end lifecycle for adding a new P&amp;amp;P Site to the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Admin creates a SharePoint Online Site and Document Library
2. Admin notes the Library GUID (from the SharePoint URL or list settings)
3. Admin opens the SPO P&amp;amp;P Administration app
4. Admin creates a new Site Registry row:
      Site Name:    "Finance"
      Site URL:     https://companysite.sharepoint.com/Sites/Finance
      Library GUID: [GUID from SharePoint]
5. Admin selects the row and runs "SPO PP - Provision Site Flow" from the command bar
6. Flow runs (~10 seconds):
      - Reads Site Registry row
      - Fetches template clientdata from Dataverse
      - Replaces Site URL and Library GUID placeholders
      - Creates new Flow: "SPO PP - Approval Trigger - Finance"
      - Adds Flow to SPOPPSiteConfig solution
      - Writes Flow ID back to Site Registry
      - Sends Adaptive Card to The user that initiated the process via Teams
7. Admin clicks "Open Flow" in the Teams card
8. Admin saves and turns on the Flow
9. Finance P&amp;amp;P Site is live — approval triggers are active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From step 4 to a live Flow takes under a minute of admin time, with no Flow editing or cloning required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why string replacement instead of a proper API?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Power Automate does not expose a public API for cloning Flows. The &lt;code&gt;clientdata&lt;/code&gt; approach is the only programmatic path available without leaving the Power Platform. It is undocumented but stable — Microsoft's own import/export tooling uses the same &lt;code&gt;workFlows&lt;/code&gt; ("Processes")("Processes") table and &lt;code&gt;clientdata&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why store Library GUID and not Library Name?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
GUIDs are immutable. Library names can be changed by Site owners without affecting the trigger. Storing the GUID avoids a class of hard-to-diagnose breakages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why manual activation?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Connection references in Power Automate require user consent. There is no API to activate a Flow with unauthenticated connection references. The Adaptive Card makes the manual step as lightweight as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why add the Flow to the solution?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
It keeps all provisioned Flows grouped under &lt;code&gt;SPOPPSiteConfig&lt;/code&gt; in the environment. This makes bulk management, export, and environment promotion practical — rather than having dozens of orphaned Flows scattered across the default solution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment Constraint: Unmanaged Required&lt;/strong&gt;&lt;br&gt;
⚠️ One big constraint worth flagging: the Environment where the provisioned flows are created must be &lt;strong&gt;unmanaged&lt;/strong&gt;.&lt;br&gt;
The cloning mechanism writes new rows directly to the workflows table at runtime. Those flows are unmanaged artefacts — they exist outside the solution package and outside the ALM pipeline. That is intentional; they are site-specific, runtime-generated, and self-service by design.&lt;br&gt;
But it means a managed-solution environment is incompatible with this pattern. The provisioning Flow will run fine as a managed component, but the governance model of a managed environment conflicts with generating unmanaged Flow records inside it.&lt;/p&gt;

&lt;p&gt;The default environment is the most common candidate simply because it is typically unmanaged — but the constraint is unmanaged vs. managed, not default vs. non-default. Any unmanaged environment works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The project is delivered a self-service Site provisioning capability built on a single mechanism: reading a Flow's &lt;code&gt;clientdata&lt;/code&gt; from Dataverse, replacing two placeholder strings, and writing the result back as a new workFlow row. The Site Registry table is the operational hub — it drives provisioning and records the outcome. The template Flow is the blueprint — stable, never activated, just there to be cloned.&lt;/p&gt;

&lt;p&gt;The result is a system where adding a new P&amp;amp;P Site is a data entry task, not a development task.&lt;/p&gt;

</description>
      <category>powerautomate</category>
      <category>sharepoint</category>
      <category>powerplatform</category>
      <category>microsoftdataverse</category>
    </item>
    <item>
      <title>Generate a Dataverse ERD for Azure Dev Ops Wiki</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Sun, 16 Nov 2025 09:44:04 +0000</pubDate>
      <link>https://dev.to/mcombp/generate-a-dataverse-erd-for-azure-dev-ops-wiki-3m4</link>
      <guid>https://dev.to/mcombp/generate-a-dataverse-erd-for-azure-dev-ops-wiki-3m4</guid>
      <description>&lt;p&gt;Nobody likes manually writing and updating Documentation. ERDs (Entity Relationship Diagrams) provide a really helpful overview of what your database relationships look like.&lt;/p&gt;

&lt;p&gt;Context: We are using Azure DevOps (ADO) to manage our CI/CD Pipelines using the &lt;a href="https://learn.microsoft.com/en-us/power-platform/alm/devops-build-tools" rel="noopener noreferrer"&gt;Power Platform Tools&lt;/a&gt; and therefore the Solutions are unpacked and stored in the Repository (there may be multiple Solutions inside of a Project).&lt;/p&gt;

&lt;p&gt;The idea: working with Power Platform solutions stored in ADO, it would be great to visualise complex entity relationships right inside ADO. Visualizing these relationships can make solution design and troubleshooting much easier, it certainly helps me.&lt;/p&gt;

&lt;p&gt;In this quick post, we'll use a short prompt using &lt;a href="https://code.visualstudio.com/docs/copilot/chat/getting-started-chat" rel="noopener noreferrer"&gt;Chat in VS Code&lt;/a&gt; to get an AI Agent to generate a &lt;strong&gt;Mermaid ERD&lt;/strong&gt; from our Dataverse solution files and then we can copy it and publish it to our ADO Wiki. &lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Why This Matters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Better Documentation&lt;/strong&gt;: ERDs help us understand entity relationships at a glance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Rendering&lt;/strong&gt;: Mermaid diagrams render natively in ADO Wiki.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation Ready&lt;/strong&gt;: This process could be adapted for CI/CD pipelines later. For this we can implement &lt;a href="https://mightora.io/tools/cicd/upload-md-to-wiki/" rel="noopener noreferrer"&gt;Ian Tweedie - Upload Markdown to Wiki
&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: Clone the Repository in VS Code&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;Azure DevOps&lt;/strong&gt;, go to &lt;strong&gt;Repos → Clone → IDE&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjqxs72pved3ub22yx0d.png" alt="azure-repo" width="800" height="320"&gt;
---&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Clone in VS Code&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbddkwz0emux4yl82dd52.png" alt="Clone in VS Code" width="427" height="317"&gt;
---&lt;/li&gt;
&lt;li&gt;Choose a local folder where the repo will be stored.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Locate a Relationships File&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In VS Code, navigate to the Power Platform solution folder inside the repo.&lt;/li&gt;
&lt;li&gt;Go to: &lt;strong&gt;Other → Relationships.xml&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrw32z50uop8b6tus0ty.png" alt="Relationships XML" width="568" height="695"&gt;
---
(if there is more than one Relationships.xml file, you may need to repeat these steps)&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;&lt;code&gt;Relationships.xml&lt;/code&gt;&lt;/strong&gt; in VS Code.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3: Use AI to Generate the ERD&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Chat&lt;/strong&gt; in VS Code with &lt;code&gt;Relationships.xml&lt;/code&gt; in file context.&lt;/li&gt;
&lt;li&gt;Paste this prompt:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Please scan this file and identify all Dataverse-related entities and their relationships. Focus on extracting entity names, attributes, and how they relate to each other (e.g. one-to-many, many-to-one, many-to-many). Then, generate a Mermaid entity-relationship diagram (ERD) that includes cardinality for each relationship.

 Format the output as a Markdown file containing the Mermaid ERD code block. Ensure the diagram is readable and reflects the structure of the Dataverse schema as accurately as possible.

If any assumptions are made due to missing or ambiguous information, please list them at the end of the Markdown file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Copy the Mermaid Diagram&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;From the AI response, copy everything starting from:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;erDiagram&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;erDiagram

    CUSTOMER ||--o{ ORDER : "1:N"
    CUSTOMER ||--o{ ADDRESS : "1:N"

    ORDER ||--o{ ORDER_LINE : "1:N"
    ORDER_LINE }o--|| PRODUCT : "N:1"

    PRODUCT ||--o{ PRODUCT_CATEGORY : "N:1"

    PRODUCT ||--o{ INVENTORY : "1:N"
    INVENTORY ||--|| WAREHOUSE : "1:1"

    PAYMENT ||--|| ORDER : "1:1"
    SHIPMENT ||--o{ ORDER : "1:N"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example Mermaid&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Step 5: Publish to ADO Wiki&lt;/strong&gt;
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In &lt;strong&gt;Azure DevOps&lt;/strong&gt;, go to &lt;strong&gt;Overview → Wiki&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New Page&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Paste the Markdown (including the Mermaid block) into the &lt;strong&gt;left-hand pane&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give the page a title.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi11p2i6jozaiy4bgvutm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi11p2i6jozaiy4bgvutm.png" alt="Dataverse Mermaid" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ Pro Tip
&lt;/h3&gt;

&lt;p&gt;Mermaid diagrams render natively in ADO Wiki, so your ERD will be interactive and easy to read 😜.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Possible Next Steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Automate this process using a pipeline - this is going to require me to learn some fancy PowerShell Scripts to parse the XML 😵‍💫.&lt;/li&gt;
&lt;li&gt;This could allow Automatic Documentation as part of a Release, keeping the ERD up to date.&lt;/li&gt;
&lt;li&gt;Add entity attributes for deeper insights.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powerplatform</category>
      <category>devops</category>
      <category>documentation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Extract table data from Documents using Azure AI Document Intelligence</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Wed, 16 Oct 2024 15:30:29 +0000</pubDate>
      <link>https://dev.to/mcombp/extract-table-data-from-documents-using-azure-ai-document-intelligence-1maa</link>
      <guid>https://dev.to/mcombp/extract-table-data-from-documents-using-azure-ai-document-intelligence-1maa</guid>
      <description>&lt;h2&gt;
  
  
  Extract table data from Documents Using Azure AI Document Intelligence Layout Model with Power Automate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6so0e1ifuegxe8n025f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6so0e1ifuegxe8n025f.png" alt="Document Intelligence Studio" width="800" height="379"&gt;&lt;/a&gt;&lt;br&gt;
Azure AI Document Intelligence is a cloud-based service that allows you to build intelligent document processing solutions. It uses machine learning to extract text, tables, and key-value pairs from documents, providing structured data for easy use.&lt;/p&gt;

&lt;p&gt;In this post, I’m going to go through how to send a PDF document using an Azure AI Document Intelligence REST API request and return the text and table information using the Layout model. I’ll go through some of my processes for transforming the received JSON output into an array that can be used to create rows in a Dataverse table, all within Power Automate.&lt;/p&gt;

&lt;p&gt;Power Automate isn’t Power Query, so I will use some techniques to get it to try and replicate some of the filtering, cleansing, and sorting capabilities of Power Query. Some of the filtering can be quite specific to the documents you have, so I will start quite general and then cover some techniques to cleanse the data by type alongside.&lt;/p&gt;
&lt;h1&gt;
  
  
  Choosing a Model
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I would highly recommend that you use the &lt;a href="https://documentintelligence.ai.azure.com/studio" rel="noopener noreferrer"&gt;Azure AI Document Intelligence Studio&lt;/a&gt; to find the most suitable model for your documents. You can try out models in the studio with your own documents. I did this and was confused about how you can actually use the model. The studio provides code examples, but you are kind of left to it from then on. When I’d found the &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/how-to-guides/use-sdk-rest-api?view=doc-intel-3.1.0&amp;amp;tabs=windows&amp;amp;pivots=programming-language-rest-api" rel="noopener noreferrer"&gt;REST API Documentation &lt;/a&gt;I certainly felt more comfortable, as I can use this with Power Automate!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test multiple documents as you may discover inconsistencies in the results. To get around these, you can adjust some of the actions and expressions in your Flow to deal with them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  My Example
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7a1nnasppsumcncxboc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7a1nnasppsumcncxboc.png" alt="Example PDF Preview" width="724" height="363"&gt;&lt;/a&gt;&lt;br&gt;
I have created a simple document that includes one table and I like sausages, but in the real world, I have dealt with documents of all shapes and sizes. For example, where the tables start on a different page (we can append &lt;code&gt;&amp;amp;pages=&lt;/code&gt; to the URI to extract a specific page 🙂).&lt;/p&gt;
&lt;h1&gt;
  
  
  Passing the Document to Azure AI Document Intelligence
&lt;/h1&gt;

&lt;p&gt;I have to start by mentioning that I could not have done this first part without the help of this YouTube video from the excellent &lt;a href="https://damobird365.com/" rel="noopener noreferrer"&gt;Damien Bird&lt;/a&gt; on &lt;a href="https://youtu.be/fLHmEwcg8Jo?si=whD4IAvsr-oojTm5" rel="noopener noreferrer"&gt;Power Automate Invoice Processing Tutorial AI Builder and Azure&lt;/a&gt;. Here Damien explores the AI Builder option and compares the costs, introduces the Document Intelligence Service on Azure, polling the services, reviewing the Response, and selecting data from the response.&lt;/p&gt;

&lt;p&gt;We’re basically going to do the same, but select the &lt;strong&gt;Layout Model API Version 2023-07-31&lt;/strong&gt; from Azure AI Document Intelligence.&lt;/p&gt;
&lt;h1&gt;
  
  
  Azure Services
&lt;/h1&gt;

&lt;p&gt;Before we start building our Flow, we will need some Azure Services set up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource Group&lt;/li&gt;
&lt;li&gt;Azure AI Services &amp;gt; Document Intelligence (Select the F0 Pricing tier for up to 500 documents per month for free, much better than AI Builder).&lt;/li&gt;
&lt;li&gt;Azure Key Vault Secret&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure to copy the Endpoint, as we will need this in our Flow. To keep things secure keep your key inside Azure Key Vault. I can recommend following this video &lt;a href="https://www.youtube.com/watch?v=HA1BMJJCDF0" rel="noopener noreferrer"&gt;Power Automate +Azure Key Vault - get key and secret and pass to Flow&lt;/a&gt; from Sean Astrakhan on how to do this.&lt;/p&gt;
&lt;h1&gt;
  
  
  Flow Structure
&lt;/h1&gt;

&lt;p&gt;Ok, let’s get on with the Flow!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz8jn4un0gb38m0q3wq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz8jn4un0gb38m0q3wq3.png" alt="Flow Overview" width="488" height="1504"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Manual Trigger
&lt;/h3&gt;

&lt;p&gt;Start with a manual trigger with a File Content Input. This will be used to bring the PDF Document into the Flow.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmkb5imws0u18s39zag9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmkb5imws0u18s39zag9.png" alt="Manual Trigger" width="785" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and then set up some variables....&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialize variable – Status&lt;/strong&gt; - We will need to use this String Variable to set the outcome of the second HTTP request status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialize variable – Metadata&lt;/strong&gt; - We will use this Object Variable to contextualise and reference the Document that we have extracted from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialize variable - ParsedCellsArray&lt;/strong&gt; - We will use this Array Variable to loop through the table cells.&lt;/p&gt;
&lt;h3&gt;
  
  
  Azure Key Vault Get secret
&lt;/h3&gt;

&lt;p&gt;Enter the name of your Azure Key Vault secret to return its value. In settings, enable ‘Secure inputs’ and ‘Secure outputs’ to ensure the secret is hidden.&lt;/p&gt;
&lt;h3&gt;
  
  
  HTTP Request
&lt;/h3&gt;

&lt;p&gt;This is where the fun starts. Add an HTTP Action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL&lt;/strong&gt;: &lt;em&gt;{Your Azure AI Service Endpoint}/formrecognizer/documentModels/prebuilt-layout:analyze?api-version=2023-07-31&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There’s some &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/how-to-guides/use-sdk-rest-api?view=doc-intel-3.1.0&amp;amp;tabs=windows&amp;amp;pivots=programming-language-rest-api" rel="noopener noreferrer"&gt;more information here&lt;/a&gt; on versions and models. Here I have chosen the Model: &lt;strong&gt;prebuilt-layout&lt;/strong&gt; and the api-version of &lt;strong&gt;2023-07-31&lt;/strong&gt; – which is GA at the time of writing. It’s worth noting that I struggled to use the newer preview models due to my Azure AI services location being UK South (we have to wait for the new toys!).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Method&lt;/em&gt;: &lt;em&gt;POST&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Headers&lt;/em&gt;: &lt;em&gt;Ocp-Apim-Subscription-Key: {Azure Key Vault Secret Value}&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Body&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;{"base64Source": triggerBody()?['file']?'contentBytes']}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For the body, we are passing the '‘File content contentBytes’' value from the Manual Trigger. This converts the file into base64 format so that it can be uploaded as part of the request.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyc252s5cud511kvmmvq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyc252s5cud511kvmmvq.png" alt="HTTP Request" width="560" height="454"&gt;&lt;/a&gt;&lt;br&gt;
Now we have a way to send the Document, we then need a way to wait until it has been processed.&lt;/p&gt;
&lt;h3&gt;
  
  
  Do Until
&lt;/h3&gt;

&lt;p&gt;Create a Do Until polling loop using &lt;strong&gt;Loop Until&lt;/strong&gt; so that we can wait for the service to process the document and send back a response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj98iyfkbhr0qe1ciz0fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj98iyfkbhr0qe1ciz0fd.png" alt="Do until" width="558" height="427"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Variable Status is equal to “succeeded”, with a Count of 5 to limit the number of retries, with a 1 hour timeout.
&lt;/h3&gt;

&lt;p&gt;Within the Do Until we need:&lt;/p&gt;
&lt;h3&gt;
  
  
  Delay: 10 Second Delay
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;HTTP_1:&lt;/strong&gt; This HTTP action uses the ‘Operation-Location’ Header from the first HTTP action to check if the service has completed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;em&gt;outputs('HTTP')?['headers/Operation-Location']&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Method&lt;/strong&gt;: &lt;em&gt;GET&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Headers&lt;/strong&gt;: &lt;em&gt;Ocp-Apim-Subscription-Key: {Azure Key Vault Secret Value}&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6msc60366cne584brks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6msc60366cne584brks.png" alt="HTTP_1" width="553" height="297"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Set the Status Variable:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Value&lt;/strong&gt;: &lt;em&gt;body('HTTP_1')?['status']&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  After the Do Until:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;body('HTTP_1')?['analyzeResult']&lt;/code&gt;&lt;br&gt;
Now we have a response from the Azure AI Document Intelligence Model, we can start to deal with the JSON that we get back. As Damien Bird mentions, it is helpful to visualise this output using &lt;a href="https://codebeautify.org/jsonviewer" rel="noopener noreferrer"&gt;https://codebeautify.org/jsonviewer&lt;/a&gt; to give yourself an idea of the output structure and how to reference and extract the data in your Flow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Extract the Document Information
&lt;/h3&gt;

&lt;p&gt;I have decided that it is important to extract some of the headers from the document as well as the table data in order to give context to the information.&lt;/p&gt;
&lt;h3&gt;
  
  
  Parse JSON – Paragraphs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqaqjstqbld0htmvujkws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqaqjstqbld0htmvujkws.png" alt="Layout Model Paragraphs" width="800" height="407"&gt;&lt;/a&gt;&lt;br&gt;
As the Layout model gives us the text in the &lt;code&gt;body('HTTP_1')?['analyzeResult/paragraphs']&lt;/code&gt; we can just select that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content&lt;/strong&gt;: &lt;em&gt;body('HTTP_1')?['analyzeResult/paragraphs']&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Schema&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we only need the Content properties, I have shortened the Schema so that only the &lt;strong&gt;['content']&lt;/strong&gt; is returned in the output.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2f70yc9qbizpbr3aqvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2f70yc9qbizpbr3aqvw.png" alt="Parse JSON - Paragraphs" width="548" height="525"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Set variable - Metadata Object
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt;: &lt;em&gt;Metadata&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Value&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Report_Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{body('Parse_JSON_-_Paragraphs')['content']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Issue_No"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{first(split(&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;body('Parse_JSON_-_Paragraphs')['content'],'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;'))}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Issue_Date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{last(split(&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;body('Parse_JSON_-_Paragraphs')['content'],'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;'))}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the index of the &lt;strong&gt;['content']&lt;/strong&gt; property to extract the values that we want. This is where it is helpful to preview the output using &lt;a href="https://codebeautify.org/jsonviewer" rel="noopener noreferrer"&gt;https://codebeautify.org/jsonviewer&lt;/a&gt; so that you can work out the index beforehand. In my example, the Report Name is the 2nd value &lt;strong&gt;[1]&lt;/strong&gt; and then I have used a &lt;code&gt;Split()&lt;/code&gt; expression on the &lt;strong&gt;"|"&lt;/strong&gt; to allow me to capture the &lt;code&gt;first()&lt;/code&gt; and &lt;code&gt;last()&lt;/code&gt; values either side of the &lt;strong&gt;“|”&lt;/strong&gt; giving us the &lt;strong&gt;Issue_No&lt;/strong&gt; and &lt;strong&gt;Issue_Date&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is just an example, you will want to adjust the structure of your output to meet your own needs. We have captured this as an object here so that we can then align it alongside the table rows later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting the table rows
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h98l1aharcj2kr8bec3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6h98l1aharcj2kr8bec3.png" alt="Layout Tables" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Parse JSON
&lt;/h3&gt;

&lt;p&gt;We can now use a Parse JSON to extract all the Table data by selecting the child property &lt;code&gt;/tables&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrtv3a7thcdsfcxiidph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvrtv3a7thcdsfcxiidph.png" alt="Parse JSON" width="557" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content&lt;/strong&gt;: &lt;em&gt;body('HTTP_1')?['analyzeResult/tables']&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Schema&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"columnCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cells"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rowIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"columnIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rowIndex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"columnIndex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"columnCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cells"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the full schema as we do need a few of the properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  For each – Table
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Input&lt;/strong&gt;: &lt;em&gt;body('Parse_JSON')&lt;/em&gt;&lt;br&gt;
If you have multiple tables to go through, this is useful; if not, you can skip this action.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwxwjjogev1vwbiq2k6p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwxwjjogev1vwbiq2k6p.png" alt="For each - Table" width="557" height="416"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Set variable ParsedCellsArray
&lt;/h3&gt;

&lt;p&gt;Set the &lt;strong&gt;ParsedCellsArray&lt;/strong&gt; variable with 'cells' - this is the tables cells.&lt;br&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;em&gt;ParsedCellsArray&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Value&lt;/strong&gt;: &lt;em&gt;items('For_each&lt;/em&gt;-&lt;em&gt;Table')['cells']&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Select
&lt;/h3&gt;

&lt;p&gt;So, if we were to just use....&lt;/p&gt;
&lt;h3&gt;
  
  
  From: "@variables('ParsedCellsArray')",
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Map:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"rowIndex"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'rowIndex'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"columnIndex"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'columnIndex'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then we would get each cell on top of each other in separate objects,&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49z5qcip8rrng422884z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49z5qcip8rrng422884z.png" alt="Image description" width="345" height="345"&gt;&lt;/a&gt;&lt;br&gt;
which we would have to somehow ZigZag across or remap, as we want every object in our array to contain a Name and a Price. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnesj9gledrh6w3gxijmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnesj9gledrh6w3gxijmk.png" alt="Standard Select Power Query" width="511" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this we need to integrate a Range into the Select action using:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;range(0, length(variables('ParsedCellsArray')))&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 to generate an index to iterate over the array.&lt;/p&gt;
&lt;h4&gt;
  
  
  From
&lt;/h4&gt;

&lt;p&gt;In order to get the value of the adjacent column in the Select action, we can use &lt;code&gt;range(startIndex: integer, count: integer)&lt;/code&gt; and use &lt;code&gt;length()&lt;/code&gt; to use the length of &lt;strong&gt;ParsedCellsArray&lt;/strong&gt;. This allows the Select action to iterate over each element in **ParsedCellsArray **and generate a sequence of numbers.&lt;/p&gt;
&lt;h4&gt;
  
  
  Map
&lt;/h4&gt;

&lt;p&gt;This number sequence is then used in the &lt;strong&gt;Map&lt;/strong&gt; for the last Property to get the next element “&lt;strong&gt;nextContent&lt;/strong&gt;”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;nextContent&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@{if(less(item(), sub(length(variables('ParsedCellsArray')), 1)), variables('ParsedCellsArray')[add(item(), 1)]?['content'], null)}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This expression uses the &lt;code&gt;If()&lt;/code&gt; function to check if the current index &lt;code&gt;item()&lt;/code&gt; is less than the last index of the array &lt;code&gt;sub(length(variables('ParsedCellsArray')), 1))&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If true, it retrieves the content of the next element &lt;code&gt;variables('ParsedCellsArray')[add(item(), 1)]?['content'])&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If false (i.e., the current element is the last one), it returns null.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to put this all together we get:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From&lt;/strong&gt;: &lt;em&gt;range(0, length(variables('ParsedCellsArray')))&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Map&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{variables('ParsedCellsArray')[item()]?['content']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rowIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{variables('ParsedCellsArray')[item()]?['rowIndex']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"columnIndex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{variables('ParsedCellsArray')[item()]?['columnIndex']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isColumnHeader"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{equals(variables('ParsedCellsArray')[item()]?['kind'], 'columnHeader')}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{item()}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nextContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{if(less(item(), sub(length(variables('ParsedCellsArray')), 1)), variables('ParsedCellsArray')[add(item(), 1)]?['content'], null)}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2og84d2lnvd4vviw1qc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb2og84d2lnvd4vviw1qc.png" alt="Select" width="513" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The outcome of this starts to line up for us, but still needs Filtering... For example we only need the first Column and we can ignore the column headers.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhfvpj0dadrr6r9u35zo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhfvpj0dadrr6r9u35zo.png" alt="WorkingExport - Power Query" width="349" height="296"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Filter Array
&lt;/h3&gt;

&lt;p&gt;We can use Filter Array to filter the returned data. This is especially useful if the tables contain some data that we don’t want, such as comments or notes. Rather than using a single, &lt;strong&gt;basic filter&lt;/strong&gt;, we need to switch to &lt;strong&gt;advanced mode&lt;/strong&gt; 🤓 to combine the multiple conditions we want. &lt;br&gt;
You may want to use other conditions inside the &lt;code&gt;and()&lt;/code&gt; expression to fine-tune your data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From&lt;/strong&gt;: &lt;em&gt;body('Select')&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Advanced Filter:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;@and(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;equals(item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'columnIndex'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//Only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;column&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;equals(item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'isColumnHeader'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//Ignores&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Header&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;not(equals(item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'')),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Blanks&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;not(equals(item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'nextContent'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'')),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//No&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Blanks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvatdbb4qtpe8c4gd0fd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpvatdbb4qtpe8c4gd0fd.png" alt="Filter Array" width="555" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Select – Filtered Table
&lt;/h3&gt;

&lt;p&gt;We can merge in some of the metadata extracted from the Document, which we can now align in the context of each record in the array. The first three in my example are from the Metadata Object created earlier. You can also use this opportunity to further cleanse the values returned using string expressions if you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From&lt;/strong&gt;: &lt;em&gt;body('Filter_Array')&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Map&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Report_Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;variables('Metadata')?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'Report_Name'&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Issue_No"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;variables('Metadata')?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'Issue_No'&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Issue_Date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;variables('Metadata')?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'Issue_Date'&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;item()?&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'nextContent'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjcclchanvq4zmu03k6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjcclchanvq4zmu03k6i.png" alt="Select - Filtered Table" width="518" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set variable - Filtered TransformedArray
&lt;/h2&gt;

&lt;p&gt;Value: &lt;code&gt;body('Select_-_Filtered_Table')&lt;/code&gt;&lt;br&gt;
We will capture the table data into an array so that we can use it elsewhere. Here you can do whatever you want with your array. If you want to use an Add a new row Dataverse action, you can map each array property in the action for example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Thank you for reading this post. Hopefully, this is useful to anyone who needs to use this particular Azure AI Document Intelligence Model to process their Documents and place the data into a more organised format.  Please leave feedback in the Comments if you found this useful, or if there are any improvements you could recommend. Please experiment with it too, I'd love to hear some of the outcomes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>azure</category>
      <category>powerautomate</category>
      <category>json</category>
    </item>
    <item>
      <title>Using Mermaid JS to generate a diagram from Power Automate</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Tue, 20 Feb 2024 19:52:20 +0000</pubDate>
      <link>https://dev.to/mcombp/using-mermaid-js-to-generate-a-diagram-from-power-automate-1f68</link>
      <guid>https://dev.to/mcombp/using-mermaid-js-to-generate-a-diagram-from-power-automate-1f68</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;This is the "bonus part" of my blog series on &lt;br&gt;
&lt;a href="https://dev.to/mcombp/conversational-approval-process-using-adaptive-cards-and-json-objects-4mih"&gt;creating a Conversational Approval Process using Power Automate and Microsoft Teams.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I needed a way to produce an easy to read depiction of the steps of a process to users or even a way to debug what happened. After seeing Jon Russell and Mike Gowland's amazing demo of&lt;br&gt;
&lt;strong&gt;JustAskIt&lt;/strong&gt; on a &lt;a href="https://youtu.be/yderRfy2wWA?si=gi11PmzVnYzyI_DP&amp;amp;t=3344" rel="noopener noreferrer"&gt;Microsoft 365 &amp;amp; Power Platform Development community call&lt;/a&gt; I have been keen to try out Mermaid JS in Power Automate.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Mermaid JS?
&lt;/h3&gt;

&lt;p&gt;Mermaid JS is a tool that lets you create diagrams and charts such as flowcharts, sequence diagrams, Gantt charts using simple text commands. It works by converting your text commands into a graphical representation that you can customize and share.&lt;br&gt;
&lt;a href="https://mermaid.live/" rel="noopener noreferrer"&gt;https://mermaid.live/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Using Mermaid JS we easily can convert Markdown like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

flowchart TD
    A[Christmas] --&amp;gt;|Get money| B(Go shopping)
    B --&amp;gt; C{Let me think}
    C --&amp;gt;|One| D[Laptop]
    C --&amp;gt;|Two| E[iPhone]
    C --&amp;gt;|Three| F[fa:fa-car Car]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;into an image like this that lists the steps in an easy to read graphical format:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4ibxk4m1ifw0bw156nf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4ibxk4m1ifw0bw156nf.png" alt="Mermaid Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual Trigger
&lt;/h3&gt;

&lt;p&gt;We start off our Flow with the manual trigger. The first text input is &lt;strong&gt;varSteps&lt;/strong&gt;, this is the steps that we are going to either create or append to our existing Flowchart.&lt;br&gt;
The other text input is the Flowchart, so this is where we could insert an existing Flowchart to our Power Automate Flow. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5inkmqwjnnhvklegv7is.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5inkmqwjnnhvklegv7is.png" alt="Manual Trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Alphabets Variable
&lt;/h3&gt;

&lt;p&gt;The first variable to initialise is Alphabets, here we will have a string value of the 26 character alphabet. We will use this to pick out the next alphabetical character if we need to append to an existing Flowchart. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxw8tw1umoooxjavqvkaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxw8tw1umoooxjavqvkaz.png" alt="Alphabets Variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Flowchart Variable
&lt;/h3&gt;

&lt;p&gt;The next variable to initialise is Flowchart, which is a string variable that will capture the Flowchart text input from the manual trigger. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz95zy37uiofpxtsogu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz95zy37uiofpxtsogu5.png" alt="Flowchart Variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  varSteps Variable
&lt;/h3&gt;

&lt;p&gt;Then the next variable to initialise is the &lt;strong&gt;varSteps&lt;/strong&gt;, which is an object variable. It will capture the text input for &lt;strong&gt;varSteps&lt;/strong&gt; from the manual input, but we will wrap this inside JSON() so that it is converted to JSON from a string value. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6bv2mspejta1l7nxvyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6bv2mspejta1l7nxvyl.png" alt="varSteps Variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Condition
&lt;/h2&gt;

&lt;p&gt;We will start the actions with a Condition action. The condition will check whether variable('Flowchart') is equal to Null. If it is  Null, then we'll create on from scratch. If it's not, then we can append to it with further Steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlqxk6qjnc7upqvo1aqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlqxk6qjnc7upqvo1aqy.png" alt="Condition - 1st Time Around"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a new FlowChart
&lt;/h2&gt;

&lt;p&gt;I will first go through the steps that are required in the Yes or True side. In here we are just creating a brand new Flowchart that is created in the format ready for Mermaid JS. Set the variable &lt;strong&gt;FlowChart&lt;/strong&gt; and in the value box we will build our markdown so that it can be read inside the Mermaid JS. We will reference the Object properties from the &lt;strong&gt;varSteps&lt;/strong&gt; to build that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define the type using "flowchart TD"&lt;/li&gt;
&lt;li&gt;Starting with &lt;strong&gt;“A”&lt;/strong&gt; we will enter &lt;strong&gt;variables('varSteps')?['Trigger']&lt;/strong&gt; .&lt;/li&gt;
&lt;li&gt;Then for &lt;strong&gt;“B”&lt;/strong&gt; we will add the &lt;strong&gt;variables('varSteps')?['Condition']&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;Then &lt;strong&gt;”B--&amp;gt;”&lt;/strong&gt; for variables('varSteps')?['Outcome'] &lt;/li&gt;
&lt;li&gt;Lastly we will insert the &lt;strong&gt;“C”&lt;/strong&gt; &lt;strong&gt;variables('varSteps')?['Action']&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That should give us all we need to create a new Flowchart.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zvp9rlxjr433gxf6o1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zvp9rlxjr433gxf6o1y.png" alt="Set Variable - FlowChart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appending to an Existing Flowchart
&lt;/h2&gt;

&lt;p&gt;On the No / False side of the Condition Action we will be appending to an existing Flowchart. This is useful if there are multiple child flows that make up part of the full process that you are trying to build into a Flowchart. In my Conversational Approval process, there may only be one run of the Approver's Adaptive card, or there may be a need to the Requestor to respond to an Adaptive Card also. Having the Append option allows us to keep building on top of the initial FlowChart that gets created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose - Last Step
&lt;/h2&gt;

&lt;p&gt;We will start by extracting the alphabetical character for the last step that exists in the Flowchart. For example; if the existing Flowchart is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

flowchart TD
A[RM Request] --&amp;gt; B{Matthew Collinge - receives Approval Card}
B --&amp;gt;|Pending| C[Send back to Matthew Collinge]
Then we want the letter **“C”**


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Using a Compose action we can use. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 sub(lastIndexOf(variables('flowchart'), '['), 1), 1)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mzzl9brk8ec2n40v698.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mzzl9brk8ec2n40v698.png" alt="Compose - Last Step"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will need to use this value to increment to the next letter in the alphabet. &lt;/p&gt;

&lt;h2&gt;
  
  
  Compose Letter No
&lt;/h2&gt;

&lt;p&gt;We now get the next letter's number by using the next compose action. Referencing the alphabet variable we get the next number (D would be 4). &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 outputs('Compose_-_Last_Step'),1)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gxjgs4hmfgu4kn5ebwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gxjgs4hmfgu4kn5ebwj.png" alt="Compose - Letter No"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose Approval
&lt;/h2&gt;

&lt;p&gt;The next compose step is actually composing the approval Markdown text, starting with the original Flowchart and then appending the next steps. It will start with the starting letter that we have derived from the previous few actions. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@{variables('FlowChart')}
@{outputs('Compose_-_Last_Step')} --&amp;gt; @{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),2),1)
}@{variables('varSteps')?['Condition']}
@{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),2),1)
} --&amp;gt;@{variables('varSteps')?['Outcome']} @{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),3),1)
}@{variables('varSteps')?['Action']}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwehwgmjg8qtgvljzzth3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwehwgmjg8qtgvljzzth3.png" alt="Compose Approval"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step on this append side is setting the Flowchart variable with the output from the previous compose action, so that we have a variable with the whole Flowchart ready to convert to Mermaid JS.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzpyam2i1ty7o4c5vv7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzpyam2i1ty7o4c5vv7c.png" alt="Set variable - FlowChart Appended"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compose - URL
&lt;/h3&gt;

&lt;p&gt;After the Condition Action we have a single compose action. &lt;br&gt;
This will concatenate a string starting with &lt;strong&gt;'&lt;a href="https://mermaid.ink/img/" rel="noopener noreferrer"&gt;https://mermaid.ink/img/&lt;/a&gt;'&lt;/strong&gt;, and here is where we convert the Flowchart variable to base 64 giving us the correct encoding to create a URL.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

concat('https://mermaid.ink/img/',base64(variables('FlowChart')))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5q0heechxjc4wznos7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5q0heechxjc4wznos7p.png" alt="Compose - URL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Respond to App or Flow
&lt;/h3&gt;

&lt;p&gt;All we need to do now, is respond to the App or Flow that triggered this Flow. We will use Text input of Flowchart using the Flowchart variable and a text input of Mermaid URL which includes the URL we have just generated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fgvyisoo7coouw643w4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fgvyisoo7coouw643w4.png" alt="Respond to App or Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inserting this Child Flow into another Flow to capture the steps
&lt;/h2&gt;

&lt;p&gt;Using the &lt;strong&gt;"[child] - Approver Card"&lt;/strong&gt; as an example from my previous posts. In our Parent Flow we can construct a JSON Object inside of a Compose action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compose - Outcome
&lt;/h3&gt;

&lt;p&gt;Capture the outcome of the Adaptive Card&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp64pumgbabhndg836dee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp64pumgbabhndg836dee.png" alt="Compose - Outcome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId']


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Compose - Action
&lt;/h3&gt;

&lt;p&gt;Convert this outcome using "If(equals" to what the follow on Action will be:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'approve'),'Complete Approval',
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'respond'), 'Send back to Requestor', 
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'reject'),'Inform Requestor',
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'acAssignCard'), 'Assign to other person'))))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8z61g6wg49d5pa07m51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8z61g6wg49d5pa07m51.png" alt="Compose - Action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose - varSteps
&lt;/h2&gt;

&lt;p&gt;The JSON Object will be written in a format that could capture various outcomes and statuses that happen within the flow in . For Example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
  "Trigger": "[@{variables('varObject')?['Request_Type']} Request]",
  "Condition": "{@{variables('varObject')?['Approver_Name']} - receives Approval Card}",
  "Outcome": "|@{outputs('Compose_-_Status')}|",
  "Action": "[@{outputs('Compose_-_Outcome')}]"
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1wrtozxww9asu3c27ub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1wrtozxww9asu3c27ub.png" alt="Compose - varSteps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we can trigger the Child Flow using 'Compose - varSteps' as a String.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

string(outputs('Compose_-_varSteps'))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and then FlowChart. If we're carrying over a previous one held inside our varObject or Null if we're creating a new one.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

if(empty(variables('varObject')['FlowChart']), 'null', variables('varObject')['FlowChart'])



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqy2upa1p8vqkiveh9r4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqy2upa1p8vqkiveh9r4.png" alt="Run a Child Flow - Generate Mermaid Flowchart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Capture the Response
&lt;/h3&gt;

&lt;p&gt;To then capture from our child flow and add it to our varObject.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbye58sdydt138krtdxm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbye58sdydt138krtdxm8.png" alt="Update Property"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9z09ol5tmvjrtbhabvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9z09ol5tmvjrtbhabvb.png" alt="Set FlowChart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading this post. Please leave feedback in the Comments if you found this useful, or if there are any improvements you could recommend. Please experiment with it too, I'd love to hear some of the outcomes.&lt;/p&gt;

&lt;p&gt;For such a simple Flow, I found it quite difficult to write and explain, so I hope it all makes sense!&lt;/p&gt;

</description>
      <category>powerautomate</category>
      <category>powerplatform</category>
      <category>javascript</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Conversational Approval process using Adaptive Cards and JSON objects</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Mon, 29 Jan 2024 20:49:16 +0000</pubDate>
      <link>https://dev.to/mcombp/conversational-approval-process-using-adaptive-cards-and-json-objects-4mih</link>
      <guid>https://dev.to/mcombp/conversational-approval-process-using-adaptive-cards-and-json-objects-4mih</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is Part 3 of a series of posts where I try to explain how to send Form answers from a Power App into Power Automate and then send Adaptive Cards in Microsoft Teams to allow the Requestor and Approver make incremental changes to the Form answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mcombp/passing-objects-from-power-apps-to-power-automate-31ji"&gt;Part 1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mcombp/passing-a-choices-list-from-power-apps-to-an-adaptive-card-2nga"&gt;Part 2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're going to start Part 3 by building a second Flow. In this Form Approval Process this Flow will actually be the one that sends an Adaptive Card to our Approver.&lt;/p&gt;

&lt;p&gt;Here is an overview of what we're going to do here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgle4iksyoo6f8ar1aazy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgle4iksyoo6f8ar1aazy.png" alt="Solution Process Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why don't I use the standard Approvals Actions?
&lt;/h2&gt;

&lt;p&gt;What I'm trying to achieve is a more conversational process for the Approval, where the Requestor and Approver can both edit the data inside Message Cards in Teams until the Approver decides what to do with it.. We'll do this by passing our Object again, plus a few other useful Expressions.&lt;/p&gt;

&lt;p&gt;We're going to trigger this from our "[proxy] - Object Flow" we made in an earlier Post: &lt;a href="https://dev.to/mcombp/passing-objects-from-power-apps-to-power-automate-31ji"&gt;Passing Objects from Power Apps to Power Automate&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Approver's Flow
&lt;/h2&gt;

&lt;p&gt;As this will be a Child Flow. We'll need a Manual trigger and I'll call this Flow &lt;strong&gt;"[child] - Approver Card"&lt;/strong&gt;. We just need one text Input. &lt;strong&gt;varObject&lt;/strong&gt; as this can contain everything we need.&lt;/p&gt;

&lt;p&gt;Then, we'll Initialize a Variable that will convert the Text Input to JSON. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

json(triggerBody()['text'])


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8doqcunjsnxb3vtbhjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8doqcunjsnxb3vtbhjq.png" alt="Power Automate - Manual Trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adaptive Card
&lt;/h2&gt;

&lt;p&gt;Let's add another action - &lt;strong&gt;Post adaptive card and wait for a response&lt;/strong&gt;. We'll need to construct an Adaptive card for this. Again I would recommend you build your full Adaptive Card by previewing it with the Help of &lt;a href="https://adaptivecards.io/designer" rel="noopener noreferrer"&gt;The Adaptive Card Designer&lt;/a&gt; or the &lt;a href="https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.vscode-adaptive-cards#:~:text=The%20Microsoft%20Adaptive%20Card%20Previewer,%2C%20and%20high%2Dcontrast%20themes." rel="noopener noreferrer"&gt;VSCode Microsoft Adaptive Card Previewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxyss5pinjgy81at54s3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxyss5pinjgy81at54s3.png" alt="Power Automate - Post adaptive card and wait for a response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text Inputs&lt;/strong&gt;&lt;br&gt;
I will start with the Text value we're getting for the "Name" field in the Form.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need a type, this will be &lt;strong&gt;Input.Text&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;We can specify the Label above the field as "Name"&lt;/li&gt;
&lt;li&gt;For the Value, we can reference the property inside of &lt;strong&gt;varObject&lt;/strong&gt; by inserting "@{variables('varObject')?['Name']}" &lt;/li&gt;
&lt;li&gt;Finally, each field in the Adaptive Card needs an id, which we can keep exactly the same as the Object Property name to make it easier to reference in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my scenario, we already have an input value, so 'Placeholder Text' isn't really needed.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    {
      "type": "Input.Text",
      "label": "Name",
      "value": "@{variables('varObject')?['Name']}",
      "id": "Name"
    },


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use the example above to build any other Text Inputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toggle&lt;/strong&gt;&lt;br&gt;
The 'Mortgaged' field is a Boolean field in our Power Apps Form, so we can replicate this with &lt;strong&gt;Input.Toggle&lt;/strong&gt;, similar to the Text Input we need to give an &lt;strong&gt;id&lt;/strong&gt;, &lt;strong&gt;label&lt;/strong&gt; and &lt;strong&gt;value&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    {
      "type": "Input.Toggle",
      "title": "Mortgaged",
      "id": "Mortgaged",
      "label": "Mortgaged",
      "value": "@{variables('varObject')?['Mortgaged']}"
    },


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Choices&lt;/strong&gt;&lt;br&gt;
I have previously described how we can have a Choices Dropdown in this Blog Post: &lt;br&gt;
&lt;a href="https://dev.to/mcombp/passing-a-choices-list-from-power-apps-to-an-adaptive-card-2nga"&gt;Passing a Choices List from Power Apps to an Adaptive Card&lt;/a&gt; - in this example, we can add a Label: &lt;strong&gt;"Country"&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    {
    "type": "Input.ChoiceSet",
    "label": "Country",
    "choices": @{variables('varObject')?['Countries']},
    "id": "Country",
    "value": "@{variables('varObject')?['Country']}"
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the &lt;strong&gt;Rating&lt;/strong&gt; Field, we know there's only five options, so we might as well enter these manually, giving us this: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
      "type": "Input.ChoiceSet",
      "choices": [
        {
          "title": "1",
          "value": "1"
        },
        {
          "title": "2",
          "value": "2"
        },
        {
          "title": "3",
          "value": "3"
        },
        {
          "title": "4",
          "value": "4"
        },
                {
          "title": "5",
          "value": "5"
        }
      ],
      "label": "Rating",
      "id": "Rating",
      "value": "@{variables('varObject')?['Rating']}"
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt;&lt;br&gt;
That's the fields done, now we need some actions to respond with. These can be placed at the foot of the Adaptive Card like this and will be where the Approver makes their decision.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  "actions": [
        {
            "type": "Action.Submit",
            "title": "Approve",
            "style": "positive",
            "id": "approve"
        },
        {
            "type": "Action.Submit",
            "title": "Respond",
            "id": "respond"
        },
        {
            "type": "Action.Submit",
            "title": "Reject",
            "style": "destructive",
            "id": "reject"
        },
        {
        "type": "Action.ShowCard",
        "title": "ReAssign",
        "card": {
            "type": "AdaptiveCard",
            "body": [
                {
                "type": "Input.Text",
                "label": "ReAssign to",
                "style": "Email",
                "id": "ReAssign_To",
                "isRequired": true,
                "errorMessage": "Please enter an email address"
                }
            ],
            "actions": [
                {
                "type": "Action.Submit",
                "title": "ReAssign"
                }
            ]
        },
        "id": "acAssignCard"
        }
    ]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Feel free to elaborate on the options you provide, such as comments, or even an option to reassign. We'll use these soon in a Switch Action to decide on the next step. IF you want to use this you'll see that there's an expandable section (&lt;a href="https://adaptivecards.io/explorer/Action.ShowCard.html)%5BAction.ShowCard%5D" rel="noopener noreferrer"&gt;https://adaptivecards.io/explorer/Action.ShowCard.html)[Action.ShowCard]&lt;/a&gt; when &lt;strong&gt;ReAssign&lt;/strong&gt; is selected, this is to ensure an email address is entered. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih8l00gjkemqfszfmwt8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih8l00gjkemqfszfmwt8.png" alt="Adaptive Card - Approver Actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/madmat01/devtoBlog/blob/main/Approver%20Card.json" rel="noopener noreferrer"&gt;The JSON for the Approval Adaptive Card is here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Union
&lt;/h2&gt;

&lt;p&gt;Now we have an Adaptive card that we can send to an Approver and Wait for a response. &lt;/p&gt;

&lt;p&gt;We can now capture any changes that the Approver may have made back into our original &lt;strong&gt;varObject&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Start by adding a Compose Action, and in the Compose Action we can enter this Expression&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

union(variables('varObject'),outputs('Post_adaptive_card_and_wait_for_a_response').body.data)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpehha063xcao82dsbfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpehha063xcao82dsbfj.png" alt="Compose - Union Body Data with varObject"&gt;&lt;/a&gt;&lt;br&gt;
What the Union expression does is combine the properties from the varObject with the data obtained from the response of the action &lt;strong&gt;Post_adaptive_card_and_wait_for_a_response&lt;/strong&gt;. This is done to update or synchronize the values in the &lt;strong&gt;varObject&lt;/strong&gt; with the field values changed in the Adaptive Card.&lt;/p&gt;

&lt;p&gt;Then we just need to feed the output of our Compose expression into Set the &lt;strong&gt;varObject&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46viu3n3x7xrlcijz0q7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46viu3n3x7xrlcijz0q7.png" alt="Set variable - varObject"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch
&lt;/h2&gt;

&lt;p&gt;We'll add a switch Condition to allow us to decide the path of the Flow from this point using some Switch Cases, which match the the ID of our Actions.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId']


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We have the options: &lt;strong&gt;approve&lt;/strong&gt;, &lt;strong&gt;respond&lt;/strong&gt;, &lt;strong&gt;reject&lt;/strong&gt;, &lt;strong&gt;ReAssign_To&lt;/strong&gt; and we can decide what we we want to happen with each of these paths. I'll come to &lt;strong&gt;respond&lt;/strong&gt; in a moment, but I will first give some examples of how you can use the other Switch Cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frw1sewu61kir7vzwn796.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frw1sewu61kir7vzwn796.png" alt="Power Automate - Switch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approve&lt;/strong&gt;&lt;br&gt;
This would be where you would define what you want to happen if the Adaptive Card is approved; Send an Email, update a SharePoint List Item etc...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reject&lt;/strong&gt;&lt;br&gt;
Here you could send Post a Standard Adaptive card or send an email to the Requestor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ReAssign&lt;/strong&gt;&lt;br&gt;
A good example for using the &lt;strong&gt;setProperty()&lt;/strong&gt; expression would be if you had a defined Approver_Email in your varObject. You could use a Compose Action with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

setProperty(variables('varObject'), 'Approver_Email', outputs('Approver_adaptive_card')?['body/data/ReAssign_To'])


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, Set Variable - varObject again with the output of the Compose Action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fq2s12iroz5syxmnkmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fq2s12iroz5syxmnkmv.png" alt="Power Automate - ReAssign"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Respond&lt;/strong&gt;&lt;br&gt;
So for the Respond Case, we're going to create another Child Flow, it will be a copy of this Flow where the Requestor can make changes to re-submit to the Approver.&lt;/p&gt;

&lt;h3&gt;
  
  
  Respond to a PowerApp or flow
&lt;/h3&gt;

&lt;p&gt;To make sure this Child Card let's the Parent Flow it has run, place in a &lt;strong&gt;Respond to a PowerApp or flow&lt;/strong&gt; as the last Action.&lt;/p&gt;

&lt;h2&gt;
  
  
  [proxy] - Object Flow
&lt;/h2&gt;

&lt;p&gt;Before we move on to the Requestor Card, we need to trigger this Child Flow from the &lt;strong&gt;[proxy] - Object Flow&lt;/strong&gt; by making this the 3rd action in the Flow.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc55672arxmxdf2qm64g0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc55672arxmxdf2qm64g0.png" alt="Object Flow - Run a Child Flow - [child] - Approver Card.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requestor Card
&lt;/h2&gt;

&lt;p&gt;Now - back to the &lt;strong&gt;[child] - Approver Card]&lt;/strong&gt; Flow, so we can copy it....&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexjjtr61ge4day8b0v6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fexjjtr61ge4day8b0v6u.png" alt="Copy of - [child] - Approver Card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll call this: &lt;strong&gt;[child] - Requestor Card&lt;/strong&gt; and add it to the same solution as our App and other Flows.&lt;/p&gt;

&lt;p&gt;Let's first make a couple of small adjustments to the &lt;strong&gt;Post adaptive card and wait for a response&lt;/strong&gt; Message in this one by changing the first TextBlock with text Property from:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90eynqlss86t35550rb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90eynqlss86t35550rb7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    {
      "type": "TextBlock",
      "size": "Medium",
      "weight": "Bolder",
      "text": "Requestor Request"
    },


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we just need to swap out the &lt;strong&gt;"actions": []&lt;/strong&gt; section at the bottom of the Card. The Requestor won't have the same options as the Approver, so we can swap out the whole section for this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  "actions": [
        {   
        "type": "Action.Submit",
        "title": "Re-Submit 🔁",
        "id": "acSubmit",
        "style": "positive"
      },
      {
        "type": "Action.Submit",
        "title": "Cancel Request ❌",
        "id": "acCancelRequest",
        "style": "destructive"
      }
    ]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And of course, change the Receipient, so that the message goes to our Receipient. The Union action is still here to update any changes which then update our varObject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch
&lt;/h2&gt;

&lt;p&gt;As we've changed the Actions, we'll now need to change the cases to &lt;strong&gt;"acSubmit"&lt;/strong&gt; and &lt;strong&gt;"acCancelRequest"&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugdhlkvq4g2c9wv0rkyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugdhlkvq4g2c9wv0rkyq.png" alt="Power Automate - Requestor Switch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;acCancelRequest&lt;/strong&gt;&lt;br&gt;
Again, I'll leave it up to you what to do with this path; you may want to imform the Approver with another standard Adaptive Card message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;acSubmit&lt;/strong&gt;&lt;br&gt;
So here's the key feature that allows us to perform this conversation loop between the Approver and Requestor. - We're now going to re-trigger &lt;strong&gt;[child] - Approver Card&lt;/strong&gt; and with that, pass back the varObject as a String again.&lt;/p&gt;

&lt;p&gt;Create a &lt;strong&gt;'Run a Child Flow'&lt;/strong&gt; action and select our &lt;strong&gt;[child] - Approver Card&lt;/strong&gt; Flow we created earlier. For the varObject Text Input enter: &lt;strong&gt;&lt;em&gt;string(variables('varObject'))&lt;/em&gt;&lt;/strong&gt; - this converts the &lt;strong&gt;varObject&lt;/strong&gt; into a String again to pass to Approver Flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubt2qdgft8igtp7i0n86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubt2qdgft8igtp7i0n86.png" alt="Child - Approver Card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Respond to a PowerApp or flow
&lt;/h2&gt;

&lt;p&gt;Again, to make sure this Child Card let's the Parent Flow it has run, place in a &lt;strong&gt;Respond to a PowerApp or flow&lt;/strong&gt; as the last Action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/madmat01/devtoBlog/blob/main/Requestor%20Card.json" rel="noopener noreferrer"&gt;JSON for the Requestor Adaptive Card is here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to [child] - Approver Card to finish off
&lt;/h2&gt;

&lt;p&gt;All we need to do now is the mirror opposite of this in the &lt;strong&gt;[child] - Approver Card&lt;/strong&gt; Flow. Go back to that flow and underneath the &lt;strong&gt;Case - respond&lt;/strong&gt; create a &lt;strong&gt;'Run a Child Flow'&lt;/strong&gt; action and select our &lt;strong&gt;[child] - Requestor Card&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7oj1sulh4pdr6khwse4i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7oj1sulh4pdr6khwse4i.png" alt="Run a Child Flow - [child] - Requestor Card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and fine tuning
&lt;/h2&gt;

&lt;p&gt;To prevent any confusing repeat runs of these Flows, I would recommend changing the Retry Policy&lt;br&gt;
on each Run a Child Flow Action to &lt;strong&gt;None&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqb4qi9z6w9r9sk4u46f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqb4qi9z6w9r9sk4u46f.png" alt="Retry Policy - None"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, here are some examples that we should see in Microsoft Teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Power App Form
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoh1avf7c2tx9bpq8klx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoh1avf7c2tx9bpq8klx.png" alt="Power App Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Approver's 1st Message
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnqbp9bapdg8e2fibysx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnqbp9bapdg8e2fibysx.png" alt="Approver's 1st Message "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Requestor's 1st Message
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyq28vlocm90db1qe112.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyq28vlocm90db1qe112.png" alt="Requestor's 1st Message "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Approver's 2nd Message
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7zu2rfav31v1f7u0dob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7zu2rfav31v1f7u0dob.png" alt="Approver's 2nd Message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Hopefully, that was a helpful guide. I have one final Post to add a little bit extra to this type of Approval process. Please leave any questions or comments below.&lt;/p&gt;

</description>
      <category>powerautomate</category>
      <category>powerplatform</category>
      <category>javascript</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>Passing a Choices List from Power Apps to an Adaptive Card</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Sat, 13 Jan 2024 18:53:31 +0000</pubDate>
      <link>https://dev.to/mcombp/passing-a-choices-list-from-power-apps-to-an-adaptive-card-2nga</link>
      <guid>https://dev.to/mcombp/passing-a-choices-list-from-power-apps-to-an-adaptive-card-2nga</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a follow-on from my previous post &lt;a href="https://dev.to/mcombp/passing-objects-from-power-apps-to-power-automate-31ji"&gt;Passing Objects from Power Apps to Power Automate&lt;/a&gt; I am now going to cover a method I have found to 'render' our JSON Object in an Adaptive Card that is part of an Approval Process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;After we have got our Object into Power Automate I mentioned that we can then reference a property by first referencing the name of the Object (varObject) and then the property name &lt;code&gt;variables('varObject')?['____']&lt;/code&gt;. These can be used inside of an Adaptive Card to show single text values, but what if we want to have a dynamically generated dropdown (choice) list inside of the Adaptive Card?&lt;/p&gt;

&lt;h2&gt;
  
  
  ChoiceSet
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3hq5qpbzttizr2325md.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3hq5qpbzttizr2325md.png" alt="Adaptive Card - ChoiceSet"&gt;&lt;/a&gt; &lt;a href="https://adaptivecards.io/explorer/Input.ChoiceSet.html" rel="noopener noreferrer"&gt;Adaptive Card - ChoiceSet&lt;/a&gt;&lt;br&gt;
The Adaptive Card Input.ChoiceSet needs a &lt;strong&gt;Title&lt;/strong&gt; and a &lt;strong&gt;Value&lt;/strong&gt; for each choice. &lt;strong&gt;Title&lt;/strong&gt; is the user friendly name and &lt;strong&gt;Value&lt;/strong&gt; is the value passed to your app our service.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
            "type": "Input.ChoiceSet",
            "choices": [
                {
                    "title": "Choice 1",
                    "value": "Choice 1"
                },
                {
                    "title": "Choice 2",
                    "value": "Choice 2"
                }
            ],
            "placeholder": "Placeholder text"
        }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, we need to prepare our data in that format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choice Collection
&lt;/h2&gt;

&lt;p&gt;The method I have been using to do this is done back in Power Apps...&lt;br&gt;
This is where I am already using a Collection inside my OnVisible property of the Form Screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6il1irs1uyw24eoucxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6il1irs1uyw24eoucxz.png" alt="Power Apps - colCountry Collection"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a very basic collection, but in practice your use case may involve generating this from a Datasource Such as Dataverse or SharePoint &lt;a href="https://www.matthewdevaney.com/powerapps-collections-cookbook/create-a-collection-from-datasource/" rel="noopener noreferrer"&gt;Create A Collection (from a datasource) - Matthew Devaney&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Collect(
    colCountry,
    "England",
    "Northern Ireland",
    "Scotland",
    "Wales"
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This Collection works well for the Country Combobox in our Form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3qszploqjct21gztex9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3qszploqjct21gztex9.jpg" alt="Power Apps - colCountry Combobox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, this Collection isn't quite right for a ChoiceSet yet as it only has a "Value" column. So, to sort this out I have written this, which can run before we create the varObject.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

ClearCollect(
    colCurrentCountries,//Array of Current countries for passing to Adaptive Card
    ForAll(
        ShowColumns(
            SortByColumns(//Sort Alphabetically
                colCountry,
                "Value",
                SortOrder.Ascending
            ),
            "Value"//We only want this column
        ),
        {
            value: Value,
            title: Value
        }
    )
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I will break down what is happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First we create a collection called "colCurrentCountries" &lt;code&gt;ClearCollect(colCurrentCountries,&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then we're going to loop through each country in the existing "colCountry" Collection using ForAll() to do the looping. But, we only need the "Value" column (this isn't strictly necessary in my example). Neither is 'SortByColumns()', but it keeps things tidy 🙂.&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ForAll(&lt;br&gt;
       ShowColumns(&lt;br&gt;
           SortByColumns(//Sort Alphabetically&lt;br&gt;
               colCountry,&lt;br&gt;
               "Value",&lt;br&gt;
               SortOrder.Ascending&lt;br&gt;
           ),&lt;br&gt;
           "Value"//We only want this column&lt;br&gt;
       ),{% raw %}&lt;code&gt;&lt;br&gt;
&lt;/code&gt;``&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The last part to construct is the schema of each record inside this Collection. To do this I'm just repeating the "Value" column for both the Title and Value as I've no need to for these to be different in the Adaptive Card.
&lt;code&gt;`
    {
        value: Value,
        title: Value
    }
`&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding the Collection to the Object
&lt;/h2&gt;

&lt;p&gt;To add the Collection or Array to the Object we just need to add the line 'Countries: colCurrentCountries' like so:&lt;br&gt;
&lt;code&gt;`&lt;br&gt;
Set(&lt;br&gt;
    varObject,&lt;br&gt;
    {&lt;br&gt;
        Name: txt_Name.Text,&lt;br&gt;
        Address_1: txt_Address_1.Text,&lt;br&gt;
        Address_2: txt_Address_2.Text,&lt;br&gt;
        City: txt_City.Text,&lt;br&gt;
        Country: cbx_Country.Selected.Value,&lt;br&gt;
        Mortgaged: tgl_Mortgaged.Value,&lt;br&gt;
        Rating: rtg_Rating.Value,&lt;br&gt;
        **Countries: colCurrentCountries**&lt;br&gt;
    }&lt;br&gt;
`&lt;/code&gt;&lt;br&gt;
So now with everything put together in Power Apps we have this in the OnSelect Property that runs the Flow:&lt;br&gt;
&lt;code&gt;`&lt;br&gt;
ClearCollect(&lt;br&gt;
    colCurrentCountries,//Array of Current countries for passing to Adaptive Card&lt;br&gt;
    ForAll(&lt;br&gt;
        ShowColumns(&lt;br&gt;
            SortByColumns(//Sort Alphabetically&lt;br&gt;
                colCountry,&lt;br&gt;
                "Value",&lt;br&gt;
                SortOrder.Ascending&lt;br&gt;
            ),&lt;br&gt;
            "Value"//We only want this column&lt;br&gt;
        ),&lt;br&gt;
        {&lt;br&gt;
            value: Value,&lt;br&gt;
            title: Value&lt;br&gt;
        }&lt;br&gt;
    )&lt;br&gt;
);&lt;br&gt;
Set(&lt;br&gt;
    varObject,&lt;br&gt;
    {&lt;br&gt;
        Name: txt_Name.Text,&lt;br&gt;
        Address_1: txt_Address_1.Text,&lt;br&gt;
        Address_2: txt_Address_2.Text,&lt;br&gt;
        City: txt_City.Text,&lt;br&gt;
        Country: cbx_Country.Selected.Value,&lt;br&gt;
        Mortgaged: tgl_Mortgaged.Value,&lt;br&gt;
        Rating: rtg_Rating.Value,&lt;br&gt;
        Countries: colCurrentCountries&lt;br&gt;
    }&lt;br&gt;
);&lt;br&gt;
'[proxy]-ObjectFlow'.Run(&lt;br&gt;
    JSON(&lt;br&gt;
        varObject&lt;br&gt;
    )&lt;br&gt;
)&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Power Automate
&lt;/h2&gt;

&lt;p&gt;To see this in an Adaptive Card in Power Automate for now we need to add an Action: "Post card in a chat or channel" Posting As: &lt;strong&gt;Flow Bot&lt;/strong&gt; and Post In: &lt;strong&gt;Chat with Flow Bot&lt;/strong&gt;. For testing add your email address for our "Approver" and then we can construct the Message.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fownwyd8ofxksxgbph7wd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fownwyd8ofxksxgbph7wd.png" alt="Power Automate - Adaptive Card Message"&gt;&lt;/a&gt; &lt;em&gt;Yes, this screenshot is from the V2 designer&lt;/em&gt; 😫&lt;/p&gt;

&lt;h2&gt;
  
  
  Adaptive Card
&lt;/h2&gt;

&lt;p&gt;I would recommend you build your full Adaptive Card by previewing it with the Help of (&lt;a href="https://adaptivecards.io/designer/)%5BThe" rel="noopener noreferrer"&gt;https://adaptivecards.io/designer/)[The&lt;/a&gt; Adaptive Card Designer] or the (&lt;a href="https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.vscode-adaptive-cards#:%7E:text=The%20Microsoft%20Adaptive%20Card%20Previewer,%2C%20and%20high%2Dcontrast%20themes.)%5BVSCode" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.vscode-adaptive-cards#:~:text=The%20Microsoft%20Adaptive%20Card%20Previewer,%2C%20and%20high%2Dcontrast%20themes.)[VSCode&lt;/a&gt; Microsoft Adaptive Card Previewer]&lt;/p&gt;

&lt;p&gt;To help us out a little, here is a basic Adaptive Card with a Single ChoiceSet Input, with it waiting for you to type in some choices:&lt;br&gt;
&lt;code&gt;`&lt;br&gt;
{&lt;br&gt;
    "type": "AdaptiveCard",&lt;br&gt;
    "body": [&lt;br&gt;
        {&lt;br&gt;
            "type": "Input.ChoiceSet",&lt;br&gt;
            "choices": [&lt;br&gt;
                {&lt;br&gt;
                    "title": "Choice 1",&lt;br&gt;
                    "value": "Choice 1"&lt;br&gt;
                },&lt;br&gt;
                {&lt;br&gt;
                    "title": "Choice 2",&lt;br&gt;
                    "value": "Choice 2"&lt;br&gt;
                }&lt;br&gt;
            ],&lt;br&gt;
            "placeholder": "Placeholder text"&lt;br&gt;
        }&lt;br&gt;
    ],&lt;br&gt;
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",&lt;br&gt;
    "version": "1.5"&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;br&gt;
Now to make this work with the Data passed over from our varObject Variable in Power Automate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First Remove the "choices" array - the &lt;strong&gt;[&lt;/strong&gt; to the &lt;strong&gt;]&lt;/strong&gt; and replace this with a reference to our Countries list in varObject, like this:
&lt;code&gt;`
"choices": @{variables('varObject')?['Countries']},
`&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then we can add the Country value that was selected in the Power App Form, by adding this (The ? operator is only needed for handling null values.):
&lt;code&gt;`
"value": "@{variables('varObject')?['Country']}"
`&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then  we need to add an ID for the field.
&lt;code&gt;`
"id": "Country",
`&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And for reasons I don't understand we need to change the version from 1.5 to 1.4 to work with Teams version 1.7?!?&lt;code&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;
"version": "1.4"
}
&lt;code&gt;&lt;/code&gt;`&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Power Automate
&lt;/h2&gt;

&lt;p&gt;We then have an Adaptive Card that we can paste in to the Message in Power Automate.&lt;br&gt;
&lt;code&gt;`&lt;br&gt;
{&lt;br&gt;
    "type": "AdaptiveCard",&lt;br&gt;
    "body": [&lt;br&gt;
        {&lt;br&gt;
            "type": "Input.ChoiceSet",&lt;br&gt;
            "choices": @{variables('varObject')?['Countries']},&lt;br&gt;
            "placeholder": "Placeholder text",&lt;br&gt;
            "id": "Country",&lt;br&gt;
            "value": "@{variables('varObject')?['Country']}"&lt;br&gt;
        }&lt;br&gt;
    ],&lt;br&gt;
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",&lt;br&gt;
    "version": "1.4"&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Save the Flow and Refresh the Flow in the Power App too. If you get &lt;br&gt;
"&lt;em&gt;[proxy]-ObjectFlow.Run failed: { "code":&lt;br&gt;
"InvokerConnectionOverrideFailed", "message": "Failed to parse invoker connections from trigger 'manual' outputs. Exception: Could not find any valid connection for connection reference name&lt;br&gt;
'shared_teams' in APIM header." }&lt;/em&gt;" Just Save and Re-Open the Power App 🙂.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teams
&lt;/h2&gt;

&lt;p&gt;Submit the Form in the Power Apps and Teams should now send you a message with the Selected Country and when you select the Choices, give you a selection of other Countries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk88ompzkx2pi4qpjp0lv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk88ompzkx2pi4qpjp0lv.png" alt="Teams - Selected Country"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiyw79dssrxm3tihzt3f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiyw79dssrxm3tihzt3f.png" alt="Teams - Countries"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Hopefully this is a helpful tip and I will elaborate on how we can get more in to Adaptive Cards soon.&lt;br&gt;
Please leave any questions or comments below.&lt;/p&gt;

</description>
      <category>powerapps</category>
      <category>powerautomate</category>
      <category>powerplatform</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Passing Objects from Power Apps to Power Automate</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Wed, 10 Jan 2024 16:14:33 +0000</pubDate>
      <link>https://dev.to/mcombp/passing-objects-from-power-apps-to-power-automate-31ji</link>
      <guid>https://dev.to/mcombp/passing-objects-from-power-apps-to-power-automate-31ji</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;This is the first part, in what I hope will be a series of posts describing how to trigger an advanced approval process. Starting with Power Apps capturing data in a Form and then passing that as a JSON Object or "Record". &lt;/p&gt;

&lt;p&gt;We are passing a JavaScript object as a JSON string from Power Automate to Power Automate. Power Automate has many actions and expressions available to allow us to reference and manipulate the Object once it has been received. More on that later.&lt;/p&gt;

&lt;p&gt;I have chosen to implement a JSON Object as it is a common method to store and exchange data. The benefits are that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Power Apps code is simpler with all the fields being referenced the once when creating the Object.&lt;/li&gt;
&lt;li&gt;Power Automate is kept more efficient than initiating a Variable for each field value passed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Power App
&lt;/h2&gt;

&lt;p&gt;Firstly we need a Power App where we will capture the values in a Form filled in by a user. The Form I have created is just a bunch of Text Input controls. We are going to capture these as a Record into an Object Variable using the Button Control OnSelect property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hwq7jrwecinx0f96epb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hwq7jrwecinx0f96epb.jpg" alt="Power Apps - Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To begin this Function we need to start with this in in the Button's OnSelect Property:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Set(
    varObject,
    {


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we can start building the object inside the record (curly brackets). each Field needs a unique name with no spaces. Make sure to make sure to reference the '.Text' Property of the Text Input control, it is quite strict on that.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Set(
    varObject,
    {
        Name: txt_Name.Text,
        Address_1: txt_Address_1.Text,
        Address_2: txt_Address_2.Text,
        City: txt_City.Text,
        Country: cbx_Country.Selected.Value,
        Mortgaged: tgl_Mortgaged.Value,
        Rating: rtg_Rating.Value
    }
);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Power Automate
&lt;/h2&gt;

&lt;p&gt;We can now create a Flow ready to receive this Object. We can do this from inside Power Apps or Externally, just make sure that they are both inside the same Solution.&lt;/p&gt;

&lt;p&gt;We'll call this &lt;strong&gt;[proxy] - Object Flow&lt;/strong&gt; and add a Text Input to the Trigger. As there's no JSON Input option available, we will have to convert the record variable into a JSON text string inside the Power App to allow us to capture it here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xeth9hnfq5400znvxqp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xeth9hnfq5400znvxqp.jpg" alt="Power Automate - Create Flow with Trigger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only action we need so far is &lt;strong&gt;Initialize variable&lt;/strong&gt; this is where varObject can be converted back into a JSON Object like this by placing it inside 'json()'.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

json(triggerBody()?['text'])


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva85k2lfr2pxol1niyzj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fva85k2lfr2pxol1niyzj.jpg" alt="Power Automate - Initialize Object Variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Power App
&lt;/h2&gt;

&lt;p&gt;Ok, so back to the Power App. To make sure to Refresh the Flow from the Power Automate section of Power Apps, this will just ensure that the Flow is up to date and ready to receive the Text Input.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdv40zgno4ivpu2pmn0p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdv40zgno4ivpu2pmn0p.jpg" alt="Power Apps - Refresh Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in the Button's OnSelect Property and underneath where we set the Variable varObject, we can now send it to the Flow, but inside a 'JSON()'.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

'[proxy]-ObjectFlow'.Run(
    JSON(
        varObject
    )
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9hjt07vlu3mv0j60xpt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9hjt07vlu3mv0j60xpt.jpg" alt="Power Apps - Button OnSelect"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now enter some test values in the Form and click on the Button, which should now trigger the Flow and pass the Object over from the App for us.&lt;/p&gt;

&lt;p&gt;Now we have an Object ready inside our Flow that we can use to extract data from. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz89kpyini064n98f4n71.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz89kpyini064n98f4n71.jpg" alt="Power Automate - Object Received"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each time we need a value we can reference it by using ```&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;variables('varObject')?['____']&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The ? operator is only needed for handling null values.

For example:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;variables('varObject')?['Name']&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
![Power Automate - Name Property](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dgplwz8ylh9vye0zggux.jpg)

## Summary
Now we have a way to pass a record between Power Apps and Power Automate there are a few useful tricks that I will add soon. We will cover how we can bring over the alternative values for next....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>powerautomate</category>
      <category>powerapps</category>
      <category>powerplatform</category>
    </item>
    <item>
      <title>Send Multiple files as email attachments from a SharePoint Document Library Folder using Power Automate 📂➡️📨</title>
      <dc:creator>Matthew Collinge</dc:creator>
      <pubDate>Sat, 12 Aug 2023 14:02:47 +0000</pubDate>
      <link>https://dev.to/mcombp/send-multiple-files-as-email-attachments-from-a-sharepoint-document-library-folder-using-power-automate-2k1j</link>
      <guid>https://dev.to/mcombp/send-multiple-files-as-email-attachments-from-a-sharepoint-document-library-folder-using-power-automate-2k1j</guid>
      <description>&lt;p&gt;Have you ever needed to email multiple specific files as attachments from a SharePoint Document Library? There is documentation on how to send ALL the files in a given folder (&lt;a href="https://learn.microsoft.com/en-us/microsoft-365/community/power-automate-send-sharepoint-files-as-attachments" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/microsoft-365/community/power-automate-send-sharepoint-files-as-attachments&lt;/a&gt;), but here we'd like to have some control over which files we want to email as attachments without having download the files beforehand.&lt;/p&gt;

&lt;h2&gt;
  
  
  User interaction
&lt;/h2&gt;

&lt;p&gt;The previously mentioned documentation provides us with a good Power Automate solution to build from, but how do we include the interaction that allows the user to see the available files to pick from and to select just the files they'd like to email? Well, that's where Adaptive Cards for Teams comes in. Adaptive Cards let us build an interactive and dynamic structure for a user interface for the actions of a Power Automate Flow.&lt;/p&gt;

&lt;p&gt;NOTE: I'd love to develop a way to open up a new email tab with Outlook for the Web to do this, or even a PWA that's opens up in the SharePoint Sidebar... maybe one day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power Automate process in summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Flow is triggered by the "For a selected file" trigger.&lt;/li&gt;
&lt;li&gt;Flow will locate the parent folder&lt;/li&gt;
&lt;li&gt;Flow will collect all the files&lt;/li&gt;
&lt;li&gt;Flow will add the files to an attachment names string&lt;/li&gt;
&lt;li&gt;An Adaptive Card will be sent to the user that Triggered the Flow&lt;/li&gt;
&lt;li&gt;The Adaptive Card will replicate the layout of an email, with Sent To, Subject, Body and a pick list using the attachments array&lt;/li&gt;
&lt;li&gt;The user fills out the fields, selects the files and clicks send.&lt;/li&gt;
&lt;li&gt;For each of the selected files the Flow will retrieve the file content and Append to an Array.&lt;/li&gt;
&lt;li&gt;Email is sent with selected Attachments&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How the flow works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The user can trigger the Flow by selecting a single file in a SharePoint Document Library and clicking on &lt;strong&gt;Automate &amp;gt; Send Attachments&lt;/strong&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcy72mg6vfw33qizsotx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkcy72mg6vfw33qizsotx.png" alt="Flow Trigger - For Selected File"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;They are then then asked to &lt;strong&gt;\"Run Flow\"&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An Adaptive Card is sent to them via the Microsoft Teams Flow Bot where the user can input the &lt;strong&gt;Receipient Email&lt;/strong&gt; (with Regex Validation), &lt;strong&gt;Subject&lt;/strong&gt;, &lt;strong&gt;Body&lt;/strong&gt; and a Dynamic Picklist of all the other files in the chosen folder. 😃 &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once the user clicks &lt;strong&gt;\"Send\"&lt;/strong&gt; the flow will verify that they actually selected any attachments and if so, send the email with the selected attachments via email from the user's mailbox.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How it is Built
&lt;/h2&gt;

&lt;h2&gt;
  
  
  For a selected item
&lt;/h2&gt;

&lt;p&gt;To start building the Flow for a given SharePoint Document Library, you need to start from that Library and then select &lt;em&gt;Integrate &amp;gt; Power Automate &amp;gt; Create a Flow&lt;/em&gt; then we can select a Template "&lt;strong&gt;Request approval in Microsoft Teams when a SharePoint item is created&lt;/strong&gt;". We're going to remove all the actions from this apart from the trigger.&lt;br&gt;
Doing this creates the relationship between the Document Library and the 'For a Selected Item' Flow Trigger. For some reason, if you don't create the Flow this way, the Document Library refuses to show the Flow under "Automate", which is very frustrating. 😩&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables to Initialise
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;varUserEmail&lt;/li&gt;
&lt;li&gt;varFolderPath &lt;/li&gt;
&lt;li&gt;varAttachmentNames &lt;/li&gt;
&lt;li&gt;varAttachmentNamesArray
&lt;/li&gt;
&lt;li&gt;varSelectedArray&lt;/li&gt;
&lt;li&gt;BodyAsString&lt;/li&gt;
&lt;li&gt;varCardObject&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;varSiteLibrary&lt;/strong&gt; is an Object where I have set up the SharePoint Site and Document Library&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Site": "&amp;lt;SharePoint Site&amp;gt;",
    "Library": "&amp;lt;Document Library&amp;gt;"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can retreive these in our various actions just by referencing &lt;strong&gt;variables('varSiteLibrary')['Site']&lt;/strong&gt; or &lt;strong&gt;variables('varSiteLibrary')['Site']&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get parent - the folder's path
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqceqqsa221ghm3qkxlae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqceqqsa221ghm3qkxlae.png" alt="Set varFolderPath"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the expression used to return the Folder Path without the '/' at the end, so we can use it in the next action.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

substring(
    outputs('Get_file_properties_-_Folder')?['body/{Path}']
    ,0
    ,sub(
        length(outputs('Get_file_properties_-_Folder')?['body/{Path}']))
    ,1
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Get an Array of all the available files in this folder
&lt;/h2&gt;

&lt;p&gt;Using \"Get files (properties only)\" and the Filter Query: &lt;strong&gt;FSObjType eq 0&lt;/strong&gt; to return just the files and not folders. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf2qf7p516y16itk64ds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faf2qf7p516y16itk64ds.png" alt="Get files (properties only)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we use \"Apply to Each\" for each value (file) found.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20z37o5ufrn5nz0w3nno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20z37o5ufrn5nz0w3nno.png" alt="Apply to Each"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each value is run through the "Get File Content" action to then append it's File name with extension and a unique Identifier.&lt;br&gt;
Adaptive cards need to have a unique ID for each property, so I have used the Identifier property.&lt;/p&gt;
&lt;h3&gt;
  
  
  Append to String varAttachmentNames schema:
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Input.Toggle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{items('Apply_to_each')?['{FilenameWithExtension}']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Item_@{items('Apply_to_each')?['{Identifier}']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  In Parallel Append to String &lt;strong&gt;varAttachmentNamesArray&lt;/strong&gt; schema (we will need this later to get the File Names):
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{items('Apply_to_each')?['{FilenameWithExtension}']}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@{items('Apply_to_each')?['{Identifier}']}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Post adaptive card and wait for a response
&lt;/h2&gt;

&lt;p&gt;The full build of the Adaptive card is &lt;a href="https://github.com/madmat01/devtoBlog/blob/main/Adaptive_Card.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the key bit here is to include the varAttachments as a dynamic repeatable item within the Card. To do this we can place varAttachments inside of &lt;strong&gt;"items": [ ]&lt;/strong&gt; , so that each filename is shown with it's own Input Toggle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq78563ri2trvnj5uwneg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq78563ri2trvnj5uwneg.png" alt="Array inside Adaptive Card"&gt;&lt;/a&gt;&lt;br&gt;
We replace this with our varAttachmentNames String&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;variables('varAttachmentNames')&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Parse the Adaptive Card response
&lt;/h2&gt;

&lt;p&gt;Now the fun part, doing something with all the data we've gathered.&lt;br&gt;
To do this we need to Parse the JSON from the \"Data\" Property of the Adaptive Card Body.&lt;/p&gt;

&lt;p&gt;The Card body used here is in the form of this expression. We will set this as &lt;strong&gt;varCardObject&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

body('Post_adaptive_card_and_wait_for_a_response')['data']


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will set this as &lt;strong&gt;\"varCardObject\"&lt;/strong&gt; and use is in the Content for the Parse JSON action.&lt;br&gt;
 &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1p8i7zuzjgryv2eb6f5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1p8i7zuzjgryv2eb6f5g.png" alt="Parse JSON"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON Schema used&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"RecEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"EmailBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Item_"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Parsing this Data Object will allow us to extract the &lt;strong&gt;RecEmail&lt;/strong&gt;, &lt;strong&gt;Subject&lt;/strong&gt; and &lt;strong&gt;EmailBody&lt;/strong&gt; for use in the Send Email Action later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting the selected files
&lt;/h3&gt;

&lt;p&gt;A challenging part of this process is extracting the list of just the Selected Files from the Adaptive Card. We only want to get the ones that have the Toggle set to \"True\".&lt;/p&gt;

&lt;p&gt;To do this we'll need to convert the &lt;strong&gt;varCardObject&lt;/strong&gt; in to a String so that we can split it using an expression.&lt;/p&gt;

&lt;p&gt;Set the &lt;strong&gt;\"BodyAsString\"&lt;/strong&gt; variable the the &lt;strong&gt;\"Parse JSON Body\"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then bring that into a Compose action to use a &lt;strong&gt;Split&lt;/strong&gt; expression.&lt;br&gt;
 &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhygdk94td7vjtooq4m73.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhygdk94td7vjtooq4m73.png" alt="Split Card Object String"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Compose - Split
&lt;/h3&gt;

&lt;p&gt;We will split the string at each &lt;strong&gt;_item&lt;/strong&gt; &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Split(variables('BodyAsString'),'Item_')


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Apply to Each Split Item
&lt;/h3&gt;

&lt;p&gt;In order to return just the selected (True) files we need to do an Apply to Each on the Output of &lt;strong&gt;Compose - Split&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7wid06gpugrvvef30ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7wid06gpugrvvef30ah.png" alt="Apply to Each Split Item"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Condition
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CurrentItem&lt;/strong&gt; Contains:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

":"True"   


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Append to array variable - varSelectedArray
&lt;/h3&gt;

&lt;p&gt;With each Current Item that matches the Condition we will Append it to our &lt;strong&gt;varSelectedArray&lt;/strong&gt; and apply an expression to just give us just the File Identifier that we need. To do that we will use the expression: &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

replace(
    replace(
        items('Apply_to_each_-_Split_Item'),
        '":"True","',''
        )
        ,'":"True"}',''
    )


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Failover - No Attachments have been selected
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forluwcayizht7inb6xju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forluwcayizht7inb6xju.png" alt="Has Attachments Condition"&gt;&lt;/a&gt;&lt;br&gt;
In case the user forgets to actually select any attachments we can add a failover that checks the number of attachments &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

length(variables('varSelectedArray'))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;is or greater than 1. If the Condition is not matched, then we will send the User a Teams message \"No attachments were selected, please try sending this again\". Then the Flow will be Terminated&lt;/p&gt;

&lt;h2&gt;
  
  
  Get the File Content and prepare attachments for each of the files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Apply to Each - Selected File
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wadfz84aa1uzx8h5gn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2wadfz84aa1uzx8h5gn4.png" alt="Apply to Each Selected File"&gt;&lt;/a&gt;&lt;br&gt;
To give us an Array of Attachments that Outlook can send in an email we need to find two things for each selected file: The File Name with Extension and the ContentBytes.&lt;/p&gt;

&lt;p&gt;To acheive this we can run two actions in Parallel for each item from the &lt;strong&gt;varSelectedArray&lt;/strong&gt; which has a list of file identifiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter array - for Identifier
&lt;/h3&gt;

&lt;p&gt;From: &lt;strong&gt;varAttachmentNamesArray&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;item().Identifier&lt;/strong&gt; is equal to &lt;strong&gt;Current Item&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Advanced mode:&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@equals(item().Identifier, items('Apply_to_each_-_Selected_File'))


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Using the expression below this can return us the matching FileName for the File Identifier&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;first(body('Filter_array_-_for_Identifier'))?['FileName']&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Get file content - Each File&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Search the Site Address for the Current Item (file identifier) to return the File Content.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;@{body('Get_file_content_-_Each_File')}&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Append to array variable - AttachmentsArray&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;So, to join these two together we Append each of these to the &lt;strong&gt;AttachmentsArray&lt;/strong&gt; variable like this:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;br&gt;
 json&lt;br&gt;
{&lt;br&gt;
  "Name": @{first(body('Filter_array_-&lt;em&gt;for_Identifier'))?['FileName']},&lt;br&gt;
  "ContentBytes": @{body('Get_file_content&lt;/em&gt;-_Each_File')}&lt;br&gt;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Send an email&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;We can now use the data we've gathered above to compose and send an email.&lt;/p&gt;

&lt;p&gt;An important point to mention is that to add the AttchmentsArray we need to select the &lt;strong&gt;\"Switch to input entire array\"&lt;/strong&gt; toggle so that we can insert the combined Name and Content that we put together in &lt;strong&gt;AttachmentsArray&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs4k78g3zc3ndk2bvtgp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs4k78g3zc3ndk2bvtgp.png" alt="Send an Email"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>powerautomate</category>
      <category>powerplatform</category>
      <category>sharepoint</category>
      <category>adaptivecards</category>
    </item>
  </channel>
</rss>
