<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>Modern Work App Consult Blog articles</title>
    <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bg-p/ModernWorkAppConsult</link>
    <description>Modern Work App Consult Blog articles</description>
    <pubDate>Wed, 06 May 2026 21:34:38 GMT</pubDate>
    <dc:creator>ModernWorkAppConsult</dc:creator>
    <dc:date>2026-05-06T21:34:38Z</dc:date>
    <item>
      <title>How to Export an Agent Developed in Copilot Studio</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/how-to-export-an-agent-developed-in-copilot-studio/ba-p/4391562</link>
      <description>&lt;P&gt;If you've developed an agent in Copilot Studio and need to export it, follow these steps:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;STRONG&gt;Open Copilot Studio:&lt;/STRONG&gt;&amp;nbsp;Navigate to&amp;nbsp;&lt;A href="https://copilotstudio.microsoft.com" target="_blank"&gt;https://copilotstudio.microsoft.com&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Access Solutions:&lt;/STRONG&gt;&amp;nbsp;On the left-hand side menu, click on the three dots (...) and then select&amp;nbsp;&lt;STRONG&gt;Solutions&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;/OL&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;3. Create a New Solution:&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;In the top menu bar, click on&amp;nbsp;&lt;STRONG&gt;+ New solution&lt;/STRONG&gt;.&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;Fill in the details for the new solution, including the display name, name, selecting or creating a new publisher, defining the version, and then click&amp;nbsp;&lt;STRONG&gt;Create&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;4. Navigate to the Solution List:&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Return to the list of solutions.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;&lt;/LI&gt;
&lt;LI&gt;Find and click on the name of the solution you want to export, which is usually located within&amp;nbsp;&lt;STRONG&gt;default solutions&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;5. Select the Agent:&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Click on&amp;nbsp;&lt;STRONG&gt;Agents&lt;/STRONG&gt;&amp;nbsp;and select the agent you wish to export.&lt;/LI&gt;
&lt;LI&gt;In the top menu or by clicking on the three dots (...), select&amp;nbsp;&lt;STRONG&gt;Advanced&lt;/STRONG&gt;&amp;nbsp;and then&amp;nbsp;&lt;STRONG&gt;+ Add to solution&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;6. Add Agent to Solution:&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;hoose the solution you created earlier and click &lt;STRONG&gt;Save&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI&gt;Return to the list of solutions.&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;7. Export the solution&lt;/STRONG&gt;:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Click on the solution that you have exported the agent to and click on &lt;STRONG&gt;Export Solution&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;Click on&amp;nbsp;&lt;STRONG&gt;Next&lt;/STRONG&gt;:&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;You have the option to enable the solution checker during the export process. Once you've made your selection, click on&amp;nbsp;&lt;STRONG&gt;Export&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;Once the export process is complete, the download button will become available. Click on the&amp;nbsp;&lt;STRONG&gt;Download&lt;/STRONG&gt;&amp;nbsp;button to save the .zip file with your agent.&lt;/LI&gt;
&lt;/UL&gt;
&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;By following these steps, you can successfully export your agent from Copilot Studio.&lt;/P&gt;
&lt;H2&gt;How to Import an Agent Developed in Copilot Studio&lt;/H2&gt;
&lt;P&gt;If you need to import an agent into Copilot Studio, follow these steps:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;STRONG&gt;Open Copilot Studio:&lt;/STRONG&gt;&amp;nbsp;Navigate to&amp;nbsp;&lt;A href="https://copilotstudio.microsoft.com" target="_blank"&gt;https://copilotstudio.microsoft.com&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Access Solutions:&lt;/STRONG&gt;&amp;nbsp;On the left-hand side menu, click on the three dots (...) and then select&amp;nbsp;&lt;STRONG&gt;Solutions&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;3. Import the Solution:&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;In the top menu bar, click on&amp;nbsp;&lt;STRONG&gt;Import solution&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;Click on the&amp;nbsp;&lt;STRONG&gt;Browse&lt;/STRONG&gt;&amp;nbsp;button to open the file selection dialog. Navigate to the location where your solution file with the agent is stored, select the file, and then click Open. After selecting the file, click on&amp;nbsp;&lt;STRONG&gt;Next&lt;/STRONG&gt;&amp;nbsp;to proceed with the import process.&lt;BR /&gt;&lt;BR /&gt;&lt;IMG src="https://github.com/luishdemetrio/exportagentcopilotstudio/raw/main/images/15.png" /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI&gt;Carefully review all the information to ensure it is correct. Once you have double-checked the details, click on&amp;nbsp;&lt;STRONG&gt;Next&lt;/STRONG&gt;&amp;nbsp;to proceed.&lt;BR /&gt;&lt;BR /&gt;&lt;IMG src="https://github.com/luishdemetrio/exportagentcopilotstudio/raw/main/images/15.png" /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI&gt;Click on&amp;nbsp;&lt;STRONG&gt;Import&lt;/STRONG&gt;&amp;nbsp;to finish.&lt;BR /&gt;&lt;BR /&gt;&lt;IMG src="https://github.com/luishdemetrio/exportagentcopilotstudio/raw/main/images/16.png" /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;4. Verify the Import:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Make sure that the solution was imported successfully.&lt;BR /&gt;&lt;BR /&gt;&lt;IMG src="https://github.com/luishdemetrio/exportagentcopilotstudio/raw/main/images/17.png" /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI&gt;After the import is complete, go back to the Copilot Studio home page and check for your agent.&lt;BR /&gt;&lt;BR /&gt;&lt;IMG src="https://github.com/luishdemetrio/exportagentcopilotstudio/raw/main/images/18.png" /&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;In this post, you learned how to export and import an agent in Copilot Studio. If you have any questions or run into issues, feel free to reach out for support. Happy exporting and importing!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 10 Mar 2025 17:20:10 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/how-to-export-an-agent-developed-in-copilot-studio/ba-p/4391562</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2025-03-10T17:20:10Z</dc:date>
    </item>
    <item>
      <title>ServiceNow Declarative Agent for M365 Copilot (non-official)</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/servicenow-declarative-agent-for-m365-copilot-non-official/ba-p/4282082</link>
      <description>&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;This project showcases the use of a Declarative Agent for Microsoft 365 Copilot, designed to create personalized experiences through specific instructions, actions, and knowledge. Specializing in ServiceNow, this declarative agent leverages the following capabilities and actions:&lt;/P&gt;
&lt;P class=""&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL id="pragma-line-6"&gt;
&lt;LI id="pragma-line-6"&gt;
&lt;P&gt;&lt;STRONG&gt;SharePoint Integration&lt;/STRONG&gt;: Enables users to retrieve information from files, which can be useful for providing context to ServiceNow tickets and for creating new tickets.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-8"&gt;
&lt;P&gt;&lt;STRONG&gt;Graph Connectors&lt;/STRONG&gt;: Utilizes the Knowledge Base and Services Catalog connectors from ServiceNow to enhance functionality.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-10"&gt;
&lt;P&gt;&lt;STRONG&gt;Plugins&lt;/STRONG&gt;: Incorporates a ServiceNow Scripted REST API that allows users to list their incidents and create new ones.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;Note&lt;/STRONG&gt;: This sample code is intended for illustrative purposes only and should not be deployed in a production environment without thorough review. It demonstrates how to build a simple Declarative Copilot using Visual Studio Code and the Teams Toolkit. Please note, I do not have the rights or permission from&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;ServiceNow&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Adventure Time&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(Ice King logo).&lt;/P&gt;
&lt;H2 id="prompt-samples"&gt;Prompt Samples&lt;/H2&gt;
&lt;P&gt;1. ServiceNow Knowledge Graph Connector:&lt;/P&gt;
&lt;OL id="pragma-line-16"&gt;
&lt;LI id="pragma-line-16" style="list-style-type: none;"&gt;
&lt;OL id="pragma-line-16"&gt;&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-18"&gt;
&lt;P&gt;List the articles regarding Outlook 2010. Place the results in a table with the article title in one column and a brief summary in the other&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-22" start="2"&gt;
&lt;LI id="pragma-line-22"&gt;ServiceNow Service Catalog Graph Connector&lt;/LI&gt;
&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-24"&gt;
&lt;P&gt;How do I request a new laptop?&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-28" start="3"&gt;
&lt;LI id="pragma-line-28"&gt;SharePoint Capability&lt;/LI&gt;
&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-30"&gt;
&lt;P&gt;List the items from the snow spreadsheet and format as a table. Also, please add an integer column as the first column, listing the item numbers.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-34" start="4"&gt;
&lt;LI id="pragma-line-34"&gt;Plugin: List my incidents&lt;/LI&gt;
&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-36"&gt;
&lt;P&gt;List my incidents&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-40" start="5"&gt;
&lt;LI id="pragma-line-40"&gt;Plugin: Create a new incidents&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;The following prompt uses the list of incidents returned from the previously executed snow spreadsheet file. I’m asking Copilot to create a new incident based on the sixth item in that list.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-44"&gt;
&lt;P&gt;Create a new incident for the item 6&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="build-a-basic-declarative-agent-with-api-plugin"&gt;Build a basic declarative agent with API plugin&lt;/H2&gt;
&lt;P&gt;Declarative agents are customized versions of Microsoft 365 Copilot that help you to create personalized experiences by declaring specific instructions, actions, and knowledge.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;With the declarative agent, you can build a custom version of Copilot that can be used for specific scenarios, such as for specialized knowledge, implementing specific processes, or simply to save time by reusing a set of AI prompts. For example, a grocery shopping Copilot declarative agent can be used to create a grocery list based on a meal plan that you send to Copilot.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;You can extend declarative agents using plugins to retrieve data and execute tasks on external systems. A declarative agent can utilize multiple plugins at the same time.&lt;/P&gt;
&lt;DIV id="tinyMceEditorluisdem_0" class="mceNonEditable lia-copypaste-placeholder"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;H2 id="get-started"&gt;Get started&lt;BR /&gt;&lt;BR /&gt;&lt;/H2&gt;
&lt;BLOCKQUOTE id="pragma-line-61"&gt;
&lt;P&gt;&lt;STRONG&gt;Prerequisites&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;To run this app template in your local dev machine, you will need:&lt;/P&gt;
&lt;UL id="pragma-line-65"&gt;
&lt;LI id="pragma-line-65"&gt;&lt;A href="https://nodejs.org/" target="_blank" rel="noopener"&gt;Node.js&lt;/A&gt;, supported versions: 16, 18&lt;/LI&gt;
&lt;LI id="pragma-line-66"&gt;A&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts" target="_blank" rel="noopener"&gt;Microsoft 365 account for development&lt;/A&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-67"&gt;&lt;A href="https://aka.ms/teams-toolkit" target="_blank" rel="noopener"&gt;Teams Toolkit Visual Studio Code Extension&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;version 5.0.0 and higher or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://aka.ms/teamsfx-toolkit-cli" target="_blank" rel="noopener"&gt;Teams Toolkit CLI&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-68"&gt;&lt;A href="https://learn.microsoft.com/microsoft-365-copilot/extensibility/prerequisites#prerequisites" target="_blank" rel="noopener"&gt;Microsoft 365 Copilot license&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-69"&gt;&lt;A href="https://developer.servicenow.com/dev.do" target="_blank" rel="noopener"&gt;ServiceNow Developer Instance&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/BLOCKQUOTE&gt;
&lt;OL id="pragma-line-71"&gt;
&lt;LI id="pragma-line-71"&gt;First, select the Teams Toolkit icon on the left in the VS Code toolbar.&lt;/LI&gt;
&lt;LI id="pragma-line-72"&gt;In the Account section, sign in with your&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts" target="_blank" rel="noopener"&gt;Microsoft 365 account&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if you haven't already.&lt;/LI&gt;
&lt;LI id="pragma-line-73"&gt;Create Teams app by clicking&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Provision&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;in "Lifecycle" section.&lt;/LI&gt;
&lt;LI id="pragma-line-74"&gt;Select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Preview in Copilot (Edge)&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Preview in Copilot (Chrome)&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the launch configuration dropdown.&lt;/LI&gt;
&lt;LI id="pragma-line-75"&gt;Select your declarative agent from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Copilot&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;app.&lt;/LI&gt;
&lt;LI id="pragma-line-76"&gt;Send a prompt.&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 id="whats-included-in-the-project"&gt;&lt;A id="pragma-line-80" target="_blank"&gt;&lt;/A&gt;What's included in the project&lt;BR /&gt;&lt;BR /&gt;&lt;/H2&gt;
&lt;TABLE id="pragma-line-82"&gt;
&lt;THEAD&gt;
&lt;TR id="pragma-line-82"&gt;
&lt;TH id="pragma-line-82"&gt;Folder&lt;/TH&gt;
&lt;TH id="pragma-line-82"&gt;Contents&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR id="pragma-line-84"&gt;
&lt;TD&gt;&lt;CODE&gt;.vscode&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;VSCode files for debugging&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-85"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;Templates for the Teams application manifest, the plugin manifest and the API specification&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-86"&gt;
&lt;TD&gt;&lt;CODE&gt;env&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;Environment files&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;BR /&gt;The following files can be customized and demonstrate an example implementation to get you started.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;TABLE id="pragma-line-90"&gt;
&lt;THEAD&gt;
&lt;TR id="pragma-line-90"&gt;
&lt;TH id="pragma-line-90"&gt;File&lt;/TH&gt;
&lt;TH id="pragma-line-90"&gt;Contents&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR id="pragma-line-92"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage/declarativeCopilot.json&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;Define the behaviour and configurations of the declarative agent.&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-93"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage/manifest.json&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;Teams application manifest that defines metadata for your declarative agent.&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-94"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage/instructions.txt&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;Define how the agent should communicate. An agent might be concise, detailed, interactive, or suggestive. Also include any restrictions that should be applied.&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-95"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage/ai-plugin.json&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;It contains everything Copilot needs to know about the API that isn’t in the Swagger file. It breaks the API down into “functions” that share a common URL path and result set.&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-96"&gt;
&lt;TD&gt;&lt;CODE&gt;appPackage/apiSpecificationFile/openapi.json&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;It is the Swagger file for the API.&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&lt;BR /&gt;The following are Teams Toolkit specific project files. You can&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview" target="_blank" rel="noopener"&gt;visit a complete guide on Github&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to understand how Teams Toolkit works.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;TABLE id="pragma-line-100"&gt;
&lt;THEAD&gt;
&lt;TR id="pragma-line-100"&gt;
&lt;TH id="pragma-line-100"&gt;File&lt;/TH&gt;
&lt;TH id="pragma-line-100"&gt;Contents&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR id="pragma-line-102"&gt;
&lt;TD&gt;&lt;CODE&gt;teamsapp.yml&lt;/CODE&gt;&lt;/TD&gt;
&lt;TD&gt;This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions.&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;H3 id="addition-information-and-references"&gt;&lt;BR /&gt;&lt;A id="pragma-line-104" target="_blank"&gt;&lt;/A&gt;Addition information and references&lt;/H3&gt;
&lt;UL id="pragma-line-106"&gt;
&lt;LI id="pragma-line-106"&gt;&lt;A href="https://aka.ms/teams-toolkit-declarative-agent" target="_blank" rel="noopener"&gt;Declarative agents for Microsoft 365&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-107"&gt;&lt;A href="https://aka.ms/teamsfx-copilot-plugin" target="_blank" rel="noopener"&gt;Extend Microsoft 365 Copilot&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-108"&gt;&lt;A href="https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-message-extension-bot" target="_blank" rel="noopener"&gt;Message extensions for Microsoft 365 Copilot&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-109"&gt;&lt;A href="https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-graph-connector" target="_blank" rel="noopener"&gt;Microsoft Graph Connectors for Microsoft 365 Copilot&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-110"&gt;&lt;A href="https://learn.microsoft.com/microsoft-365-copilot/extensibility/samples" target="_blank" rel="noopener"&gt;Microsoft 365 Copilot extensibility samples&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2 id="servicenow-plugin"&gt;&lt;BR /&gt;&lt;A id="pragma-line-113" target="_blank"&gt;&lt;/A&gt;ServiceNow Plugin&lt;/H2&gt;
&lt;P&gt;This project consumes two ServiceNow Scripted REST APIs to integrate with ServiceNow's platform, enabling seamless data exchange and automation of specific workflows.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;H3 id="servicenow-scripted-rest-apis"&gt;&lt;A id="pragma-line-117" target="_blank"&gt;&lt;/A&gt;&lt;BR /&gt;ServiceNow Scripted REST APIs&lt;/H3&gt;
&lt;P&gt;Scripted REST APIs in ServiceNow allow developers to create custom web service APIs that can interact with ServiceNow data and services. These APIs can be configured to support various HTTP methods (GET, POST, PUT, DELETE) and can be used to perform operations like querying data, creating records, updating records, and deleting records.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;H3 id="creating-scripted-rest-apis"&gt;&lt;A id="pragma-line-121" target="_blank"&gt;&lt;/A&gt;Creating Scripted REST APIs&lt;/H3&gt;
&lt;P&gt;This section includes the code for the Scripted REST API designed to list the incidents associated with a user.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-126"&gt;
&lt;LI id="pragma-line-126"&gt;&lt;STRONG&gt;Navigate to System Web Services&lt;/STRONG&gt;: Go to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;System Web Services &amp;gt; Scripted Web Services &amp;gt; Scripted REST APIs&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-127"&gt;&lt;STRONG&gt;Create a New API&lt;/STRONG&gt;: Click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;New&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and provide a name and namespace for your API.&lt;/LI&gt;
&lt;LI id="pragma-line-128"&gt;&lt;STRONG&gt;Define Resources&lt;/STRONG&gt;: Create resources by specifying the HTTP method, relative path, and any path parameters.&lt;/LI&gt;
&lt;LI id="pragma-line-129"&gt;&lt;STRONG&gt;Write Scripts&lt;/STRONG&gt;: In the scripting window, write the necessary scripts to handle the request and response objects.&lt;/LI&gt;
&lt;LI id="pragma-line-130"&gt;&lt;STRONG&gt;Test the API&lt;/STRONG&gt;: Use the REST API Explorer to test your API and ensure it works as expected.&lt;/LI&gt;
&lt;/OL&gt;
&lt;HR /&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="list-my-incidents-rest-api"&gt;&lt;A id="pragma-line-134" target="_blank"&gt;&lt;/A&gt;List my Incidents REST API&lt;/H3&gt;
&lt;P&gt;The API is built on ServiceNow's platform, utilizing its robust scripting capabilities to fetch and return incident data.&lt;/P&gt;
&lt;P&gt;&lt;BR /&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This API supports the GET method and responds with a JSON object containing the relevant incident details.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE id="pragma-line-141" class="language-javascript hljs"&gt;(&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;process&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;&lt;SPAN class="hljs-regexp"&gt;/*RESTAPIRequest*/&lt;/SPAN&gt; request, &lt;SPAN class="hljs-regexp"&gt;/*RESTAPIResponse*/&lt;/SPAN&gt; response&lt;/SPAN&gt;) &lt;/SPAN&gt;{

 
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; username = request.pathParams.username;
    &lt;SPAN class="hljs-comment"&gt;//var username = request.queryParams.username;&lt;/SPAN&gt;
	
	&lt;SPAN class="hljs-comment"&gt;// Get the ServiceNow instance name&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; instanceName = gs.getProperty(&lt;SPAN class="hljs-string"&gt;'instance_name'&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;'default_instance'&lt;/SPAN&gt;);

	&lt;SPAN class="hljs-comment"&gt;// Replace '+' with ' ' (space)&lt;/SPAN&gt;
    username = username.replace(&lt;SPAN class="hljs-regexp"&gt;/\+/g&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;' '&lt;/SPAN&gt;);

    &lt;SPAN class="hljs-comment"&gt;// Query the Incident table for incidents associated with the email&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; gr = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; GlideRecord(&lt;SPAN class="hljs-string"&gt;'incident'&lt;/SPAN&gt;);
    gr.addQuery(&lt;SPAN class="hljs-string"&gt;'caller_id.name'&lt;/SPAN&gt;, username);
    gr.query();

    &lt;SPAN class="hljs-comment"&gt;// Prepare the result&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; result = [];
    &lt;SPAN class="hljs-keyword"&gt;while&lt;/SPAN&gt; (gr.next()) {
        result.push({
            &lt;SPAN class="hljs-attr"&gt;number&lt;/SPAN&gt;: gr.getValue(&lt;SPAN class="hljs-string"&gt;'number'&lt;/SPAN&gt;),
            &lt;SPAN class="hljs-attr"&gt;short_description&lt;/SPAN&gt;: gr.getValue(&lt;SPAN class="hljs-string"&gt;'short_description'&lt;/SPAN&gt;),
            &lt;SPAN class="hljs-attr"&gt;state&lt;/SPAN&gt;: gr.getValue(&lt;SPAN class="hljs-string"&gt;'state'&lt;/SPAN&gt;)
        });
    }

    &lt;SPAN class="hljs-comment"&gt;// Check if incidents were found&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (result.length === &lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;) {
        response.setStatus(&lt;SPAN class="hljs-number"&gt;404&lt;/SPAN&gt;);
        response.setBody({&lt;SPAN class="hljs-attr"&gt;error&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;'No incidents found for the user name'&lt;/SPAN&gt;});
    } &lt;SPAN class="hljs-keyword"&gt;else&lt;/SPAN&gt; {
        &lt;SPAN class="hljs-comment"&gt;// Set the result as the response body&lt;/SPAN&gt;
        response.setStatus(&lt;SPAN class="hljs-number"&gt;200&lt;/SPAN&gt;);
        response.setBody(result);
    }

})(request, response);
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;H3 id="create-a-new-incident"&gt;&lt;BR /&gt;&lt;A id="pragma-line-184" target="_blank"&gt;&lt;/A&gt;Create a new incident&lt;/H3&gt;
&lt;P&gt;This section includes the code for the Scripted REST API designed to create new incidents on ServiceNow.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This API leverages ServiceNow’s capabilities to accept incident details through a POST request and create a corresponding incident record in the ServiceNow system. The request body should contain the necessary incident data, and upon successful creation, the API responds with the details of the newly created incident.&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE id="pragma-line-192" class="language-javascript hljs"&gt;(&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;process&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;&lt;SPAN class="hljs-regexp"&gt;/*RESTAPIRequest*/&lt;/SPAN&gt; request, &lt;SPAN class="hljs-regexp"&gt;/*RESTAPIResponse*/&lt;/SPAN&gt; response&lt;/SPAN&gt;) &lt;/SPAN&gt;{

    &lt;SPAN class="hljs-comment"&gt;// Parse the request body to get incident details&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; requestBody = request.body.data;

    &lt;SPAN class="hljs-comment"&gt;// Check required fields in the request body&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (!requestBody || !requestBody.short_description || !requestBody.username) {
        response.setStatus(&lt;SPAN class="hljs-number"&gt;400&lt;/SPAN&gt;); &lt;SPAN class="hljs-comment"&gt;// Bad Request&lt;/SPAN&gt;
        response.setBody({&lt;SPAN class="hljs-attr"&gt;error&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Missing required fields: short_description or username"&lt;/SPAN&gt;});
        &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt;;
    }

    &lt;SPAN class="hljs-comment"&gt;// Lookup caller_id based on the username&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; userGr = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; GlideRecord(&lt;SPAN class="hljs-string"&gt;'sys_user'&lt;/SPAN&gt;);
    userGr.addQuery(&lt;SPAN class="hljs-string"&gt;'name'&lt;/SPAN&gt;, requestBody.username);
    userGr.query();

    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (!userGr.next()) {
        response.setStatus(&lt;SPAN class="hljs-number"&gt;404&lt;/SPAN&gt;); &lt;SPAN class="hljs-comment"&gt;// Not Found&lt;/SPAN&gt;
        response.setBody({&lt;SPAN class="hljs-attr"&gt;error&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"User not found with username: "&lt;/SPAN&gt; + requestBody.username});
        &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt;;
    }

    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; callerId = userGr.sys_id;

    &lt;SPAN class="hljs-comment"&gt;// Create a new incident record in ServiceNow&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; gr = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; GlideRecord(&lt;SPAN class="hljs-string"&gt;'incident'&lt;/SPAN&gt;);
    gr.initialize();
    gr.short_description = requestBody.short_description;
	gr.description = requestBody.description;
    gr.caller_id = callerId; &lt;SPAN class="hljs-comment"&gt;// Assign the found caller's sys_id&lt;/SPAN&gt;
    gr.category = requestBody.category || &lt;SPAN class="hljs-string"&gt;'inquiry'&lt;/SPAN&gt;; &lt;SPAN class="hljs-comment"&gt;// Optional: Default category&lt;/SPAN&gt;
    gr.impact = requestBody.impact || &lt;SPAN class="hljs-number"&gt;3&lt;/SPAN&gt;; &lt;SPAN class="hljs-comment"&gt;// Optional: Default to low impact&lt;/SPAN&gt;
    gr.urgency = requestBody.urgency || &lt;SPAN class="hljs-number"&gt;3&lt;/SPAN&gt;; &lt;SPAN class="hljs-comment"&gt;// Optional: Default to low urgency&lt;/SPAN&gt;

    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; incidentNumber;
    &lt;SPAN class="hljs-keyword"&gt;try&lt;/SPAN&gt; {
        &lt;SPAN class="hljs-comment"&gt;// Insert the incident record&lt;/SPAN&gt;
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; sysId = gr.insert();
        incidentNumber = gr.number;

        &lt;SPAN class="hljs-comment"&gt;// Set success response&lt;/SPAN&gt;
        response.setStatus(&lt;SPAN class="hljs-number"&gt;201&lt;/SPAN&gt;); &lt;SPAN class="hljs-comment"&gt;// Created&lt;/SPAN&gt;
        response.setBody({
            &lt;SPAN class="hljs-attr"&gt;result&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;'Incident created successfully'&lt;/SPAN&gt;,
            &lt;SPAN class="hljs-attr"&gt;sys_id&lt;/SPAN&gt;: sysId,
            &lt;SPAN class="hljs-attr"&gt;number&lt;/SPAN&gt;: incidentNumber
        });
    } &lt;SPAN class="hljs-keyword"&gt;catch&lt;/SPAN&gt; (e) {
        &lt;SPAN class="hljs-comment"&gt;// Handle error during record insertion&lt;/SPAN&gt;
        gs.error(&lt;SPAN class="hljs-string"&gt;"Error creating incident: "&lt;/SPAN&gt; + e.message);
        response.setStatus(&lt;SPAN class="hljs-number"&gt;500&lt;/SPAN&gt;); &lt;SPAN class="hljs-comment"&gt;// Internal Server Error&lt;/SPAN&gt;
        response.setBody({&lt;SPAN class="hljs-attr"&gt;error&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Error creating incident: "&lt;/SPAN&gt; + e.message});
    }

})(request, response);&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;There are two versions.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;The official one, shared at Microsoft repo, uses a REST API developed by Cristiano to connect to ServiceNow:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;A href="https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-SnowWizard" target="_blank" rel="noopener"&gt;copilot-pro-dev-samples/samples/da-SnowWizard at main · pnp/copilot-pro-dev-samples · GitHub&lt;/A&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;My version, uses the Scripted REST API from ServiceNow and uses two ServiceNow Graph Connectors:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;A href="https://github.com/luishdemetrio/SnowKing" target="_blank" rel="noopener"&gt;GitHub - luishdemetrio/SnowKing&lt;/A&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;STRONG&gt;THIS CODE IS PROVIDED&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;EM&gt;AS IS&lt;/EM&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.&lt;/STRONG&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 30 Oct 2024 13:28:53 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/servicenow-declarative-agent-for-m365-copilot-non-official/ba-p/4282082</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2024-10-30T13:28:53Z</dc:date>
    </item>
    <item>
      <title>Step by Step: Integrate Advanced RAG Service with Your Own Data into Copilot Studio</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/step-by-step-integrate-advanced-rag-service-with-your-own-data/ba-p/4215097</link>
      <description>&lt;P&gt;This post is going to explain how to use&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/exploring-the-advanced-rag-retrieval-augmented-generation/ba-p/4197836" target="_blank" rel="nofollow noopener"&gt;Advanced RAG Service&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;easily verify proper RAG tech performance for your own data, and integrate it as a service endpoint into Copilot Studio.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This time we use CSV as a sample. CSV is text structure data, when we use basic RAG to process a multiple pages CSV file as Vector Index and perform similarity search using Nature Language on it, the grounded data is always chunked and hardly make LLM to understand the whole data picture.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For example, if we have 10,000 rows in a CSV file, when we ask "how many rows does the data contain and what's the mean value of the visits column", usually general semantic search service cannot give exact right answers if it just handles the data as unstructured. We need to use different advanced RAG method to handle the CSV data here.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Thanks to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.llamaindex.ai/en/stable/examples/query_engine/pandas_query_engine/" target="_blank" rel="nofollow noopener"&gt;LLamaIndex Pandas Query Engine&lt;/A&gt;, which provides a good idea of understanding data frame data through natural language way. However to verify its performance among others and integrate to existing Enterprise environment, such as Copilot Studio or other user facing services, it definitely needs AI service developing experience and takes certain learning curve and time efforts from POC to Production.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/exploring-the-advanced-rag-retrieval-augmented-generation/ba-p/4197836" target="_blank" rel="nofollow noopener"&gt;Advanced RAG Service&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;supports 6 latest advanced indexing techs including CSV Query Eninge, with it developers can leverage it to shorten development POC stage, and achieve Production purpose. Here is detail step to step guideline:&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H2 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 class="heading-element" dir="auto" tabindex="-1"&gt;POC Stage&lt;/H2&gt;
&lt;A id="user-content-poc-stage" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#poc-stage" target="_blank" rel="noopener" aria-label="Permalink: POC Stage"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Prerequisite&lt;/H3&gt;
&lt;A id="user-content-prerequist" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#prerequist" target="_blank" rel="noopener" aria-label="Permalink: Prerequist"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H4 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H4&gt;
&lt;P class="heading-element"&gt;a. Deploy below models in [Azure OpenAI Service](&lt;A href="https://learn.microsoft.com/en-us/azure/ai-services/multi-service-resource?pivots=azportal" target="_blank" rel="noopener"&gt;https://learn.microsoft.com/en-us/azure/ai-services/multi-service-resource?pivots=azportal&lt;/A&gt;), get its Service URL and Key:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-deploy-below-models-in-azure-openai-service" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#deploy-below-models-in-azure-openai-service" target="_blank" rel="noopener" aria-label="Permalink: Deploy below models in Azure OpenAI Service:"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;gpt-4o mini or gpt-4o

text-embedding-3-small
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;P class="heading-element"&gt;b. Create [Azure Document Intelligent](&lt;A href="https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/create-document-intelligence-resource?view=doc-intel-4.0.0" target="_blank" rel="noopener"&gt;https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/create-document-intelligence-resource?view=doc-intel-4.0.0&lt;/A&gt;)&amp;nbsp; Service, get its Service URL and Key.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-create-azure-document-intelligent-service-get-its-service-url-and-key" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#create-azure-document-intelligent-service-get-its-service-url-and-key" target="_blank" rel="noopener" aria-label="Permalink: Create Azure Document Intelligent Service, get its Service URL and Key"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;P class="heading-element"&gt;c. One Docker Environment:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-one-docker-environment" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#one-docker-environment" target="_blank" rel="noopener" aria-label="Permalink: One Docker Environment:"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;Windows Docker, WSL2, Debian or Ubuntu
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Setup&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-setup" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#setup" target="_blank" rel="noopener" aria-label="Permalink: Setup"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;a. In Docker environment, run this command to clone the dockerfile and related config sample:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;git clone https://github.com/freistli/AdvancedRAG.git
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;b. In the AdvancedRAG folder, rename .env.sample to .env&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;cd AdvancedRAG
mv .env.sample .env
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;c. In the .env file, configure necessary environment variables. In this tutorial, let's configure:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;#Azure OpenAI Setting
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_Deployment=gpt-4o-mini
AZURE_OPENAI_EMBEDDING_Deployment=text-embedding-3-small
AZURE_OPENAI_ENDPOINT=https://[name].openai.azure.com/

# Azure Document Intellenigence
DOC_AI_BASE=https://[name].cognitiveservices.azure.com/
DOC_AI_KEY=
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;NOTE:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;Azure AI Search is not required here because the CSV Query Engine doesn't need it. If you want to try Azure AI Search Index, then configure it.&lt;BR /&gt;&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;d. Build your own docker image:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;docker build -t dockerimage:tag .
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;e. Run this docker:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;docker run -p 8000:8000 dockerimage:tag
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;f. Access&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="http://localhost:8000/" target="_blank" rel="nofollow noopener"&gt;http://localhost:8000/&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;Now we can see this service UI
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Evaluate Response Quality &amp;amp; Performance&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-evaluate-respone-quality--performance" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#evaluate-respone-quality--performance" target="_blank" rel="noopener" aria-label="Permalink: Evaluate Respone Quality &amp;amp; Performance"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;a. Click the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;CSV Query Engine&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;tab, upload a test CSV file, click Submit&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;b. Click the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Chat Mode&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;tab, now we can use Natural Language to test how good the CSV Query Engine at understanding CSV content:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Expose it as a REST API Endpoint&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-expose-it-as-a-rest-api-endpoint" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#expose-it-as-a-rest-api-endpoint" target="_blank" rel="noopener" aria-label="Permalink: Expose it as a REST API Endpoint"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;The Advanced RAG Service is built with Gradio and FAST API. It opens necessary&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/freistli/AdvancedRAG?tab=readme-ov-file#call-advragsvc-through-rest-api-call" target="_blank" rel="noopener"&gt;API Endpoints&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;by default. We can turn off any of them in the .env settings.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The Chat endpoint can be used for different index types query/search. Since we are using "CSV Query Engine", now it is:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;POST {{LocalURL}}/advchatbot/run/chat
content-type: application/json

{
"data": [
    "how many records does it have",   
    "", 
    "CSV Query Engine",   
    "/tmp/gradio/86262b8036b56db1a2ed40087bbc772f619d0df4/titanic_train.csv",    
    "You are a friendly AI Assistant" ,
    false   
]
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;The response is:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;{
"data": [
    "The dataset contains a total of 891 records. If you have any more questions about the data, feel free to ask!",
    null
],
"is_generating": true,
"duration": 3.148253917694092,
"average_duration": 3.148253917694092,
"render_config": null,
"changed_state_ids": []
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;Using this method, we can easily integrate the specific RAG capability to our own service, such as Copilot Studio. Before that, let's publish the service first.&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Publish to Azure&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-publish-to-azure" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#publish-to-azure" target="_blank" rel="noopener" aria-label="Permalink: Publish to Azure"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;We have different methods to release docker as an app service. Here are the generate steps when we use Azure Contain Registry and Azure Container App.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;a. Create Azure Container Registry resource [ACRNAME], upload your tested docker image to it. The command is:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;az login
az account set -s [your subscription]
az acr login -n [ACRNAME]
docker push [ACRNAME].azurecr.io/dockerimage:tag
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;b. Create an Azure Container App, deploy this docker image, and deploy it. Don't forget enable Session Affinity for the Container App.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To automate the Azure Container App deployment, I provided&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/freistli/AdvancedRAG/blob/main/deploy_acr_app.sh" target="_blank" rel="noopener"&gt;deploy_acr_app.sh&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;in the repo.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;#!/bin/bash
set -e

if [ $# -eq 0 ]
then
echo "No SUF_FIX supplied, it should be an integer or a short string"
docker image list
exit 1
fi

SUF_FIX=$1
RESOURCE_GROUP="rg-demo-${SUF_FIX}"
LOCATION="eastus"
ENVIRONMENT="env-demo-containerapps"
API_NAME="advrag-demo-${SUF_FIX}"
FRONTEND_NAME="advrag-ui-${SUF_FIX}"
TARGET_PORT=8000
ACR_NAME="advragdemo${SUF_FIX}"

az group create --name $RESOURCE_GROUP --location "$LOCATION"

az acr create --resource-group $RESOURCE_GROUP --name $ACR_NAME --sku Basic --admin-enabled true

az acr build --registry $ACR_NAME --image $API_NAME .

az containerapp env create --name $ENVIRONMENT --resource-group $RESOURCE_GROUP --location "$LOCATION"

az containerapp create --name $API_NAME --resource-group $RESOURCE_GROUP --environment $ENVIRONMENT --image $ACR_NAME.azurecr.io/$API_NAME --target-port $TARGET_PORT --ingress external --registry-server $ACR_NAME.azurecr.io --query properties.configuration.ingress.fqdn

az containerapp ingress sticky-sessions set -n $API_NAME -g $RESOURCE_GROUP --affinity sticky
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;To use it:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;PRE class="notranslate"&gt;&lt;CODE&gt;chmod a+x deploy_acr_azure.sh&lt;BR /&gt;az login&lt;BR /&gt;az account set -s [your subscription]&lt;BR /&gt;./deploy_acr_azure.sh [suffix number]
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;DIV class="zeroclipboard-container"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;Note: for more details about this sh, can refer to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/container-apps/tutorial-code-to-cloud?tabs=bash%2Ccsharp&amp;amp;pivots=acr-remote" target="_blank" rel="nofollow noopener"&gt;this guideline&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;After around 7~8 minutes, the Azure Container App will be ready. You can check the output and access it directly:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To protect your container app, can follow this guide to enable authentication on it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/container-apps/authentication-entra" target="_blank" rel="nofollow noopener"&gt;Enable authentication and authorization in Azure Container Apps with Microsoft Entra ID&lt;/A&gt;&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;Integrate into Copilot Studio&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-integrate-into-copilot-studio" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#integrate-into-copilot-studio" target="_blank" rel="noopener" aria-label="Permalink: Integrate into Copilot Studio"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;By default, we need to upload a CSV to the AdvRAG service before analysis. The service always saves the uploaded file to its local temp folder on server side. And then we can use temp file path to start the analysis query.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To skip this step, we can save common files in subfolder&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;rules&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;of the AdvancedRAG folder, and then build your docker image. The files will be copy to the docker itself. As a demo, I can put a CSV file in AdvancedRAG/rules/files, and then pubish the docker to Azure.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;a. Open&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://copilotstudio.microsoft.com/" target="_blank" rel="nofollow noopener"&gt;Copilot Studio&lt;/A&gt;, create a new Topic, use "CSV Query" to trigger it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;b. For demo purpose, I upload a test CSV file and got its path, then put it into a variable:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;c. Now let's add a Question step to ask what question the user want to ask:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;d. Click "+", "Add an Action", "Create a flow". We will use this new flow to call AdvancedRAG service endpoint.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;e. We need Query, File_Path, System_Message as input variables.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;e. In the flow Editor, let's add an HTTP step. In the step, post the request to the AdvancedRAG endpoint as below:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Save the flow as ADVRAGSVC_CSV, and publish it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;f. Back to Copilot Studio topic, we will add the action as below, and set input variables as need:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;g. Publish and open this Custom Copilot in Teams Channel based on this&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://microsoftlearning.github.io/mslearn-copilotstudio/Instructions/Labs/09-deploy-copilot-teams.html#:~:text=With%20your%20Copilot%20open%20in%20Microsoft%20Copilot%20Studio%2C,and%20shared%20users.%20Select%20your%20user.%20Select%20Share." target="_blank" rel="nofollow noopener"&gt;guide&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;h. Now we can test this topic lit this, as we see, even I used gpt-4o-mini here, the response accuracy is very good:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;From above, it shows how to quickly verify potential useful RAG techs (Pandas Query Engine) in the AdvancedRAG service studio, expose and publish it as REST API endpoint which can be used by other service, such as Copilot Studio.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV&gt;
&lt;DIV&gt;&lt;SPAN&gt;The overall process can be applied to Knowledge Graph, GraphRAG, Tree Mode Summary and other type indexes with this AdvnacedRAG service. The main difference is for CSV, we can use its file path as the index name directly, for other indexes, we need to build index firstly, and then use index folder path as Index name. In this way developers can efficiently move from proof of concept to production, leveraging advanced RAG capabilities in their own services. &lt;/SPAN&gt;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H2 class="heading-element" dir="auto" tabindex="-1"&gt;More Information&lt;/H2&gt;
&lt;A id="user-content-more-information" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#more-information" target="_blank" rel="noopener" aria-label="Permalink: More Information"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;How to improve&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-how-to-improve" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#how-to-improve" target="_blank" rel="noopener" aria-label="Permalink: How to improve"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;The AdvancedRAG service focuses on key logic and stability of different important index types, the efficiency to be landed into M365 AI use cases. For any feature improvement ideas, feel free to visit below repos to create issues, fork projects and create PRs.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Docker Deploy Repo:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/freistli/AdvancedRAG" target="_blank" rel="noopener"&gt;https://github.com/freistli/AdvancedRAG&lt;/A&gt;&lt;/P&gt;
&lt;DIV class="markdown-heading" dir="auto"&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 class="heading-element" dir="auto" tabindex="-1"&gt;General Introduction&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;A id="user-content-general-introduction" class="anchor" href="https://github.com/freistli/AdvancedRAG/blob/main/blogs/Integrate_CSVQE_to_CopilotStudio.md#general-introduction" target="_blank" rel="noopener" aria-label="Permalink: General Introduction"&gt;&lt;/A&gt;&lt;/DIV&gt;
&lt;P&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/exploring-the-advanced-rag-retrieval-augmented-generation/ba-p/4197836" target="_blank" rel="nofollow noopener"&gt;Exploring the Advanced RAG (Retrieval Augmented Generation) Service&lt;/A&gt;&lt;/P&gt;</description>
      <pubDate>Fri, 06 Sep 2024 03:56:31 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/step-by-step-integrate-advanced-rag-service-with-your-own-data/ba-p/4215097</guid>
      <dc:creator>freistli</dc:creator>
      <dc:date>2024-09-06T03:56:31Z</dc:date>
    </item>
    <item>
      <title>Exploring the Advanced RAG (Retrieval Augmented Generation) Service</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/exploring-the-advanced-rag-retrieval-augmented-generation/ba-p/4197836</link>
      <description>&lt;P&gt;In the ever-evolving landscape of AI, LLM + RAG (Retrieval Augmented Generation) is a typical use scenario. Retrieving accurate related chunked data from complicated docs and then improving LLM response quality becomes challenge. There is no a silver bullet RAG can address all requirements so far. Developers need to verify different advanced RAG techs to find out which is a proper one for their scenarios considering accuracy, response speed, costs, etc. In order to solve this, with &lt;EM&gt;&lt;STRONG&gt;Azure Intelligent Document&lt;/STRONG&gt;&lt;/EM&gt;, &lt;STRONG&gt;&lt;EM&gt;Azure OpenAI&lt;/EM&gt;&lt;/STRONG&gt;, &lt;STRONG&gt;&lt;EM&gt;LlamaIndex&lt;/EM&gt;&lt;/STRONG&gt;, &lt;STRONG&gt;&lt;EM&gt;LangChain&lt;/EM&gt;&lt;/STRONG&gt;, &lt;STRONG&gt;&lt;EM&gt;Gradio&lt;/EM&gt;&lt;/STRONG&gt;...,&amp;nbsp; I developed &lt;A href="https://github.com/freistli/AdvancedRAG" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;&lt;EM&gt;this AdvancedRAG service&lt;/EM&gt;&lt;/STRONG&gt;&lt;/A&gt;. This service is encapsulated in a Docker container, offers a streamlined way to experiment with different indexing techniques, evaluate their accuracy, and optimize performance for various RAG use cases. Whether you're building a quick MVP, a proof of concept, or simply exploring different indexing strategies, this service provides a versatile playground.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="introduction" class="code-line" dir="auto" data-line="4"&gt;Introduction&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="6"&gt;The Advanced RAG AI Service enables developers to quickly verify different RAG indexing techniques, assessing both accuracy and performance. From index generation to output verification through chat mode and proofreading mode, this service offers comprehensive support. It can run locally in a Docker container or be deployed to Azure Container Apps, providing flexibility for various deployment scenarios.&lt;/P&gt;
&lt;P class="code-line" data-line="6"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="6"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="6"&gt;Docker Deploy Repo:&amp;nbsp;&lt;A href="https://github.com/freistli/AdvancedRAG" target="_blank" rel="noopener"&gt;https://github.com/freistli/AdvancedRAG&lt;/A&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="6"&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="key-features" class="code-line" dir="auto" data-line="8"&gt;Key Features&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL class="code-line" dir="auto" data-line="10"&gt;
&lt;LI class="code-line" dir="auto" data-line="10"&gt;&lt;STRONG&gt;Knowledge Graph&amp;nbsp; - LlamaIndex&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="11"&gt;&lt;STRONG&gt;Recursive Retriever Query&amp;nbsp; &amp;nbsp;- LlamaIndex&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="12"&gt;&lt;STRONG&gt;Tree Mode Summarization&amp;nbsp; - LlamaIndex&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;CSV Query Engine - LlamaIndex&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="13"&gt;&lt;STRONG&gt;Semantic Hybrid Search + Sub Query Engine - Azure AI Search&lt;/STRONG&gt;&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="14"&gt;&lt;STRONG&gt;Microsoft GraphRAG (Local Search + Global Search)&amp;nbsp;&lt;/STRONG&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="quick-start-guide" class="code-line" dir="auto" data-line="16"&gt;Quick Start Guide&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="18"&gt;To get started with the Advanced RAG service, follow these steps:&lt;/P&gt;
&lt;P class="code-line" data-line="18"&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="cloning-the-repository" class="code-line" dir="auto" data-line="20"&gt;Cloning the Repository&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="code-line language-bash" dir="auto" data-line="22"&gt;git &lt;SPAN class="hljs-built_in"&gt;clone&lt;/SPAN&gt; https://github.com/freistli/AdvancedRAG.git
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;H3 id="setting-up-environment-variables" class="code-line" dir="auto" data-line="26"&gt;Setting Up Environment Variables&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="28"&gt;Rename&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;.env_4_SC.sample&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;.env_4_SC&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and input the necessary environment variables. Note that an Azure OpenAI resource and an Azure Document Intelligence resource are required. Azure AI Search is optional unless you plan to build or use an Azure AI Search index.&lt;/P&gt;
&lt;H3 class="code-line" dir="auto" data-line="30"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="building-and-running-the-docker-image" class="code-line" dir="auto" data-line="30"&gt;Building and Running the Docker Image&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="32"&gt;Build the Docker image:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="code-line language-bash" dir="auto" data-line="34"&gt;docker build -t docaidemo .
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P class="code-line" data-line="38"&gt;Run the image locally:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="code-line language-bash" dir="auto" data-line="40"&gt;docker run -p 8000:8000 docaidemo
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P class="code-line" data-line="44"&gt;Access the service at&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="http://localhost:8000" target="_blank" rel="noopener"&gt;http://localhost:8000&lt;/A&gt;&lt;/CODE&gt;.&lt;/P&gt;
&lt;H3 class="code-line" dir="auto" data-line="46"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="running-on-azure-container-app" class="code-line" dir="auto" data-line="46"&gt;Running on Azure Container App&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL class="code-line" dir="auto" data-line="48"&gt;
&lt;LI class="code-line" dir="auto" data-line="48"&gt;Publish your Docker image to Azure Container Registry or Docker Hub.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="49"&gt;Create an Azure Container App, select the published Docker image, and deploy the revision pod without any extra commands.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="50"&gt;Set environment variables in the Azure Container App if you didn't include&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;.env_4_SC&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;in the image.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 class="code-line" dir="auto" data-line="52"&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="building-an-index" class="code-line" dir="auto" data-line="52"&gt;Building an Index&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL class="code-line" dir="auto" data-line="54"&gt;
&lt;LI class="code-line" dir="auto" data-line="54"&gt;Click the "Index Build" tab.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="55"&gt;Upload a file to the file section. (PDF format is recommended for complex content.)&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="56"&gt;Click "Submit" and wait for the index building to complete. Status updates will appear in real-time on the right pane.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 4. Once you see the completion message, you can download the index for local use.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="code-line language-plaintext" dir="auto" data-line="59"&gt;2024-06-13T10:04:54.120027: Index is persisted in /tmp/index_cache/yourfilename
/tmp/index_cache/yourfilename can be used as your Index name.
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;H2 class="code-line" dir="auto" data-line="101"&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="optional-setup-your-own-index-in-the-docker-image" class="code-line" dir="auto" data-line="101"&gt;Setup Your Own Index in the Docker Image&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="103"&gt;"rules" Index Name is predefined for Knowledge Graph Index of Japanese proofread demo in this solution. Developers can use their own indexes in other folders for the docker:&lt;/P&gt;
&lt;P class="code-line" data-line="105"&gt;To make it work:&lt;/P&gt;
&lt;OL class="code-line" dir="auto" data-line="107"&gt;
&lt;LI class="code-line" dir="auto" data-line="107"&gt;Move to the folder which contains the AdvancedRAG dockerfile&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="108"&gt;Create a folder to keep the index, for example, index123&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="109"&gt;Extract the index zip file you get from the step 6 in the "Build Index" section, save index files you downloaded into ./index123&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="110"&gt;Build the docker image again.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="code-line" data-line="112"&gt;After this, you can use index123 as index name in the Chat mode.&lt;/P&gt;
&lt;H2 class="code-line" dir="auto" data-line="68"&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="using-the-advanced-rag-service" class="code-line" dir="auto" data-line="68"&gt;Using the Advanced RAG Service&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="calling-the-service-through-rest-api" class="code-line" dir="auto" data-line="70"&gt;Calling the Service through REST API&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL class="code-line" dir="auto" data-line="72"&gt;
&lt;LI class="code-line" dir="auto" data-line="72"&gt;&lt;STRONG&gt;Endpoint:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="https://{BASEURL}/proofreadaddin/run/predict" target="_blank" rel="noopener"&gt;https://{BASEURL}/proofreadaddin/run/predict&lt;/A&gt;&lt;/CODE&gt;
&lt;UL&gt;
&lt;LI class="code-line" dir="auto" data-line="72"&gt;
&lt;P&gt;&amp;nbsp;"rules" Index Name is predefined for Knowledge Graph Index of proofread addin. Please save Default knowledge graph index into&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;./rules/storage/rules_original&lt;/STRONG&gt;&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="73"&gt;&lt;STRONG&gt;Method:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;POST&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="74"&gt;&lt;STRONG&gt;Header:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Content-Type: application/json&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="75"&gt;&lt;STRONG&gt;Sample Data:&lt;/STRONG&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;PRE&gt;&lt;CODE class="code-line language-json code-active-line" dir="auto" data-line="77"&gt;{&lt;BR /&gt;"data": [ &lt;BR /&gt;&lt;BR /&gt;"今回は半導体製造装置セクターの最近の動きを分析します。",  &lt;BR /&gt;&lt;BR /&gt;false &amp;lt;--- Streaming&lt;BR /&gt;&lt;BR /&gt;]&lt;BR /&gt;}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;UL class="code-line" dir="auto" data-line="72"&gt;
&lt;LI class="code-line" dir="auto" data-line="72"&gt;&lt;STRONG&gt;Endpoint:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;https://{BASEURL}&lt;SPAN&gt;/advchatbot/run/chat&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="73"&gt;&lt;STRONG&gt;Method:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;POST&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="74"&gt;&lt;STRONG&gt;Header:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Content-Type: application/json&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="75"&gt;&lt;STRONG&gt;Sample Data:&lt;/STRONG&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;PRE&gt;&lt;CODE class="code-line" dir="auto" data-line="130"&gt;{
  "data": [
    "When did the Author convince his farther",  &amp;lt;----- Prompt
    "", &amp;lt;--- History Object, don't change it
    "Azure AI Search",  &amp;lt;------ Index Type
    "azuresearch_0",   &amp;lt;------- Index Name or Folder
    "You are a friendly AI Assistant",    &amp;lt;----- System Message&lt;BR /&gt;    false &amp;lt;----- Streaming
  ]
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;H3 id="consuming-the-service-in-office-add-in" class="code-line" dir="auto" data-line="87"&gt;Consuming the Service REST API in Copilot Studio Power Autoflow Action&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="consuming-the-service-in-office-add-in" class="code-line" dir="auto" data-line="87"&gt;Consuming the Service in Office Add-In&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="89"&gt;For the Proofread Addin use case, refer to:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A title="https://github.com/freistli/ProofreadAddin" href="https://github.com/freistli/ProofreadAddin" target="_blank" rel="noopener" data-href="https://github.com/freistli/ProofreadAddin"&gt;Proofread Addin&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&lt;VIDEO controls="controls" width="960" height="720"&gt;
&lt;SOURCE src="https://github.com/freistli/ProofreadAddin/assets/8623897/4300701b-bad7-403f-9b30-d81e00fca35b" type="video/mp4"&gt;&lt;/SOURCE&gt;&lt;/VIDEO&gt;&lt;/P&gt;
&lt;P class="code-line" data-line="89"&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 class="code-line" dir="auto" data-line="91"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="chat-mode" class="code-line" dir="auto" data-line="91"&gt;Chat Mode&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL class="code-line" dir="auto" data-line="93"&gt;
&lt;LI class="code-line" dir="auto" data-line="93"&gt;Click "Chat Mode."&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="94"&gt;Choose the index type.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="95"&gt;Enter the index path or Azure AI Search Index name in the "Index Name" text field.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="96"&gt;Interact with the document using various system messages if needed.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3 class="code-line" dir="auto" data-line="98"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="proofread-mode" class="code-line" dir="auto" data-line="98"&gt;Proofread Mode&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="100"&gt;Proofread mode is tailored for non-English languages and requires a Knowledge Graph Index. Steps to generate this index are the same as for other indices.&lt;/P&gt;
&lt;H3 class="code-line" dir="auto" data-line="102"&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="viewing-the-knowledge-graph-index" class="code-line" dir="auto" data-line="102"&gt;Viewing the Knowledge Graph Index&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL class="code-line" dir="auto" data-line="104"&gt;
&lt;LI class="code-line" dir="auto" data-line="104"&gt;Click the "View Knowledge Graph Index" tab.&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="105"&gt;Enter your Knowledge Graph Index name and click "Submit."&lt;/LI&gt;
&lt;LI class="code-line" dir="auto" data-line="106"&gt;After a short wait, click "Download Knowledge Graph View" to see the result.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 class="code-line" dir="auto" data-line="108"&gt;More Information&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/step-by-step-integrate-advanced-rag-service-with-your-own-data/ba-p/4215097" target="_blank" rel="noopener"&gt;Step by Step: Integrate Advanced CSV RAG Service with your own data into Copilot Studio (microsoft.com)&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="conclusion" class="code-line" dir="auto" data-line="108"&gt;Conclusion&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="code-line" data-line="110"&gt;The Advanced RAG Service is a convenient solution for developers looking to explore and optimize retrieval augmented generation techniques. By providing a flexible, Docker-based environment, it enables rapid experimentation and deployment, making it easier to find the best indexing strategies for specific use cases, and provide REST API Endpoints. Whether you're working locally or deploying to the cloud, this service streamlines the process of developing and testing advanced AI solutions.&lt;/P&gt;</description>
      <pubDate>Fri, 06 Sep 2024 03:55:55 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/exploring-the-advanced-rag-retrieval-augmented-generation/ba-p/4197836</guid>
      <dc:creator>freistli</dc:creator>
      <dc:date>2024-09-06T03:55:55Z</dc:date>
    </item>
    <item>
      <title>Create a Jenkins pipeline to deploy Desktop Apps as MSIX - Part 2: Packaging a Visual Studio solutio</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-a-jenkins-pipeline-to-deploy-desktop-apps-as-msix-part-2/ba-p/4157259</link>
      <description>&lt;BLOCKQUOTE id="pragma-line-7"&gt;
&lt;P&gt;Parts:&lt;/P&gt;
&lt;OL id="pragma-line-9"&gt;
&lt;LI id="pragma-line-10"&gt;&lt;STRONG&gt;&lt;A href="https://techcommunity.microsoft.com/t5/windows-dev-appconsult/create-a-jenkins-pipeline-to-deploy-desktop-apps-as-msix-part-1/ba-p/3160398" target="_blank" rel="noopener"&gt;Setup the Jenkins environment&lt;/A&gt;&lt;/STRONG&gt;: install Jenkins and the required tools.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-a-jenkins-pipeline-to-deploy-desktop-apps-as-msix-part-2/ba-p/4157259#M759" target="_self"&gt;Packaging a Visual Studio solution&lt;/A&gt;:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for applications that use Visual Studio IDE, like Windows Forms and WPF.&lt;/LI&gt;
&lt;LI id="pragma-line-12"&gt;&lt;STRONG&gt;&lt;A href="https://techcommunity.microsoft.com/t5/windows-dev-appconsult/create-a-jenkins-pipeline-to-deploy-msix-desktop-apps-part-3/ba-p/3160430" target="_blank" rel="noopener"&gt;Packaging a solution developed outside Visual Studio&lt;/A&gt;:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for applications developed outside VS, i.e., in others IDEs like Eclipse or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Visual Studio Code&lt;/STRONG&gt;, for&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Java GUI&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;application.&lt;/LI&gt;
&lt;LI id="pragma-line-13"&gt;&lt;STRONG&gt;Packaging using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/luishdemetrio/Vb6VirtualRegistry" target="_blank" rel="noopener"&gt;VB6RegistryTool&lt;/A&gt;:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;despite of the name, the tool can be used by any technology.&lt;/LI&gt;
&lt;/OL&gt;
&lt;/BLOCKQUOTE&gt;
&lt;H2 id="1-visual-studio-solution"&gt;&lt;A id="pragma-line-15" target="_blank"&gt;&lt;/A&gt;1. Visual Studio Solution&lt;/H2&gt;
&lt;P&gt;In this section it will be demonstrated how to create the Visual Studio solution with a Windows Forms application and the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.microsoft.com/windows/msix/desktop/desktop-to-uwp-packaging-dot-net" target="_blank" rel="noopener"&gt;Windows Application Packaging Project&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;project used to generate the MSIX file.&lt;/P&gt;
&lt;P&gt;In case you prefer, you can skip this step, since the solution is already available on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/luishdemetrio/jenkins_msix/tree/main/01_VisualStudio" target="_blank" rel="noopener"&gt;jenkins_msix&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;repo.&lt;/P&gt;
&lt;H3 id="create-the-windows-form-application"&gt;&lt;A id="pragma-line-21" target="_blank"&gt;&lt;/A&gt;Create the Windows Form application&lt;/H3&gt;
&lt;P&gt;On Visual Studio 2019, select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create a new project&lt;/STRONG&gt;, select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Windows Forms app&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;project template and click&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Next&lt;/STRONG&gt;:&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Provide the project name WinForms.App and click Next:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;I am choosing the .NET 6.0 framework, but feel free to use any other version. Click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is the WinForms project structure. I am using only a PictureBox and a button, but it could be an empty project, as the idea is just to show how to package a WinForm application:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Follows the code-behind:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="csharp"&gt;namespace WinForms.App
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;So far, the WinForm project is a .NET 6.0 application that depends of the .NET runtime available in the client machine. To remove this dependency, it is possible to publish the project as self-contained where the .NET runtime and runtime libraries are bundled together with the application and third-party assemblies.&lt;/P&gt;
&lt;P&gt;Add the following lines to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;SelfContained&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;RuntimeIdentifier&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to the WinForms.App project file:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="csharp"&gt;&amp;lt;PropertyGroup&amp;gt;
    &amp;lt;OutputType&amp;gt;WinExe&amp;lt;/OutputType&amp;gt;
    &amp;lt;TargetFramework&amp;gt;net6.0-windows&amp;lt;/TargetFramework&amp;gt;
    &amp;lt;Nullable&amp;gt;enable&amp;lt;/Nullable&amp;gt;
    &amp;lt;UseWindowsForms&amp;gt;true&amp;lt;/UseWindowsForms&amp;gt;
    &amp;lt;ImplicitUsings&amp;gt;enable&amp;lt;/ImplicitUsings&amp;gt;
    
    &amp;lt;SelfContained&amp;gt;true&amp;lt;/SelfContained&amp;gt;
    &amp;lt;RuntimeIdentifier&amp;gt;win-x64&amp;lt;/RuntimeIdentifier&amp;gt;
    
  &amp;lt;/PropertyGroup&amp;gt;&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-77"&gt;
&lt;P&gt;More details at:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained" target="_blank" rel="noopener"&gt;Trim self-contained deployments and executables&lt;/A&gt;&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Build the application to ensure that no errors appear.&lt;/P&gt;
&lt;H3 id="create-the-the-windows-application-packaging-project"&gt;&lt;A id="pragma-line-81" target="_blank"&gt;&lt;/A&gt;Create the the Windows Application Packaging project&lt;/H3&gt;
&lt;P&gt;The next step is adding the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.microsoft.com/windows/msix/desktop/desktop-to-uwp-packaging-dot-net" target="_blank" rel="noopener"&gt;Windows Application Packaging Project&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to the solution.&lt;/P&gt;
&lt;P&gt;Right-click on the solution, select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;New Project...&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Select the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Windows Application Packaging Project&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;and click&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Next&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Provide the&amp;nbsp;&lt;STRONG&gt;project name&lt;/STRONG&gt;&amp;nbsp;WinForms.Packaging and click&amp;nbsp;&lt;STRONG&gt;Create&lt;/STRONG&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The next step is to provide the target and minimum platform version supported by your application. I am selecting both versions to&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;19041&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;to keep it simple, as I need to install the same SDKs on my Jenkins server environment:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;WinForms.Packaging&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;project, right-click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Dependencies&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;node and click&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add Project Reference...&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Select the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Windows Application Packaging Project&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;and click&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Next&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Observe that the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;WinForms.App&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;project was added to the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;WinForms.Packaging&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;project:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The Windows Application Packaging Project don't accept the target&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Any CPU&lt;/STRONG&gt;. Therefore, we need to change the processor target to x86 or x64 for both projects.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;Open&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Configuration Manager&lt;/STRONG&gt;, change the Active solution platform to x86, change the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;WinForms.App&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;WinForms.Packaging&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;projects to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;x86&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P class=""&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;Build the application to ensure that no errors appear.&lt;/P&gt;
&lt;H3 id="build-the-application-using-msbuild-command-line"&gt;&lt;A id="pragma-line-123" target="_blank"&gt;&lt;/A&gt;Build the application using MSBuild command line&lt;/H3&gt;
&lt;P&gt;Before creating a Jenkins pipeline, let's make sure that the MSBuild command line that will be used to build our application is working.&lt;/P&gt;
&lt;P&gt;Open the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Package Manager Console&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(you can press CTRL+Q and type package manager console):&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Before running the following command, make sure to provide the MSBuild.exe PATH available in your environment:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="powershell"&gt;&amp;amp;"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" /p:AppxBundlePlatforms=X86 /p:AppxBundle=Never /p:UapAppxPackageBuildMode=Sideloading  /p:AppxPackageSigningEnabled=false&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In my case, the msix package file was generated on:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;C:\github\msixdemos\01_VisualStudio\WinForms.App\WinForms.Packaging\AppPackages\WinForms.Packaging_1.0.0.0_AnyCPU_Debug_Test&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;In the next section, it will demonstrate how to build a Jenkins Pipeline for this project.&lt;/P&gt;
&lt;H2 id="2-jenkins-pipeline"&gt;&lt;A id="pragma-line-148" target="_blank"&gt;&lt;/A&gt;2. Jenkins Pipeline&lt;/H2&gt;
&lt;P&gt;Pipeline is a series of tasks required to build, test, and deploy an application.&lt;/P&gt;
&lt;H3 id="create-a-new-job"&gt;&lt;A id="pragma-line-152" target="_blank"&gt;&lt;/A&gt;Create a new job&lt;/H3&gt;
&lt;P&gt;In the Jenkins Dashboard, click on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create a job&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;option:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Provide a name for the job, check the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Pipeline&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;type of job and click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;OK&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;to proceed.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In the pipeline configuration page, check the GitHub Project to specify that this is a GitHub project and provide a GitHub&amp;nbsp;&lt;A href="https://github.com/luishdemetrio/jenkins_msix" target="_blank" rel="noopener"&gt;URL&lt;/A&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;Scroll down under the Pipeline section and change the definition to&amp;nbsp;&lt;STRONG&gt;Pipeline script from SCM&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;img /&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;STRONG&gt;Provide the&amp;nbsp;Repository URL&amp;nbsp;as well. Because this is a public project, we can skip the credentials:&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;img /&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Scroll-down to the Branches to build section, change the branch name to *&lt;STRONG&gt;/main&lt;/STRONG&gt;, the Jenkins script path to&amp;nbsp;&lt;STRONG&gt;Jenkinsfile01&lt;/STRONG&gt;&amp;nbsp;and click on&amp;nbsp;&lt;STRONG&gt;Save&lt;/STRONG&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Those actions were needed as we want to use the Jenkins pipeline file available in the main branch of the following&amp;nbsp;&lt;A href="https://github.com/luishdemetrio/jenkins_msix" target="_blank" rel="noopener"&gt;repo&lt;/A&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="jenkins-pipeline-file"&gt;Jenkins Pipeline File&lt;/H3&gt;
&lt;P&gt;In the previous section it was demonstrated how to setup the Jenkins pipeline to use a Jenkins script file available on our GitHub repository.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Follows the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Jenkinsfile01&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;content:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="yaml"&gt;pipeline {
  
  agent any

  environment {
    MSBUILD = "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Msbuild\\Current\\Bin\\MSBuild.exe"
    CONFIG = 'Release'
    PLATFORM = 'x86'
  }
  
  stages {
    
    stage('Update manifest version') {
      steps {
          powershell '''
            $manifest = "01_VisualStudio\\WinForms.App\\WinForms.Packaging\\Package.appxmanifest"     
            [xml]$xmlDoc = Get-Content $manifest
            $version = $xmlDoc.Package.Identity.Version
            $trimmedVersion = $version -replace '.[0-9]+$', '.'
            $xmlDoc.Package.Identity.Version = $trimmedVersion + ${env:BUILD_NUMBER}
            $xmlDoc.Save($manifest)
          '''
      }
    }
    
    stage('Build') {
      steps {
        bat "dotnet restore 01_VisualStudio\\WinForms.App\\WinForms.App\\WinForms.App.csproj"

        bat "\"${MSBUILD}\" 01_VisualStudio\\WinForms.App\\WinForms.app.sln /p:Configuration=${env.CONFIG} /p:AppxBundlePlatforms=${env.PLATFORM}  /p:AppxBundle=Never /p:UapAppxPackageBuildMode=Sideloading  /p:AppxPackageSigningEnabled=false"
        
      }
      post{
          always {
           archiveArtifacts artifacts: '**/*.msix', followSymlinks: false
          }
      }
    }
  }
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;pipeline&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;directive is the complete script from beginning to end.&lt;/P&gt;
&lt;P class=""&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;agent&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;directive instructs Jenkins to allocate an executor and workspace for the entire Pipeline. In our case, we are justing saying it can run on any agent. For example, it could be specified that it could run in a Docker container or run on a specific node.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;environment&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;directive specifies a sequence of key-value pairs which will be defined as environment variables for all steps, or stage-specific steps, depending on where the environment directive is located within the Pipeline.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In our case, it is defined the variables&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;MSBUILD&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;that contains the MSBUILD path, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;CONFIG&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Release&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and PLATFORM with the value x86. Those variables will be used in the command line used to build our application.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;stages&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;block contains on or more&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;stage&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;block, and each stage is going to have one or more&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;steps&lt;/STRONG&gt;. In our case, we have only one stage named Build, that has two steps to restore the dotnet WinForms.App project and to build the solution.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;post&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section defines the additional step needed to keep the msix file artifact available in our build, as workspace is a temporary directory.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-244"&gt;
&lt;P&gt;You can find more details about the Jenkins pipeline syntax in the post&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.jenkins.io/doc/book/pipeline/getting-started/" target="_blank" rel="noopener"&gt;Getting started with Pipeline&lt;/A&gt;.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In addition, there is a great post about how to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://medium.com/southworks/creating-a-jenkins-pipeline-for-a-net-core-application-937a2165b073" target="_blank" rel="noopener"&gt;Creating a Jenkins pipeline for a .NET Core application&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Switch back to Jenkins, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Dashboard&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and click on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Visual Studio Solution&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;pipeline:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Build Now&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;to start the build:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The job starts by checking out the source code to next restore and build our solution as defined in the Jenkinsfile01.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;After the build is done, the build icon will be green and the msix artifact will be available:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The next post demonstrate how to package an application that not uses the Visual Studio IDE, but that uses the Windows Application Packaging Project to generate the MSIX file.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 31 May 2024 18:49:49 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-a-jenkins-pipeline-to-deploy-desktop-apps-as-msix-part-2/ba-p/4157259</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2024-05-31T18:49:49Z</dc:date>
    </item>
    <item>
      <title>How Copilot and Intelligent Recap are Aggregating Value for Microsoft Teams’ Users</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/how-copilot-and-intelligent-recap-are-aggregating-value-for/ba-p/3897594</link>
      <description>&lt;H4&gt;&lt;SPAN data-contrast="none"&gt;A comparison of two AI-powered features that help you get the most out of your meetings&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Meetings are an essential part of our work, where we share ideas, make decisions, and collaborate with others. However, meetings can also be challenging, especially when we have to deal with multiple agendas, diverse perspectives, and complex topics. How can we make our meetings more productive, engaging, and accessible? How can we capture the key insights and outcomes of our meetings, and act on them effectively? How can we leverage the power of artificial intelligence (AI) to enhance our meeting experience and productivity?&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;In this blog post, we will introduce you to two new AI-powered features in Microsoft Teams that aim to answer these questions: Intelligent Recap and Microsoft M365 Copilot. These are two innovative tools that help you get the most out of your meetings, by providing you with relevant and personalized information and insights based on your needs and preferences. We will explain what these features are, how they work, and how they differ from each other. We will also show you how to use them together to improve your meeting experience and productivity.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 aria-level="2"&gt;&lt;SPAN data-contrast="none"&gt;What are Intelligent Recap and Copilot?&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;134245418&amp;quot;:true,&amp;quot;134245529&amp;quot;:true,&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:160,&amp;quot;335559739&amp;quot;:80,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap and Microsoft M365 Copilot are two different AI-powered features in Microsoft Teams that help you get the most out of your meetings. They both use artificial intelligence to analyze the meeting content and context and provide you with relevant and personalized information. However, they have different purposes and functionalities, as we will explain below.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 aria-level="3"&gt;&lt;SPAN data-contrast="none"&gt;How does Intelligent Recap work?&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;134245418&amp;quot;:true,&amp;quot;134245529&amp;quot;:true,&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:160,&amp;quot;335559739&amp;quot;:80,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap in Teams Premium uses AI (artificial intelligence) to deliver a personalized and intelligent overview of each meeting, helping you discover the information that matters to you most, whether you attended the meeting or missed it. It is available in Teams, on a new tab called 'Recap', where you can watch a meeting recording directly within this Teams recap page, and many of its components will also be available when you watch a meeting recording in Stream.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&lt;div data-video-id="https://www.youtube.com/watch?v=n-ub_VdpkAI" data-video-remote-vid="https://www.youtube.com/watch?v=n-ub_VdpkAI" class="lia-video-container lia-media-is-center lia-media-size-large"&gt;&lt;iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fn-ub_VdpkAI%3Ffeature%3Doembed&amp;amp;display_name=YouTube&amp;amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dn-ub_VdpkAI&amp;amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fn-ub_VdpkAI%2Fhqdefault.jpg&amp;amp;key=b0d40caa4f094c68be7c29880b16f56e&amp;amp;type=text%2Fhtml&amp;amp;schema=youtube" allowfullscreen="" style="max-width: 100%"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap automatically generates a summary of the key points and action items discussed in the meeting, using natural language processing and understanding. You can edit, add, or delete any notes as you wish, and share them with your colleagues or other stakeholders. You can also search for specific keywords or phrases in the notes, and jump to the corresponding part of the recording where they were mentioned.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap also identifies potential tasks or follow-ups that emerged from the meeting, and suggests them to you as recommendations. You can review these recommendations, assign them to yourself or others, set deadlines, and track their progress. You can also create new tasks from scratch, or link existing ones from other apps like Planner or To Do.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap creates a customized set of highlights for each meeting participant, based on their role, interests, and interactions in the meeting. For example, you might see highlights such as when your name was mentioned, when someone asked you a question, when you shared your screen, when you gave feedback, or when someone praised your work. These highlights help you catch up on what you might have missed, or revisit what you contributed to the meeting. You can also see other people's highlights by clicking on their profile pictures in the recap page.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Some of the components of Intelligent Recap are:&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="1" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A summary of the meeting highlights, such as key topics, decisions, actions, and follow-ups.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="2" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A transcript of the meeting conversation, with speaker attribution and time stamps.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="3" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A list of the meeting participants, with their roles and attendance status.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="4" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A collection of the meeting attachments, such as files, links, polls, and whiteboards.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="6" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A word cloud of the most frequently used words in the meeting.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI data-leveltext="" data-font="Symbol" data-listid="3" data-list-defn-props="{&amp;quot;335552541&amp;quot;:1,&amp;quot;335559684&amp;quot;:-2,&amp;quot;335559685&amp;quot;:1440,&amp;quot;335559991&amp;quot;:360,&amp;quot;469769226&amp;quot;:&amp;quot;Symbol&amp;quot;,&amp;quot;469769242&amp;quot;:[8226],&amp;quot;469777803&amp;quot;:&amp;quot;left&amp;quot;,&amp;quot;469777804&amp;quot;:&amp;quot;&amp;quot;,&amp;quot;469777815&amp;quot;:&amp;quot;hybridMultilevel&amp;quot;}" aria-setsize="-1" data-aria-posinset="7" data-aria-level="2"&gt;&lt;SPAN data-contrast="auto"&gt;A timeline of the meeting events, such as when someone joined, left, or shared their screen.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap helps you stay on top of your meetings, review the important information, and take action on the next steps.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;Learn more about Microsoft Teams Premium: &lt;/SPAN&gt;&lt;A href="https://www.microsoft.com/en-us/microsoft-teams/premium" target="_blank" rel="noopener"&gt;&lt;SPAN data-contrast="none"&gt;Microsoft Teams Premium | Microsoft Teams&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 aria-level="3"&gt;&lt;SPAN data-contrast="none"&gt;How does Copilot work?&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;134245418&amp;quot;:true,&amp;quot;134245529&amp;quot;:true,&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:160,&amp;quot;335559739&amp;quot;:80,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Microsoft 365 Copilot is a dynamic and interactive feature that leverages advanced language query to deliver real-time responses to your requests. It builds on the Intelligent Recap insights by applying conversational AI (powered by GPT), and returning insights tailored to your unique prompts. While Intelligent Recap proactively offers these insights, Copilot responds to your questions and prompts, ready to help you dig deeper into the meeting context.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Copilot uses a natural language interface that allows you to ask questions or make requests using your own words. It understands the meeting context and the intent of your queries and provides relevant and accurate answers. For example, you can ask Copilot questions like "What did John say about the budget?" or "What are the next steps for the project?" and Copilot will search for the information in the meeting transcript, notes, tasks, and attachments, and provide you with concise and helpful answers.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&lt;div data-video-id="https://www.youtube.com/watch?v=B2-8wrF9Okc&amp;amp;t=20s" data-video-remote-vid="https://www.youtube.com/watch?v=B2-8wrF9Okc&amp;amp;t=20s" class="lia-video-container lia-media-is-center lia-media-size-large"&gt;&lt;iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FB2-8wrF9Okc%3Fstart%3D20%26feature%3Doembed%26start%3D20&amp;amp;display_name=YouTube&amp;amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DB2-8wrF9Okc&amp;amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FB2-8wrF9Okc%2Fhqdefault.jpg&amp;amp;key=b0d40caa4f094c68be7c29880b16f56e&amp;amp;type=text%2Fhtml&amp;amp;schema=youtube" allowfullscreen="" style="max-width: 100%"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Copilot is also a collaborative tool that enables you to share your Copilot insights with other meeting participants or save them for later reference. You can send Copilot answers to the meeting chat, or export them to other apps like Word, PowerPoint, or OneNote.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Copilot also has a feedback mechanism that allows you to rate the quality of the Copilot responses and help improve the system over time. You can give Copilot a thumbs up or a thumbs down, or provide more detailed feedback on how Copilot can improve its answers. Copilot will learn from your feedback and provide better answers in the future.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Copilot helps you get more value out of your meetings, by providing you with real-time information and insights that enhance your understanding and decision making.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Learn more about Microsoft 365 Copilot: &lt;A class="Hyperlink SCXW260195699 BCX8" href="https://www.youtube.com/watch?v=E5g20qmeKpg" target="_blank" rel="noreferrer noopener"&gt;&lt;SPAN class="TextRun Underlined SCXW260195699 BCX8" data-contrast="none"&gt;&lt;SPAN class="NormalTextRun SCXW260195699 BCX8" data-ccp-charstyle="Hyperlink"&gt;The Copilot System: Explained by Microsoft&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN class="TextRun SCXW260195699 BCX8" data-contrast="none"&gt;&lt;SPAN class="NormalTextRun SCXW260195699 BCX8"&gt; and &lt;/SPAN&gt;&lt;/SPAN&gt;&lt;A class="Hyperlink SCXW260195699 BCX8" href="https://www.youtube.com/watch?v=B2-8wrF9Okc" target="_blank" rel="noreferrer noopener"&gt;&lt;SPAN class="TextRun Underlined SCXW260195699 BCX8" data-contrast="none"&gt;&lt;SPAN class="NormalTextRun SCXW260195699 BCX8" data-ccp-charstyle="Hyperlink"&gt;How Microsoft 365 Copilot works&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN class="TextRun SCXW260195699 BCX8" data-contrast="none"&gt;&lt;SPAN class="NormalTextRun SCXW260195699 BCX8"&gt;.&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;SPAN class="EOP SCXW260195699 BCX8" data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 aria-level="2"&gt;&lt;SPAN data-contrast="none"&gt;What are the differences and similarities between Intelligent Recap and Copilot?&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;134245418&amp;quot;:true,&amp;quot;134245529&amp;quot;:true,&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:160,&amp;quot;335559739&amp;quot;:80,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P aria-level="2"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent Recap and Copilot are both AI-powered features that help you get the most out of your meetings in Microsoft Teams. They both use artificial intelligence to analyze the meeting content and context and provide you with relevant and personalized information. However, they have different purposes and functionalities, as summarized in the table below.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;TABLE style="font-weight: 400;" data-tablestyle="MsoTableGrid" data-tablelook="1696" aria-rowcount="7"&gt;
&lt;TBODY&gt;
&lt;TR aria-rowindex="1"&gt;
&lt;TD data-celllook="4097"&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="1"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;What&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="1"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Current Way&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="17"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;New Way&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="2"&gt;
&lt;TD data-celllook="4096"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Intelligent recap in Teams Premium&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Capture key meeting ideas and follow up actions&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Attendees have to manually take notes and action items during the meeting&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="16"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Automatically get consistent meeting notes and action items for attendees&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="3"&gt;
&lt;TD data-celllook="4096"&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Listen to or watch what happened during a meeting&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Spend hours after the workday watching meeting recordings and jumping around trying to find specific points of conversation&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="16"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Save time with an auto-generated, immersive recap of a specific meeting or series, all in one place. Skim chapters and meeting topics that are automatically created and focus on the most relevant places for you to listen in&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="4"&gt;
&lt;TD data-celllook="4096"&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Find times in the meeting relevant for you&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Spend time sifting through for specific, relevant points just for you&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="16"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Quickly find and jump to when your name was mentioned, when a screen was shared, when you joined/left the meeting, when your colleagues spoke, and by different topics throughout the discussion&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="5"&gt;
&lt;TD data-celllook="4096"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Copilot in Teams&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Stay on topic during a meeting&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Spend valuable meeting minutes summarizing progress, discussing agenda, handling conflict and compiling feedback&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="16"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Ask Copilot to automatically recap discussion and decisions made; proactively suggest agenda items and points to stay on topic, suggest questions, and even identify project and timeline conflicts.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="6"&gt;
&lt;TD data-celllook="4096"&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Author or generate content from discussions&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="0"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Time and effort to brainstorm ideas, draft new content, and format concepts into diagrams&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="16"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Get more done and save time by leveraging Copilot to brainstorm ideas on top of your own, synthesize and format information, build tables and diagrams, and organize text.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR aria-rowindex="7"&gt;
&lt;TD data-celllook="4352"&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="256"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Gain deeper insights from meeting, chat, and channel conversations&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="256"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Spend hours watching meeting recordings, asking colleagues for input, or scrolling through chats to understand what happened&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;TD data-celllook="272"&gt;
&lt;P&gt;&lt;SPAN data-contrast="auto"&gt;Quickly understand why a decision was made, different perspectives from the group, unresolved questions, and more with specific, context-baed questions to Copilot.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN data-contrast="none"&gt;How to use Intelligent Recap and Copilot together&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;To get the best results from Intelligent Recap and Copilot, you need to enable them in your Microsoft Teams settings. You also need to have a Microsoft Teams Premium subscription and Copilot license to access Intelligent Recap. Once you have enabled these features, you can use them in the following ways:&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;&lt;STRONG&gt;- Before the meeting&lt;/STRONG&gt;, you can use Copilot to prepare for the meeting by asking it questions about the meeting agenda, participants, goals, and background information. Copilot will search for relevant documents, emails, chats, and other sources of information and provide you with concise and helpful answers. You can also use Copilot to create a meeting outline with key points and questions to guide the discussion.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;&lt;STRONG&gt;- During the meeting&lt;/STRONG&gt;, you can use Copilot to keep track of the conversation and capture important information. Copilot will listen to the meeting audio and transcribe it in real time. It will also identify areas of agreement, disagreement, options, decisions, actions, and follow-ups. You can ask Copilot questions or give it commands using natural language, such as "What are the pros and cons of option A?" or "Remind me to send an email to John after the meeting". Copilot will respond with relevant insights and suggestions on how to move forward.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;&lt;STRONG&gt;- After the meeting&lt;/STRONG&gt;, you can use Intelligent Recap to review the meeting summary and highlights. Intelligent Recap will provide you with a comprehensive report that includes a transcript of the meeting audio, a list of attendees and their engagement level, a timeline of key moments and topics discussed, a summary of decisions made and actions assigned, a collection of files shared during the meeting, and a feedback survey for participants. You can also use Intelligent Recap to search for specific information or keywords within the report.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&lt;div data-video-id="https://www.youtube.com/watch?v=rLC2frnUasw" data-video-remote-vid="https://www.youtube.com/watch?v=rLC2frnUasw" class="lia-video-container lia-media-is-center lia-media-size-large"&gt;&lt;iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FrLC2frnUasw&amp;amp;display_name=YouTube&amp;amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DrLC2frnUasw&amp;amp;image=http%3A%2F%2Fi.ytimg.com%2Fvi%2FrLC2frnUasw%2Fhqdefault.jpg&amp;amp;key=b0d40caa4f094c68be7c29880b16f56e&amp;amp;type=text%2Fhtml&amp;amp;schema=youtube" allowfullscreen="" style="max-width: 100%"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 aria-level="1"&gt;&lt;SPAN data-contrast="none"&gt;Conclusion&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;134245418&amp;quot;:true,&amp;quot;134245529&amp;quot;:true,&amp;quot;201341983&amp;quot;:0,&amp;quot;335559738&amp;quot;:360,&amp;quot;335559739&amp;quot;:80,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-contrast="none"&gt;Intelligent Recap and Copilot are two AI-powered features that enhance your meetings in Microsoft Teams. They both use artificial intelligence to analyze the meeting content and context and provide you with relevant and personalized information. However, they have different purposes and functionalities, as Intelligent Recap provides insights after the meeting has concluded, while Copilot provides real-time value during the meeting. By using these features, you can improve your meeting experience and productivity, and get more out of your collaboration.&lt;/SPAN&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN data-ccp-props="{&amp;quot;201341983&amp;quot;:0,&amp;quot;335559739&amp;quot;:160,&amp;quot;335559740&amp;quot;:279}"&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 22 Jan 2024 17:13:19 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/how-copilot-and-intelligent-recap-are-aggregating-value-for/ba-p/3897594</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2024-01-22T17:13:19Z</dc:date>
    </item>
    <item>
      <title>Upskilling: ChatGPT Prompt Engineering for Developers now in C#</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/upskilling-chatgpt-prompt-engineering-for-developers-now-in-c/ba-p/3832014</link>
      <description>&lt;P&gt;Like most developers today, I'm upskilling on the new Generative AI technologies like &lt;A href="https://openai.com/blog/chatgpt" target="_blank" rel="noopener"&gt;OpenAI's ChatGPT&lt;/A&gt;, &lt;A href="https://www.microsoft.com/en-us/edge/features/bing-chat?form=MT00D8" target="_blank" rel="noopener"&gt;Microsoft Bing Chat&lt;/A&gt;, &lt;A href="https://github.com/features/copilot" target="_blank" rel="noopener"&gt;GitHub Copilot&lt;/A&gt;, and the &lt;A href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/overview" target="_blank" rel="noopener"&gt;Azure OpenAI Service&lt;/A&gt;.&amp;nbsp;The key to effectively using these technologies lies in crafting the right prompts. A prompt includes instructions that describe the desired output and provide context to the AI service.&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;Writing effective prompts is a valuable skill, and it has even given rise to the role of a "Prompt Engineer." While you don't need to be a Prompt Engineer to use these technologies efficiently, understanding the different types of prompts is essential to unlock their full potential. To help with this, &lt;A href="https://www.deeplearning.ai/" target="_blank" rel="noopener"&gt;DeepLearning.AI&lt;/A&gt; offers a free course called &lt;A href="https://learn.deeplearning.ai/chatgpt-prompt-eng" target="_blank" rel="noopener"&gt;ChatGPT Prompt Engineering for Developers&lt;/A&gt;. This course is highly recommended as it provides excellent descriptions and hands-on walkthroughs of various prompt types. The interactive samples in the course are implemented in Python using Jupyter Notebooks, which is a popular language in the AI space.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For those who want to use the samples with the &lt;A href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/ai.openai-readme?view=azure-dotnet-preview" target="_blank" rel="noopener"&gt;Azure OpenAI service&lt;/A&gt; and the &lt;A href="https://www.nuget.org/packages/Azure.AI.OpenAI" target="_blank" rel="noopener"&gt;Azure OpenAI client library for .NET&lt;/A&gt;, I have ported the Python samples to C#. I am using the &lt;A href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode" target="_blank" rel="noopener"&gt;Polyglot Notebooks&lt;/A&gt; extension for Visual Studio Code to run the notebooks. The Polyglot Notebooks extension includes the &lt;A href="https://github.com/dotnet/interactive" target="_blank" rel="noopener"&gt;.NET Interactive&lt;/A&gt; runtime, providing support for C# and .NET.&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;The notebooks are available on GitHub &lt;A href="https://github.com/mjfusa/azure-openai-csharp-prompt-engineering-samples" target="_blank" rel="noopener"&gt;here&lt;/A&gt;: Azure OpenAI C# Prompt Engineering Samples.&lt;/P&gt;</description>
      <pubDate>Fri, 26 May 2023 15:33:38 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/upskilling-chatgpt-prompt-engineering-for-developers-now-in-c/ba-p/3832014</guid>
      <dc:creator>MikeFrancis</dc:creator>
      <dc:date>2023-05-26T15:33:38Z</dc:date>
    </item>
    <item>
      <title>A better way to identify external users in an Outlook mail</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/a-better-way-to-identify-external-users-in-an-outlook-mail/ba-p/3793131</link>
      <description>&lt;P&gt;&lt;SPAN&gt;Many companies are working in a context where content sensitivity is very important. You should be very careful on what information you are sharing and with whom, especially if one or more recipients don't belong to your organization. To help employees being more diligent, the Outlook team developed a while ago a feature called&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips" target="_blank"&gt;MailTips&lt;/A&gt;&lt;SPAN&gt;, which is a way to provide contextual information to a user when a specific condition happens. One of the conditions that IT admins can turn on is external users checking. The image below shows an example of what happens in my Outlook client when I try to send a mail that includes a recipient who is outside Microsoft:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;You can notice that, as soon as I add my personal mail address, the recipient is highlighted in yellow and I get a message at the top of the mail notifying me that there's one external recipient, with the option to quickly remove it. The MailTip, however, is just a warning. If I try to send this mail, I don't get further notifications and the mail is simply sent.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For some companies who are very sensitive to the confidentiality topic, this isn't enough. They need a more direct way to alert an employee when they're sharing a mail with an external user, to make sure that they aren't sharing any confidential information.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this blog post, we're going to build an Outlook add-in that we can use to achieve this goal. The add-in is going to intercept the click on the Send button and, in case one or more recipients are outside the organization, will display a warning pop-up. The user will need to confirm a second time before actually sending the mail:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="setup-the-project"&gt;Setup the project&lt;/H3&gt;
&lt;P&gt;To set up the project, we will need to use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/develop/yeoman-generator-overview" target="_blank"&gt;Yeoman generator&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to create an Office add-in using the web development model. Once you have installed all the required tools&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/develop/yeoman-generator-overview" target="_blank"&gt;highlighted in the documentation&lt;/A&gt;, open a terminal, and create a folder where you want to store your project:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-19" class="language-powershell hljs"&gt;mkdir outlook-external-users
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Then start Yeoman with the following command:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-25" class="language-powershell hljs"&gt;yo office
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;You're going to start a wizard to scaffold the starting project:&lt;/P&gt;
&lt;UL id="pragma-line-31"&gt;
&lt;LI id="pragma-line-31"&gt;&lt;STRONG&gt;Choose a project type&lt;/STRONG&gt;: choose one of the available options for the category&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Office add-ins Task Pane&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;project. There are multiple options based on your favorite web framework. In my case, I picked up the React based one.&lt;/LI&gt;
&lt;LI id="pragma-line-32"&gt;&lt;STRONG&gt;Choose a script type&lt;/STRONG&gt;: also in this case, it's a matter of preference, but I highly suggest using&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;TypeScript&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;rather than plain JavaScript.&lt;/LI&gt;
&lt;LI id="pragma-line-33"&gt;&lt;STRONG&gt;What do you want to name your add-in?&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Give it a meaningful name, like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outlook external users&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-34"&gt;&lt;STRONG&gt;Which Office client application would you like to support?&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outlook&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Now let Yeoman generate the project and restore all the required dependencies from NPM, the Node Package Manager.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="configure-the-project-to-intercept-events"&gt;Configure the project to intercept events&lt;/H3&gt;
&lt;P&gt;The default template generated by Yeoman supports the two standard scenarios:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;task pane&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(the sidebar that is displayed on the left) and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;command&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(an operation that is directly executed on the current context, like a selected portion of the mail text). In our scenario, instead, we must support a third use case:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;event-based activation&lt;/STRONG&gt;. Our add-in must stay "silent" in background and come alive when a specific action happens, in our case the user pressing the Send button on a mail compose window. The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/outlook/autolaunch?tabs=xmlmanifest" target="_blank"&gt;official documentation&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;lists all the supported events that we can intercept. The one we are interested in is called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OnMessageSend&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The first change we must make is in the manifest file, called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;manifest.xml&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and included in the root of the project. Inside the file, you will find a section called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ExtensionPoint&lt;/CODE&gt;, which sits under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;DesktopFormFactor&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section, with, as type,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;MessageReadCommandSurface&lt;/CODE&gt;. This is the default extension point of the template, which means that the add-in buttons will be visible when you're reading an e-mail. We can ignore it for our scenario. Let's add, below this extension, another one with the following syntax:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-45" class="language-xml hljs"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;ExtensionPoint&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;xsi:type&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"LaunchEvent"&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;LaunchEvents&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
    &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;LaunchEvent&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;Type&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"OnMessageSend"&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;FunctionName&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"onMessageSendHandler"&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;SendMode&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"PromptUser"&lt;/SPAN&gt; /&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;/&lt;SPAN class="hljs-name"&gt;LaunchEvents&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-comment"&gt;&amp;lt;!-- Identifies the runtime to be used (also referenced by the Runtime element). --&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;SourceLocation&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;resid&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"WebViewRuntime.Url"&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class="hljs-tag"&gt;&amp;lt;/&lt;SPAN class="hljs-name"&gt;ExtensionPoint&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We are creating a new&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ExtensionPoint&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;entry with type&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;LaunchEvent&lt;/CODE&gt;, which is our event-based activation scenario. Inside, we must specify a collection of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;LaunchEvent&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;entries, one for each event we want to listen to. In our scenario, it's only one event:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OnMessageSend&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The second parameter is equally important:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;FunctionName&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is the name of the TypeScript function we're going to create to handle the event. The final property is called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;SendMode&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and it's applied only to events which are related to a Send event, like in this case (another example is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;SendAppointment&lt;/CODE&gt;). The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PromptUser&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;value means that in case the condition we have defined is not met, the user will see a prompt that will give them the option to cancel the operation or send the mail anyway.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This is the correct behavior for our add-in, thus it's important to specify it, otherwise Outlook will switch to the default behavior, which is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;SoftBlock&lt;/CODE&gt;. In this case, the pop-up will be displayed without any option to send the mail anyway, the user will be blocked until the condition is fixed. This isn't the behavior we want: the pop-up should be an extra layer of warning that external users are included, but we shouldn't completely prevent users from sending mails to people outside the organization!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The next step is to create a file that will host our event management code. The basic project already contains two folders for the two default scenarios (&lt;CODE&gt;taskpane&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;commands&lt;/CODE&gt;), let's add a new one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;launchevent&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, inside it, let's create a file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;launchevent.js&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;Since this is a new JavaScript file which isn't included in the default template, we must manually reference it. Open the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;commands.html&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;commands&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;folder and, inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;head&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section, add the following entry:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-64" class="language-javascript hljs"&gt;&amp;lt;script type=&lt;SPAN class="hljs-string"&gt;"text/javascript"&lt;/SPAN&gt; src=&lt;SPAN class="hljs-string"&gt;"../launchevent/launchevent.js"&lt;/SPAN&gt;&amp;gt;&lt;SPAN class="xml"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;/&lt;SPAN class="hljs-name"&gt;script&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The last step is to change the Webpack configuration. When you test or publish your add-in, Webpack takes care of bundling all the required assets together. Since now we have a new JavaScript file in our project, we must make sure it's properly included. Open the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;webpack.config.js&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file in the root of the project and, inside the plugins section, add the following entry:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-70" class="language-json hljs"&gt;new CopyWebpackPlugin({
        patterns: [
          {
            from: &lt;SPAN class="hljs-string"&gt;"./src/launchevent/launchevent.js"&lt;/SPAN&gt;,
            to: &lt;SPAN class="hljs-string"&gt;"launchevent.js"&lt;/SPAN&gt;,
          },
        ],
      }),
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;That's it. Now we have all the building blocks which are needed to intercept the mail send. We just need the code to do it &lt;span class="lia-unicode-emoji" title=":grinning_face_with_big_eyes:"&gt;😃&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="implement-the-event-handler"&gt;Implement the event handler&lt;/H3&gt;
&lt;P&gt;Let's look at the code that we need to include in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;launchevent.js&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV class="code-badge-language"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-86" class="language-javascript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; customerDomain = &lt;SPAN class="hljs-string"&gt;"@contoso.com"&lt;/SPAN&gt;;

&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;onMessageSendHandler&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;event&lt;/SPAN&gt;) &lt;/SPAN&gt;{
  &lt;SPAN class="hljs-keyword"&gt;let&lt;/SPAN&gt; externalRecipients = [];
  Office.context.mailbox.item.to.getAsync(&lt;SPAN class="hljs-function"&gt;(&lt;SPAN class="hljs-params"&gt;asyncResult&lt;/SPAN&gt;) =&amp;gt;&lt;/SPAN&gt; {
    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (asyncResult.status === Office.AsyncResultStatus.Succeeded) {
      &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; recipients = asyncResult.value;
      recipients.forEach(&lt;SPAN class="hljs-function"&gt;(&lt;SPAN class="hljs-params"&gt;recipient&lt;/SPAN&gt;) =&amp;gt;&lt;/SPAN&gt; {
        &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (!recipient.emailAddress.includes(customerDomain)) {
          externalRecipients.push(recipient.emailAddress);
        }

        &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (externalRecipients.length &amp;gt; &lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;) {
          event.completed({
            &lt;SPAN class="hljs-attr"&gt;allowEvent&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;false&lt;/SPAN&gt;,
            &lt;SPAN class="hljs-attr"&gt;errorMessage&lt;/SPAN&gt;:
              &lt;SPAN class="hljs-string"&gt;"The mail includes some external recipients, are you sure you want to send it?\n\n"&lt;/SPAN&gt; +
              externalRecipients.join(&lt;SPAN class="hljs-string"&gt;"\n"&lt;/SPAN&gt;) +
              &lt;SPAN class="hljs-string"&gt;"\n\nClick Send to send the mail anyway."&lt;/SPAN&gt;,
          });
        } &lt;SPAN class="hljs-keyword"&gt;else&lt;/SPAN&gt; {
          event.completed({ &lt;SPAN class="hljs-attr"&gt;allowEvent&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt; });
        }
      });
    } &lt;SPAN class="hljs-keyword"&gt;else&lt;/SPAN&gt; {
      event.completed({ &lt;SPAN class="hljs-attr"&gt;allowEvent&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt; });
    }
  });
}

&lt;SPAN class="hljs-comment"&gt;// IMPORTANT: To ensure your add-in is supported in the Outlook client on Windows, remember to map the event handler name specified in the manifest's LaunchEvent element to its JavaScript counterpart.&lt;/SPAN&gt;
&lt;SPAN class="hljs-comment"&gt;// 1st parameter: FunctionName of LaunchEvent in the manifest; 2nd parameter: Its implementation in this .js file.&lt;/SPAN&gt;
&lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (Office.context.platform === Office.PlatformType.PC || Office.context.platform == &lt;SPAN class="hljs-literal"&gt;null&lt;/SPAN&gt;) {
  Office.actions.associate(&lt;SPAN class="hljs-string"&gt;"onMessageSendHandler"&lt;/SPAN&gt;, onMessageSendHandler);
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The core of the file is the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onMessagesendHandler&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function, which is registered as handler every time the Send button of the mail compose window is clicked. Let's see, step by step, the operations performed inside the handler:&lt;/P&gt;
&lt;OL id="pragma-line-127"&gt;
&lt;LI id="pragma-line-127"&gt;First, we use the Office APIs to retrieve the list of recipients of the mail, by calling&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Office.context.mailbox.item.to.getAsync()&lt;/CODE&gt;. This is an asynchronous API, so we need to manage the result inside a callback.&lt;/LI&gt;
&lt;LI id="pragma-line-128"&gt;Inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;status&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the result, we get the information if the operation has succeeded. If that's the case (&lt;CODE&gt;Office.AsyncResultStatus.Succeeded&lt;/CODE&gt;), we use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;value&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property, which is a collection of recipients.&lt;/LI&gt;
&lt;LI id="pragma-line-129"&gt;We use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;forEach()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;statement to iterate through all the recipients.&lt;/LI&gt;
&lt;LI id="pragma-line-130"&gt;For each recipient, we check the value of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;emailAddress&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property, and we test if it includes the domain we consider internal (in this example, we check if it's a &lt;a href="javascript:void(0)" data-lia-user-mentions="" data-lia-user-uid="1119020" data-lia-user-login="Contoso" class="lia-mention lia-mention-user"&gt;Contoso&lt;/a&gt;.com mail address).&lt;/LI&gt;
&lt;LI id="pragma-line-131"&gt;Every recipient that doesn't qualify is added to a collection, called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;externalRecipients&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-132"&gt;In case the collection contains more than one item, it means that there are external users as recipients. We call&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;event.completed()&lt;/CODE&gt;, to notify Outlook that we have completed our checks, but we set the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;allowEvent&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;false&lt;/CODE&gt;, which means that our condition isn't satisfied, thus we must display the pop-up. In this case, we populate also the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;errorMessage&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter with the text that we want to display to the user, which includes the list of external recipients we have identified.&lt;/LI&gt;
&lt;LI id="pragma-line-133"&gt;In case the collection doesn't contain any item, instead, it means there are no external recipients. We call&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;event.completed()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as well but, in this case, we set the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;allowEvent&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;true&lt;/CODE&gt;. The event can be executed, so we allow Outlook to send the mail without showing any prompt.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;This is the code implementation. We're ready to debug our add-in!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="debug-the-outlook-add-in"&gt;Debug the Outlook add-in&lt;/H3&gt;
&lt;P&gt;From the debug point of view, there's no difference between an event-based activation add-in and a traditional Outlook add-in. The easiest way to test it is by using Visual Studio Code. When you open your project and you move to the Debug tab, you will find multiple debug profiles, including one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outlook Desktop (Edge Chromium)&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Select it and press F5 (or click on the Play icon). Visual Studio Code will launch Outlook desktop and will sideload the add-in. Now compose a new e-mail and add, in the To field, at least one e-mail address which domain doesn't match with the one you have specified in the&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;customerDomain&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;property. If you did everything correctly, you should see the same experience I've shared at the beginning of the post:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When you click on Send anyway, the compose window will be closed and the mail will be sent; if you click on Don't send, instead, the user will go back to the Compose window and they will be able to change the content of the mail or the list of recipients.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this post, we have learned how to build an Outlook add-in which uses the event-based activation model to provide a more restrictive external recipients experience to users. Thanks to this add-in, we can add an extra layer of security to make sure that employees aren't sending confidential information to external users.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;You can find the complete project&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A&gt;on GitHub&lt;/A&gt;. Remember that this add-in uses the web model so, if you want to use it in your environment, you will have to host it on your own. For example, you can leverage Azure Storage by following&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/publish/publish-add-in-vs-codehttps://learn.microsoft.com/en-us/office/dev/add-ins/publish/publish-add-in-vs-code" target="_blank"&gt;the guidance on the official documentation&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;</description>
      <pubDate>Wed, 12 Apr 2023 13:00:00 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/a-better-way-to-identify-external-users-in-an-outlook-mail/ba-p/3793131</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-04-12T13:00:00Z</dc:date>
    </item>
    <item>
      <title>Microsoft Teams: Capture Chat Messages and Replies</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/microsoft-teams-capture-chat-messages-and-replies/ba-p/3787357</link>
      <description>&lt;P&gt;You may have the need to archive chat messages or capture chat sessions for further processing. Activities like summarizing the conversation, extract action items, or suggesting meeting times can be done by analyzing the chat conversation. This starts with extracting the conversation using the Microsoft Graph APIs. In Microsoft Teams, a chat conversation is made up of messages and replies. A 'message' is the initiating text. Responses to the initial prompt by recipients of the message and the message initiator are 'replies'. For example:&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;In this case the 'message' is 'Where did you want to meet for lunch?'. The remainder of the conversation are 'replies'. The &lt;A href="https://learn.microsoft.com/en-us/connectors/teams/" target="_blank" rel="noopener"&gt;Microsoft Teams Connector&lt;/A&gt;, provides methods we need to extract the messages. However, no method is provided for collecting the message replies. For this, we will call the Microsoft Graph using a custom connector.&lt;/P&gt;
&lt;P&gt;An exported solution with the following implemented can be downloaded &lt;A href="https://techcommunity.microsoft.com/gxcuf89792/attachments/gxcuf89792/ModernWorkAppConsult/700.3/1/GetMessages_1_0_0_1.zip" target="_self"&gt;here&lt;/A&gt;.&lt;/P&gt;
&lt;H2 id="requirements-scope"&gt;Requirements Scope&lt;/H2&gt;
&lt;OL&gt;
&lt;LI&gt;The solution must only capture messages for Teams that a specified user (Service Account) is a member.&lt;/LI&gt;
&lt;LI&gt;The solution must capture both messages and replies for that message.&lt;/LI&gt;
&lt;LI&gt;The solution must capture messages in both public and private channels.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 id="tools"&gt;Tools&lt;/H2&gt;
&lt;P&gt;The Postman Graph API collection is very useful for experimenting with the Graph APIs. See here: Use Postman with the &lt;A href="https://learn.microsoft.com/en-us/graph/use-postman" target="_blank" rel="noopener"&gt;Microsoft Graph API - Microsoft Graph | Microsoft Learn&lt;/A&gt; This useful for getting the &lt;STRONG&gt;Team&lt;/STRONG&gt;, &lt;STRONG&gt;Channel&lt;/STRONG&gt; and &lt;STRONG&gt;Message ids&lt;/STRONG&gt; you'll need for testing the custom connector.&lt;/P&gt;
&lt;H2 id="implementation"&gt;Implementation&lt;/H2&gt;
&lt;H3 id="in-azure-active-directory-aad---for-the-global-admin---create-a-service-account-and-app-registration"&gt;In Azure Active Directory (AAD) - For the global admin - Create a Service account and app registration&lt;/H3&gt;
&lt;OL&gt;
&lt;LI&gt;Create a Service Account (A user account not associated with an actual user) with the default 'User' role.&lt;/LI&gt;
&lt;LI&gt;Create an App registration with the following &lt;STRONG&gt;delegated&lt;/STRONG&gt; permissions:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;These permissions require consent by the Global admin for the organization.&lt;/LI&gt;
&lt;LI&gt;In 'Authentication' Add the 'Web' platform and specify the following Redirect URI: &lt;A href="https://global.consent.azure-apim.net/redirect" target="_blank" rel="noopener"&gt;https://global.consent.azure-apim.net/redirect&lt;/A&gt;.&lt;/LI&gt;
&lt;LI&gt;Create a secret. Note the secret, Client Id, and Tenant Id, of the app registration. You'll need these when creating the custom connector.&lt;/LI&gt;
&lt;LI&gt;Assign the Power Apps, and Microsoft 365 licenses as you normally do when onboarding users.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 id="power-automate---for-app-makers---create-the-custom-connector-for-calling-the-ms-graph"&gt;Power Automate - For app makers - Create the Custom Connector for calling the MS Graph&lt;/H2&gt;
&lt;P&gt;In this step we'll create a custom connector that will call the MS Graph API:&lt;/P&gt;
&lt;P&gt;&lt;CODE&gt;GET /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies&lt;/CODE&gt;&lt;/P&gt;
&lt;P&gt;Connector Name: &lt;STRONG&gt;GetMessageReplies&lt;/STRONG&gt; Key Fields: General:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE&gt;Host: graph.microsoft.com
Security:
Authentication Type: OAuth 2.0
Identity Provider: Azure Active Directory
Client ID: &amp;lt;client id from AAD section above&amp;gt;
Authorization URL: https://login.microsoftonline.com
Tenant ID: common
Resource URL: https://graph.microsoft.com
Redirect URL: https://global.consent.azure-apim.net/redirect

Definition:

General:
    Summary: Get message replies
    Description: Get message replies
    Operation ID: Get-message-replies
    Visibility: Important
Request:
    Import from sample:
        GET
        URL: https://graph.microsoft.com/beta/teams/{TeamId}/channels/{ChannelId}/messages/{MessageId}/replies
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;PRE&gt;&lt;CODE class="language-json"&gt;&lt;SPAN class="hljs-comment"&gt;// Response:&lt;/SPAN&gt;
&lt;SPAN class="hljs-comment"&gt;// Import from sample:&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;{&lt;/SPAN&gt;
    &lt;SPAN class="hljs-attr"&gt;"@odata.context"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"https://graph.microsoft.com/beta/$metadata#teams('eb825a11-3b12-4c5f-8258-fd405b1395c8')/channels('19%3Aq0-BRQ6TDgcZczl9O7V2Ye078k1eOIzgghUkpRzTlW41%40thread.tacv2')/messages('1677699468267')/replies"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
    &lt;SPAN class="hljs-attr"&gt;"@odata.count"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
    &lt;SPAN class="hljs-attr"&gt;"value"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-punctuation"&gt;[&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;]&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;}&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Test:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Click &lt;STRONG&gt;Update connector&lt;/STRONG&gt; to save.&lt;/LI&gt;
&lt;LI&gt;Create a connection using the &lt;STRONG&gt;Service Account&lt;/STRONG&gt; created in the AAD section above.&lt;/LI&gt;
&lt;LI&gt;Provide a TeamId, ChannelId, and Message Id, acquired from the PostMan Graph API helper. (See &lt;STRONG&gt;Tools&lt;/STRONG&gt; section.)&lt;/LI&gt;
&lt;LI&gt;Click 'Test operation'. A successful test will return a 200 status code. See screenshot below.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;H2 id="power-automate---for-app-maker---create-scheduled-and-power-app-flows"&gt;Power Automate - For app maker - Create Scheduled and Power App Flows&lt;/H2&gt;
&lt;P&gt;In the following steps, we'll create a flow that will collect chat messages and replies for every Team the &lt;STRONG&gt;service account&lt;/STRONG&gt; is a member. &lt;SPAN&gt;The idea here is that an admin would add this user account to each Team where they want to extract the chat messages.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this step we'll create a scheduled flow that calls a child flow that will capture the messages and replies and pass them back to the calling flow. This is required to have the flow run in the context of the 'run-only' user, which will be the &lt;STRONG&gt;Service Account&lt;/STRONG&gt; created in the AAD section above.&lt;/P&gt;
&lt;H3 id="create-child-flow"&gt;Create Child Flow&lt;/H3&gt;
&lt;OL&gt;
&lt;LI&gt;Let create the child flow first. Create an Instant flow with a PowerApps V2 trigger. Call this flow GetChatMessages.&lt;/LI&gt;
&lt;LI&gt;Initialize an array variable called 'result'.&lt;/LI&gt;
&lt;LI&gt;Use the Microsoft Teams Connector to call the following. The channels, message, and replies should be in nested 'Apply to each' controls:
&lt;OL&gt;
&lt;LI&gt;List Teams - Specify the Service Account for the connection reference.&lt;/LI&gt;
&lt;LI&gt;List Channels&lt;/LI&gt;
&lt;LI&gt;Get Messages&lt;/LI&gt;
&lt;LI&gt;Get replies for message - here you will call the custom connector.&lt;/LI&gt;
&lt;/OL&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;OL start="4"&gt;
&lt;LI&gt;Append the current team, channel, message and replies to the 'result' array.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;OL start="5"&gt;
&lt;LI&gt;Covert the array to a string and return to calling flow.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;OL start="6"&gt;
&lt;LI&gt;
&lt;P&gt;Save the Flow&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;On the Flow's detail page, edit the 'Run only users' and set the 'Connections Used' for Microsoft Teams and GetAllTeams (Custom Connector) to be the Service Account.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Share the flow with the Service Account.&lt;/LI&gt;
&lt;LI&gt;Optionally specify the Service Account as an owner of the flow.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3 id="create-main-flow"&gt;Create Main Flow&lt;/H3&gt;
&lt;OL&gt;
&lt;LI&gt;Create a Scheduled flow. Name this &lt;STRONG&gt;GetTeamsChannelsMessages&lt;/STRONG&gt;. By default, the flow will run every minute, you may want to change this to a longer interval (weekly) while you are developing the flow. You can always trigger this manually during development.&lt;/LI&gt;
&lt;LI&gt;Add the action to call a Child Flow. Specify &lt;STRONG&gt;GetChatMessages&lt;/STRONG&gt; created above.&lt;/LI&gt;
&lt;LI&gt;Save the Flow&lt;/LI&gt;
&lt;LI&gt;Share the flow with the &lt;STRONG&gt;Service Account&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI&gt;Optionally specify the &lt;STRONG&gt;Service Account&lt;/STRONG&gt; as an owner of the flow.&lt;/LI&gt;
&lt;LI&gt;When your testing is complete, update the schedule to the interval you need. You should run a few tests to determine the maximum runtime for the flow and adjust the schedule accordingly.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2 id="results"&gt;Results&lt;/H2&gt;
&lt;P&gt;Here is an example of the output of the chat in the first screenshot:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-json"&gt;&lt;SPAN class="hljs-punctuation"&gt;{&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Demo Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Channel"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Project Silver"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098428650"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Where did you want to meet for lunch?"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098885814"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"I'd don't want to go all the way out to Dunmore."&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;}&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;{&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Demo Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Channel"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Project Silver"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098428650"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Where did you want to meet for lunch?"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098838074"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"No seafood. How about Cugino's?"&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;}&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;{&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Demo Team"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"Channel"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Project Silver"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098428650"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"MessageText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"Where did you want to meet for lunch?"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyId"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"1677098447360"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;,&lt;/SPAN&gt;
  &lt;SPAN class="hljs-attr"&gt;"ReplyText"&lt;/SPAN&gt;&lt;SPAN class="hljs-punctuation"&gt;:&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"How about Cooper's?"&lt;/SPAN&gt;
&lt;SPAN class="hljs-punctuation"&gt;}&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 19 May 2023 21:03:47 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/microsoft-teams-capture-chat-messages-and-replies/ba-p/3787357</guid>
      <dc:creator>MikeFrancis</dc:creator>
      <dc:date>2023-05-19T21:03:47Z</dc:date>
    </item>
    <item>
      <title>Using Adaptive Cards to Collect Feedback on ServiceNow Tickets in Teams.</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/using-adaptive-cards-to-collect-feedback-on-servicenow-tickets/ba-p/3781124</link>
      <description>&lt;P&gt;In any organization, gathering feedback from customers and employees is essential to improve products and services, as well as to enhance the overall experience. However, obtaining that feedback can be a challenging task, especially when you need to reach out to a large number of people.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Fortunately, with the rise of digital communication tools and collaborative apps, it's becoming easier than ever to gather feedback efficiently and in a timely manner. Among these apps, Microsoft Teams stands out as one of the most popular and versatile options, providing a unified platform for team communication and collaboration.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post, we'll explore how you can leverage Microsoft Teams to gather feedback from your customers or employees via surveys sent as Adaptive Cards, using ServiceNow as the source of information. Specifically, we'll discuss how to set up an automated process that sends Adaptive Cards to Microsoft Teams after a ticket is closed on ServiceNow, making it easy for respondents to provide feedback directly from Teams. By doing so, you'll not only save time and effort but also increase the response rate and improve the quality of the feedback you receive.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="prerequisites"&gt;Prerequisites&lt;/H2&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="service-now-environment"&gt;&lt;A id="pragma-line-13" target="_blank"&gt;&lt;/A&gt;ServiceNow Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;ServiceNow provides a free development environment called the Now Platform (&lt;A href="https://developer.servicenow.com/dev.do" target="_blank" rel="noopener"&gt;https://developer.servicenow.com/dev.do&lt;/A&gt;), which enables developers to quickly build, test, and deploy applications to improve workflows and productivity within their organizations. With a free account on the Now Platform, you can start building powerful applications that leverage ServiceNow's advanced capabilities for workflow management, incident management, and service requests. Whether you're an experienced developer or just getting started, the Now Platform offers a flexible and easy-to-use environment for creating custom solutions tailored to your organization's unique needs.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="microsoft-teams"&gt;&lt;A id="pragma-line-16" target="_blank"&gt;&lt;/A&gt;Microsoft Teams&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-17"&gt;
&lt;LI id="pragma-line-17"&gt;
&lt;P&gt;Go to the Microsoft Teams website (&lt;A href="https://www.microsoft.com/en-us/microsoft-teams/group-chat-software" target="_blank" rel="noopener"&gt;https://www.microsoft.com/en-us/microsoft-teams/group-chat-software&lt;/A&gt;) and click on the "Sign up for free" button.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-19"&gt;
&lt;P&gt;Enter your email address and click on "Next". If you already have a Microsoft account, you can sign in with that account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-21"&gt;
&lt;P&gt;Create a new password and click on "Sign up".&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-23"&gt;
&lt;P&gt;Follow the prompts to complete the setup process, such as entering your name and company information.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-25"&gt;
&lt;P&gt;Once your account is set up, you can download the Microsoft Teams app on your desktop or mobile device and start collaborating with your team.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;With a free Microsoft Teams account, you can chat with colleagues, make voice and video calls, share files, and collaborate on projects from anywhere. Plus, with its seamless integration with other Microsoft products, such as Office 365 and SharePoint, Microsoft Teams can help streamline your organization's workflows and improve productivity.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="power-automate-environment"&gt;&lt;A id="pragma-line-29" target="_blank"&gt;&lt;/A&gt;Power Automate Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Microsoft provides a free Power Automate environment (&lt;A href="https://powerautomate.microsoft.com/en-us/#home-signup" target="_blank" rel="noopener"&gt;https://powerautomate.microsoft.com/en-us/#home-signup&lt;/A&gt;), which enables users to automate workflows across multiple applications and services. With Power Automate, you can create automated processes, such as approvals, notifications, and data synchronization, to save time and increase efficiency. Whether you're a business user or a developer, the Power Automate environment provides an intuitive and easy-to-use interface for creating and managing workflows. Plus, with its seamless integration with other Microsoft products, such as Teams and SharePoint, Power Automate can help streamline your organization's workflows and improve collaboration.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="power-automate-or-logic-apps-part"&gt;&lt;A id="pragma-line-32" target="_blank"&gt;&lt;/A&gt;Power Automate or Azure Logic Apps Part&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To automate the process of sending Adaptive Cards to Microsoft Teams after a ServiceNow ticket is closed, you can use either Power Automate or Azure Logic Apps. With these tools, you can create a flow that retrieves the necessary information from ServiceNow and then prepares and sends the Adaptive Card to Teams.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post, I am demonstrating how to use Power Automate to automate the process of sending Adaptive Cards to Microsoft Teams after closing a ServiceNow ticket. However, the process is similar if you decide to use Azure Logic Apps instead, so feel free to choose the tool that best suits your needs and preferences.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;It's worth noting that this solution utilizes Microsoft 365 seeded licenses and standard connectors, which makes it a cost-effective option for organizations looking to streamline their feedback process. Power Automate and Azure Logic Apps provide a range of pre-built connectors and templates that allow you to easily connect different apps and automate workflows, without the need for extensive coding or development work.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;By leveraging these tools, you can reduce manual effort and errors, improve collaboration and communication across teams, and gain valuable insights into the needs and preferences of your users. And with the use of seeded licenses and standard connectors, you can achieve all of this at a minimal cost, making it an ideal option for small and medium-sized businesses.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3&gt;Steps:&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-39"&gt;
&lt;LI id="pragma-line-39"&gt;
&lt;P&gt;Open a web browser and go to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://make.powerautomate.com/" target="_blank" rel="noopener"&gt;Power Automate portal&lt;/A&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-41"&gt;
&lt;P&gt;Sign into Power Automate using your Microsoft account or organizational account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-43"&gt;
&lt;P&gt;Once you're signed in, you should see the Power Automate portal dashboard. On the left-hand menu, click on "Create" to create a new flow.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-45"&gt;
&lt;P&gt;Choose a template from the available options or start from scratch by selecting "Instant - from blank".&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-49" start="5"&gt;
&lt;LI id="pragma-line-49"&gt;Give your flow a name, such as&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Closed Ticket Survey via ServiceNow&lt;/STRONG&gt;. Choose the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;When an HTTP request is received&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as the trigger, and click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-53" class=""&gt;
&lt;P&gt;HTTP triggers are a powerful feature in Power Automate that allows us to create custom APIs and automate processes with external systems. By creating a custom HTTP trigger, we can make the approval process more flexible and scalable, and enable other systems to interact with Microsoft Teams Approvals without having to go through ServiceNow.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To set up the "When a HTTP request is received" trigger, we need to define the input parameters that will be sent via a rule from ServiceNow (we will create this rule later).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-57" start="6"&gt;
&lt;LI id="pragma-line-57"&gt;Click on the "When a HTTP request is received" trigger to select it. Paste the following Request Body JSON Schema that will be sent by ServiceNow into the text box:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DETAILS&gt;
&lt;SUMMARY&gt;Click here to visualize the JSON&lt;/SUMMARY&gt;
&lt;LI-CODE lang="json"&gt;{
    "type": "object",
    "properties": {
        "number": {
            "type": "string"
        },
        "short_description": {
            "type": "string"
        },
        "description": {
            "type": "string"
        },
        "resolved_by_name": {
            "type": "string"
        },
        "resolved_by_email": {
            "type": "string"
        },
        "state": {
            "type": "string"
        },
        "closed_at": {
            "type": "string"
        },
        "opened_by_name": {
            "type": "string"
        },
        "opened_by_email": {
            "type": "string"
        },
        "closed_by_name": {
            "type": "string"
        },
        "closed_by_email": {
            "type": "string"
        },
        "opened_at": {
            "type": "string"
        },
        "resolved_at": {
            "type": "string"
        }
    }
}
&lt;/LI-CODE&gt;&lt;/DETAILS&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The next step is sending the adaptive card to Teams.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-123" start="7"&gt;
&lt;LI id="pragma-line-123"&gt;Add a new action after the "When a HTTP request is received" step. In the search box, type "Teams" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Post adaptive card and wait for a response&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-127" start="8"&gt;
&lt;LI id="pragma-line-127"&gt;
&lt;P&gt;Select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Chat with Flow bot&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Post in&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;type field.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-129"&gt;
&lt;P&gt;Copy and paste the following JSON code into the message field to create the Adaptive Card:&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DETAILS&gt;
&lt;SUMMARY&gt;Click here to visualize the JSON&lt;/SUMMARY&gt;
&lt;LI-CODE lang="json"&gt;
{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "ColumnSet",
            "horizontalAlignment": "Center",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "@{triggerBody()?['number']}",
                            "wrap": true,
                            "horizontalAlignment": "Left",
                            "weight": "Bolder",
                            "size": "Large"
                        },
                        {
                            "type": "TextBlock",
                            "spacing": "None",
                            "text": "@{triggerBody()?['short_description']}",
                            "wrap": true,
                            "size": "Large",
                            "horizontalAlignment": "Left"
                        },
                        {
                            "type": "TextBlock",
                            "text": "@{triggerBody()?['description']}",
                            "wrap": true,
                            "spacing": "Medium",
                            "horizontalAlignment": "Left"
                        }
                    ],
                    "width": "stretch"
                }
            ],
            "width": 1
        },
        {
            "type": "ColumnSet",
            "horizontalAlignment": "Center",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "ColumnSet",
                            "horizontalAlignment": "Center",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "url": "https://adaptivecards.io/content/LocationGreen_A.png",
                                            "altText": "Location A"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "**Opened at**",
                                            "wrap": true
                                        },
                                        {
                                            "type": "TextBlock",
                                            "spacing": "None",
                                            "text": "@{formatDateTime(triggerBody()?['opened_at'], 'yyyy-MM-dd')}",
                                            "wrap": true,
                                            "horizontalAlignment": "Center"
                                        }
                                    ],
                                    "width": "auto"
                                }
                            ]
                        }
                    ],
                    "width": 1
                },
                {
                    "type": "Column",
                    "spacing": "Large",
                    "separator": true,
                    "items": [
                        {
                            "type": "ColumnSet",
                            "horizontalAlignment": "Center",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "url": "https://messagecardplayground.azurewebsites.net/assets/LocationBlue_B.png",
                                            "altText": "Location B"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "**Resolved at**",
                                            "wrap": true
                                        },
                                        {
                                            "type": "TextBlock",
                                            "spacing": "None",
                                            "text": "@{formatDateTime(triggerBody()?['resolved_at'], 'yyyy-MM-dd')}",
                                            "wrap": true,
                                            "horizontalAlignment": "Center"
                                        }
                                    ],
                                    "width": "auto"
                                }
                            ]
                        }
                    ],
                    "width": 1
                },
                {
                    "type": "Column",
                    "spacing": "Large",
                    "separator": true,
                    "items": [
                        {
                            "type": "ColumnSet",
                            "horizontalAlignment": "Center",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "url": "https://messagecardplayground.azurewebsites.net/assets/LocationRed_C.png",
                                            "altText": "Location C"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "**Closed at**",
                                            "wrap": true
                                        },
                                        {
                                            "type": "TextBlock",
                                            "spacing": "None",
                                            "text": "@{formatDateTime(triggerBody()?['closed_at'], 'yyyy-MM-dd')}",
                                            "wrap": true
                                        }
                                    ],
                                    "width": "auto"
                                }
                            ]
                        }
                    ],
                    "width": 1
                }
            ],
            "spacing": "Medium"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "horizontalAlignment": "Left",
                                            "url": "https://messagecardplayground.azurewebsites.net/assets/clock.png",
                                            "altText": "Calendar conflict",
                                            "width": "15px",
                                            "height": "15px"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "spacing": "None",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{formatDateTime(triggerBody()?['opened_at'], 'HH:MM')}",
                                            "wrap": true,
                                            "spacing": "None",
                                            "horizontalAlignment": "Center"
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": "80px",
                    "verticalContentAlignment": "Center"
                },
                {
                    "type": "Column",
                    "backgroundImage": {
                        "url": "https://messagecardplayground.azurewebsites.net/assets/SmallVerticalLineGray.png",
                        "fillMode": "RepeatVertically",
                        "horizontalAlignment": "Center"
                    },
                    "items": [
                        {
                            "type": "Image",
                            "horizontalAlignment": "Center",
                            "url": "https://adaptivecards.io/content/LocationGreen_A.png",
                            "altText": "Opened by"
                        }
                    ],
                    "width": "auto",
                    "spacing": "None"
                },
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "**@{triggerBody()?['opened_by_name']}**",
                            "wrap": true
                        },
                        {
                            "type": "ColumnSet",
                            "spacing": "None",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{triggerBody()?['opened_by_email']}",
                                            "wrap": true,
                                            "spacing": "None"
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": 40
                }
            ],
            "separator": true,
            "spacing": "Medium"
        },
        {
            "type": "ColumnSet",
            "spacing": "None",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "horizontalAlignment": "Left",
                                            "url": "https://messagecardplayground.azurewebsites.net/assets/clock.png",
                                            "altText": "Calendar conflict",
                                            "width": "15px",
                                            "height": "15px"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "spacing": "None",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{formatDateTime(triggerBody()?['resolved_at'], 'HH:MM')}",
                                            "wrap": true,
                                            "spacing": "None",
                                            "horizontalAlignment": "Center"
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": "80px",
                    "verticalContentAlignment": "Center"
                },
                {
                    "type": "Column",
                    "backgroundImage": {
                        "url": "https://messagecardplayground.azurewebsites.net/assets/SmallVerticalLineGray.png",
                        "fillMode": "RepeatVertically",
                        "horizontalAlignment": "Center"
                    },
                    "items": [
                        {
                            "type": "Image",
                            "horizontalAlignment": "Center",
                            "url": "https://messagecardplayground.azurewebsites.net/assets/LocationBlue_B.png",
                            "altText": "Location B: Flight"
                        }
                    ],
                    "width": "auto",
                    "spacing": "None"
                },
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "**@{triggerBody()?['resolved_by_name']}**",
                            "wrap": true
                        },
                        {
                            "type": "ColumnSet",
                            "spacing": "None",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{triggerBody()?['resolved_by_email']}",
                                            "wrap": true
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": 40
                }
            ]
        },
        {
            "type": "ColumnSet",
            "spacing": "None",
            "columns": [
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "Image",
                                            "horizontalAlignment": "Left",
                                            "url": "https://messagecardplayground.azurewebsites.net/assets/clock.png",
                                            "altText": "Calendar conflict",
                                            "width": "15px",
                                            "height": "15px"
                                        }
                                    ],
                                    "width": "auto"
                                },
                                {
                                    "type": "Column",
                                    "spacing": "None",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{formatDateTime(triggerBody()?['closed_at'], 'HH:MM')}",
                                            "wrap": true,
                                            "spacing": "None",
                                            "horizontalAlignment": "Center"
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": "80px",
                    "verticalContentAlignment": "Center"
                },
                {
                    "type": "Column",
                    "backgroundImage": {
                        "url": "https://messagecardplayground.azurewebsites.net/assets/SmallVerticalLineGray.png",
                        "fillMode": "RepeatVertically",
                        "horizontalAlignment": "Center"
                    },
                    "items": [
                        {
                            "type": "Image",
                            "horizontalAlignment": "Center",
                            "url": "https://messagecardplayground.azurewebsites.net/assets/LocationRed_C.png",
                            "altText": "Location B: Flight"
                        }
                    ],
                    "width": "auto",
                    "spacing": "None"
                },
                {
                    "type": "Column",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "**@{triggerBody()?['closed_by_name']}**",
                            "wrap": true
                        },
                        {
                            "type": "ColumnSet",
                            "spacing": "None",
                            "columns": [
                                {
                                    "type": "Column",
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{triggerBody()?['closed_by_email']}",
                                            "wrap": true
                                        }
                                    ],
                                    "width": "stretch"
                                }
                            ]
                        }
                    ],
                    "width": 40
                }
            ]
        },
        {
            "type": "TextBlock",
            "text": "Rate your experience!",
            "weight": "Bolder",
            "color": "Accent",
            "size": "Medium",
            "spacing": "Medium"
        },
        {
            "type": "TextBlock",
            "text": "Please rate your experience! Your feedback is very appreciated and will help improve your experience in the future. ",
            "wrap": true
        },
        {
            "type": "Input.Text",
            "placeholder": "Add a comment",
            "isMultiline": true,
            "id": "comment"
        },
        {
            "type": "Input.ChoiceSet",
            "choices": [
                {
                    "title": "Terrible",
                    "value": "Terrible"
                },
                {
                    "title": "Poor",
                    "value": "Poor"
                },
                {
                    "title": "Fair",
                    "value": "Fair"
                },
                {
                    "title": "Good",
                    "value": "Good"
                },
                {
                    "title": "Excellent",
                    "value": "Excellent"
                }
            ],
            "placeholder": "Placeholder text",
            "isRequired": true,
            "id": "rating"
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.Submit",
                    "title": "Submit"
                }
            ]
        }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4",
    "msTeams": {
        "width": "full"
    }
}
&lt;/LI-CODE&gt;&lt;/DETAILS&gt;
&lt;BLOCKQUOTE id="pragma-line-713"&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The above Adaptive Card was generated using the Adaptive Cards Designer tool (&lt;A href="https://adaptivecards.io/designer" target="_blank" rel="noopener"&gt;https://adaptivecards.io/designer&lt;/A&gt;), which allows you to create visually appealing and functional cards without the need for coding. The card was designed using the following sample data JSON.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;DETAILS&gt;
&lt;SUMMARY&gt;Click here to visualize the JSON&lt;/SUMMARY&gt;
&lt;LI-CODE lang="json"&gt;
    "number": "INC0000060",
    "short_description": "Unable to connect to email",
    "description": "I am unable to connect to the email server. It appears to be down.",
    "resolved_by_name": "user 1",
    "resolved_by_email": "user@sample.com",
    "state": "",
    "closed_at": "2016-12-14 02:46:44",
    "opened_by_name": "user 2",
    "opened_by_email": "user2@sample.com",
    "opened_at": "2016-12-12 15:19:57",
    "resolved_at":"2016-12-13 21:43:14",
    "closed_by_name": "user 3",
    "closed_by_email": "user3@sample.com"

}
&lt;/LI-CODE&gt;&lt;/DETAILS&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-741" start="10"&gt;
&lt;LI id="pragma-line-741"&gt;Click on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Recipient&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;text field, and add the dynamic value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;opened_by_email&lt;/STRONG&gt;. This value will be replaced with the email address of the person who opened the ServiceNow ticket, allowing you to send the Adaptive Card directly to them in Microsoft Teams.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-745" start="11"&gt;
&lt;LI id="pragma-line-745"&gt;Add a new action after the "Post adaptive card and wait for a response" step. In the search box, type "Data Operation" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Parse JSON&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-749" start="12"&gt;
&lt;LI id="pragma-line-749"&gt;Next, click on the Content text field and enter the expression body('Post adaptive card and wait for a response'). This expression will allow the flow to retrieve the JSON payload generated by the Adaptive Card and send it as the content of the message to Microsoft Teams. Finally, click on the Save button to save your changes.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-753" start="13"&gt;
&lt;LI id="pragma-line-753"&gt;Copy and paste the following JSON code into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Schema&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;field to get the response of the survey:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DETAILS&gt;
&lt;SUMMARY&gt;Click here to visualize the JSON&lt;/SUMMARY&gt;
&lt;LI-CODE lang="json"&gt;
{
    "type": "object",
    "properties": {
        "data": {
            "type": "object",
            "properties": {
                "comment": {
                    "type": "string"
                },
                "rating": {
                    "type": "string"
                }
            }
        }
    }
}
&lt;/LI-CODE&gt;&lt;/DETAILS&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Next, you'll want to save the survey results returned from Microsoft Teams in a SharePoint List for further analysis and processing. To do so, follow the steps outlined below.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;If you're interested in taking the feedback process to the next level, you might want to check out the post &lt;A title="How to track the user experience of your Bot with Azure, Power BI and Microsoft Teams" href="https://www.linkedin.com/pulse/how-track-user-experience-your-bot-azure-power-bi-teams-kinzelin" target="_blank" rel="noopener"&gt;How to track the user experience of your Bot with Azure, Power BI and Microsoft Teams&lt;/A&gt;, that was written by&amp;nbsp;&lt;A href="https://fr.linkedin.com/in/alexis-kinzelin-214b6b20/en?trk=article-ssr-frontend-pulse_main-author-card" target="_blank" rel="noopener"&gt;Alexis Kinzelin&lt;/A&gt;&amp;nbsp;(Cloud Solution Architect at Microsoft). In this post, Alexis shows you how to create a Power BI dashboard from the data stored on the SharePoint List, allowing you to easily visualize and analyze the feedback data and gain valuable insights into the performance and user experience of your Bot.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;By integrating Power BI with Azure and Microsoft Teams, you can create a seamless end-to-end feedback process that allows you to collect, analyze, and act on feedback from your users in a timely and efficient manner. So be sure to check out our post and take your feedback process to the next level!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-813" start="14"&gt;
&lt;LI id="pragma-line-813"&gt;After completing the "Parse JSON" step, you'll need to add a new action to create a new item in your SharePoint List. To do so, follow the steps below:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-815" start="9" type="a"&gt;
&lt;LI id="pragma-line-815"&gt;Click on the "Add an action" button.&lt;/LI&gt;
&lt;/OL&gt;
&lt;OL id="pragma-line-817" start="2" type="i"&gt;
&lt;LI id="pragma-line-817"&gt;
&lt;P&gt;In the search box, type "SharePoint" and select the Create item action.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-819"&gt;
&lt;P&gt;Select the appropriate SharePoint Site, List, and Folder where you want to create the new item.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-824" start="4" type="i"&gt;
&lt;LI id="pragma-line-824"&gt;Fill in the fields for each column in your SharePoint List, using the dynamic values generated by the Adaptive Card survey response. This will allow you to capture and store the survey responses in your SharePoint List for further analysis and reporting.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-828" start="15"&gt;
&lt;LI id="pragma-line-828"&gt;Save the flow.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-830"&gt;
&lt;P&gt;Note that when you save your trigger, you will be given a URL that you can use to invoke the trigger from external systems.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;H2&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="set-up-service-now-business-rule"&gt;&lt;A id="pragma-line-832" target="_blank"&gt;&lt;/A&gt;Set up ServiceNow Business Rule&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Business Rules are server-side logic that execute when database records are queried, updated, inserted, or deleted. For more details, please check the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.servicenow.com/en-US/bundle/rome-application-development/page/script/business-rules/concept/c_BusinessRules.html" target="_blank" rel="noopener"&gt;Service Now Business Rules documentation&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To configure a ServiceNow Business Rule that triggers a request to Microsoft Power Automate when an incident is closed, follow the steps outlined below:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-837"&gt;
&lt;LI id="pragma-line-837"&gt;Navigate to the Business Rules module in ServiceNow. This can be done by typing "Business Rules" in the navigation bar and selecting the appropriate option.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-841" start="2"&gt;
&lt;LI id="pragma-line-841"&gt;Click the "New" button to create a new business rule:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-846" start="3"&gt;
&lt;LI id="pragma-line-846"&gt;
&lt;P&gt;Give the business rule a name and description that accurately describes its purpose. For example,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Incident Closed Survey&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-848"&gt;
&lt;P&gt;Choose the table to which the business rule applies. This can be done by selecting the table from the drop-down menu in the "Applies to" field. For example,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Incident [incident]&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-850"&gt;
&lt;P&gt;Specify the conditions that must be met for the business rule to run. This is done by adding conditions to the "When to run" section of the business rule. For example, you could specify that the rule should run after a new record is created or updated.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-852"&gt;
&lt;P&gt;Add a filter condition to consider only closed tickets (state is closed).&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-856" start="7"&gt;
&lt;LI id="pragma-line-856"&gt;Click on the Advanced tab to define the actions that the business rule should take when it runs. This is done by adding script to the "Script" section of the business rule. The script can be written in JavaScript and can perform a variety of actions, such as updating a field, sending an email notification, or creating a related record.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-858"&gt;
&lt;P&gt;It's important to note that creating a business rule in ServiceNow requires some knowledge of JavaScript and the ServiceNow platform. If you're not familiar with these concepts, it may be helpful to seek assistance from a ServiceNow administrator or developer.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Add the following code:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DETAILS&gt;
&lt;SUMMARY&gt;Click here to visualize the JSON&lt;/SUMMARY&gt;
&lt;LI-CODE lang="json"&gt;
(function executeRule(current, previous /*null when async*/) {

	gs.info("Incident Survey rule is running");
	
	var snowURL = "https://dev78230.service-now.com/";
	
	var number = current.getValue("number");	
	var shortDescription = current.getValue("short_description");
	var description = current.getValue("description");
	var stateId = current.getValue("state");
	
	var openedAt = current.getValue("opened_at");
	var resolvedAt = current.getValue("resolved_at");
	
	var closedAt = current.getValue("closed_at");
	
        var statusDescription;
	
	var openedById = current.getValue("caller_id");
	var openedByName;
	var openedByEmail;
	
	var resolvedById = current.getValue("resolved_by");
	var resolvedByName;
	var resolvedByEmail;
	
	var closedById = current.getValue("closed_by");
	var closedByName;
	var closedByEmail;
	
	//this isnt the best practice. Consider change it to oauth2 authentication
	var user = 'admin';
	var password = 'CHANGE HERE';
	
	//GET THE NAME OF WHO RESOLVED THE CASE
	var resolvedByRequest = new sn_ws.RESTMessageV2();
	
	resolvedByRequest.setBasicAuth(user,password);
	resolvedByRequest.setRequestHeader("Accept","application/json");
	resolvedByRequest.setRequestHeader('Content-Type','application/json');
	
	resolvedByRequest.setEndpoint(snowURL + "api/now/table/sys_user?sysparm_query=sys_idIN"+ resolvedById +"&amp;amp;sysparm_fields=sys_id,name,email");

	resolvedByRequest.setHttpMethod("GET");		
	
	var resolvedByResponse = resolvedByRequest.execute();

	var resolvedByBody = resolvedByResponse.getBody();

	var resolvedByJson = new JSONParser().parse(resolvedByBody);

	resolvedByName = resolvedByJson.result[0].name;
	resolvedByEmail = resolvedByJson.result[0].email;
	
	
	gs.info("Incident Survey: name: " + resolvedByName + ", email: " + resolvedByEmail);
	
	//GET THE NAME OF WHO OPENED THE CASE
	var openedByRequest = new sn_ws.RESTMessageV2();
	
	openedByRequest.setBasicAuth(user,password);
	openedByRequest.setRequestHeader("Accept","application/json");
	openedByRequest.setRequestHeader('Content-Type','application/json');
	
	openedByRequest.setEndpoint(snowURL + "api/now/table/sys_user?sysparm_query=sys_idIN"+ openedById +"&amp;amp;sysparm_fields=sys_id,name,email");

	openedByRequest.setHttpMethod("GET");		
	
	var openedByResponse = openedByRequest.execute();

	var openedByBody = openedByResponse.getBody();

	var openedByJson = new JSONParser().parse(openedByBody);

	openedByName = openedByJson.result[0].name;
	openedByEmail = openedByJson.result[0].email;
 

	//GET THE NAME OF WHO CLOSED THE CASE
	var closedByRequest = new sn_ws.RESTMessageV2();
	
	closedByRequest.setBasicAuth(user,password);
	closedByRequest.setRequestHeader("Accept","application/json");
	closedByRequest.setRequestHeader('Content-Type','application/json');
	
	closedByRequest.setEndpoint(snowURL + "api/now/table/sys_user?sysparm_query=sys_idIN"+ closedById +"&amp;amp;sysparm_fields=sys_id,name,email");

	closedByRequest.setHttpMethod("GET");		
	
	var closedByResponse = closedByRequest.execute();

	var closedByBody = closedByResponse.getBody();

	var closedByJson = new JSONParser().parse(closedByBody);

	closedByName = closedByJson.result[0].name;
	closedByEmail = closedByJson.result[0].email;

	//CALL POWER AUTOMATE TO SEND TO TEAMS APPROVALS
    try {

		var body = {
				"number": number,
				"short_description": shortDescription,
				"description": description,
				"resolved_by_name": resolvedByName,
				"resolved_by_email": resolvedByEmail,
				"state":  stateId,
				"closed_at": closedAt,
				"opened_by_name": openedByName,
				"opened_by_email": openedByEmail,
				"closed_by_name": closedByName,
				"closed_by_email": closedByEmail,
				"opened_at": openedAt,
				"resolved_at": resolvedAt
		};
		
        var r = new sn_ws.RESTMessageV2();
        
        // Change the following URL by the one you have created as HTTP trigger on Power Automate
		r.setEndpoint("https://prod-96.westus.logic.azure.com:443/workflows/ee1cfb87108044f5affdeab515871234/triggers/manual/paths/invoke?api-version=2016-06-01&amp;amp;sp=%2Ftriggers%2Fmanual%2Frun&amp;amp;sv=1.0&amp;amp;sig=xndVM5q0vLUdIK0GG6mrPb1-_5pjaqfE2oKj0RHHtJI");
		
		r.setHttpMethod("POST");
        r.setRequestBody(JSON.stringify(body));
	
		r.setRequestHeader("Accept","application/json");
		r.setRequestHeader('Content-Type','application/json');
      
        var response = r.execute();
        var httpStatus = response.getStatusCode();
		
    } catch (ex) {
        var message = ex.message;
		gs.error("Error message: " + message);
    }

    gs.info("Webhook target HTTP status response: " + httpStatus);
  
    
})(current, previous);
&lt;/LI-CODE&gt;&lt;/DETAILS&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1015" start="8"&gt;
&lt;LI id="pragma-line-1015"&gt;Click on Submit to save it.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The next step is to close an incident and verify if the notification is sent to Power Automate, and subsequently to Teams. This will allow you to ensure that the integration is working correctly and that the survey feedback process is functioning as expected.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1023" start="9"&gt;
&lt;LI id="pragma-line-1023"&gt;To access the list of open incidents in ServiceNow, follow the steps below:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1025" start="9" type="a"&gt;
&lt;LI id="pragma-line-1025"&gt;Click on "Service Desk" in the ServiceNow menu.&lt;/LI&gt;
&lt;/OL&gt;
&lt;OL id="pragma-line-1026" start="2" type="i"&gt;
&lt;LI id="pragma-line-1026"&gt;Select "Incidents" from the drop-down menu.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1030" start="3" type="i"&gt;
&lt;LI id="pragma-line-1030"&gt;This will take you to the list of currently open incidents. Here, you can view the details of each incident and take appropriate action as needed.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1034" start="10"&gt;
&lt;LI id="pragma-line-1034"&gt;Close an incident:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1038" start="11"&gt;
&lt;LI id="pragma-line-1038"&gt;Switch back to Microsoft Teams and observe that your the survey was sent:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-1042" start="12"&gt;
&lt;LI id="pragma-line-1042"&gt;Once the user has responded to the survey, the flow will return to Power Automate, which will then save the survey results to the designated SharePoint List.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This allows you to easily collect and analyze feedback from your users, and track any changes or trends over time.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="conclusion"&gt;&lt;A id="pragma-line-1048" target="_blank"&gt;&lt;/A&gt;Conclusion&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post, we've explored how to use Microsoft Teams and ServiceNow to collect and analyze user feedback using Adaptive Cards and SharePoint Lists. By integrating these tools, you can streamline the feedback process, improve collaboration across teams, and gain valuable insights into the needs and preferences of your users.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;We've covered the steps involved in setting up the integration, from creating the Adaptive Card survey to configuring the Power Automate flow and SharePoint List. By following these steps, you can easily implement the feedback process in your organization and start collecting valuable insights from your users.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;We hope you found this post useful and informative. If you have any feedback or suggestions on how to improve this integration, please share them in the comments below. We look forward to hearing from you!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 17 Apr 2023 14:05:46 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/using-adaptive-cards-to-collect-feedback-on-servicenow-tickets/ba-p/3781124</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2023-04-17T14:05:46Z</dc:date>
    </item>
    <item>
      <title>Build a Viva Connections card to display stock prices - Part 3: deployment and security</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-3/ba-p/3774021</link>
      <description>&lt;P&gt;Welcome to the final post in the series about building a Viva Connections card to display stock prices! After building the backend&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-1-the/ba-p/3773345" target="_blank" rel="noopener"&gt;in the first post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and the card&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-2-the/ba-p/3773938" target="_blank" rel="noopener"&gt;in the second post&lt;/A&gt;, now it's time to deploy our solution and to take a deeper look at the security of our API.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let's start with the deployment of the backend!&lt;/P&gt;
&lt;H3 id="deploying-the-backend"&gt;Deploying the backend&lt;/H3&gt;
&lt;P&gt;Our backend is made by an Azure Function and an Azure Cache for Redis instance. The easiest way to deploy these components is to use a template, so that we don't have to manually create all the services and configure them. On the GitHub repository of the project, you can find&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/viva-connections-stocks/blob/master/function/main.bicep" target="_blank" rel="noopener"&gt;a Bicep template&lt;/A&gt;.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview" target="_blank" rel="noopener"&gt;Bicep&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is a powerful language that you can use to deploy Azure resources and define dependencies, properties, etc.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The template defines the following resources:&lt;/P&gt;
&lt;UL id="pragma-line-11"&gt;
&lt;LI id="pragma-line-11"&gt;A web app hosting plan, using the Y1 SKU, which hosts the Azure Function.&lt;/LI&gt;
&lt;LI id="pragma-line-12"&gt;An Azure Function app, hosted on Linux and which runs the .NET runtime. The function already includes, in the application settings, the properties we have set in the first post to manage the cache, like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheConnection&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheExpirationInHours&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-13"&gt;An Azure Storage instance, which is required by the Azure Function to work since it's where all the logs are stored.&lt;/LI&gt;
&lt;LI id="pragma-line-14"&gt;An Azure Cache for Redis instance. Thanks to the Bicep syntax, we can specify that the Azure Function is dependent on the cache, so that it will be created first. This will enable the URL assigned to the instance to be automatically injected into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheConnection&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;setting in the Azure Function.&lt;/LI&gt;
&lt;LI id="pragma-line-15"&gt;An Azure Application Insights instance, which is used to collect analytics and errors on the Azure Function.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;The easiest way to deploy the Bicep template is to use Visual Studio Code, by installing&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep" target="_blank" rel="noopener"&gt;the dedicated extension&lt;/A&gt;. Once you have installed it, click on the Azure icon on the left sidebar and make sure to login with an account which has the permission to create resources on your Azure subscription.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now open the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;main.bicep&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file with Visual Studio Code. Press&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;CTRL-SHIFT-P&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, from the command palette, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Bicep: Deploy Bicep file&lt;/STRONG&gt;. You will be asked for a series of information:&lt;/P&gt;
&lt;OL id="pragma-line-22"&gt;
&lt;LI id="pragma-line-22"&gt;The name of the deployment (you can leave the default one).&lt;/LI&gt;
&lt;LI id="pragma-line-23"&gt;The target subscription in which to deploy the resources.&lt;/LI&gt;
&lt;LI id="pragma-line-24"&gt;The resource group where to include the resources (it’s suggested to create a new one dedicated to this project).&lt;/LI&gt;
&lt;LI id="pragma-line-25"&gt;The Azure region where to deploy the resources.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;Then you will be asked if you want to provide a parameter file with the settings of the resources. We won’t use one, but we’ll provide the information as part of the deployment. As such, pick&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;None&lt;/STRONG&gt;. This will trigger a series of additional questions:&lt;/P&gt;
&lt;OL id="pragma-line-29"&gt;
&lt;LI id="pragma-line-29"&gt;The name to assign to the Azure Cache for Redis service. You can use the first option to automatically generate one or you can specify a custom one.&lt;/LI&gt;
&lt;LI id="pragma-line-30"&gt;The name to assign to the Azure Function. You can use the first option to automatically generate one or you can specify a custom one.&lt;/LI&gt;
&lt;LI id="pragma-line-31"&gt;The Azure region in which to deploy the resources. Use the default value (&lt;CODE&gt;resourseGroup().location&lt;/CODE&gt;) to create the resources in the same region you have chosen to host the resource group.&lt;/LI&gt;
&lt;LI id="pragma-line-32"&gt;The runtime used by the Azure Function. Leave the default value, which is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;dotnet&lt;/CODE&gt;.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;As the last question, you will be asked if you want to create a parameters file with the values you have just specified. Choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Yes&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if you’re planning to repeat the process in the future, otherwise say&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;No&lt;/STRONG&gt;. At this point, the deployment process will start. In the Output window of Visual Studio Code, you will see a URL that you can open to check the status of the deployment:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;When you open the URL, you will be redirected to the Azure portal to see the deployment in action:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Most of the resources will be deployed very quickly, while the Azure Cache for Redis service will require a while. Don’t worry, it’s expected. Once the deployment is completed, you will find in the resource group you have chosen all the resources which are required to host the solution.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="deploying-the-azure-function"&gt;Deploying the Azure Function&lt;/H3&gt;
&lt;P&gt;The previous step has created on Azure all the services which are required to host our solution, but it didn’t deploy the solution itself. Since we have built the Azure Function with Visual Studio 2022, we can use the built-in Publish feature to deploy it. Right click on the project and choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Publish&lt;/STRONG&gt;. Click&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;New&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to start the wizard to generate a publish profile:&lt;/P&gt;
&lt;UL id="pragma-line-50"&gt;
&lt;LI id="pragma-line-50"&gt;&lt;STRONG&gt;Target:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;choose Azure.&lt;/LI&gt;
&lt;LI id="pragma-line-51"&gt;&lt;STRONG&gt;Specific target:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;choose Azure Function App (Linux).&lt;/LI&gt;
&lt;LI id="pragma-line-52"&gt;&lt;STRONG&gt;Functions instance:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;pick your Azure subscription and look for the Azure Function instance that you have just created with the Bicep template.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;- Deployment type:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for this article, we'll choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Publish (generates pubxml file)&lt;/STRONG&gt;. However, if you want a better long-term solution, you can choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;CI/CD using GitHub Actions&lt;/STRONG&gt;, which will generate a workflow for GitHub Actions that can automatically build and deploy an updated version of the Azure Functions every time you commit changes to your code.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Once the process is completed, Visual Studio will display an overview of the deployment:&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;Click on &lt;STRONG&gt;Publish&lt;/STRONG&gt; to start the deployment process. Once the operation is completed, you should be all set. To test it, try to use Postman or open your browser against the URL&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;https://&amp;lt;your-function-url/api/stockPrices/MSFT&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and you should see the JSON with the latest snapshot about the Microsoft stock.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Before using the API in our card, there's an extra step we must follow. Do you remember that, in the previous post, we had to enable CORS in the local Azure Functions configuration file so that the Viva Connections dashboard would have been able to reach the API? We must do the same also now that we have published the function on Azure, since the domain which hosts it is different from SharePoint's domain. Go to the Azure portal, click on the Azure Function you have just deployed and look for the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;CORS&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;option in the left panel, under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;API&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section. Under the allowed origins section, add the URL of your SharePoint website, like in the following example, then hit&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Save&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-66"&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if you have followed&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-1-the/ba-p/3773345" target="_blank" rel="noopener"&gt;the first post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step by step and you have already created an Azure Cache for Redis instance, you can skip using the Bicep template. In this case, however, you will have to:&lt;/P&gt;
&lt;UL id="pragma-line-68"&gt;
&lt;LI id="pragma-line-68"&gt;In the Publish wizard in Visual Studio, instead of picking an existing Azure Functions instance, you must click on Create New and follow the wizard to create a new instance.&lt;/LI&gt;
&lt;LI id="pragma-line-69"&gt;Once the Azure Function is up &amp;amp; running, you must open it in the Azure portal, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Configuration&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Application Settings&lt;/STRONG&gt;, add two keys:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheConnection&lt;/CODE&gt;, with the full connection string to the Azure Cache for Redis instance, and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheExpirationInHours&lt;/CODE&gt;, with the amount of time in hours that must pass before the cache is invalidated.&lt;/LI&gt;
&lt;/UL&gt;
&lt;/BLOCKQUOTE&gt;
&lt;H3 id="deploy-the-viva-connections-card"&gt;Deploy the Viva Connections card&lt;/H3&gt;
&lt;P&gt;Viva Connections cards are considered SharePoint applications, so they are deployed through the SharePoint administrator center. The first step is to generate a package. Open Visual Studio Code on your card project, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Terminal → New terminal&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and run the two following commands:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-75" class="language-powershell hljs"&gt;gulp bundle --ship

gulp package-solution --ship
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The first command will package all the required JavaScript files into a single bundle. The second one will generate a file with .sspkg extension under the sharepoint folder of your project. This is the package we must upload to the SharePoint admin center. To access the admin center, you must open your browser on the URL&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;https://&amp;lt;your-sharepoint-tenant&amp;gt;-admin.sharepoint.com/&lt;/CODE&gt;. For example, if your company is called Contoso, the URL will be&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="https://contoso-admin.sharepoint.com/" target="_blank" rel="noopener"&gt;https://contoso-admin.sharepoint.com/&lt;/A&gt;&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-84"&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;you must have administrator rights on SharePoint to be able to access the portal and perform the next steps.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;From the panel on the left, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;More features&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and click the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Open&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;button under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Apps&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Once you are in the App Catalog, click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Upload&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;and look on your computer for the file with .sspkg extension that you have previously created. Once the file has been uploaded, you can choose to just enable the application or to also add it automatically to all the existing SharePoint sites. Pick up the option that makes more sense for your scenario. The second one makes your work easier, because the card will be already available when you start configuring your Viva Connections dashboard; otherwise, you will need to manually add it to the SharePoint site which hosts your dashboard.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;That's it. Now if you head to the SharePoint website which hosts your Viva Connections Dashboard, click on&amp;nbsp;&lt;STRONG&gt;Manage Viva Connections&lt;/STRONG&gt;&amp;nbsp;and enter editing mode, you will see your Stocks card among the available ones you can add to the dashboard:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;You will also have the option, by clicking on the pencil icon, to open the property panel and customize the properties we have defined&amp;nbsp;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-2-the/ba-p/3773938" target="_blank" rel="noopener"&gt;in the second post&lt;/A&gt;, like the Azure Functions URL and the stock symbol to track.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="secure-our-backend"&gt;Secure our backend&lt;/H3&gt;
&lt;P&gt;If you followed the article so far, you will have a working card on your Viva Connections dashboard displaying the stock price of a company. However, as we anticipated&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-2-the/ba-p/3773938" target="_blank" rel="noopener"&gt;in the previous post&lt;/A&gt;, the current implementation has a security flaw. The Azure Function URL is public, and it isn't protected in any way. Anyone with the full URL of our backend would be able to use the service we've built in their solutions and projects. From a security perspective, it might not be a big problem: in the end, we aren't returning any sensitive information; it might be, however, from a cost perspective, because we could get unexpected expensive bills from Azure or Alpha Vantage (in case we opted in for a paid plan) because someone else abused our API.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The best way to protect our API in our scenario is to use&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-overview" target="_blank" rel="noopener"&gt;the Microsoft Identity platform&lt;/A&gt;. Since our card is hosted on SharePoint, which is a platform that requires the user to be authenticated with their work account, we can leverage single sign-on. We won't have to ask the user to login to use the API, since they are already authenticated. There are two changes we must make to support this scenario:&lt;/P&gt;
&lt;UL id="pragma-line-105"&gt;
&lt;LI id="pragma-line-105"&gt;One in the backend, to protect the API. We won't have to change the code, since we'll leverage the built-in Authentication feature in Azure.&lt;/LI&gt;
&lt;LI id="pragma-line-106"&gt;One in the card, to make authenticated calls against the protected API. The HTTP request must include the Authorization header with a valid token.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Let's see step by step the changes to make.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H4 id="secure-the-azure-function"&gt;Secure the Azure Function&lt;/H4&gt;
&lt;P&gt;As mentioned, we won't have to change the code of our Azure Function, since we're going to use the built-in authentication support provided by Azure. Open the Azure portal, go to the Azure Function you have previously deployed and, in the left sidebar, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Authentication&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In the page, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add identity provider&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and pick&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Microsoft&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the list. This process will start a wizard that creates a new app registration on Azure Active Directory, which will be used to support SSO. Make sure to set the following options:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-117"&gt;
&lt;LI id="pragma-line-117"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;App registration type&lt;/STRONG&gt;, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create new app registration&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-118"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Name&lt;/STRONG&gt;, call it&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Stock Prices&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-119"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Supported account types&lt;/STRONG&gt;, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Current tenant – Single tenant&lt;/STRONG&gt;. This will ensure that only our employees will be able to use the API.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;Then configure the App Service authentication settings section with the following settings:&lt;/P&gt;
&lt;OL id="pragma-line-123"&gt;
&lt;LI id="pragma-line-123"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Restrict access&lt;/STRONG&gt;, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Require authentication&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-124"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Unauthenticated requests&lt;/STRONG&gt;, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;HTTP 401 Unauthorized&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Click&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to complete the authentication process. Our Azure Function is now protected and, if you try to hit again the URL with Postman or your browser, you will get back a 401 Unauthorized error.&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-130"&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;the previous guidance assumes that your Azure Active Directory tenant is hosted on the same domain as your SharePoint tenant. In case they are hosted on different domains, you must create the AAD app registration manually and supply the various information (application id, tenant id, client secret) to the Azure Functions configuration. You will also need to choose the option&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Any Azure AD directory - Multi tenant&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Supported account types&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;To use the AAD app we have just registered to protect our API, we must make a final change in the app configuration. Still in the Azure portal, go to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Azure Active Directory&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section and, from the sidebar, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;App registrations&lt;/STRONG&gt;. Look for the application called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Stock Prices&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;that you have just created and go to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Expose an API&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section. Click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add a scope&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and use the following settings:&lt;/P&gt;
&lt;UL id="pragma-line-134"&gt;
&lt;LI id="pragma-line-134"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Scope name&lt;/STRONG&gt;, type&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;access_as_user&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-135"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Who can consent?&lt;/STRONG&gt;, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Admins and users&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-136"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Admin content display name&lt;/STRONG&gt;, type&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Access the API on behalf of a user&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-137"&gt;Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Admin consent description&lt;/STRONG&gt;, type&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Access the API on behalf of a user&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI id="pragma-line-138"&gt;Make sure&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;State&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is set to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Enabled&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Click&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Save&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;to complete the process. Now our API is protected, so we can move on and make the required changes to the card.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H4 id="secure-the-card"&gt;Secure the card&lt;/H4&gt;
&lt;P&gt;From a data manipulation perspective, the code of our card doesn't change. We still need to perform a HTTP GET to our API and parse the JSON we receive back with the stock data about the company we have chosen. The difference, however, is the way we perform the HTTP GET, since now we must include inside the request a valid token to authenticate ourselves. Luckily, the SharePoint Framework makes this operation easy: when employees are using Viva Connections (or visiting any SharePoint site), they're already logged in with their work identity, so SPFx includes a special HTTP client which is already authenticated through single sign-on. We just need to use it instead of the traditional&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetch()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;API. First, let's add the following statement at the top of our&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-148" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;import&lt;/SPAN&gt; { AadHttpClient } &lt;SPAN class="hljs-keyword"&gt;from&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;'@microsoft/sp-http'&lt;/SPAN&gt;;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Then, let's change the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function as following:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-154" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; fetchData(): &lt;SPAN class="hljs-built_in"&gt;Promise&lt;/SPAN&gt;&amp;lt;&lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt;&amp;gt; { 

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; url = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.apiUrl + &lt;SPAN class="hljs-string"&gt;"/"&lt;/SPAN&gt; + &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.stockSymbol;

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; aadClient = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.context.aadHttpClientFactory.getClient(&lt;SPAN class="hljs-string"&gt;"api://c94af769-ef30-4d37-a660-b602aab24d5b"&lt;/SPAN&gt;);
    &lt;SPAN class="hljs-keyword"&gt;let&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; aadClient.get(url, AadHttpClient.configurations.v1);

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; json = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; response.text();
    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; parsedJson: IStockPrice = &lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.parse(json) &lt;SPAN class="hljs-keyword"&gt;as&lt;/SPAN&gt; IStockPrice;

    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.setState({stock: parsedJson});
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;getClient()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function, exposed by&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;this.context.aadHttpClientFactory&lt;/CODE&gt;, to get an authenticated client. As parameter, we must pass the special URL&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;api://&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;followed by the app id of the AAD app that we have previously registered. We can find this URL under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Expose an API&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section of our AAD app:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Once we have an authenticated client, we perform the HTTP GET request by calling the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;get()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method and passing, as parameters, the URL of our Azure Function (which is stored in the properties) and the version to use, through the constant&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;AadHttpClient.configurations.v1&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;This is the only change we must make. The rest of the code is the same: we take the response, turn it into a string and then parse it as an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object.&lt;/P&gt;
&lt;P&gt;The final change we must make is in the manifest that is generated when we create the package to upload on the SharePoint admin portal. We must declare, in fact, that our card is using an external API. You can find this configuration in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;package-solution.json&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;config&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;folder. Make sure to add, under the solution property, the following entry:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV class="code-badge-language"&gt;json&lt;/DIV&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-179" class="language-json hljs"&gt;{
  &lt;SPAN class="hljs-attr"&gt;"$schema"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json"&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"solution"&lt;/SPAN&gt;: {
    &lt;SPAN class="hljs-attr"&gt;"webApiPermissionRequests"&lt;/SPAN&gt;: [
      {
        &lt;SPAN class="hljs-attr"&gt;"resource"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Stock Prices"&lt;/SPAN&gt;,
        &lt;SPAN class="hljs-attr"&gt;"scope"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"access_as_user"&lt;/SPAN&gt;
      }
    ]
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The value of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;resource&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property must match the name we have assigned to the app registration we have created in our AAD tenant.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The last step is to repeat the two&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;gulp&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;based commands we have previously seen to generate a new .sspkg package and upload it on the Apps section of the SharePoint admin portal. This time, after having chosen if we want to only enable the app or to automatically add it to all the sites, we will see a new prompt:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;We must approve API access for our card, otherwise it won't be able to perform authenticated requests. Click on&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Go to API Access page&lt;/STRONG&gt;&lt;SPAN&gt;, in which you will find a pending request:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approve&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to complete the process.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;That's it. Now if you add the card again to your Viva Connections dashboard, everything should continue to work as before. The difference, however, is that the API is now protected and only your card will be able to retrieve the stock data. Any user outside the company, even if they know the URL of your Azure Function, won't be able to access the data: they will just get a 401 Unauthorized response.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-207"&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;the project that you will find on the GitHub repository supports both authenticated and unauthenticated requests, through a new property added to the panel called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;AAD App Id&lt;/STRONG&gt;. When you add the card to the dashboard, you can use this property to specify the App ID of the AAD application you have created to support single sign-on. If the property is set, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function will use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;AadHttpClient&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to perform the authenticated request; if the property is left empty, instead, it will use the standard unauthenticated&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetch()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;API.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this final post of the series, we have learned how to take our stock card for Viva Connections in production, by deploying the backend on Azure and by uploading the card as a SharePoint app in the administrator portal. We have also learned how to protect our API, which is optional but highly suggested to avoid unauthorized users abusing our backend.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Everything we have learned so far is available&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/viva-connections-stocks/" target="_blank" rel="noopener"&gt;on GitHub&lt;/A&gt;. The repository includes the Azure Function (in the function folder), the card (in the card folder) and the Bicep template to deploy the Azure resources (again, in the function folder).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;</description>
      <pubDate>Wed, 22 Mar 2023 14:00:00 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-3/ba-p/3774021</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-03-22T14:00:00Z</dc:date>
    </item>
    <item>
      <title>Build a Viva Connections card to display stock prices - Part 2: the card</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-2-the/ba-p/3773938</link>
      <description>&lt;P&gt;Welcome back to our series of posts about building a Viva Connections card to display the stock price of a company!&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A&gt;In the previous &lt;/A&gt;post we&amp;nbsp;built the backend which provides the data we need to display. The backend was built using an Azure Function, in companion with the Azure Cache for Redis service to improve performance and reduce the number of API calls against&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/" target="_blank" rel="noopener"&gt;Alpha Vantage&lt;/A&gt;, the free service we have chosen to retrieve stock information.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post, we're going to build the actual Viva Connections card, using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/sharepoint/dev/spfx/viva/get-started/build-first-sharepoint-adaptive-card-extension" target="_blank" rel="noopener"&gt;SharePoint AdaptiveCard extension model&lt;/A&gt;.&amp;nbsp;&lt;SPAN&gt;As a reminder, this is the look &amp;amp; feel of the actual card we're going to build:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;SharePoint Adaptive Cards supports three types of templates. In our case, we're going to choose the one called Primary Text, which supports displaying a title, a primary text, and a description. As you can notice from the screenshot, the card doesn't offer too much space to display information. This is where the Quick View feature comes into play. When we click or tap on the See more button, we'll open a panel that will be rendered using an adaptive card and that we can use to display more information and more detailed content.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now that we have understood what we're going to build, let's start to build it!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="setting-up-the-project"&gt;Setting up the project&lt;/H3&gt;
&lt;P&gt;Viva Connections is a Teams application, but it's part of the SharePoint ecosystem. The Viva Connections dashboard, in fact, is hosted on the same intranet SharePoint used by the company. As such, the developer story for Viva Connections is based on SharePoint and the same type of extensions you can build for SharePoint (like web parts) can be used also for Viva Connections. However, there's one type of extension that was recently added that is tailored specifically for Viva Connections, which is the SharePoint Adaptive Card. The name comes from the fact that parts of the layout of the card can be defined using&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://adaptivecards.io/" target="_blank"&gt;Adaptive Cards&lt;/A&gt;, a popular technology in the Microsoft 365 ecosystem to render UI components starting from a JSON template.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;On this blog, I already walked you through the steps required to create a SharePoint Adaptive Card extension on Windows, so you can head&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-viva-connections-extensions-using-spfx-with-windows-and/ba-p/3617543" target="_blank"&gt;to the article&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to discover all the requirements you need.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this blog post, we'll assume you have satisfied all the requirements, so just open a terminal, create a folder where to store your project and type the following command:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-19" class="language-powershell hljs"&gt;yo @microsoft/sharepoint
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;You will start a wizard that will guide you through the creation of the project. Make sure to choose the following values:&lt;/P&gt;
&lt;UL id="pragma-line-25"&gt;
&lt;LI id="pragma-line-25"&gt;&lt;STRONG&gt;What is your solution name?&lt;/STRONG&gt;: Choose the name you prefer, I used viva-connections-stocks&lt;/LI&gt;
&lt;LI id="pragma-line-26"&gt;&lt;STRONG&gt;Which type of side client-component to create?&lt;/STRONG&gt;: Adaptive Card Extension&lt;/LI&gt;
&lt;LI id="pragma-line-27"&gt;&lt;STRONG&gt;Which template do you want to use?&lt;/STRONG&gt;: Primary Text Card template&lt;/LI&gt;
&lt;LI id="pragma-line-28"&gt;&lt;STRONG&gt;What is your Adaptive Card Extension name?&lt;/STRONG&gt;: Viva Connections Stocks&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Yeoman will generate a starting project for us. Let's explore it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="building-the-sharepoint-adaptive-card"&gt;Building the SharePoint Adaptive Card&lt;/H3&gt;
&lt;P&gt;A SharePoint Adaptive Card project is made by multiple files, but there are four of them which are important (keep in mind that the name might change, since it's generated based on the name you have chosen for the project):&lt;/P&gt;
&lt;UL id="pragma-line-35"&gt;
&lt;LI id="pragma-line-35"&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.manifest.json&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file, which describes the card. It contains valuable information like the name, the version, and the properties.&lt;/LI&gt;
&lt;LI id="pragma-line-36"&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;, which is the entry point of the card. This file includes a class that inherits from&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;BaseAdaptiveCardExtension&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and it takes care of initializing the card. This is where we're going to load the stocks information from our Azure Function.&lt;/LI&gt;
&lt;LI id="pragma-line-37"&gt;&lt;CODE&gt;CardView.ts&lt;/CODE&gt;, which is the definition of the card. This file includes a class that inherits from&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;BasePrimaryTextCardView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(since we chose the Primary Text template; if we would have picked a different template, it would be a different base class) and it's where we supply the data that we want to display in the card (the stock information).&lt;/LI&gt;
&lt;LI id="pragma-line-38"&gt;&lt;CODE&gt;QuickView.ts&lt;/CODE&gt;, which is the definition of the adaptive card displayed when you click or tap on the card. This file includes a class that inherits from&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;BaseAdaptiveCardView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, similarly to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView&lt;/CODE&gt;, it's where we supply the data that we want to display in the quick view.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;You can use any type of editor to work on this project, but I highly suggest Visual Studio Code, since it offers a built-in debugging experience, which will come especially useful when it will be time to start testing our Viva Connections card.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let's break down the development of the card in little steps, so that hopefully it will be easier for you to follow the process.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="loading-the-data"&gt;Loading the data&lt;/H3&gt;
&lt;P&gt;The entry point of the card, which is implemented by the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file, is the place where a Viva Connections card must load the data we want to display. This class, in fact, implements a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onInit()&lt;/CODE&gt;, which is triggered when the card is loaded in the dashboard. This is the right place to load and initialize the data.&lt;/P&gt;
&lt;P&gt;Before starting to fetch the data, however, we need a way to represent the stock information coming from the Azure Function. The default template is based on TypeScript, so why not leverage the strongly typed support that this language brings over JavaScript? Let's add a new file to our project and call it&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStockPrice.ts&lt;/CODE&gt;, then copy the following definition:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-49" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;interface&lt;/SPAN&gt; IStockPrice {
    openingPrice: &lt;SPAN class="hljs-built_in"&gt;number&lt;/SPAN&gt;;
    closingPrice: &lt;SPAN class="hljs-built_in"&gt;number&lt;/SPAN&gt;;
    highestPrice: &lt;SPAN class="hljs-built_in"&gt;number&lt;/SPAN&gt;;
    lowestPrice: &lt;SPAN class="hljs-built_in"&gt;number&lt;/SPAN&gt;;
    volume: &lt;SPAN class="hljs-built_in"&gt;number&lt;/SPAN&gt;;
    time: &lt;SPAN class="hljs-built_in"&gt;Date&lt;/SPAN&gt;;
    companyName: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
    exchange: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
    symbol: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This interface is the equivalent of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;class we have defined in our Azure Function, and it matches the JSON payload we're going to receive from our backend. Now that we have an interface, we must introduce the concept of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;state&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;in a Viva Connections card. If you have experience with React, you'll find yourself at home, since it's the same concept and it's even implemented with the same APIs. State is the place where you store data that is persisted across the lifecycle of a card. It's the best place to store our data, since we can read its content from every file included in the project. This means that we can store the stock information in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onInit()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file and then read it in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file already includes a definition for the state which, however, is empty by default. We must add a new property to store the stock information, using the interface we have just created:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-68" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;interface&lt;/SPAN&gt; IStocksAdaptiveCardExtensionState {
  stock: IStockPrice;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now we can move on and define now a new function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;, which uses the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetch()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;API to retrieve the stock price from our Azure Function (we'll use the Microsoft one as a reference).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-76" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; fetchData(): &lt;SPAN class="hljs-built_in"&gt;Promise&lt;/SPAN&gt;&amp;lt;&lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt;&amp;gt; { 

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; url = &lt;SPAN class="hljs-string"&gt;"https://myfunction.azurewebsites.com/api/stockPrices/MSFT"&lt;/SPAN&gt;;

    &lt;SPAN class="hljs-keyword"&gt;let&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; fetch(url);

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; json = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; response.text();
    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; parsedJson: IStockPrice = &lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.parse(json) &lt;SPAN class="hljs-keyword"&gt;as&lt;/SPAN&gt; IStockPrice;

    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.setState({stock: parsedJson});
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The function is quite straightforward. First, we retrieve the JSON payload that contains all the information about the Microsoft stock; then we turn into a string, calling the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;text()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function and we use&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;JSON.parse()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to convert the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;string&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;into an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object. Finally, we store it into the stock property of the state, by calling&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;this.setState()&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;Now, we just need to call this function inside the &lt;CODE&gt;onInit()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt; method:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-94" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; onInit(): &lt;SPAN class="hljs-built_in"&gt;Promise&lt;/SPAN&gt;&amp;lt;&lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt;&amp;gt; {
  
  &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state = { 
    stock: &lt;SPAN class="hljs-literal"&gt;null&lt;/SPAN&gt;
  };

  
  &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.fetchData();
  

  &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.cardNavigator.register(CARD_VIEW_REGISTRY_ID, &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-params"&gt;()&lt;/SPAN&gt; =&amp;gt;&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; CardView());
  &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.quickViewNavigator.register(QUICK_VIEW_REGISTRY_ID, &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-params"&gt;()&lt;/SPAN&gt; =&amp;gt;&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; QuickView());

  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; &lt;SPAN class="hljs-built_in"&gt;Promise&lt;/SPAN&gt;.resolve();
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onInit()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function takes care of two additional tasks: first, before working with the state, we must initialize it. In our scenario, we don't have a default value, so we just set the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stock&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property in the state to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;null&lt;/CODE&gt;. Second, we must register the cards and the quick view we want to render. The card is registered into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;cardNavigator&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection, while the quick view is registered in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;quickViewNavigator&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection. The default template includes a single card and a single quick view. A more complex project might provide multiple cards (for example, one for the medium and one for the large size) and multiple quick views (for example, triggered by different actions).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now that we have the stock data, we must display it. Let's head to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file: we'll find a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data()&lt;/CODE&gt;, which implements an interface that changes based on the template we have chosen. In our case, it's called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IPrimaryTextCardParameters&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and it offers three properties, one for each of the elements that we can customize in the card: the title, the primary text, and the description.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Thanks to the state, we can set some of these properties with the stock information we have previously downloaded:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-118" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt; data(): IPrimaryTextCardParameters {
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; {
    description: &lt;SPAN class="hljs-string"&gt;"MSFT"&lt;/SPAN&gt;,
    primaryText: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.openingPrice.toString(),
    title: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.title
  };
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;description&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property has a fixed value, the MSFT symbol;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;title&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is taken from the card properties (we'll get back to it later);&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;primaryText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is taken from the state. Thanks to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;this.state&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;keyword, we can access the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object we have previously stored. Specifically, we are displaying on the card the current price of the stock, which is stored in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;openingPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;That's all we need to implement the basic experience. We're ready to test this first step.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="testing-the-viva-connections-card"&gt;Testing the Viva Connections card&lt;/H3&gt;
&lt;P&gt;SharePoint offers a feature called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;SharePoint Workbench&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to test SharePoint extensions, including Adaptive Card ones. It's a special environment, hosted on our SharePoint tenant, which can connect to our local machine and load an extension hosted locally. This tool simplifies the testing experience; otherwise, we would need each time to create a new SharePoint package, upload it to the administrator portal, test the work, identify the issues, make changes, and repeat the process again.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Before getting to the SharePoint workbench, however, we must launch the Azure Functions which we built the last time. However, it isn't enough. We must also use a tool like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://ngrok.com/" target="_blank"&gt;Ngrok&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to expose our local machine to a public URL, so that the Azure Functions can be accessed also by the SharePoint workbench. Since it runs on our SharePoint website, in fact, it's not able to connect to an API exposed on localhost. Once you have ngrok setup on your machine, launch it with the following command:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-137" class="language-powershell hljs"&gt;ngrok http &lt;SPAN class="hljs-number"&gt;7071&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;You will get back a URL like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;https://&amp;lt;random-value&amp;gt;.ngrok.io&lt;/CODE&gt;. Make sure to include it in the code, by assigning it to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;url&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function.&lt;/P&gt;
&lt;P&gt;Now you can go back to the Viva Connections card project. Inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;.vscode&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;folder, you will find a file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;launch.json&lt;/CODE&gt;, which defines the debugging profile. You will find a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;url&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property, with the following setting:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-145" class="language-json hljs"&gt;&lt;SPAN class="hljs-string"&gt;"url"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"https://enter-your-SharePoint-site/_layouts/workbench.aspx"&lt;/SPAN&gt;,
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Replace the first part of the URL with the real URL of your SharePoint domain. Now you must perform two operations in Visual Studio Code:&lt;/P&gt;
&lt;UL id="pragma-line-151"&gt;
&lt;LI id="pragma-line-151"&gt;
&lt;P&gt;In the top menu, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Terminal → New Terminal&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to open a terminal on the current folder. Run the following command:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV class="code-badge-language"&gt;powershell&lt;/DIV&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-153" class="language-powershell hljs"&gt;gulp serve --nobrowser
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This command will run a local server that will host the Adaptive Card extension and it will enable the Live Reload feature, so that you can make changes to the code without having to redeploy the project.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-159"&gt;
&lt;P&gt;Move to the Debug tab from the left sidebar and press F5. Visual Studio Code will launch a browser on the SharePoint workbench, and you will have to login with a valid SharePoint account.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;This is how the SharePoint Workbench looks like:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When you click on +, you will see a series of components that you can add to test them. You will see a special section called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Local&lt;/STRONG&gt;, with the name of your project. Click on it to add it to the workbench. If you have done everything correctly, you will see a card with the Microsoft stock price.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In case, instead, you get an error, you can leverage the developers' tools in the browser (press F12 in Edge or Chrome) to see what's going on. It's likely that the first time you will experience a CORS failure: since the SharePoint website is hosted on a different domain than your Azure Functions, the request is blocked by default. While you're in development, you can overcome this problem by adding the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CORS&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;entry under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Host&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;local.settings.json&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file of your Azure Function:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-171" class="language-json hljs"&gt;{
  &lt;SPAN class="hljs-attr"&gt;"IsEncrypted"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;false&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"Values"&lt;/SPAN&gt;: {
    &lt;SPAN class="hljs-attr"&gt;"AzureWebJobsStorage"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"FUNCTIONS_WORKER_RUNTIME"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"dotnet"&lt;/SPAN&gt;,
  },
  &lt;SPAN class="hljs-attr"&gt;"Host"&lt;/SPAN&gt;: {
    &lt;SPAN class="hljs-attr"&gt;"CORS"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"*"&lt;/SPAN&gt;
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This configuration will open the usage of the API from any client app. You can also choose to restrict it, by adding the domain of your SharePoint website (like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="https://contoso.sharepoint.com" target="_blank"&gt;https://contoso.sharepoint.com&lt;/A&gt;&lt;/CODE&gt;).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Everything works, but the current implementation has a lot of limitations. We have hardcoded information, like the URL of our Azure Function or the stock price we want to track. This means that we would need to make changes to the code every time we want to track a stock different than Microsoft or the URL of our backend changes. Let's fix that by adding properties.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="adding-properties"&gt;Adding properties&lt;/H3&gt;
&lt;P&gt;When you have added the Stocks card in the SharePoint Workbench, you have noticed that a panel opened on the right, which gave you the option to customize the title and to choose the size of the card. These are properties: the administrator who configures the Viva Connections dashboard can define them and, in code, you can read their value so that you can customize the behavior of the card. We have already seen an example of the property in action. Let's look again at the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function implemented in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-192" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt; data(): IPrimaryTextCardParameters {
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; {
    description: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.symbol, 
    primaryText: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.openingPrice.toString(),
    title: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.title
  };
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The title of the card is taken from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;this.properties&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection, which means that the administrator who configures the dashboard can customize the title without needing to ask the developer to make changes.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let's use the same approach to add two other properties: one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;, to store the company we want to track; one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiUrl&lt;/CODE&gt;, to store the URL of our backend.&lt;/P&gt;
&lt;P&gt;The first place where you must define properties is the manifest file. You will find a section called properties, where you can list them with their default value. This is how we must change it:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-208" class="language-json hljs"&gt;&lt;SPAN class="hljs-string"&gt;"properties"&lt;/SPAN&gt;: {
  &lt;SPAN class="hljs-attr"&gt;"title"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Stocks"&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"stockSymbol"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"apiUrl"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;
},
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;CODE&gt;title&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;was already included, with a default value of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Stocks&lt;/CODE&gt;. We add&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiUrl&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and we use an empty string as default value, since we don't have a way to guess which will be the stock symbol to track or the backend URL.&lt;/P&gt;
&lt;P&gt;The next step is to update the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStocksAdaptiveCardExtensionProps&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;interface, included in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file, which describes the properties, in a similar way to what we did before with the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStocksAdaptiveCardExtensionState&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;interface for the state. This is how we must change it:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-220" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;interface&lt;/SPAN&gt; IStocksAdaptiveCardExtensionProps {
  title: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
  stockSymbol: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
  apiUrl: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We are simply adding two new properties,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiUrl&lt;/CODE&gt;, which type is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;string&lt;/CODE&gt;.&lt;/P&gt;
&lt;P&gt;Now that we have our properties, we must change the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function we have previously created so that the API URL and the stock symbol are taken from the properties, instead of being hardcoded:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-232" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; fetchData(): &lt;SPAN class="hljs-built_in"&gt;Promise&lt;/SPAN&gt;&amp;lt;&lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt;&amp;gt; { 

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; url = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.apiURL + &lt;SPAN class="hljs-string"&gt;"/api/stockPrices/"&lt;/SPAN&gt; + &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.properties.stockSymbol;

    &lt;SPAN class="hljs-keyword"&gt;let&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; fetch(url);

    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; json = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; response.text();
    &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; parsedJson: IStockPrice = &lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.parse(json) &lt;SPAN class="hljs-keyword"&gt;as&lt;/SPAN&gt; IStockPrice;

    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.setState({stock: parsedJson});
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;There's one last step to complete the task. We have defined two new properties, but we haven't added a way to set them. The panel that is displayed when you add a card to the dashboard, in fact, isn't automatically generated by SharePoint, but it's managed by a file included in the project (the one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksPropertyPane.ts&lt;/CODE&gt;, in our case). Inside this file, you use TypeScript objects to define the various fields. Let's take a look:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-248" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;import&lt;/SPAN&gt; { IPropertyPaneConfiguration, PropertyPaneTextField } &lt;SPAN class="hljs-keyword"&gt;from&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;'@microsoft/sp-property-pane'&lt;/SPAN&gt;;
&lt;SPAN class="hljs-keyword"&gt;import&lt;/SPAN&gt; * &lt;SPAN class="hljs-keyword"&gt;as&lt;/SPAN&gt; strings &lt;SPAN class="hljs-keyword"&gt;from&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;'StocksAdaptiveCardExtensionStrings'&lt;/SPAN&gt;;

&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; StocksPropertyPane {
  &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; {
      pages: [
        {
          header: { description: strings.PropertyPaneDescription },
          groups: [
            {
              groupFields: [
                PropertyPaneTextField(&lt;SPAN class="hljs-string"&gt;'title'&lt;/SPAN&gt;, {
                  label: strings.TitleFieldLabel
                }),
                PropertyPaneTextField(&lt;SPAN class="hljs-string"&gt;'stockSymbol'&lt;/SPAN&gt;, {
                  label: strings.StockCodeLabel
                }),
                PropertyPaneTextField(&lt;SPAN class="hljs-string"&gt;'apiUrl'&lt;/SPAN&gt;, {
                  label: strings.ApiUrlLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This class is very flexible, since you can define an arbitrary number of properties, group them into different sections, etc. For the sake of simplicity, in our case we are displaying all the properties into a unique group. Each property is mapped to a specific&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PropertyPane&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object. In our scenario, all the properties are strings, so they are rendered with a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PropertyPaneTextField&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, which generates a text input. The SharePoint Framework includes many other types of objects, like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PropertyPaneDropdown&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PropertyPaneCheckbox&lt;/CODE&gt;, to support several types of parameters, like boolean or numbers.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When we initialize the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;PropertyPaneTextField&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, we must specify two information:&lt;/P&gt;
&lt;UL id="pragma-line-282"&gt;
&lt;LI id="pragma-line-282"&gt;The name of the property we want to bind to the field.&lt;/LI&gt;
&lt;LI id="pragma-line-283"&gt;The label we want to display as header of the field. By default, SharePoint Adaptive Card extensions are built with localization in mind, so labels aren't hardcoded, but they're taken from an interface included in the project called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IStocksAdaptiveCardExtensionStrings&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and defined in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;mystring.d.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file. This interface loads the strings from a localization file: by default, the project includes only English as a language, so you'll find the strings in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;en-us.js&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksPropertyPane.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file already included a field to support the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;title&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property. As we have seen in the previous snippet, we just must add two new fields to support the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiUrl&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;properties.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This was the last required step to support properties. There's only one catch that we must keep in mind: properties, by default, are loaded only when the card is initialized. This means that, with the current implementation, when you drag the card into the dashboard and you start changing properties, you won't see the changes until you refresh the page. If you want to make the card more dynamic, you can use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onPropertyPaneFieldChanged&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event, which is available in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StocksAdaptiveCardExtension.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file. Let's look at the implementation:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-289" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;protected&lt;/SPAN&gt; onPropertyPaneFieldChanged(propertyPath: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;, oldValue: &lt;SPAN class="hljs-built_in"&gt;any&lt;/SPAN&gt;, newValue: &lt;SPAN class="hljs-built_in"&gt;any&lt;/SPAN&gt;): &lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt; {
  &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (propertyPath === &lt;SPAN class="hljs-string"&gt;'stockSymbol'&lt;/SPAN&gt; || propertyPath === &lt;SPAN class="hljs-string"&gt;'apiUrl'&lt;/SPAN&gt; &amp;amp;&amp;amp; newValue !== oldValue) {
    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (newValue) {
      &lt;SPAN class="hljs-built_in"&gt;void&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.fetchData();
    } 
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;propertyPath&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter, we check which property was changed. In our case, we want to detect changes in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiUrl&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;properties. If the administrator simply changes the title of the card, in fact, we don't need to retrieve new data from our backend. If one of these two properties has changed and there's indeed a new value, we call again the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetchData()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function we have previously defined to query our backend, using the new API URL or the new stock symbol.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now that we have completed the implementation, we can use the SharePoint Workbench again to test our project. This time, when you add the Stocks card to the dashboard, the property pane on the right should include two new fields other than the title one:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now set the two fields with a valid stock symbol and with the URL generated by Ngrok: the card should automatically update with the data coming from our Azure Function.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="implementing-the-quick-view"&gt;Implementing the quick view&lt;/H3&gt;
&lt;P&gt;The last feature we're going to implement today is the quick view. We don't have to do much in code, since the default template already implements the quick view the way we need: when the user clicks on the button on the card, we want to open the quick view to display more details about the stock.&lt;/P&gt;
&lt;P&gt;Let's open the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file and let's look at how the button is implemented:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-312" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt; cardButtons(): [ICardButton] | [ICardButton, ICardButton] | &lt;SPAN class="hljs-literal"&gt;undefined&lt;/SPAN&gt; {
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; [
    {
      title: strings.QuickViewButton,
      action: {
        &lt;SPAN class="hljs-keyword"&gt;type&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;'QuickView'&lt;/SPAN&gt;,
        parameters: {
          view: QUICK_VIEW_REGISTRY_ID
        }
      }
    }
  ];
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The file includes a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;cardButtons()&lt;/CODE&gt;, which can return zero, one or two&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ICardButton&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;objects. A card, in fact, doesn't need to have a button (you can also manage the click on the card itself) but, if you want to have them, you can have only one when the card is displayed in medium size and two when the card is displayed in large size.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;A card button supports various types of interactions. The Quick View panel is a built-in one, so you just need to supply a label for the button (using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;title&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property) and, as action type, the fixed&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;QuickView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;value. Then, as parameter, you must supply the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;view&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property with the identifier of the quick view you want to display, among the ones that have been registered inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;quickViewNavigator&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This is about triggering the quick view. But how is a quick view defined? Let's look at the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;QuickView.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file. The basic structure is the same as the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CardView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;one. It includes, in fact, a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function which we must use to supply the information we want to display on the card. However, this time the function implements the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IQuickViewData&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;interface, which is generic. Unlike with the basic card (which supports only three templates, each of them with a limited number of parameters), we have lot of flexibility with an adaptive card, so we can supply an undefined number of parameters.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The core implementation of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;QuickView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file, however, is the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;template()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function, in which we must supply the JSON that defines the Adaptive Card. In the default template, this JSON is defined in a dedicated file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;QuickViewTemplate.json&lt;/CODE&gt;, so the function simply uses the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;require()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;JavaScript method to load the content of that file:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-336" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt; template(): ISPFxAdaptiveCard {
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; &lt;SPAN class="hljs-built_in"&gt;require&lt;/SPAN&gt;(&lt;SPAN class="hljs-string"&gt;'./template/QuickViewTemplate.json'&lt;/SPAN&gt;);
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The default project contains a basic template, which isn't a good fit, however, for our scenario. The easiest way to generate a better template is to leverage the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://adaptivecards.io/designer/" target="_blank"&gt;Adaptive Card Designer&lt;/A&gt;, a tool which you can use to visually design your card and get the corresponding JSON. Additionally, the template gallery includes&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://adaptivecards.io/samples/StockUpdate.html" target="_blank"&gt;a stock template&lt;/A&gt;, which is perfect for our needs. This is the definitive version of the template, modified for our needs:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-345" class="language-json hljs"&gt;{
  &lt;SPAN class="hljs-attr"&gt;"$schema"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"http://adaptivecards.io/schemas/adaptive-card.json"&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"AdaptiveCard"&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"version"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"1.2"&lt;/SPAN&gt;,
  &lt;SPAN class="hljs-attr"&gt;"body"&lt;/SPAN&gt;: [
      {
          &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Container"&lt;/SPAN&gt;,
          &lt;SPAN class="hljs-attr"&gt;"items"&lt;/SPAN&gt;: [
              {
                  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"TextBlock"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"text"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${companyName}"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"size"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Medium"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"wrap"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;
              },
              {
                  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"TextBlock"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"text"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${primaryExchange}: ${symbol}"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"isSubtle"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"spacing"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"None"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"wrap"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;
              },
              {
                &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"TextBlock"&lt;/SPAN&gt;,
                &lt;SPAN class="hljs-attr"&gt;"text"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${time}"&lt;/SPAN&gt;,
                &lt;SPAN class="hljs-attr"&gt;"wrap"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;
            }
          ]
      },
      {
          &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Container"&lt;/SPAN&gt;,
          &lt;SPAN class="hljs-attr"&gt;"spacing"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"None"&lt;/SPAN&gt;,
          &lt;SPAN class="hljs-attr"&gt;"items"&lt;/SPAN&gt;: [
              {
                  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"ColumnSet"&lt;/SPAN&gt;,
                  &lt;SPAN class="hljs-attr"&gt;"columns"&lt;/SPAN&gt;: [
                      {
                          &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Column"&lt;/SPAN&gt;,
                          &lt;SPAN class="hljs-attr"&gt;"width"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"stretch"&lt;/SPAN&gt;,
                          &lt;SPAN class="hljs-attr"&gt;"items"&lt;/SPAN&gt;: [
                              {
                                  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"TextBlock"&lt;/SPAN&gt;,
                                  &lt;SPAN class="hljs-attr"&gt;"text"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${formatNumber(latestPrice, 2)} "&lt;/SPAN&gt;,
                                  &lt;SPAN class="hljs-attr"&gt;"size"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"ExtraLarge"&lt;/SPAN&gt;,
                                  &lt;SPAN class="hljs-attr"&gt;"wrap"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;
                              }
                          ]
                      },
                      {
                          &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Column"&lt;/SPAN&gt;,
                          &lt;SPAN class="hljs-attr"&gt;"width"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"auto"&lt;/SPAN&gt;,
                          &lt;SPAN class="hljs-attr"&gt;"items"&lt;/SPAN&gt;: [
                              {
                                  &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"FactSet"&lt;/SPAN&gt;,
                                  &lt;SPAN class="hljs-attr"&gt;"facts"&lt;/SPAN&gt;: [
                                      {
                                          &lt;SPAN class="hljs-attr"&gt;"title"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"High"&lt;/SPAN&gt;,
                                          &lt;SPAN class="hljs-attr"&gt;"value"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${high} "&lt;/SPAN&gt;
                                      },
                                      {
                                          &lt;SPAN class="hljs-attr"&gt;"title"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Low"&lt;/SPAN&gt;,
                                          &lt;SPAN class="hljs-attr"&gt;"value"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"${low} "&lt;/SPAN&gt;
                                      }
                                  ]
                              }
                          ]
                      }
                  ]
              }
          ]
      }
  ]
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We won't go through the details of implementing an adaptive card, you can refer to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/adaptive-cards/" target="_blank"&gt;official documentation&lt;/A&gt;. One thing I want to highlight, however, is that you can notice how some values aren't hardcoded, but there's a special string that acts as a placeholder, like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;${companyName}&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;${symbol}&lt;/CODE&gt;. These are parameters, which are replaced at runtime with the parameters we supply to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;QuickView&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file. This is how our&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function looks like:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE id="pragma-line-422" class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt; data(): IQuickViewData {
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; {
    subTitle: strings.SubTitle,
    title: strings.Title,
    companyName: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.companyName,
    symbol: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.symbol,
    latestPrice: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.openingPrice,
    high: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.highestPrice,
    low: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.lowestPrice,
    primaryExchange: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.exchange,
    time: &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.stock.time
  };
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Thanks to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;this.state&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;keyword, we can access the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stock&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object we have previously stored and retrieve the various information we want to display, like the company name, the symbol, or the latest price. The properties names match the placeholders we have placed in the JSON template of the Adaptive Card.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;We're good to test our code now. Press F5 again in Visual Studio Code to launch the SharePoint workbench, add the Stocks card to the dashboard and set up its properties. Now, if you click on the See more button on the card, you should see the adaptive card being rendered, which displays a bit more information about the stock than the one you see in the card:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this second part of the series we have built our Viva Connections card. We didn't just build a card to display stock prices but, in the process, we have learned the basics on how to build a Viva Connections card. Everything we have learned, in fact, can be easily reapplied to display any type of data, coming from any type of 3rd party service.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In the next and final post of the series, we'll talk about deployment and security. The current implementation, in fact, has a security flaw: anyone with the URL of our Azure Function will be able to call the API and get the stock data. We'll learn how to protect the API, so that only authorized users who are logged in into Teams and Viva Connections can use it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In the meantime, remember that the full code of the final solution is available&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/viva-connections-stocks" target="_blank"&gt;on GitHub&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;</description>
      <pubDate>Tue, 21 Mar 2023 12:54:30 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-2-the/ba-p/3773938</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-03-21T12:54:30Z</dc:date>
    </item>
    <item>
      <title>Build a Viva Connections card to display stock prices - Part 1: the backend</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-1-the/ba-p/3773345</link>
      <description>&lt;P&gt;Recently, a customer engaged with my team because they have started to adopt Viva Connections in their company. They are happy about the experience, but they miss one piece of information that they consider important: the stock price of the company. It is quite a common use case, but Viva Connections doesn't provide this feature as built in. So, I decided: "&lt;STRONG&gt;Why not take advantage of this engagement and build something that can be useful to every customer who is adopting Viva Connections?&lt;/STRONG&gt;". In this series of posts, we're going to build a Viva Connections card to display the price of any stock on the market, powered by a set of Azure services like Azure Functions and Azure Cache for Redis. This post will be focused on the backend, while in the next one we're going to build the actual card.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is the final look of the project we're going to build:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The card displays the basic information (stock name and current price). When you click on it, an Adaptive Card will display more details on the stock.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="building-the-backend"&gt;Building the backend&lt;/H3&gt;
&lt;P&gt;We're going to leverage a service called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/" target="_blank"&gt;Alpha Vantage&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to get the stock prices. It's a powerful service that offers an API that can return either a JSON or a CSV file and it supports a lot of market data. The company offers a free tier with some limitations (5 API requests per minute and 500 requests per day), so you might want to consider purchasing&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/premium/" target="_blank"&gt;a paid tier&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for a production scenario. However, our backend will include a cache based on Azure Cache for Redis, which will help us to reduce the number of calls we must perform against the API.&lt;/P&gt;
&lt;P&gt;We're going to use two different APIs offered by Alpha Vantage:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The Intraday API, which gives an historical list of all the prices across the day. You can see an example&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&amp;amp;symbol=IBM&amp;amp;interval=5min&amp;amp;apikey=demo" target="_blank"&gt;here&lt;/A&gt;. We'll use it to get the stock prices.&lt;/LI&gt;
&lt;LI&gt;The Overview API, which gives you detailed stock information about a company. You can see an example&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/query?function=OVERVIEW&amp;amp;symbol=IBM&amp;amp;apikey=demo" target="_blank"&gt;here&lt;/A&gt;. We'll use it to get some accessory information like the company name and the market.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;The first step is to head to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.alphavantage.co/support/#api-key" target="_blank"&gt;official website&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and register. You will receive an API key: store it somewhere since we'll need to use it later.&lt;/P&gt;
&lt;P&gt;As I mentioned in the introduction, we won't call this API directly from the Viva Connections card, but we're going to build a middleware API, which will help us to improve performance and reduce the number of API calls we must perform against the Alpha Vantage APIs. We're going to build this API using Azure Functions: thanks to the serverless model, we can focus only on the code we need to write, without worrying about the infrastructure and the configuration. Additionally,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/pricing/details/functions/" target="_blank"&gt;pricing is very low&lt;/A&gt;, with one million executions per month included in the free plan.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Since I'm a .NET developer, I'm going to build the Azure Function in C# using Visual Studio 2022. If you're a web developer, you can use Visual Studio Code to create a project based on Node.js as runtime, which enables you to write the function with JavaScript / TypeScript. Start by creating a new project in Visual Studio using the Azure Functions template (you will need the Azure workload enabled), pick&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;.NET 6.0&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as runtime and choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Http Trigger with Open API&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as function type. For testing purposes, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Anonymous&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as authentication.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As a first step, it's not required but I suggest you give a more meaningful name to the file and the class name that is generated by Visual Studio. But, most of all, set a meaningful name inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;[FunctionName]&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;attribute, since it will define the name of the API endpoint:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;[&lt;SPAN class="hljs-meta"&gt;FunctionName(&lt;SPAN class="hljs-meta-string"&gt;"StockPricesApi"&lt;/SPAN&gt;)&lt;/SPAN&gt;]
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The second change we must make is on the signature of the Azure Function. We need a way to get an input parameter from the caller, which is the stock symbol we want to track. There are multiple ways to do that: the one we're going to use is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&amp;amp;pivots=programming-language-csharp" target="_blank"&gt;routing&lt;/A&gt;. The final segment of our API's URL will include the stock symbol. Let's change the signature of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Run()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method like this:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; Task&amp;lt;IActionResult&amp;gt; &lt;SPAN class="hljs-title"&gt;Run&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;[HttpTrigger(AuthorizationLevel.Anonymous, &lt;SPAN class="hljs-string"&gt;"get"&lt;/SPAN&gt;, Route = &lt;SPAN class="hljs-string"&gt;"stockPrices/{stockSymbol}"&lt;/SPAN&gt;&lt;/SPAN&gt;)] HttpRequest req, &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; stockSymbol)
&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Let's say we want to get the stock price of Microsoft. Thanks to this change, we can call the API with the following URL:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-plaintext hljs"&gt;https://&amp;lt;azure-function-url&amp;gt;/api/stockPrices/MSFT
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The Azure Functions runtime will automatically inject into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;stockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter of the function the final segment of the URL, which includes the stock symbol. Now we have all the information we need to call the Alpha Vantage APIs but, before doing it, let's make our life easier, by adding a NuGet package to our project that will help us to work with the Alpha Vantage APIs without having to manually perform HTTP requets or to parse JSON. Right click on the project, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Manage NuGet packages&lt;/STRONG&gt;, look and install a package called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;AlphaVantage.Net.Stocks&lt;/STRONG&gt;. Now we have everything we need to start calling the Alpha Vantage APIs. However, as mentioned in the introduction, the information we want our Azure Function to return is the combination of two different Alpha Vantage APIs: Intraday and Overview. A such, let's create a new class that will combine the information we want to display in the Viva Connections card in a single entity:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;StockPrice&lt;/SPAN&gt;
{
    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;decimal&lt;/SPAN&gt; OpeningPrice { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;decimal&lt;/SPAN&gt; ClosingPrice { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;decimal&lt;/SPAN&gt; HighestPrice { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;decimal&lt;/SPAN&gt; LowestPrice { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;long&lt;/SPAN&gt; Volume { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; CompanyName { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; Exchange { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; Symbol { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }

    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; DateTime Time { &lt;SPAN class="hljs-keyword"&gt;get&lt;/SPAN&gt;; &lt;SPAN class="hljs-keyword"&gt;set&lt;/SPAN&gt;; }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now let's write some code using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;AlphaVantage.Net.Stocks&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;library which creates a new&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object pulling data from the two APIs:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;[&lt;SPAN class="hljs-meta"&gt;FunctionName(&lt;SPAN class="hljs-meta-string"&gt;"StockPricesApi"&lt;/SPAN&gt;)&lt;/SPAN&gt;]
&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; Task&amp;lt;IActionResult&amp;gt; &lt;SPAN class="hljs-title"&gt;Run&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;
           [HttpTrigger(AuthorizationLevel.Anonymous, &lt;SPAN class="hljs-string"&gt;"get"&lt;/SPAN&gt;, Route = &lt;SPAN class="hljs-string"&gt;"stockPrices/{stockSymbol}"&lt;/SPAN&gt;&lt;/SPAN&gt;)] HttpRequest req, &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; stockSymbol)&lt;/SPAN&gt;
{
   &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; apiKey = &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;;
   &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; client = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; AlphaVantageClient(apiKey);
   &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stocksClient = client.Stocks();

   StockTimeSeries stock = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; stocksClient.GetTimeSeriesAsync(stockSymbol, Interval.Min60, OutputSize.Compact, isAdjusted: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;);

   &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; query = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Dictionary&amp;lt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;, &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;&amp;gt;()
   {
       {&lt;SPAN class="hljs-string"&gt;"symbol"&lt;/SPAN&gt;, stockSymbol }
   };

   JsonDocument company = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; client.RequestParsedJsonAsync(ApiFunction.OVERVIEW, query);

   &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stockQuote = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; StockPrice {
       OpeningPrice = stock.DataPoints.FirstOrDefault().OpeningPrice,
       ClosingPrice = stock.DataPoints.FirstOrDefault().ClosingPrice,
       HighestPrice = stock.DataPoints.FirstOrDefault().HighestPrice,
       LowestPrice = stock.DataPoints.FirstOrDefault().LowestPrice,
       Time = stock.DataPoints.FirstOrDefault().Time,
       Volume = stock.DataPoints.FirstOrDefault().Volume,
       CompanyName = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Name"&lt;/SPAN&gt;).GetString(),
       Exchange = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Exchange"&lt;/SPAN&gt;).GetString(),
       Symbol = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Symbol"&lt;/SPAN&gt;).GetString()
   };

  
   &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; OkObjectResult(stockQuote);

}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The first step is to create an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;AlphaVantageClient&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, which requires the API key we got from the Alpha Vantage portal as initializer. Then, since we want to work with stock prices, we must get a specific client to call the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Stocks()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function. Getting the prices is quite easy: we use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;GetTimeSeriesAsync()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method, passing various parameters to customize the output (like the time interval or that we want the most recent prices instead of the whole history). Most importantly, as the first parameter we specify the stock symbol we want to track, which is coming from the URL of the API. In response, we get a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockTimeSeries&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, with a collection of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;DataPoints&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;objects. Each of them represents a stock value on a specific date and time, from the most recent to the oldest one. Our API needs to return the most recent one, so we pick the first item in the collection, and we use some of its properties to populate our&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Getting the information about the company is a bit different, since the &lt;STRONG&gt;AlphaVantage.Net&lt;/STRONG&gt; library doesn't include a specific client (and, thus, specific objects) to deal with the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Company&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;entity like it does with stocks. As such, we must use the generic method&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;RequestParsedJsonAsync()&lt;/CODE&gt;, to which we pass:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The Alpha Vantage API we want to call, using one of the values of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ApiFunction&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;enumerator. In our case, it's&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OVERVIEW&lt;/CODE&gt;.&lt;/LI&gt;
&lt;LI&gt;The input parameters for the API, through a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Dictionary&amp;lt;string, string&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection. In our case, we must provide only one parameter called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;symbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with, as value, the stock symbol of the company (again, we're getting this from the API URL).&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;We get back a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;JsonDocument&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, which isn't strongly typed. As such, we must use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;GetProperty()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method to get out the values from the JSON. The ones we are interested in are&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Name&lt;/CODE&gt;,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Exchange&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Symbol&lt;/CODE&gt;. All of them are strings, so we extract their values by calling&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;GetString()&lt;/CODE&gt;. Thanks to this code, we can extract the information we need to complete our&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, which we can wrap into an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OkObjectResult&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object and return it. This special ASP.NET object will automatically serialize our&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, returning an equivalent JSON to the caller.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;We can test our work by pressing F5. Visual Studio will launch the local Azure Functions emulator and our API will be hosted locally. To see if it's working, we can use a tool like Postman (or just open your browser) and hit the URL&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="http://localhost:7071/api/stockPrices/{stockSymbol" target="_blank"&gt;http://localhost:7071/api/stockPrices/{stockSymbol&lt;/A&gt;}&lt;/CODE&gt;, replacing&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;{stockSymbol}&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with the stock symbol of a company, like MSFT or AAPL. If you did everything correctly, you would see a JSON like the following one:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="protecting-our-api-key"&gt;Protecting our API key&lt;/H3&gt;
&lt;P&gt;The previous code has a flaw: we're keeping the API key in code, which is anything but good practice. First, because if we're hosting our project on a source control repository, everyone who has access to it will be able to see our key. Second, because if at any point in time we must change the API key, we are forced to build and deploy an updated version of our function. Let's improve our code and move the API key into the application settings. However, we aren't going to use the default ones (the ones stored in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;local.settings.json&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file) because also this file must be considered public, and it's typically committed as part of the project on a repository. We're going to use a .NET feature called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&amp;amp;tabs=windows#secret-manager" target="_blank"&gt;Secret Manager&lt;/A&gt;. The way it works is like a regular settings file: it's a JSON file, in which we can store key / value pairs that are available to the web application. However, this file is stored in a location outside the project, so that it doesn't become part of the files that are included into the repository. What makes it powerful is that this file is completely transparent to us as developers since its content gets merged with the default application settings at runtime. As such, we can read settings from this file using the same exact code we use to read the other application settings.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To start, right click on your project and choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add → Connected service&lt;/STRONG&gt;. Under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Service Dependencies&lt;/STRONG&gt;, click on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;+&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;icon and look for a feature called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Secrets.json (local)&lt;/STRONG&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Confirm the changes described in the next step and click&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Finish&lt;/STRONG&gt;. This will install a NuGet package called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Microsoft.Extensions.Configuration.UserSecrets&lt;/STRONG&gt;. Now right click on the project and choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Manage user secrets&lt;/STRONG&gt;. This step will do two operations:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;A new property will be added to the .csproj file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;UserSecretsId&lt;/CODE&gt;. It's a GUID that univocally identifies the secrets of your application.&lt;/LI&gt;
&lt;LI&gt;It will open a file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;secrets.json,&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;which is stored in the path&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;%APPDATA%\Microsoft\UserSecrets\&amp;lt;userSecretsId&amp;gt;\&lt;/CODE&gt;.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Let's add our API key in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;secrets.json&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-json hljs"&gt;{
  &lt;SPAN class="hljs-attr"&gt;"AlphaVantageApiKey"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now we must change the code of our Azure Function so that the API key is read from the configuration. As mentioned, we don't have to make many changes, since the secrets are automatically merged into the Azure Functions configuration. All we need to do is to change the public constructor of our function to add a new parameter of type&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IConfiguration&lt;/CODE&gt;:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;private&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;readonly&lt;/SPAN&gt; ILogger&amp;lt;StockPricesApi&amp;gt; _logger;
&lt;SPAN class="hljs-keyword"&gt;private&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;readonly&lt;/SPAN&gt; IConfiguration _configuration;

&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;StockPricesApi&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;ILogger&amp;lt;StockPricesApi&amp;gt; log, IConfiguration configuration&lt;/SPAN&gt;)&lt;/SPAN&gt;
{
    _logger = log;
    _configuration = configuration;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Thanks to dependency injection, the runtime will automatically inject into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IConfiguration&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object all the settings read from the secrets and the application settings. This means that, to get the API key, we just need to change the following line of code from:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; apiKey = &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;;
&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; client = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; AlphaVantageClient(apiKey);
&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stocksClient = client.Stocks();
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;to:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; apiKey = _configuration.GetValue&amp;lt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;&amp;gt;(&lt;SPAN class="hljs-string"&gt;"AlphaVantageApiKey"&lt;/SPAN&gt;);
&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; client = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; AlphaVantageClient(apiKey);
&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stocksClient = client.Stocks();
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;If you want to test that everything is still working, just press F5 and try to call again the endpoint&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="http://localhost:7071/api/stockPrices/MSFT" target="_blank"&gt;http://localhost:7071/api/stockPrices/MSFT&lt;/A&gt;&lt;/CODE&gt;. You should get a JSON again with all the stock information about Microsoft.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="adding-caching"&gt;Adding caching&lt;/H3&gt;
&lt;P&gt;The current implementation works fine, but it has a limit. Every time we invoke the Azure Function, we're calling the Alpha Vantage APIs. If we pause and think to the full context of our solution (displaying the stock price in a Viva Connections card), this means that potentially hundreds or thousands of employees might open the Viva Connections dashboard in a very short time and all of them will try to get a fresh copy of the stock data from the API. This introduces two problems:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;One is about performance since we might overload the Alpha Vantage APIs with requests.&lt;/LI&gt;
&lt;LI&gt;One is about API limitation. As we have learned in the beginning of the post, the Alpha Vantage APIs have some limitations (especially in the free tier) when it comes to the number of requests you can make hourly and daily.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;To reduce the impact of these two problems, we're going to introduce caching, using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/products/cache/" target="_blank"&gt;Azure Cache for Redis&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;service, which is one of the most popular solutions to support caching scenarios in your applications. By using this service, we're going to store the most recent stock pricing information into a cache. When the Azure Function gets called, it will try to get the price first from the cache; only if it doesn't exist or if the value is too old, we're going to hit the real Alpha Vantage APIs. Redis is powerful because, other than being simple to use and very efficient, it also takes care automatically of many of the challenges that you must face when you implement a caching mechanism. For example, it automatically manages the cache expiration, by deleting an item once the expiration time you have set has passed.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The easiest way to add caching is to right click again on your project, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add → Connected service&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Service Dependencies&lt;/STRONG&gt;, click the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;+&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;symbol. This time, we're going to pick&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Azure Cache for Redis&lt;/STRONG&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;For this project, we're going to leverage the real Azure Cache for Redis service, so that we can simplify the developer environment. However, if you prefer to work on a completely local and offline solution, you can choose the option&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Redis Cache on container (local)&lt;/STRONG&gt;, which will create the Redis cache on a local container. This option requires you to install&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://docs.docker.com/desktop/" target="_blank"&gt;Docker Desktop&lt;/A&gt;, since the container is based on a Docker image.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;If you haven't already done it, you will be asked to login with an account which is connected to an Azure subscription. Then click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create new&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to start the wizard to create a new instance of the service. Feel free to pick the region, resource group and name you prefer. In terms of SKU, a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Basic C0&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is good enough for our scenario, since we'll need to store only JSON data. Once you have created and selected the service, Visual Studio will help you to configure your application to connect to it. First, it will add a setting in your configuration called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheConnection&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with, as value, the connection string. Since we have already set up the Secrets Manager support, make sure to select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Local user secrets file: Secrets.json (local)&lt;/STRONG&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;Once you complete the wizard, it's time to install a NuGet package that will help us to read and store data into the cache. Right click again on the project, choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Manage NuGet Packages&lt;/STRONG&gt;, search for and install a package called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;StackExchange.Redis&lt;/STRONG&gt;. Now we can leverage dependency injection so we can simplify the cache integration in our function. However, to do it, we must customize the initialization process of the Azure Function, which can be achieved by adding a new file to our project called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Startup.cs&lt;/CODE&gt;. This is the implementation you must supply:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;[&lt;SPAN class="hljs-meta"&gt;assembly: FunctionsStartup(typeof(StockPricesApi.Startup))&lt;/SPAN&gt;]

&lt;SPAN class="hljs-keyword"&gt;namespace&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;StockPricesApi&lt;/SPAN&gt;
{
    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;Startup&lt;/SPAN&gt;: &lt;SPAN class="hljs-title"&gt;FunctionsStartup&lt;/SPAN&gt;
    {
        &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;override&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;void&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;Configure&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;IFunctionsHostBuilder builder&lt;/SPAN&gt;)&lt;/SPAN&gt;
        {
           
        }
    }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Startup&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;class must implement a class called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;FunctionsStartup&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, as header, we must use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;assembly&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;attribute to declare the definition of the current class as the one to use when the Azure Functions starts up. Inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Configure()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method we have access to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IFunctionsHostBuilder&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, which we can use to customize the Azure Functions initialization. Among the various tasks we can perform, we can use it to register new objects in the dependency injection container other than the default ones we have already used (like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ILogger&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IConfiguration&lt;/CODE&gt;). We can use it to initialize the Redis connection as following:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;using&lt;/SPAN&gt; Microsoft.Azure.Functions.Extensions.DependencyInjection;
&lt;SPAN class="hljs-keyword"&gt;using&lt;/SPAN&gt; Microsoft.Extensions.DependencyInjection;
&lt;SPAN class="hljs-keyword"&gt;using&lt;/SPAN&gt; StackExchange.Redis;

[&lt;SPAN class="hljs-meta"&gt;assembly: FunctionsStartup(typeof(StockPricesApi.Startup))&lt;/SPAN&gt;]

&lt;SPAN class="hljs-keyword"&gt;namespace&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;StockPricesApi&lt;/SPAN&gt;
{
    &lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;Startup&lt;/SPAN&gt;: &lt;SPAN class="hljs-title"&gt;FunctionsStartup&lt;/SPAN&gt;
    {
        &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;override&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;void&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;Configure&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;IFunctionsHostBuilder builder&lt;/SPAN&gt;)&lt;/SPAN&gt;
        {
            &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; connection = builder.GetContext().Configuration[&lt;SPAN class="hljs-string"&gt;"CacheConnection"&lt;/SPAN&gt;];
            &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; muxer = ConnectionMultiplexer.Connect(connection);
            builder.Services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;(muxer);
        }
    }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;First, we retrieve the connection string from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;CacheConnection&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;entry in the configuration. Then, we create a connection using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ConnectionMultiplexer.Connect()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method, passing as parameter the connection string. Finally, we register the connection object in the dependency injection container, by passing it to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;builder.Services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method.&lt;/P&gt;
&lt;P&gt;Now, to access to the cache in our function, we just need to add a new&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IConnectionMultiplexer&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter to the the initializer of our Function class:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;private&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;readonly&lt;/SPAN&gt; ILogger&amp;lt;StockPricesApi&amp;gt; _logger;
&lt;SPAN class="hljs-keyword"&gt;private&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;readonly&lt;/SPAN&gt; IConnectionMultiplexer _redisCache;
&lt;SPAN class="hljs-keyword"&gt;private&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;readonly&lt;/SPAN&gt; IConfiguration _configuration;

&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;StockPricesApi&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;ILogger&amp;lt;StockPricesApi&amp;gt; log, IConnectionMultiplexer redisCache, IConfiguration configuration&lt;/SPAN&gt;)&lt;/SPAN&gt;
{
    _logger = log;
    _redisCache = redisCache;
    _configuration = configuration;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now we can use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;_redisCache&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object to read and write data against the Azure Cache for Redis service we have just set up. Redis works similarly to the application settings, so using a key / value pair approach. In our case, the key will be dynamic: since the Viva Connections card can be used to display information about any stock, we need to create a different entry in the cache for each stock; the value will be the JSON returned by the Alpha Vantage API for that stock.&lt;/P&gt;
&lt;P&gt;Let's see how our function code changes to use the cache:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-csharp hljs"&gt;&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;public&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; Task&amp;lt;IActionResult&amp;gt; &lt;SPAN class="hljs-title"&gt;Run&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;
    [HttpTrigger(AuthorizationLevel.Anonymous, &lt;SPAN class="hljs-string"&gt;"get"&lt;/SPAN&gt;, Route = &lt;SPAN class="hljs-string"&gt;"stockPrices/{stockSymbol}"&lt;/SPAN&gt;&lt;/SPAN&gt;)] HttpRequest req, &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; stockSymbol)&lt;/SPAN&gt;
{
    _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;"C# HTTP trigger function processed a request."&lt;/SPAN&gt;);
    _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;$"Requested stock symbol: &lt;SPAN class="hljs-subst"&gt;{stockSymbol}&lt;/SPAN&gt;"&lt;/SPAN&gt;);

    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; db = _redisCache.GetDatabase();
    &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; json = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; db.StringGetAsync(&lt;SPAN class="hljs-string"&gt;$"StockPrice-&lt;SPAN class="hljs-subst"&gt;{stockSymbol}&lt;/SPAN&gt;"&lt;/SPAN&gt;);

    StockPrice stockQuote;

    &lt;SPAN class="hljs-keyword"&gt;if&lt;/SPAN&gt; (&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;.IsNullOrEmpty(json))
    {
        _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;$"Stock price for &lt;SPAN class="hljs-subst"&gt;{stockSymbol}&lt;/SPAN&gt; not available in cache, getting a fresh value"&lt;/SPAN&gt;);

        &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt; apiKey = _configuration.GetValue&amp;lt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;&amp;gt;(&lt;SPAN class="hljs-string"&gt;"AlphaVantageApiKey"&lt;/SPAN&gt;);
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; client = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; AlphaVantageClient(apiKey);
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stocksClient = client.Stocks();

        StockTimeSeries stock = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; stocksClient.GetTimeSeriesAsync(&lt;SPAN class="hljs-string"&gt;"MSFT"&lt;/SPAN&gt;, Interval.Min60, OutputSize.Compact, isAdjusted: &lt;SPAN class="hljs-literal"&gt;true&lt;/SPAN&gt;);

        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; query = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Dictionary&amp;lt;&lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;, &lt;SPAN class="hljs-keyword"&gt;string&lt;/SPAN&gt;&amp;gt;()
        {
            {&lt;SPAN class="hljs-string"&gt;"symbol"&lt;/SPAN&gt;, stockSymbol }
        };

        JsonDocument company = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; client.RequestParsedJsonAsync(ApiFunction.OVERVIEW, query);

        stockQuote = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; StockPrice {
            OpeningPrice = stock.DataPoints.FirstOrDefault().OpeningPrice,
            ClosingPrice = stock.DataPoints.FirstOrDefault().ClosingPrice,
            HighestPrice = stock.DataPoints.FirstOrDefault().HighestPrice,
            LowestPrice = stock.DataPoints.FirstOrDefault().LowestPrice,
            Time = stock.DataPoints.FirstOrDefault().Time,
            Volume = stock.DataPoints.FirstOrDefault().Volume,
            CompanyName = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Name"&lt;/SPAN&gt;).GetString(),
            Exchange = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Exchange"&lt;/SPAN&gt;).GetString(),
            Symbol = company.RootElement.GetProperty(&lt;SPAN class="hljs-string"&gt;"Symbol"&lt;/SPAN&gt;).GetString()
        };

        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; stockQuoteJson = JsonConvert.SerializeObject(stockQuote);
        &lt;SPAN class="hljs-keyword"&gt;int&lt;/SPAN&gt; cacheExpireInHours = &lt;SPAN class="hljs-number"&gt;6&lt;/SPAN&gt;;
        &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; db.StringSetAsync(&lt;SPAN class="hljs-string"&gt;$"StockPrice-&lt;SPAN class="hljs-subst"&gt;{stockSymbol}&lt;/SPAN&gt;"&lt;/SPAN&gt;, stockQuoteJson, TimeSpan.FromHours(cacheExpireInHours));
        _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;$"Json saved into in Redis: &lt;SPAN class="hljs-subst"&gt;{stockQuoteJson}&lt;/SPAN&gt;"&lt;/SPAN&gt;);
    }
    &lt;SPAN class="hljs-keyword"&gt;else&lt;/SPAN&gt;
    {
        stockQuote = JsonConvert.DeserializeObject&amp;lt;StockPrice&amp;gt;(json);
        _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;$"Stock price for &lt;SPAN class="hljs-subst"&gt;{stockSymbol}&lt;/SPAN&gt; retrieved from Redis cache"&lt;/SPAN&gt;);
        _logger.LogInformation(&lt;SPAN class="hljs-string"&gt;$"Json read from Redis: &lt;SPAN class="hljs-subst"&gt;{json}&lt;/SPAN&gt;"&lt;/SPAN&gt;);
    }

    &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; stockQuote != &lt;SPAN class="hljs-literal"&gt;null&lt;/SPAN&gt; ? &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; OkObjectResult(stockQuote) : &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; NotFoundResult();

}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;First, we get a reference to the Redis database by calling the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;GetDatabase()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method. Then, from the database, we try to read a value identified by the key&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice-{StockSymbol}&lt;/CODE&gt;, where&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockSymbol&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is the symbol of the stock that the user is trying to retrieve. For example, if the API has been called with the endpoint&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;/api/stockPrices/MSFT&lt;/CODE&gt;, the function will look on Redis for an item with key&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice-MSFT&lt;/CODE&gt;. The value we're expecting is a JSON string, so we use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StringGetAsync()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method. If the value is empty, it means that it hasn't cached before or that the value is expired.&lt;/P&gt;
&lt;P&gt;In this case, we proceed with the original code we have previously written, which retrieves the data from the Alpha Vantage API to build a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object.&lt;/P&gt;
&lt;P&gt;The only difference is that, before returning it to the caller, we convert the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object into a JSON string (using&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;JsonConvert.SerializeObject()&lt;/CODE&gt;) and we store it into the Redis cache by calling&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StringSetAsync()&lt;/CODE&gt;. As parameters, we pass the key specific for this stock symbol, the JSON string, and the cache expiration. In this case, we're passing a fixed value (6 hours), but you can also turn this parameter into an application setting, so that you can change it at a later stage without redeploying the function. Cache expiration means that Redis will automatically invalidate the value after the time you have specified has passed. In this case, it means that the Azure Function will get a new one from Alpha Vantage the first time the API is called after 6 hours since the data for that stock symbol was originally acquired.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In case, instead, we have a valid value in the cache, we deserialize the JSON back into a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object and we return it to the caller, without interacting at all with the Alpha Vantage APIs.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;That's it. Now if you press F5 and you try again to hit the URL&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="http://localhost:7071/api/stockPrices/MSFT" target="_blank"&gt;http://localhost:7071/api/stockPrices/MSFT&lt;/A&gt;&lt;/CODE&gt;, the first time the JSON payload will come from the Alpha Vantage APIs, while every other call will get the value from the cache.&lt;/P&gt;
&lt;P&gt;If you want to check that the Redis data is there (and eventually experiment with it), you can use a tool called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://redis.com/redis-enterprise/redis-!%5B%5D(Images/ssl.png)/" target="_blank"&gt;Redis Insights&lt;/A&gt;, which gives you a UI to explore the content of a Redis cache. Once you have installed it, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add Redis database&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add Database manually&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;option. Before adding it, however, you must temporarily disable SSL-only support for your Redis cache, otherwise the tool won't be able to connect. To do that, open the Azure portal and look for the Azure Cache for Redis you have previously created from Visual Studio. On the left blade, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Advanced Settings&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and set to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;No&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;the option&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Allow access only via SSL&lt;/STRONG&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;For security reasons, you should change the option back to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Yes&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as soon as you have finished using Redis Insight.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Now you can go back to the Redis Insight and add the connection with the following parameters:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG&gt;Host:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;it's the URL of your Redis service, you can get it from the Overview page on Azure.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Port&lt;/STRONG&gt;: it's 6379.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Database alias&lt;/STRONG&gt;: use the full URL of your Redis service followed by the port (for example,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;your-service.redis.cache.windows.net:6379&lt;/CODE&gt;).&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Username&lt;/STRONG&gt;: leave it empty.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Password&lt;/STRONG&gt;: use the Primary password that you can find in the Access Keys section on Azure.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Then click&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add Redis database&lt;/STRONG&gt;. If all the information is correct, you will see your service listed in the main table and, clicking on it, you will see the available data. If you have implemented caching in your Azure Function correctly, you will see an entry with the name&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;StockPrice-&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;followed by the stock symbol you have chosen and, if you click on it, you will see the full JSON from your API:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The tool is helpful also to debug and diagnose the cache, since you can edit and delete the stored value. This way, for example, you can trigger a new Alpha Vantage API request from the Azure Function without having to wait for the cache to expire.&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this post, we have focused on the backend of our Viva Connections solution to display stock prices. In the next one, we'll complete the project, and we'll build the actual card, which will use the backend we have just built to get the data to display.&lt;/P&gt;
&lt;P&gt;You can find the project written so far on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/viva-connections-stocks" target="_blank"&gt;GitHub&lt;/A&gt;. The project also includes a Bicep template that you can use to deploy on Azure all the required resources (Azure Functions and Azure Cache for Redis) configured in the correct way. We'll talk more about this in the final post of this series.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 20 Mar 2023 19:08:20 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/build-a-viva-connections-card-to-display-stock-prices-part-1-the/ba-p/3773345</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-03-20T19:08:20Z</dc:date>
    </item>
    <item>
      <title>Bring the ChatGPT model into our applications</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bring-the-chatgpt-model-into-our-applications/ba-p/3766574</link>
      <description>&lt;P&gt;In the first two posts of this series we have developed an Outlook add-in that, using the Open AI models, helps users to generate professional business mails starting from a simple sentence.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank"&gt;In the first post&lt;/A&gt;, we achieved this goal by directly using the OpenAI APIs;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-open-ai-into-an-outlook-add-in-moving-to-azure-open-ai/ba-p/3745592" target="_blank"&gt;in the second one&lt;/A&gt;, instead, we have leveraged the Azure OpenAI service. In both cases, however, we didn't use the model that powers up ChatGPT, because it wasn't available yet at the time of writing, but we leveraged a similar one called Davinci.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Since a few days, both&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://openai.com/blog/introducing-chatgpt-and-whisper-apis" target="_blank"&gt;OpenAI&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/blog/chatgpt-is-now-available-in-azure-openai-service/" target="_blank"&gt;Azure OpenAI&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;have finally made available the ChatGPT model, so that developers can integrate it into their experiences. In the previous posts, we said that the when this would have happened, we wouldn't have needed to change much code, probably just the model's name. This turned out to be partially true: the overall architecture of the add-in is unchanged, but the way you provide the prompt to the ChatGPT model is slightly different than the way you do it with the Davinci one. The different approach comes from the fact that, while Davinci was created mainly for content generation scenarios, ChatGPT targets specifically conversational ones. As such, you must be able to send entire conversations to the API, so that it can gather the required context to handle a conversation without having to specify all the information at each prompt.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post, we're going to learn the changes we must make based on the service we're using.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="using-chatgpt-with-the-openai-apis"&gt;Using ChatGPT with the OpenAI APIs&lt;/H3&gt;
&lt;P&gt;I've already anticipated this a few days ago, when I updated&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank"&gt;the original post&lt;/A&gt;, but I wanted to consolidate everything in a single article, so let's review the required changes when you want to directly use the OpenAI platform.&lt;/P&gt;
&lt;P&gt;OpenAI released the ChatGPT model as&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;gpt-3.5-turbo&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and the good news is that&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://openai.com/pricing" target="_blank"&gt;it's 10 times cheaper&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;than the Davinci one. The model, in fact, is priced $0.002 / 1K tokens, while the Davinci one we have used in the original version of the project is priced $0.0200 / 1K tokens.&lt;/P&gt;
&lt;P&gt;Before starting to make the changes which are required to use this model, we must open a terminal on the folder which contains our project and upgrade the OpenAI library with the following command:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-powershell hljs"&gt;npm update openai
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This command will install version 3.1.0 of the library, which includes a new API to interact with the ChatGPT model. Now let's take a look at the updated&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generateText()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function which uses this new API:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;generateText = &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; () =&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; current = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; configuration = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Configuration({
    apiKey: &lt;SPAN class="hljs-string"&gt;"your-API-key"&lt;/SPAN&gt;,
  });
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; openai = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; OpenAIApi(configuration);
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; openai.createChatCompletion({
    model: &lt;SPAN class="hljs-string"&gt;"gpt-3.5-turbo"&lt;/SPAN&gt;,
    messages: [
      {
        role: &lt;SPAN class="hljs-string"&gt;"system"&lt;/SPAN&gt;,
        content: &lt;SPAN class="hljs-string"&gt;"You are a helpful assistant that can help users to create professional business content."&lt;/SPAN&gt;,
      },
      { role: &lt;SPAN class="hljs-string"&gt;"user"&lt;/SPAN&gt;, content: &lt;SPAN class="hljs-string"&gt;"Turn the following text into a professional business mail: "&lt;/SPAN&gt; + &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.startText },
    ],
  });
  current.setState({ generatedText: response.data.choices[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].message.content });
};
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;There are three main differences compared to the previous implementation. The first one is that we have a new API to interact with ChatGPT, called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;createChatCompletion()&lt;/CODE&gt;, which we must use instead of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;createCompletion()&lt;/CODE&gt;. The second difference, even if it's a small one, is that we have to change the model name to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;gpt-3.5-turbo&lt;/CODE&gt;. The third difference is the most important one. To support the conversational scenario, the prompt you pass to the model to generate text isn't based any more on a single sentence, but on on a collection of messages object. Each of them has two properties:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;CODE&gt;role&lt;/CODE&gt;, which is used to define who generated the prompt. It could be the user, the system or the assistant itself.&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;content&lt;/CODE&gt;, which is the actual text.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;In the code sample, you can see two types of messages: one with&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;system&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as role, which we're using to instruct the model on the type of outcome we want to achieve (&lt;EM&gt;you are an assistant that can generate business content&lt;/EM&gt;); one with&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;user&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as role, which is instead the actual ask (&lt;CODE&gt;turn the following text into a business mail&lt;/CODE&gt;).&lt;/P&gt;
&lt;P&gt;This scenario might not be the best one to explain this approach, since we're using ChatGPT to generate content and not to handle a conversation. Let's see another example taken from the documentation, which should be more helpful to better understand how it works.&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; openai.createChatCompletion({
     model: &lt;SPAN class="hljs-string"&gt;"gpt-3.5-turbo"&lt;/SPAN&gt;,
     messages: [
       {&lt;SPAN class="hljs-string"&gt;"role"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"system"&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;"content"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"You are a helpful assistant."&lt;/SPAN&gt;},
       {&lt;SPAN class="hljs-string"&gt;"role"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"user"&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;"content"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Who won the world series in 2020?"&lt;/SPAN&gt;},
       {&lt;SPAN class="hljs-string"&gt;"role"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"assistant"&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;"content"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"The Los Angeles Dodgers won the World Series in 2020."&lt;/SPAN&gt;},
       {&lt;SPAN class="hljs-string"&gt;"role"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"user"&lt;/SPAN&gt;, &lt;SPAN class="hljs-string"&gt;"content"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"Where was it played?"&lt;/SPAN&gt;}
     ],
   });
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;As we mentioned, ChatGPT is born to support conversational scenarios, however the conversation history isn't managed by OpenAI, but it must be managed by the developer. This means that we must use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;messages&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection to provide the whole history, including the messages generated by ChatGPT using&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;assistant&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as a role. This way, the model is able to provide an answer to the last question (&lt;CODE&gt;Where was it played?&lt;/CODE&gt;) even if it doesn't include the full context.&lt;/P&gt;
&lt;P&gt;However, even if we aren't in a conversational scenario, ChatGPT is very powerful and works great for content creation. Additionally, the ChatGPT model is much cheaper than the Davinci one, so it's great fit for our Outlook add-in.&lt;/P&gt;
&lt;H3 id="using-chatgtp-apis-with-azure-open-ai"&gt;Using ChatGTP APIs with Azure Open AI&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Since a few days&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/blog/chatgpt-is-now-available-in-azure-openai-service/" target="_blank"&gt;Microsoft has announced the public preview of ChatGPT&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;inside Azure Open AI. This means that, if you have been approved to use the Azure Open AI service (see&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-open-ai-into-an-outlook-add-in-moving-to-azure-open-ai/ba-p/3745592" target="_blank"&gt;my previous post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;for more details on the approval process), now you can head to the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Model deployments&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;section and, in the dropdown, you will see a new option called&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;gpt-35-turbo&lt;/STRONG&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;STRONG&gt;Please note&lt;/STRONG&gt;: make sure that your service instance is deployed in East US. At the time of writing, this is the only region where the ChatGPT model is available.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;From a code perspective, working with Azure OpenAI requires less changes compared to the OpenAI APIs, since the format is the same. You must submit a HTTP POST request to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;openai/deployments/{deploymentName}/completions?api-version=2022-12-01&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;URL, prefixed by the URL of your instance. Inside the HTTP request, you must provide the API key as header and the payload with the prompt you want to submit as body. The main difference is the way you create the prompt. Azure OpenAI follows the same conversational approach we have seen with OpenAI but, instead of using a collection of messages, it uses a single message with special placeholders to define the history. Let's look at the updated version of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generateText()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;generateText = &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; () =&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; current = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; apiKey = &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; endpoint = &lt;SPAN class="hljs-string"&gt;"your-url"&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; prompt =
    &lt;SPAN class="hljs-string"&gt;"&amp;lt;|im_start|&amp;gt;system\nThe system is an AI assistant that helps people to write professional business mails.\n&amp;lt;|im_end|&amp;gt;\n&amp;lt;|im_start|&amp;gt;user\nTurn the following text into a professional business mail: "&lt;/SPAN&gt; +
    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.startText +
    &lt;SPAN class="hljs-string"&gt;"\n&amp;lt;|im_end|&amp;gt;\n&amp;lt;|im_start|&amp;gt;assistant"&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; deploymentName = &lt;SPAN class="hljs-string"&gt;"your-deployment-name"&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; url = endpoint + &lt;SPAN class="hljs-string"&gt;"openai/deployments/"&lt;/SPAN&gt; + deploymentName + &lt;SPAN class="hljs-string"&gt;"/completions?api-version=2022-12-01"&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; payload = {
    prompt: prompt,
    max_tokens: &lt;SPAN class="hljs-number"&gt;1000&lt;/SPAN&gt;,
  };

  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; fetch(url, {
    method: &lt;SPAN class="hljs-string"&gt;"POST"&lt;/SPAN&gt;,
    headers: {
      &lt;SPAN class="hljs-string"&gt;"Content-Type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;,
      &lt;SPAN class="hljs-string"&gt;"api-key"&lt;/SPAN&gt;: apiKey,
    },
    body: &lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.stringify(payload),
  });

  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; data = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; response.json();
  current.setState({ generatedText: data.choices[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].text });
};
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;As you can see, the code to perform the HTTP request is the same, including the way we handle the response. However, the prompt property of the payload combines multiple prompts, which are delimited by the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;|im_start|&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;|im_end|&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;placeholders. After the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;|im_start|&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;placeholder, before including the actual prompt, we specify who generated it, using keywords like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;system&lt;/CODE&gt;,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;user&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;assistant&lt;/CODE&gt;. The prompt ends with an open sentence: the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;|im_start|&amp;gt;&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;placeholder, followed by the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;assistant&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;role. By not closing it, we tell Azure Open AI that we want the AI model to take the input we have provided and use it to generate something for us. If you want to know more details on how to work with this model, you can read&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/chatgpt" target="_blank"&gt;this good article from the official documentation&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;The downside of this approach is that it make slightly more complicated the prompt generation compared to Open AI. The good news, however, is that you won't have to change much your code to use the ChatGPT model, while Open AI requires more work on the code.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;The advent of ChatGPT is a game changer in the AI ecosystem, since it opens new and exciting scenarios to change the way we work, and we create content. Thanks to OpenAI and Azure OpenAI, now we can bring the power of this model right into our applications. And if we have already built experiences using the Davinci model, like our business mail generator, moving to ChatGPT is quite simple, as we have learned in this post.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As usual, you can find the source code of the project&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/outlook-businessmails-openai" target="_blank"&gt;on GitHub&lt;/A&gt;.&lt;/P&gt;</description>
      <pubDate>Mon, 13 Mar 2023 15:13:36 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bring-the-chatgpt-model-into-our-applications/ba-p/3766574</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-03-13T15:13:36Z</dc:date>
    </item>
    <item>
      <title>ServiceNow with Microsoft Teams Approvals Part 2 - HTTP Trigger</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/servicenow-with-microsoft-teams-approvals-part-2-http-trigger/ba-p/3766470</link>
      <description>&lt;P&gt;As more and more organizations move towards remote work, communication and collaboration tools have become essential for seamless and efficient workflows. Microsoft Teams is one such tool that allows teams to communicate, collaborate, and stay productive from anywhere. Teams Approvals is a feature within Microsoft Teams that enables users to create, manage, and track approvals directly within Teams. On the other hand, ServiceNow is a powerful platform for managing workflows, incidents, and service requests. Integrating Teams Approvals with ServiceNow can streamline approval processes and improve overall efficiency.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;There are different ways to implement this integration, in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/integrating-service-now-with-microsoft-teams-approvals-via-power/ba-p/3760994" target="_blank" rel="noopener"&gt;first part&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;of the post, we saw how we could integrate ServiceNow with Microsoft Teams Approvals to simplify the approval process for ServiceNow change requests.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this second part of the post, we will explore how we can create a custom HTTP trigger in Power Automate to allow ServiceNow to trigger the approval process in Microsoft Teams. This will enable us to automate the approval process even further and integrate it with other systems that may not be directly connected to ServiceNow.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;By the end of this post, you'll have a better understanding of how to integrate Microsoft Teams Approvals with ServiceNow and the benefits it can bring to your organization.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2 id="prerequisites"&gt;Prerequisites&lt;/H2&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="service-now-environment"&gt;&lt;A id="pragma-line-21" target="_blank"&gt;&lt;/A&gt;ServiceNow Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;ServiceNow provides a free development environment called the Now Platform (&lt;A href="https://developer.servicenow.com/dev.do" target="_blank" rel="noopener"&gt;https://developer.servicenow.com/dev.do&lt;/A&gt;), which enables developers to quickly build, test, and deploy applications to improve workflows and productivity within their organizations. With a free account on the Now Platform, you can start building powerful applications that leverage ServiceNow's advanced capabilities for workflow management, incident management, and service requests. Whether you're an experienced developer or just getting started, the Now Platform offers a flexible and easy-to-use environment for creating custom solutions tailored to your organization's unique needs.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="microsoft-teams"&gt;&lt;A id="pragma-line-24" target="_blank"&gt;&lt;/A&gt;Microsoft Teams&lt;/H3&gt;
&lt;OL id="pragma-line-25"&gt;
&lt;LI id="pragma-line-25"&gt;
&lt;P&gt;Go to the Microsoft Teams website (&lt;A href="https://www.microsoft.com/en-us/microsoft-teams/group-chat-software" target="_blank" rel="noopener"&gt;https://www.microsoft.com/en-us/microsoft-teams/group-chat-software&lt;/A&gt;) and click on the "Sign up for free" button.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-27"&gt;
&lt;P&gt;Enter your email address and click on "Next". If you already have a Microsoft account, you can sign in with that account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-29"&gt;
&lt;P&gt;Create a new password and click on "Sign up".&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-31"&gt;
&lt;P&gt;Follow the prompts to complete the setup process, such as entering your name and company information.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-33"&gt;
&lt;P&gt;Once your account is set up, you can download the Microsoft Teams app on your desktop or mobile device and start collaborating with your team.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;With a free Microsoft Teams account, you can chat with colleagues, make voice and video calls, share files, and collaborate on projects from anywhere. Plus, with its seamless integration with other Microsoft products, such as Office 365 and SharePoint, Microsoft Teams can help streamline your organization's workflows and improve productivity.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="power-automate-environment"&gt;&lt;A id="pragma-line-37" target="_blank"&gt;&lt;/A&gt;Power Automate Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Microsoft provides a free Power Automate environment (&lt;A href="https://powerautomate.microsoft.com/en-us/#home-signup" target="_blank" rel="noopener"&gt;https://powerautomate.microsoft.com/en-us/#home-signup&lt;/A&gt;), which enables users to automate workflows across multiple applications and services. With Power Automate, you can create automated processes, such as approvals, notifications, and data synchronization, to save time and increase efficiency. Whether you're a business user or a developer, the Power Automate environment provides an intuitive and easy-to-use interface for creating and managing workflows. Plus, with its seamless integration with other Microsoft products, such as Teams and SharePoint, Power Automate can help streamline your organization's workflows and improve collaboration.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H2&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="scenarios"&gt;&lt;A id="pragma-line-40" target="_blank"&gt;&lt;/A&gt;Scenarios&lt;/H2&gt;
&lt;P&gt;First, ensure that you have a valid account for both Microsoft Teams and ServiceNow, as well as access to Power Automate.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="1-scheduled-trigger"&gt;&lt;A id="pragma-line-43" target="_blank"&gt;&lt;/A&gt;1. Scheduled Trigger&lt;/H3&gt;
&lt;P&gt;This scenario is available in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/integrating-service-now-with-microsoft-teams-approvals-via-power/ba-p/3760994" target="_blank" rel="noopener"&gt;first part&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;of this post.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="2-http-trigger"&gt;&lt;A id="pragma-line-48" target="_blank"&gt;&lt;/A&gt;2. Http Trigger&lt;/H3&gt;
&lt;P&gt;HTTP triggers are a powerful feature in Power Automate that allows us to create custom APIs and automate processes with external systems. By creating a custom HTTP trigger, we can make the approval process more flexible and scalable, and enable other systems to interact with Microsoft Teams Approvals without having to go through ServiceNow.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Here's a scenario on how to integrate Microsoft Teams Approvals with ServiceNow using Power Automate's scheduled trigger:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-54"&gt;
&lt;LI id="pragma-line-54"&gt;
&lt;P&gt;Open a web browser and go to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://make.powerautomate.com/" target="_blank" rel="noopener"&gt;Power Automate portal&lt;/A&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-56"&gt;
&lt;P&gt;Sign in to Power Automate using your Microsoft account or organizational account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-58"&gt;
&lt;P&gt;Once you're signed in, you should see the Power Automate portal dashboard. On the left-hand menu, click on "Create" to create a new flow.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-60"&gt;
&lt;P&gt;Choose a template from the available options, or start from scratch by selecting "Instant - from blank".&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-64" start="5"&gt;
&lt;LI id="pragma-line-64"&gt;Give your flow a name, such as&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Pending Approvals via ServiceNow&lt;/STRONG&gt;. Choose the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;When an HTTP request is received&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;as the trigger, and click on Create.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To set up the "When a HTTP request is received" trigger, we need to define the input parameters that will be sent via a rule from ServiceNow (we will create this rule later).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-70" start="6"&gt;
&lt;LI id="pragma-line-70"&gt;Click on the "When a HTTP request is received" trigger to select it. Paste the following Request Body JSON Schema that will be sent by ServiceNow into the text box:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE id="pragma-line-72" class="language-json hljs"&gt;{
    &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"object"&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"properties"&lt;/SPAN&gt;: {
        &lt;SPAN class="hljs-attr"&gt;"sysapproval"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"sys_id"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"due_date"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"sys_created_by"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"approver"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"short_description"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"description"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"opened_by"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"approver_email"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"requestor_email"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        },
        &lt;SPAN class="hljs-attr"&gt;"link_details"&lt;/SPAN&gt;: {
            &lt;SPAN class="hljs-attr"&gt;"type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"string"&lt;/SPAN&gt;
        }
    }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The next step is sending the approval request to Microsoft Teams Approvals app.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-118" start="7"&gt;
&lt;LI id="pragma-line-118"&gt;Add a new action after the "When a HTTP request is received" step. In the search box, type "Approvals" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-122" start="8"&gt;
&lt;LI id="pragma-line-122"&gt;
&lt;P&gt;Select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approve/Reject - Everyone must approve&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for the Approval type field.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-124"&gt;
&lt;P&gt;Set up the mapping between the ServiceNow data fields and the corresponding fields in the Power Automate action:&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;TABLE id="pragma-line-126"&gt;
&lt;THEAD&gt;
&lt;TR id="pragma-line-126"&gt;
&lt;TH id="pragma-line-126"&gt;Field&lt;/TH&gt;
&lt;TH id="pragma-line-126"&gt;Value&lt;/TH&gt;
&lt;/TR&gt;
&lt;/THEAD&gt;
&lt;TBODY&gt;
&lt;TR id="pragma-line-128"&gt;
&lt;TD&gt;Title&lt;/TD&gt;
&lt;TD&gt;short_description&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-129"&gt;
&lt;TD&gt;Assigned to&lt;/TD&gt;
&lt;TD&gt;approver_email&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-130"&gt;
&lt;TD&gt;Details&lt;/TD&gt;
&lt;TD&gt;description&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-131"&gt;
&lt;TD&gt;Item link&lt;/TD&gt;
&lt;TD&gt;link_details&lt;/TD&gt;
&lt;/TR&gt;
&lt;TR id="pragma-line-132"&gt;
&lt;TD&gt;Item link description&lt;/TD&gt;
&lt;TD&gt;ServiceNow more detail&lt;/TD&gt;
&lt;/TR&gt;
&lt;/TBODY&gt;
&lt;/TABLE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-137" start="10"&gt;
&lt;LI id="pragma-line-137"&gt;Add a new action after the "Start and wait for an approval" step. In the search box, type "Control" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Condition&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-141" start="11"&gt;
&lt;LI id="pragma-line-141"&gt;Inform the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outcome&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step and the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approve&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-146" start="12"&gt;
&lt;LI id="pragma-line-146"&gt;Add a new action for the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;If yes&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step. In the search box, type "ServiceNow" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Update Record&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-151"&gt;
&lt;P&gt;This action will update the corresponding approval records in ServiceNow with the latest information retrieved from Microsoft Teams.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;OL id="pragma-line-153" start="13"&gt;
&lt;LI id="pragma-line-153"&gt;
&lt;P&gt;In the Record Type field, make sure to select the second "Approval" item in the dropdown list, which corresponds to the "approval" table from production environment and not the first one which is for the staging environment.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-155"&gt;
&lt;P&gt;In the System ID field, select the sys_id from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;When a HTTP request is received&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-159" start="15"&gt;
&lt;LI id="pragma-line-159"&gt;
&lt;P&gt;In the Comments field, select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Responses Comments&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-161"&gt;
&lt;P&gt;In the State field, inform the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;approved&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-165" start="17"&gt;
&lt;LI id="pragma-line-165"&gt;Repeat the steps 12-16, but add the new action inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;If not&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step and the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for the State field.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-169" start="18"&gt;
&lt;LI id="pragma-line-169"&gt;Save the flow.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-171"&gt;
&lt;P&gt;Note that when you save your trigger, you will be given a URL that you can use to invoke the trigger from external systems.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="set-up-service-now-business-rule"&gt;&lt;A id="pragma-line-173" target="_blank"&gt;&lt;/A&gt;Set up ServiceNow Business Rule&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Business Rules are server-side logic that execute when database records are queried, updated, inserted, or deleted. For more details, please check the ServiceNow Business Rules documentation.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;To set up a ServiceNow Business Rule to send a request to Microsoft Power Automate every time there is a new pending approval, you can follow these general steps:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-178"&gt;
&lt;LI id="pragma-line-178"&gt;Navigate to the Business Rules module in ServiceNow. This can be done by typing "Business Rules" in the navigation bar and selecting the appropriate option.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-182" start="2"&gt;
&lt;LI id="pragma-line-182"&gt;Click the "New" button to create a new business rule:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-187" start="3"&gt;
&lt;LI id="pragma-line-187"&gt;
&lt;P&gt;Give the business rule a name and description that accurately describes its purpose. For example,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;MS Approvals&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-189"&gt;
&lt;P&gt;Choose the table to which the business rule applies. This can be done by selecting the table from the drop-down menu in the "Applies to" field. For example,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approval [sysapproval_approver]&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-191"&gt;
&lt;P&gt;Specify the conditions that must be met for the business rule to run. This is done by adding conditions to the "When to run" section of the business rule. For example, you could specify that the rule should run after a new record is created or updated.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-193"&gt;
&lt;P&gt;Add a filter condition to consider only pending approvals (state is requested).&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-197" class="" start="7"&gt;
&lt;LI id="pragma-line-197"&gt;Click on the Advanced tab to define the actions that the business rule should take when it runs. This is done by adding script to the "Script" section of the business rule. The script can be written in JavaScript and can perform a variety of actions, such as updating a field, sending an email notification, or creating a related record.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-199"&gt;
&lt;P&gt;It's important to note that creating a business rule in ServiceNow requires some knowledge of JavaScript and the ServiceNow platform. If you're not familiar with these concepts, it may be helpful to seek assistance from a ServiceNow administrator or developer.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Add the following code:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;CODE id="pragma-line-203" class="language-javascript hljs"&gt;(&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;executeRule&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;current, previous&lt;/SPAN&gt;) &lt;/SPAN&gt;{
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; snowURL = &lt;SPAN class="hljs-string"&gt;"https://dev128124.service-now.com/"&lt;/SPAN&gt;;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; sysapproval = current.getValue(&lt;SPAN class="hljs-string"&gt;"sysapproval"&lt;/SPAN&gt;);
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; approver = current.getValue(&lt;SPAN class="hljs-string"&gt;"approver"&lt;/SPAN&gt;);
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; openedBy;
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; jsonTask ;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; jsonApprover ;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; jsonRequestor ;

    &lt;SPAN class="hljs-comment"&gt;//this isnt the best practice. Consider change it to oauth2 authentication&lt;/SPAN&gt;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; user = &lt;SPAN class="hljs-string"&gt;'CHANGE THIS VALUE'&lt;/SPAN&gt;;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; password = &lt;SPAN class="hljs-string"&gt;'CHANGE THIS VALUE'&lt;/SPAN&gt;;
	
	&lt;SPAN class="hljs-comment"&gt;//GET TASK with the details of the pending approval &lt;/SPAN&gt;
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; taskRequest = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; sn_ws.RESTMessageV2();
	
	taskRequest.setBasicAuth(user,password);
	taskRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;"Accept"&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;);
	taskRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;'Content-Type'&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;'application/json'&lt;/SPAN&gt;);
	
	taskRequest.setEndpoint(snowURL + &lt;SPAN class="hljs-string"&gt;"api/now/table/task?sysparm_query=sys_idIN"&lt;/SPAN&gt; + sysapproval + &lt;SPAN class="hljs-string"&gt;"&amp;amp;sysparm_exclude_reference_link=true&amp;amp;sysparm_fields=number%2Cshort_description%2Copened_by%2Csys_id%2Cimpact%2Copened_at%2Cpriority%2Cdescription"&lt;/SPAN&gt;);

	taskRequest.setHttpMethod(&lt;SPAN class="hljs-string"&gt;"GET"&lt;/SPAN&gt;);		
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; responseTask = taskRequest.execute();

	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; taskBody = responseTask.getBody();

	jsonTask = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; JSONParser().parse(taskBody);

	openedBy = jsonTask.result[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].opened_by;
	
	&lt;SPAN class="hljs-comment"&gt;//GET APPROVER EMAIL that will be used to send the card to Teams Approvals&lt;/SPAN&gt;
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; approverRequest = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; sn_ws.RESTMessageV2();
	
	approverRequest.setBasicAuth(user,password);
	approverRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;"Accept"&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;);
	approverRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;'Content-Type'&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;'application/json'&lt;/SPAN&gt;);
	
	approverRequest.setEndpoint(snowURL + &lt;SPAN class="hljs-string"&gt;"api/now/table/sys_user?sysparm_query=sys_idIN"&lt;/SPAN&gt;+ approver +&lt;SPAN class="hljs-string"&gt;"&amp;amp;sysparm_fields=sys_id,name,email"&lt;/SPAN&gt;);

	approverRequest.setHttpMethod(&lt;SPAN class="hljs-string"&gt;"GET"&lt;/SPAN&gt;);		
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; responseApprover = approverRequest.execute();

	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; approverBody = responseApprover.getBody();

	jsonApprover = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; JSONParser().parse(approverBody);

	&lt;SPAN class="hljs-comment"&gt;//GET REQUESTOR EMAIL that will be used on Teams Approvals card&lt;/SPAN&gt;
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; requestorRequest = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; sn_ws.RESTMessageV2();
	
	requestorRequest.setBasicAuth(user,password);
	requestorRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;"Accept"&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;);
	requestorRequest.setRequestHeader(&lt;SPAN class="hljs-string"&gt;'Content-Type'&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;'application/json'&lt;/SPAN&gt;);
	
	requestorRequest.setEndpoint(snowURL + &lt;SPAN class="hljs-string"&gt;"api/now/table/sys_user?sysparm_query=sys_idIN"&lt;/SPAN&gt;+ openedBy +&lt;SPAN class="hljs-string"&gt;"&amp;amp;sysparm_fields=sys_id,name,email"&lt;/SPAN&gt;);

	requestorRequest.setHttpMethod(&lt;SPAN class="hljs-string"&gt;"GET"&lt;/SPAN&gt;);		
	
	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; requestorResponse = requestorRequest.execute();

	&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; bodyRequestor = requestorResponse.getBody();

	jsonRequestor = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; JSONParser().parse(bodyRequestor);

	&lt;SPAN class="hljs-comment"&gt;//CALL POWER AUTOMATE TO SEND TO TEAMS APPROVALS&lt;/SPAN&gt;
    &lt;SPAN class="hljs-keyword"&gt;try&lt;/SPAN&gt; {

		&lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; body = {
				&lt;SPAN class="hljs-string"&gt;"sysapproval"&lt;/SPAN&gt;: sysapproval,
				&lt;SPAN class="hljs-string"&gt;"sys_id"&lt;/SPAN&gt;: current.getValue(&lt;SPAN class="hljs-string"&gt;"sys_id"&lt;/SPAN&gt;),
				&lt;SPAN class="hljs-string"&gt;"due_date"&lt;/SPAN&gt;: current.getValue(&lt;SPAN class="hljs-string"&gt;"due_date"&lt;/SPAN&gt;),
				&lt;SPAN class="hljs-string"&gt;"sys_created_by"&lt;/SPAN&gt;: current.getValue(&lt;SPAN class="hljs-string"&gt;"sys_created_by"&lt;/SPAN&gt;),
				&lt;SPAN class="hljs-string"&gt;"approver"&lt;/SPAN&gt;: approver,
				&lt;SPAN class="hljs-string"&gt;"short_description"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt; + jsonTask.result[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].short_description,
				&lt;SPAN class="hljs-string"&gt;"description"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt; + jsonTask.result[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].description,
				&lt;SPAN class="hljs-string"&gt;"opened_by"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt; + openedBy,
				&lt;SPAN class="hljs-string"&gt;"approver_email"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt; + jsonApprover.result[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].email,
				&lt;SPAN class="hljs-string"&gt;"requestor_email"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt; + jsonRequestor.result[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].email,
				&lt;SPAN class="hljs-string"&gt;"link_details"&lt;/SPAN&gt; : snowURL + &lt;SPAN class="hljs-string"&gt;"now/nav/ui/classic/params/target/sysapproval_approver.do%3Fsys_id%3D"&lt;/SPAN&gt; + current.getValue(&lt;SPAN class="hljs-string"&gt;"sys_id"&lt;/SPAN&gt;)
		};
		
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; r = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; sn_ws.RESTMessageV2();
        
        &lt;SPAN class="hljs-comment"&gt;// Change the following URL by the one you have created as HTTP trigger on Power Automate&lt;/SPAN&gt;
		r.setEndpoint(&lt;SPAN class="hljs-string"&gt;"https://xxx.westus.logic.azure.com:443/workflows/xxxxxxxx56f6/triggers/manual/paths/invoke?api-version=2016-06-01&amp;amp;sp=%2Ftriggers%2Fmanual%2Frun&amp;amp;sv=1.0&amp;amp;sig=EKb91O-VP86wsFeP144H63-vZHK8XFpcuu7HLHc7DEM"&lt;/SPAN&gt;);
		
		r.setHttpMethod(&lt;SPAN class="hljs-string"&gt;"POST"&lt;/SPAN&gt;);
        r.setRequestBody(&lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.stringify(body));
	
		r.setRequestHeader(&lt;SPAN class="hljs-string"&gt;"Accept"&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;);
		r.setRequestHeader(&lt;SPAN class="hljs-string"&gt;'Content-Type'&lt;/SPAN&gt;,&lt;SPAN class="hljs-string"&gt;'application/json'&lt;/SPAN&gt;);
      
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; response = r.execute();
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; httpStatus = response.getStatusCode();
		
    } &lt;SPAN class="hljs-keyword"&gt;catch&lt;/SPAN&gt; (ex) {
        &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; message = ex.message;
		gs.error(&lt;SPAN class="hljs-string"&gt;"Error message: "&lt;/SPAN&gt; + message);
    }

    gs.info(&lt;SPAN class="hljs-string"&gt;"Webhook target HTTP status response: "&lt;/SPAN&gt; + httpStatus);
  
    
})(current, previous);
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-316" start="8"&gt;
&lt;LI id="pragma-line-316"&gt;Click on Submit to save it.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-321" class="" start="9"&gt;
&lt;LI id="pragma-line-321"&gt;To get to the list of pending approvals in ServiceNow:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-323" start="9" type="a"&gt;
&lt;LI id="pragma-line-323"&gt;In the navigation bar at the top of the screen, click on the "My Approvals" link. This will take you to the "My Approvals" page.&lt;/LI&gt;
&lt;/OL&gt;
&lt;OL id="pragma-line-325" start="2" type="i"&gt;
&lt;LI id="pragma-line-325"&gt;On the "My Approvals" page, you should see a list of all of your pending approvals. This list will include the details of each approval request, such as the name of the item being approved, the requester, and the date the request was submitted.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-327"&gt;
&lt;P&gt;In case you can't see any pending approval, consider removing the filter on the top of the page by clicking on All.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-331" start="10"&gt;
&lt;LI id="pragma-line-331"&gt;To view the details of a specific approval request, click on the request in the list. This will take you to the approval form, where you can review the details of the request. Change the status to Requested and click on Update.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-335" start="11"&gt;
&lt;LI id="pragma-line-335"&gt;Switch back to Microsoft Teams and observe that your pending approval request is there:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 17 Apr 2023 14:03:06 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/servicenow-with-microsoft-teams-approvals-part-2-http-trigger/ba-p/3766470</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2023-04-17T14:03:06Z</dc:date>
    </item>
    <item>
      <title>Integrating ServiceNow with Microsoft Teams Approvals via Power Automate</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/integrating-servicenow-with-microsoft-teams-approvals-via-power/ba-p/3760994</link>
      <description>&lt;UL id="pragma-line-0"&gt;
&lt;LI id="pragma-line-0"&gt;Summary
&lt;UL id="pragma-line-1"&gt;
&lt;LI id="pragma-line-1"&gt;&lt;A href="#community--1-prerequisites" target="_blank" rel="noopener"&gt;Prerequisites&lt;/A&gt;
&lt;UL id="pragma-line-2"&gt;
&lt;LI id="pragma-line-2"&gt;&lt;A href="#community--1-service-now-enviroment" target="_blank" rel="noopener"&gt;Service Now Environment&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-3"&gt;&lt;A href="#community--1-power-automate-enviroment" target="_blank" rel="noopener"&gt;Power Automate Environment&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-4"&gt;&lt;A href="#community--1-microsoft-teams" target="_blank" rel="noopener"&gt;Microsoft Teams&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-5"&gt;&lt;A href="#community--1-Scenarios" target="_blank" rel="noopener"&gt;Scenarios&lt;/A&gt;
&lt;UL id="pragma-line-6"&gt;
&lt;LI id="pragma-line-6"&gt;&lt;A href="#community--1-1-scheduled-trigger" target="_blank" rel="noopener"&gt;Scheduled Trigger&lt;/A&gt;&lt;/LI&gt;
&lt;LI id="pragma-line-7"&gt;&lt;A href="#community--1-2-http-trigger" target="_blank" rel="noopener"&gt;HTTP Trigger&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1 id="integrating-service-now-with-microsoft-teams-approvals-via-power-automate"&gt;Integrating ServiceNow with Microsoft Teams Approvals via Power Automate&lt;/H1&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As more and more organizations move towards remote work, communication and collaboration tools have become essential for seamless and efficient workflows. Microsoft Teams is one such tool that allows teams to communicate, collaborate, and stay productive from anywhere. Teams Approvals is a feature within Microsoft Teams that enables users to create, manage, and track approvals directly within Teams. On the other hand, ServiceNow is a powerful platform for managing workflows, incidents, and service requests. Integrating Teams Approvals with ServiceNow can streamline approval processes and improve overall efficiency.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=""&gt;There are different ways to implement this integration, but in this post, we will discuss two scenarios. The first scenario involves setting up a scheduler trigger on Power Automate to retrieve pending approvals from Teams Approvals and update them in ServiceNow. The second scenario involves receiving pending approvals directly in ServiceNow and updating them accordingly. By the end of this post, you'll have a better understanding of how to integrate Microsoft Teams Approvals with ServiceNow and the benefits it can bring to your organization.&lt;/P&gt;
&lt;H2 class=""&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="prerequisites" class=""&gt;&lt;A id="pragma-line-15" target="_blank"&gt;&lt;/A&gt;Prerequisites&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="service-now-environment"&gt;&lt;A id="pragma-line-17" target="_blank"&gt;&lt;/A&gt;Service Now Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;ServiceNow provides a free development environment called the Now Platform (&lt;A href="https://developer.servicenow.com/dev.do" target="_blank" rel="noopener"&gt;https://developer.servicenow.com/dev.do&lt;/A&gt;), which enables developers to quickly build, test, and deploy applications to improve workflows and productivity within their organizations. With a free account on the Now Platform, you can start building powerful applications that leverage ServiceNow's advanced capabilities for workflow management, incident management, and service requests. Whether you're an experienced developer or just getting started, the Now Platform offers a flexible and easy-to-use environment for creating custom solutions tailored to your organization's unique needs.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="microsoft-teams"&gt;&lt;A id="pragma-line-20" target="_blank"&gt;&lt;/A&gt;Microsoft Teams&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-21"&gt;
&lt;LI id="pragma-line-21"&gt;
&lt;P&gt;Go to the Microsoft Teams website (&lt;A href="https://www.microsoft.com/en-us/microsoft-teams/group-chat-software" target="_blank" rel="noopener"&gt;https://www.microsoft.com/en-us/microsoft-teams/group-chat-software&lt;/A&gt;) and click on the "Sign up for free" button.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-23"&gt;
&lt;P&gt;Enter your email address and click on "Next". If you already have a Microsoft account, you can sign in with that account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-25"&gt;
&lt;P&gt;Create a new password and click on "Sign up".&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-27"&gt;
&lt;P&gt;Follow the prompts to complete the setup process, such as entering your name and company information.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-29"&gt;
&lt;P&gt;Once your account is set up, you can download the Microsoft Teams app on your desktop or mobile device and start collaborating with your team.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;With a free Microsoft Teams account, you can chat with colleagues, make voice and video calls, share files, and collaborate on projects from anywhere. Plus, with its seamless integration with other Microsoft products, such as Office 365 and SharePoint, Microsoft Teams can help streamline your organization's workflows and improve productivity.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="power-automate-environment"&gt;&lt;A id="pragma-line-33" target="_blank"&gt;&lt;/A&gt;Power Automate Environment&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Microsoft provides a free Power Automate environment (&lt;A href="https://powerautomate.microsoft.com/en-us/#home-signup" target="_blank" rel="noopener"&gt;https://powerautomate.microsoft.com/en-us/#home-signup&lt;/A&gt;), which enables users to automate workflows across multiple applications and services. With Power Automate, you can create automated processes, such as approvals, notifications, and data synchronization, to save time and increase efficiency. Whether you're a business user or a developer, the Power Automate environment provides an intuitive and easy-to-use interface for creating and managing workflows. Plus, with its seamless integration with other Microsoft products, such as Teams and SharePoint, Power Automate can help streamline your organization's workflows and improve collaboration.&lt;/P&gt;
&lt;H2&gt;&amp;nbsp;&lt;/H2&gt;
&lt;H2 id="scenarios"&gt;&lt;A id="pragma-line-36" target="_blank"&gt;&lt;/A&gt;Scenarios&lt;/H2&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;First, ensure that you have a valid account for both Microsoft Teams and ServiceNow, as well as access to Power Automate.&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="1-scheduled-trigger"&gt;&lt;A id="pragma-line-39" target="_blank"&gt;&lt;/A&gt;1. Scheduled Trigger&lt;/H3&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Here's a scenario on how to integrate Microsoft Teams Approvals with ServiceNow using Power Automate's scheduled trigger:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-43"&gt;
&lt;LI id="pragma-line-43"&gt;
&lt;P&gt;Open a web browser and go to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://make.powerautomate.com/" target="_blank" rel="noopener"&gt;Power Automate portal&lt;/A&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-45"&gt;
&lt;P&gt;Sign into Power Automate using your Microsoft account or organizational account.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-47"&gt;
&lt;P&gt;Once you're signed in, you should see the Power Automate portal dashboard. On the left-hand menu, click on "Create" to create a new flow.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-49"&gt;
&lt;P&gt;Choose a template from the available options, or start from scratch by selecting "Instant - from blank" or "Scheduled - from blank" depending on whether you want to trigger the flow manually or automatically at a scheduled time.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-53" start="5"&gt;
&lt;LI id="pragma-line-53"&gt;Give your flow a name, such as&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;"Get pending approvals from ServiceNow"&lt;/STRONG&gt;. Set the trigger to run at a suitable interval, such as every 30 minutes, and click on Create.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-57" start="6"&gt;
&lt;LI id="pragma-line-57"&gt;Next, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;+New step&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to add a new actions to your flow:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-61" start="7"&gt;
&lt;LI id="pragma-line-61"&gt;In the search bar, type "ServiceNow" and select the "ServiceNow" connector from the list of available connectors. Choose the appropriate action from the ServiceNow connector, in this case, the "List Records" connector:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-65" start="8"&gt;
&lt;LI id="pragma-line-65"&gt;If this is your first time using the ServiceNow connector, you will be prompted to sign in to your ServiceNow account. Enter your ServiceNow instance name, username, and password to authenticate the connection.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The "Instance name" field should contain the name of your ServiceNow instance, such as "myinstance.service-now.com". The "Username" and "Password" fields should contain your ServiceNow login credentials.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-73" start="9"&gt;
&lt;LI id="pragma-line-73"&gt;Select the second Approval item from the Record Type tables in ServiceNow. This will enable you to retrieve pending approvals from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;sysapproval_approver&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;table instead of the sc_ic_aprvl_defn_staging one.&lt;/LI&gt;
&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-75"&gt;
&lt;P&gt;In case you want to know more about the ServiceNow tables, ServiceNow provides the REST API Explorer, which is a web-based tool that allows you to explore and test the ServiceNow REST APIs. You can use the REST API Explorer to understand the different REST APIs provided by ServiceNow and how to use them to perform various operations on your ServiceNow instance. It is available within your ServiceNow instance under the "System Web Services" menu.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-79" start="10"&gt;
&lt;LI id="pragma-line-79"&gt;Click on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Show advanced options&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;item and set the query to filter the results to only include pending approvals in the "requested" state that were created within the last 30 minutes (the same interval from our Schedule trigger).&lt;/LI&gt;
&lt;/OL&gt;
&lt;BLOCKQUOTE id="pragma-line-81"&gt;
&lt;P&gt;This is the query value: state=requested^sys_updated_on&amp;gt;@{getPastTime(30,'Minute','yyyy-MM-ddTHH:mm:ss')}&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-86" start="11"&gt;
&lt;LI id="pragma-line-86"&gt;Click on the three dots (...) in the top right corner of the "List Records" step to access the dropdown menu. Select Rename, and in the "Rename step" dialog box, enter "Get pending approvals" as the new name for the step.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-93" start="12"&gt;
&lt;LI id="pragma-line-93"&gt;Add a new step after the "Get pending approvals" step. In the search box, type "apply" and select the "Apply to each" control connector.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-97" start="13"&gt;
&lt;LI id="pragma-line-97"&gt;
&lt;P&gt;In the "Select an output from previous steps" box, select the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;result&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the "Format pending approval result" step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-99"&gt;
&lt;P&gt;Click on the "Add an action" button to add a new action to the step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-101"&gt;
&lt;P&gt;In the search bar, type "ServiceNow" and select the "ServiceNow" connector from the list of available connectors. Choose the "Get Record" connector:&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-105"&gt;
&lt;P&gt;This step is needed to get the details about the approval, like short description, description and opened by.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;OL id="pragma-line-107" start="16"&gt;
&lt;LI id="pragma-line-107"&gt;In the Record Type field, make sure to select the second "Task" item in the dropdown list, which corresponds to the "task" table and not the first table which is for the staging environment:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-111" start="17"&gt;
&lt;LI id="pragma-line-111"&gt;
&lt;P&gt;In the System ID field, select the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approval for&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-113"&gt;
&lt;P&gt;Click on the three dots (...) in the top right corner of the "Get Record" step to access the dropdown menu. Select Rename, and in the "Rename step" dialog box, enter "Get Task Detail" as the new name for the step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-115"&gt;
&lt;P&gt;Click on the "Add an action" button to add a new action to the step. This step is to get the approver email.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-117"&gt;
&lt;P&gt;In the search bar, type "ServiceNow" and select the "ServiceNow" connector from the list of available connectors. Choose the "Get Record" connector.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-119"&gt;
&lt;P&gt;In the Record Type field, make sure to select the second "User" item in the dropdown list, which corresponds to the "user" table and not the first one which is for the staging environment.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-123" start="22"&gt;
&lt;LI id="pragma-line-123"&gt;
&lt;P&gt;In the System ID field, select the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approver&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-125"&gt;
&lt;P&gt;Click on the three dots (...) in the top right corner of the "Get Record" step to access the dropdown menu. Select Rename, and in the "Get Approver email" dialog box, enter "Get Task Detail" as the new name for the step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-127"&gt;
&lt;P&gt;Repeat the steps 19-21 to get the requestor email.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-129"&gt;
&lt;P&gt;In the System ID field, select the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Opened By&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-133" start="26"&gt;
&lt;LI id="pragma-line-133"&gt;Click on the three dots (...) in the top right corner of the "Get Record" step to access the dropdown menu. Select Rename, and in the "Get Requestor email" dialog box, enter "Get Task Detail" as the new name for the step.&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;Follows the expected result:&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-133" start="26"&gt;&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;The next step is sending the approval request to Microsoft Teams Approvals app.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-142" start="27"&gt;
&lt;LI id="pragma-line-142"&gt;Add a new action after the "Get Requestor email" step, but yet inside the Apply to each loop. In the search box, type "Approvals" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-146" start="28"&gt;
&lt;LI id="pragma-line-146"&gt;
&lt;P&gt;Select&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Approve/Reject - Everyone must approve&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for the Approval type field.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-148"&gt;
&lt;P&gt;Select the Short description for the title, the approver email for the Assigned to field and the description for the details field.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-152" start="30"&gt;
&lt;LI id="pragma-line-152"&gt;Add a new action after the "Start and wait for an approval" step, but yet inside the Apply to each loop. In the search box, type "Control" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Condition&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-156" start="31"&gt;
&lt;LI id="pragma-line-156"&gt;Inform the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outcome&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step and the value&lt;SPAN&gt;&amp;nbsp;&lt;STRONG&gt;A&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;STRONG&gt;pprove&lt;/STRONG&gt;:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;OL id="pragma-line-161" start="32"&gt;
&lt;LI id="pragma-line-161"&gt;Add a new action for the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;If yes&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step. In the search box, type "ServiceNow" and select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Update Record&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control:&lt;/LI&gt;
&lt;/OL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE id="pragma-line-166"&gt;
&lt;P&gt;This action will update the corresponding approval records in ServiceNow with the latest information retrieved from Microsoft Teams.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;OL id="pragma-line-168" start="33"&gt;
&lt;LI id="pragma-line-168"&gt;
&lt;P&gt;In the Record Type field, make sure to select the second "Approval" item in the dropdown list, which corresponds to the "approval" table from production environment and not the first one which is for the staging environment.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-170"&gt;
&lt;P&gt;In the System ID field, select the Sys ID from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Get pending approvals&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-172"&gt;
&lt;P&gt;In the Comments field, select the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Responses Comments&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Start and wait for an approval&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-174"&gt;
&lt;P&gt;In the State field, inform the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;approved&lt;/STRONG&gt;.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI id="pragma-line-176"&gt;
&lt;P&gt;Repeat the steps 32-35, but add the new action inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;If not&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;step and the value&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;rejected&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for the State field.&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Save the flow and enable it to run at the scheduled interval you specified.&lt;/P&gt;
&lt;P&gt;As approvals are requested and processed in Microsoft Teams, the scheduled flow will retrieve the pending approvals and update the corresponding records in ServiceNow.&lt;/P&gt;
&lt;P&gt;Access Microsoft Teams, open the Approvals App and observe that ServiceNow pending approvals will be there:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3&gt;&amp;nbsp;&lt;/H3&gt;
&lt;H3 id="2-http-trigger"&gt;2. Http Trigger&lt;/H3&gt;
&lt;P&gt;This topic is covered in the &lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/service-now-with-microsoft-teams-approvals-part-2-http-trigger/ba-p/3766470" target="_self"&gt;part 2&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3&gt;Related - Azure App Logic and Adaptive Card solution&lt;/H3&gt;
&lt;P&gt;The following article&amp;nbsp;&lt;SPAN&gt;demonstrates how to use Logic Apps (or Power Automate) to detect approval requests on ServiceNow and enable approvers to approve or reject requests directly from Teams via Adaptive Cards and Logic Apps.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Furthermore, the fact that this solution does not require premium connectors and only requires user access that is cheap is a significant advantage. This means that even smaller organizations with limited budgets can benefit from this solution and automate their approval processes without having to invest heavily in expensive tools and software. Overall, this solution seems like an excellent way to automate approval processes and improve the efficiency of organizational workflows.&lt;/P&gt;
&lt;H1 class="post-title"&gt;&lt;FONT size="4"&gt;&lt;A href="https://tech-peanuts.com/2023/03/03/integrating-service-now-and-teams-with-logic-apps-approvals/" target="_blank" rel="bookmark noopener"&gt;Integrating ServiceNow and Teams with Logic Apps – Approvals&lt;/A&gt;&lt;/FONT&gt;&lt;/H1&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 17 Apr 2023 14:03:56 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/integrating-servicenow-with-microsoft-teams-approvals-via-power/ba-p/3760994</guid>
      <dc:creator>luisdem</dc:creator>
      <dc:date>2023-04-17T14:03:56Z</dc:date>
    </item>
    <item>
      <title>Bringing Open AI into an Outlook add-in: moving to Azure Open AI</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-open-ai-into-an-outlook-add-in-moving-to-azure-open-ai/ba-p/3745592</link>
      <description>&lt;P&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank" rel="noopener"&gt;In the previous post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;we have learned how to use AI to help users write better business mails. We developed an add-in for Outlook that, using the AI models and the APIs provided by Open AI, can generate a business mail starting from one or two sentences and, using the Office SDK, to include it into the body of the mail.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For this sample project, we have directly leveraged the APIs provided by Open AI. We created an account on their website and we obtained an API key required to manage the authentication.&lt;/P&gt;
&lt;P&gt;But what if you're an enterprise customer and you're already using Azure to host many of your applications and services? It would be great if there was a way to use the powerful models of Open AI, but leveraging at the same time the security, privacy and billing provided by Azure.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Well, actually, this way exists and it's&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/products/cognitive-services/openai-service" target="_blank" rel="noopener"&gt;the Azure Open AI service&lt;/A&gt;, which is part of the Cognitive Services family. Thanks to a partnership between Open AI and Microsoft, the same exact models that you can use with the Open AI APIs are available on Azure. The way they work is very similar, but the main difference is that the whole hosting infrastructure will be provided by Azure, with all its benefits around scaling and security.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;In this post we're going to replace the Open AI API implementation we did the last time with a new one based on Azure Open AI. Let's start!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="getting-access-to-azure-open-ai"&gt;Getting access to Azure Open AI&lt;/H3&gt;
&lt;P&gt;The starting point is, of course, to have an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/free/" target="_blank" rel="noopener"&gt;Azure subscription&lt;/A&gt;. However, wait before you rush into code, because there's a catch, even if for a very good reason. Microsoft is pushing for a responsible and ethical approach on AI, to avoid many of the misuses and biases of this technology.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;As such, before you go and create a new Azure Open AI service on your tenant, you must submit&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://go.microsoft.com/fwlink/?linkid=2222006&amp;amp;clcid=0x409&amp;amp;culture=en-us&amp;amp;country=us" target="_blank" rel="noopener"&gt;a nomination form&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and provide some important information, like who you are, what you're trying to build, the company you're working for, the ID of the subscription where you would like to host the service and so on. Once you have submitted the form, you will get a response within 10 days. If the request is approved, you will be able to open the Azure portal and start creating new instances of the Azure Open AI service.&lt;/P&gt;
&lt;P&gt;The service creation is quite simple, since you don't have many options: you choose the subscription, the resource group, the location, the pricing tier and the name.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Once the service has been created, you can deploy one or more AI models, based on the scenario you're building. Move to the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Model deployments&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;section and click&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create&lt;/STRONG&gt;&lt;SPAN&gt;. Give it a name and, under the&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Model&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;dropdown, you will find an extensive list of the available Open AI models. Choose&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;text-davinci-003&lt;/STRONG&gt;&lt;SPAN&gt;, which is the same one we have used in the previous post.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This is all we need to do to start using the model in your application.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="using-the-model-from-code"&gt;Using the model from code&lt;/H3&gt;
&lt;P&gt;Using the model we have just deployed is quite simple since we just need to perform a HTTP request against the endpoint of our Azure Open AI instance. We're going to work on the taskpane implementation, specifically in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;App.tsx&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file which contains the whole logic implementation. In the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;App&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;component, we have created&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank" rel="noopener"&gt;in the previous post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generateText()&lt;/CODE&gt;, which passes the text provided by the user to the Open AI APIs using the Open AI JavaScript library.&lt;/P&gt;
&lt;P&gt;Let's see the new implementation that, instead, connects to the Azure Open AI service we have just deployed:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;generateText = &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; () =&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; current = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; apiKey = &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; endpoint = &lt;SPAN class="hljs-string"&gt;"your-url"&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; prompt = &lt;SPAN class="hljs-string"&gt;"Turn the following text into a professional business mail: "&lt;/SPAN&gt; + &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.startText;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; deploymentName = &lt;SPAN class="hljs-string"&gt;"your-deployment-name"&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; url = endpoint + &lt;SPAN class="hljs-string"&gt;"openai/deployments/"&lt;/SPAN&gt; + deploymentName + &lt;SPAN class="hljs-string"&gt;"/completions?api-version=2022-12-01"&lt;/SPAN&gt;;

  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; payload = {
    prompt: prompt,
    max_tokens: &lt;SPAN class="hljs-number"&gt;1000&lt;/SPAN&gt;,
    temperature: &lt;SPAN class="hljs-number"&gt;0.7&lt;/SPAN&gt;
  };

  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; fetch(url, {
    method: &lt;SPAN class="hljs-string"&gt;"POST"&lt;/SPAN&gt;,
    headers: {
      &lt;SPAN class="hljs-string"&gt;"Content-Type"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"application/json"&lt;/SPAN&gt;,
      &lt;SPAN class="hljs-string"&gt;"api-key"&lt;/SPAN&gt;: apiKey,
    },
    body: &lt;SPAN class="hljs-built_in"&gt;JSON&lt;/SPAN&gt;.stringify(payload),
  });

  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; data = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; response.json();
  current.setState({ generatedText: data.choices[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].text });
};
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;In the beginning of the function, we initialize all the information we need to connect to our instance of Azure Open AI:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;P&gt;&lt;CODE&gt;apiKey&lt;/CODE&gt;, which can be retrieved from the Azure portal, under the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Keys and Endpoint&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section. Copy the value of KEY 1 and paste it inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiKey&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;constant.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt; like for the Open AI APIs, we're using this approach just for testing purposes. It isn't suggested to do this in production, since we would easily expose the API key. The suggested way is to build a middleware between Azure Open AI and your add-in (like an Azure Function) and use Azure Key Vault to protect your API key.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;&lt;CODE&gt;endpoint&lt;/CODE&gt;, which is the URL of your Azure Open AI instance. You can find it in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Keys and Endpoints&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section as well.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;&lt;CODE&gt;prompt&lt;/CODE&gt;, which is the text we want to pass to Azure Open AI. In our case, it's the prompt that describes the outcome we want to achieve (&lt;EM&gt;Turn the following text into a professional business mail&lt;/EM&gt;), followed by the text typed by the user, which is stored in the component's state.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;&lt;CODE&gt;deploymentName&lt;/CODE&gt;, which is the name we have chosen when we have deployed the model.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The next step is to build the full URL we need to reach to communicate with Azure Open AI, which has the following format:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-text hljs"&gt;https://your-endpoint.openai.azure.com/openai/deployments/your-deployment/completions?api-version=2022-12-01
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The request is sent using a HTTP POST, so we need to create a JSON payload to include in the body with the inputs. The only required one is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;prompt&lt;/CODE&gt;, with the text we want to process; in our example, we are also passing&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;max_tokens&lt;/CODE&gt;, to specify the maximum length of the text we want to get back, and the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;temperature&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(see&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank" rel="noopener"&gt;the previous post&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;for an explanation on this value). You can find the complete list of the supported body parameters&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/cognitive-services/openai/reference" target="_blank" rel="noopener"&gt;in the official documentation&lt;/A&gt;. In most cases, there's a 1:1 match between the Open AI and the Azure Open AI parameters format.&lt;/P&gt;
&lt;P&gt;Finally, we can submit the HTTP request using the standard&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;fetch()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;API. Since it's not a standard GET operation, we need to pass some extra parameters other than the URL:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;CODE&gt;method&lt;/CODE&gt;, which is POST.&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;headers&lt;/CODE&gt;, since we need to add an extra header called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;api-key&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with the API key we have retrieved from the Azure portal.&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;body&lt;/CODE&gt;, which is the payload we have previously created transformed into JSON with the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;JSON.stringify()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;The API will return a response like the following one:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-json hljs"&gt;{
    &lt;SPAN class="hljs-attr"&gt;"id"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"cmpl-4kGh7iXtjW4lc9eGhff6Hp8C7btdQ"&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"object"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"text_completion"&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"created"&lt;/SPAN&gt;: &lt;SPAN class="hljs-number"&gt;1646932609&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"model"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"ada"&lt;/SPAN&gt;,
    &lt;SPAN class="hljs-attr"&gt;"choices"&lt;/SPAN&gt;: [
        {
            &lt;SPAN class="hljs-attr"&gt;"text"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;", a dark line crossed"&lt;/SPAN&gt;,
            &lt;SPAN class="hljs-attr"&gt;"index"&lt;/SPAN&gt;: &lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;,
            &lt;SPAN class="hljs-attr"&gt;"logprobs"&lt;/SPAN&gt;: &lt;SPAN class="hljs-literal"&gt;null&lt;/SPAN&gt;,
            &lt;SPAN class="hljs-attr"&gt;"finish_reason"&lt;/SPAN&gt;: &lt;SPAN class="hljs-string"&gt;"length"&lt;/SPAN&gt;
        }
    ]
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;You can notice how this is the same exact format of the standard Open AI APIs we have used in the previous post. As such, we can retrieve the text generated by Open AI from the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;text&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the first item in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;choices&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection. The rest of the code is the same as the one we have seen&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank" rel="noopener"&gt;in the previous post&lt;/A&gt;. We store the generated text into the component's state, so that it can be displayed in the second box. This way, the user will have the chance to edit it and then copy it inside the body's mail.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this post, we have learned how we can leverage the power of the Open AI models through Azure, which might be a better option for developers and companies who have already invested into this platform and already trust Microsoft. The work we have done has been quite easy: thanks to the simplicity of the Azure Open AI APIs and the compatibility with the Open AI ecosystem, we have been able to reuse most of the code and the models we have already learned&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099" target="_blank" rel="noopener"&gt;in the previous post&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;You can find the updated sample&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/outlook-businessmails-openai/" target="_blank" rel="noopener"&gt;in the same GitHub repository&lt;/A&gt;, but on a different branch called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/outlook-businessmails-openai/tree/azure-openai" target="_blank" rel="noopener"&gt;azure-openai&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;</description>
      <pubDate>Fri, 17 Feb 2023 18:49:06 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-open-ai-into-an-outlook-add-in-moving-to-azure-open-ai/ba-p/3745592</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-02-17T18:49:06Z</dc:date>
    </item>
    <item>
      <title>Bringing OpenAI into an Outlook add-in: a business mail generator</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099</link>
      <description>&lt;P&gt;&lt;STRONG&gt;[Updated on 7th March 2023]&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;Open AI &lt;A href="https://openai.com/blog/introducing-chatgpt-and-whisper-apis" target="_blank" rel="noopener"&gt;has made the ChatGPT model available&lt;/A&gt; through its APIs. The model is called&amp;nbsp;&lt;STRONG&gt;gpt-3.5-turbo&lt;/STRONG&gt; and the good news is that &lt;A href="https://openai.com/pricing" target="_blank" rel="noopener"&gt;it's 10 times cheaper&lt;/A&gt; than the Davinci one. The model, in fact, is priced&amp;nbsp;$0.002 / 1K tokens, while the Davinci one we have used in the original version of the project is priced&amp;nbsp;$0.0200 / 1K tokens.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;However, the way this model can be used in your applications is slightly different than the one we have used in the post to leverage the Davinci model. Let's look at how the &lt;EM&gt;&lt;STRONG&gt;generateText()&lt;/STRONG&gt; &lt;/EM&gt;function must be changed to use ChatGPT.&amp;nbsp; But before, we must open a terminal on the folder which contains our project and upgrade the OpenAI library with the following command:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="powershell"&gt;npm update openai&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This command will install version 3.1.0, which includes a new API to interact with the ChatGPT model.&amp;nbsp;Let's look at the complete code:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;generateText = async () =&amp;gt; {
  var current = this;
  const configuration = new Configuration({
    apiKey: "your-API-key",
  });
  const openai = new OpenAIApi(configuration);
  current.setState({ isLoading: true });
  const response = await openai.createChatCompletion({
    model: "gpt-3.5-turbo",
    messages: [
      {
        role: "system",
        content: "You are a helpful assistant that can help users to create professional business content.",
      },
      { role: "user", content: "Turn the following text into a professional business mail: " + this.state.startText },
    ],
  });
  current.setState({ isLoading: false });
  current.setState({ generatedText: response.data.choices[0].message.content });
};&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The first difference is that we have a new API to interact with ChatGPT,&amp;nbsp;called &lt;EM&gt;&lt;STRONG&gt;createChatCompletion()&lt;/STRONG&gt;,&amp;nbsp;&lt;/EM&gt;which we must use instead of&amp;nbsp;&lt;EM&gt;&lt;STRONG&gt;createCompletion().&lt;/STRONG&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P&gt;The second difference, even if it's a small one, is that we have to change the model name to &lt;STRONG&gt;gpt-3.5-turbo&lt;/STRONG&gt;.&lt;/P&gt;
&lt;P&gt;The third difference is the most important one. The prompt you pass to the model to generate text isn't based any more on a single sentence, but on on a collection of messages object. Each of them has two properties:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;EM&gt;&lt;STRONG&gt;role&lt;/STRONG&gt;&lt;/EM&gt;, which is used to define who generated the prompt. It could be the user, the system or the assistant itself.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;content&lt;/STRONG&gt;, which is the actual text.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;In the code sample, you can see two types of messages: one with &lt;EM&gt;&lt;STRONG&gt;system&lt;/STRONG&gt; &lt;/EM&gt;as role, which we're using to instruct the model on the type of outcome we want to achieve (&lt;EM&gt;you are an assistant that can generate business content&lt;/EM&gt;); one with &lt;EM&gt;&lt;STRONG&gt;user&lt;/STRONG&gt; &lt;/EM&gt;as role, which is instead the actual ask (&lt;EM&gt;turn the following text into a business mail&lt;/EM&gt;).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;There's another reason why this model requires a collection of messages, instead of a single prompt like with Davinci. ChatGPT is born to support conversational scenarios, so that the user can interact with the AI without specifying the full context each time, like you would do with a human. However, the conversation history isn't managed by OpenAI, but it must be managed by the developer. In our scenario, we don't have to do this: we aren't using ChatGPT to have a conversation, but to generate a text to include in our mail, so we don't have to change the way the Outlook add-in works. In a conversational scenario, however, we would need to use the messages collection to provide the whole history, so that the model can infere the context. The following example from the official documentation will help you to understand better this concept:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt; const response = await openai.createChatCompletion({
      model: "gpt-3.5-turbo",
      messages: [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Who won the world series in 2020?"},
        {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
        {"role": "user", "content": "Where was it played?"}
      ],
    });&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Other than the messages generated by the user, we pass back also the messages generated by ChatGPT using &lt;EM&gt;&lt;STRONG&gt;assistant&lt;/STRONG&gt; &lt;/EM&gt;as role. This way, the model is able to get the whole context and provide an answer to the last question (&lt;EM&gt;Where was it played?&lt;/EM&gt;).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The &lt;A href="https://github.com/qmatteoq/outlook-businessmails-openai" target="_blank" rel="noopener"&gt;source code on GitHub&lt;/A&gt; has been updated to use the new model.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;The original post starts here&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The tech world has always shown a lot of interest in Artificial Intelligence, especially in the last years. However, recently, interest has started to spread also outside the tech enthusiast bubble. Dall-E, the model developed by OpenAI to create images, started to give a new meaning to "generative AI", showing the capabilities of these new powerful AI models. But it's ChatGPT that really ignited the interest, by providing a conversational model that it's very close to the human one and that, most of all, can help you accomplishing many tasks: it can make searches, it can relate content together, it can generate summaries or lists, it can create stories, etc.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;A few days ago, Microsoft demonstrated how ChatGPT isn't just a "toy" to play with,&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://blogs.microsoft.com/blog/2023/02/07/reinventing-search-with-a-new-ai-powered-microsoft-bing-and-edge-your-copilot-for-the-web/" target="_blank" rel="noopener"&gt;by announcing a new shift of the search experience&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;with the integration of a conversational and content generation experience into Bing and Edge. AI becomes a copilot, that can assist you during your daily tasks and help you to be more productive and, as the new Microsoft mission states, "do more with less". And I'm sure, in the coming months, we'll see even more integrations; some of them have already been announced, like the ones&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://www.microsoft.com/en-us/microsoft-365/blog/2023/02/01/microsoft-teams-premium-cut-costs-and-add-ai-powered-productivity/" target="_blank" rel="noopener"&gt;in Teams&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://cloudblogs.microsoft.com/dynamics365/bdm/2023/02/02/microsoft-boosts-viva-sales-with-new-gpt-seller-experience/" target="_blank" rel="noopener"&gt;Viva Sales&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;So, why don't we get ahead of the game and we start to play with the possibilities of using AI to improve productivity? In this blog post we'll combine the best of both worlds: the productivity offered by the Microsoft 365 ecosystem and the content generation capabilities of the latest AI models. We're going to build an Outlook add-in that, through&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://openai.com/api/" target="_blank" rel="noopener"&gt;OpenAI APIs&lt;/A&gt;, will help people to craft professional business mails from one or two sentences. We aren't going to use the real ChatGPT model, since it isn't available for public consumption yet, but we're going to use&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://platform.openai.com/docs/models" target="_blank" rel="noopener"&gt;one of the many powerful GPT models offered by OpenAI&lt;/A&gt;. The goal of this blog post is to help you understand the capabilities of these models and the basic steps to integrate them. Once ChatGPT will be available (directly through Open AI or using another provider, like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/blog/general-availability-of-azure-openai-service-expands-access-to-large-advanced-ai-models-with-added-enterprise-benefits/" target="_blank" rel="noopener"&gt;Azure Open AI&lt;/A&gt;, you will just need to swap the model (or the API implementation), but the basic architecture will stay the same.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let's start!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="create-the-outlook-add-in"&gt;Create the Outlook add-in&lt;/H3&gt;
&lt;P&gt;We're going to create the Outlook add-in using the new web-based model, so you will need the following tools installed on your machine:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;The latest LTS version of&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://nodejs.org/about/releases" target="_blank" rel="noopener"&gt;Node.js&lt;/A&gt;.&lt;/LI&gt;
&lt;LI&gt;Visual Studio Code&lt;/LI&gt;
&lt;LI&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/develop/yeoman-generator-overview" target="_blank" rel="noopener"&gt;Yeoman generator for Office add-ins&lt;/A&gt;.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Once you have all the requirements, open a terminal and run the following command:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-powershell hljs"&gt;yo office
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;STRONG&gt;Please note:&lt;/STRONG&gt; since it's web based development, you might be tempted to work on this project using the Windows Subsystem for Linux, which typically delivers better performances, especially on the file system. However, if you want to have a good debugging experience, it's better to create the project in Windows, so that you'll be able to debug the add-in using Outlook for desktop.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;You will be guided through a series of steps to scaffold the starting template for the project. Use the following settings:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG&gt;Choose a project type:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Office Add-in Task Pane project using React framework (here actually you can pick up the framework you prefer, but since React is the one I know better I'm going to use this template for this post).&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Choose a script type:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;TypeScript (also in this case you can pick up the language you prefer, but I highly suggest TypeScript for everything that is nothing more than a "hello world" in the web ecosystem).&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;What do you want to name your add-in?:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Give it a meaningful name, like "Outlook AI Mail generator".&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Which Office client application would you like to support?:&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;Outlook&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now the tool will scaffold the basic template and it will run&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;npm install&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to restore all the dependencies. At the end of the process, you will find in the current folder a subfolder with the same name of the add-in you picked up during the wizard. Just open it with Visual Studio Code to start the coding experience.&lt;/P&gt;
&lt;P&gt;The Outlook add-in template contains two basic implementations of the two scenarios supported by Office add-ins:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG&gt;Task pane&lt;/STRONG&gt;. The task pane is a HTML page that is displayed inside a panel placed on the right of the screen inside the application. The user can interact with the page and, through the Office SDK, perform operations that can interact with the context.&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;Commands&lt;/STRONG&gt;. These are operations that don't require any UI interaction. You click on a button and an operation is performed. Also in this case, through the Office SDK you can retrieve the context and operate on it. For example, you can select some text in the mail body and the command is able to read it.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;We're going to support both approaches, so that you can pick the one you like better.&lt;/P&gt;
&lt;P&gt;For the taskpane, this is the final look &amp;amp; feel we're going to achieve:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The user will specify the text he wants to turn into a business mail in the first box. By pressing the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Generate Text&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;button, we're going to invoke the Open AI APIs, passing a prompt followed by the input text. The result returned by the API will be displayed in the second box. Users will have the chance to adjust and then, once they're done, they can click the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Insert into mail&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;button, which will use the Office APIs to include the text in the mail's body.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;For the command, instead, we don't really have a user interface, just a button available in our ribbon:&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;The logic, however, is the same as the taskpane. The only difference is that the input text to process through Open AI won't be typed by the user in a dedicated box, but directly in the mail's body. Using the Office APIs, we'll retrieve the body and pass it to the Open AI APIs. Then, the result will be automatically pasted back into the body.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now that we have a better idea of the result, let's start to work!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="configuring-the-manifest"&gt;Configuring the manifest&lt;/H3&gt;
&lt;P&gt;An Office add-in includes a file called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;manifest.xml&lt;/CODE&gt;, which describes it: the name, the publisher, the supported actions, etc. Before we start working on the code, we must make some changes. Some of them are purely cosmetic. For example, you can use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;DisplayName&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property to change the name of the add-in that will be displayed to the user inside Outlook; or the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;IconUrl&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;one to change the icon.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;A particularly important section, however, is the one called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;MailHost&lt;/CODE&gt;, which describes where and how the add-in will be integrated inside the Outlook surface. By default, the template includes the following extension point:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-xml hljs"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;ExtensionPoint&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;xsi:type&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"MessageReadCommandSurface"&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;This means that the add-in will be integrated in the reading experience: you'll be able to invoke it when you're reading a mail. This isn't our scenario, however. We want this add-in to help us in writing a new mail, so we must change this value as in the following snippet:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-xml hljs"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;ExtensionPoint&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;xsi:type&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"MessageComposeCommandSurface"&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Finally, we can customize the section inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ShortStrings&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;element to change the labels that are associated to the buttons:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-xml hljs"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;bt:ShortStrings&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;bt:String&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;id&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"GroupLabel"&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;DefaultValue&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"AI Generator Add-in"&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;bt:String&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;id&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"TaskpaneButton.Label"&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;DefaultValue&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"Business mail AI generator"&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
  &lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;bt:String&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;id&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"ActionButton.Label"&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;DefaultValue&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"Generate business mail"&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;SPAN class="hljs-tag"&gt;&amp;lt;/&lt;SPAN class="hljs-name"&gt;bt:ShortStrings&lt;/SPAN&gt;&amp;gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now we can move to the code.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="building-the-task-pane"&gt;Building the task pane&lt;/H3&gt;
&lt;P&gt;We're going to focus on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;taskpane&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;folder of our solution, which includes the files that are used to render the web content displayed in the panel. Since I've picked up the React template, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;taskpane.html&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;page doesn't contain much. It includes only a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;div&lt;/CODE&gt;, called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;container&lt;/CODE&gt;, which is used by the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;index.tsx&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file to load the React application and render it into the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;div&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;placeholder. The real application is stored inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;components&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;folder:&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;App.tsx&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is the main component, which defines the UI and includes the interaction logic. We also have some smaller components, which are used to render specific UI elements (like the header or the progress indicator).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Let's start to build the various elements we need step by step. Since we're going to make multiple changes compared to the default template, I won't go into the details on what you need to change. Just replace the existing components and functions with the ones I'm going to describe in the rest of the article.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H4 id="getting-the-initial-text-and-pass-to-open-ai"&gt;Getting the initial text and pass to Open AI&lt;/H4&gt;
&lt;P&gt;As the first step, we need to define the state of our main component. To support our scenario, we need the state to store the initial text and the generated one. As such, we must update the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;AppState&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;interface as following:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;interface&lt;/SPAN&gt; AppState {
  generatedText: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
  startText: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Let's initialize them as well in the constructor of the component with an empty string:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;default&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; App &lt;SPAN class="hljs-keyword"&gt;extends&lt;/SPAN&gt; React.Component&amp;lt;AppProps, AppState&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;constructor&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;props&lt;/SPAN&gt;) {
    &lt;SPAN class="hljs-keyword"&gt;super&lt;/SPAN&gt;(props);
    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state = {
      generatedText: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
      startText: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
    };
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now let's look at how we can define the UI, through&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://reactjs.org/docs/introducing-jsx.html" target="_blank" rel="noopener"&gt;JSX&lt;/A&gt;, the markup language (sort of) used by React, and the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;render()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function of the component:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;render() {
    &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; (
      &amp;lt;div&amp;gt;
        &amp;lt;main&amp;gt;
          &amp;lt;h2&amp;gt; Open AI business e-mail generator &amp;lt;&lt;SPAN class="hljs-regexp"&gt;/h2&amp;gt;
          &amp;lt;p&amp;gt;Briefly describe what you want to communicate in the mail:&amp;lt;/&lt;/SPAN&gt;p&amp;gt;
          &amp;lt;textarea
            onChange={&lt;SPAN class="hljs-function"&gt;(&lt;SPAN class="hljs-params"&gt;e&lt;/SPAN&gt;) =&amp;gt;&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.setState({ startText: e.target.value })}
            rows={&lt;SPAN class="hljs-number"&gt;10&lt;/SPAN&gt;}
            cols={&lt;SPAN class="hljs-number"&gt;40&lt;/SPAN&gt;}
          /&amp;gt;
          &amp;lt;p&amp;gt;
            &amp;lt;DefaultButton onClick={&lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.generateText}&amp;gt;
              Generate text
            &amp;lt;&lt;SPAN class="hljs-regexp"&gt;/DefaultButton&amp;gt;
          &amp;lt;/&lt;/SPAN&gt;p&amp;gt;
      &amp;lt;&lt;SPAN class="hljs-regexp"&gt;/div&amp;gt;
  )
}
&lt;/SPAN&gt;&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We have added a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;textarea&lt;/CODE&gt;, which is where the user is going to type the starting text, and a button, which will invoke the Open AI APIs. As for the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;textarea&lt;/CODE&gt;, we subscribe to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onChange&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event (which is triggered every time the user types something) and we use it to store the typed text inside the state, through the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;startText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Before implementing the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onClick&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event of the button, however, we must take a step back and register to Open AI so that we can get an API key. Just go to&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://openai.com/api/" target="_blank" rel="noopener"&gt;https://openai.com/api/&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Sign Up&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;(or&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Log In&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;if you already have an account). Once you're in, click on your profile and choose&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;View API keys&lt;/STRONG&gt;. From there, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Create new secret key&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and copy somewhere the generated key. You won't be able to retrieve it again, so do it immediately. The free plan gives you 18 $ of credits to be used within 3 months, which is more than enough for the POC we're building.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Now that we have an API key, we can start using the Open AI APIs. The easiest way to do it in our add-in is through the official JavaScript library. Open a terminal on the folder which includes your project and type:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-plaintext hljs"&gt;npm install openai
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Then, at the top of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;App.tsx&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file, add the following statement to import the objects we need from the library:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;import&lt;/SPAN&gt; { Configuration, OpenAIApi } &lt;SPAN class="hljs-keyword"&gt;from&lt;/SPAN&gt; &lt;SPAN class="hljs-string"&gt;"openai"&lt;/SPAN&gt;;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now we can implement the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onClick&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event that is triggered when the user clicks on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Generate text&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;button:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;generateText = &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; () =&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;var&lt;/SPAN&gt; current = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;;
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; configuration = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Configuration({
    apiKey: &lt;SPAN class="hljs-string"&gt;"add-your-api-key"&lt;/SPAN&gt;,
  });
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; openai = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; OpenAIApi(configuration);
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; openai.createCompletion({
    model: &lt;SPAN class="hljs-string"&gt;"text-davinci-003"&lt;/SPAN&gt;,
    prompt: &lt;SPAN class="hljs-string"&gt;"Turn the following text into a professional business mail: "&lt;/SPAN&gt; + &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.startText,
    temperature: &lt;SPAN class="hljs-number"&gt;0.7&lt;/SPAN&gt;,
    max_tokens: &lt;SPAN class="hljs-number"&gt;300&lt;/SPAN&gt;,
  });
  current.setState({ generatedText: response.data.choices[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].text });
};
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;First, we create a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Configuration&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, passing in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;apiKey&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property the API key we have just generated.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;
&lt;P&gt;&lt;STRONG&gt;Please note&lt;/STRONG&gt;: we're doing this only for testing purposes, but this isn't a suggested approach for a production scenario. Since the add-in runs entirely client side, it's extremely easy to spoof the API key. You must use a more suitable approach like having a server-side middleware between the client and the API (like an Azure Function) and use services like&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://azure.microsoft.com/en-us/products/key-vault/" target="_blank" rel="noopener"&gt;Azure Key Vault&lt;/A&gt;.&lt;/P&gt;
&lt;/BLOCKQUOTE&gt;
&lt;P&gt;Then we create a new&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OpenAIApi&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object, passing as parameter the configuration we have just created. Through this object, we can interact with the various models exposed by Open AI. The one related to text is accessible through the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;createCompletion()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method, which requires as parameter an object with the following properties:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;CODE&gt;model&lt;/CODE&gt;, which is the name of the model to use. In this sample, we're using&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://platform.openai.com/docs/models/overview" target="_blank" rel="noopener"&gt;text-davinci-003&lt;/A&gt;, which is the most advanced GPT model available through the APIs at the time of writing this article. Soon, Open AI will make available also ChatGPT as one of the available options.&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;prompt&lt;/CODE&gt;, which is the text we want to process. We use a prompt that describes what we want to achieve (&lt;EM&gt;turn the following text into a professional business mail&lt;/EM&gt;), followed by the text typed by the user (which we have previously stored in the component's state).&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;temperature&lt;/CODE&gt;, which is a value between 0 and 2 that controls how much randomness is in the output. As explained&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://algowriting.medium.com/gpt-3-temperature-setting-101-41200ff0d0be" target="_blank" rel="noopener"&gt;in this good article&lt;/A&gt;, the lower the temperature, the more likely GPT-3 will choose words with a higher probability of occurrence. In our case, we set 0.7, which is a good balance between "too flat" and "too creative".&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;max_tokens&lt;/CODE&gt;, which is the maximum number of words that will be returned by the API.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;This method is asynchronous and based on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="noopener"&gt;JavaScript promises&lt;/A&gt;, so we can use the async / await pattern to invoke it. This means that the final line of our snippet will be called only when the API has returned a response. Specifically, the text generated by Open AI will be included in the text property of the first element of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data.choices&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection. We store it in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generatedText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property inside the component's state, so that we can later use it.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H4 id="use-the-generated-text-to-craft-our-mail"&gt;Use the generated text to craft our mail&lt;/H4&gt;
&lt;P&gt;Now that Open AI has generated the text of our business mail for us, we must display it to the user, give them the option to edit it and then use it as body of the mail. In order to do that, we need to add a new property in our state to store the final text to include in the mail, which might have some differences compared to the one generated by Open AI since we're giving the user the option to edit it. Here is the updated definition of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;AppState&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;interface:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;interface&lt;/SPAN&gt; AppState {
  generatedText: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
  startText: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
  finalMailText: &lt;SPAN class="hljs-built_in"&gt;string&lt;/SPAN&gt;;
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Let's not forget to initialize it as well in the component's constructor:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-keyword"&gt;export&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;default&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;class&lt;/SPAN&gt; App &lt;SPAN class="hljs-keyword"&gt;extends&lt;/SPAN&gt; React.Component&amp;lt;AppProps, AppState&amp;gt; {
  &lt;SPAN class="hljs-keyword"&gt;constructor&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;props&lt;/SPAN&gt;) {
    &lt;SPAN class="hljs-keyword"&gt;super&lt;/SPAN&gt;(props);
    &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state = {
      generatedText: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
      startText: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;,
      finalMailText: &lt;SPAN class="hljs-string"&gt;""&lt;/SPAN&gt;
    };
  }
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now that we have the property we need, let's add a new box and a new button to our application, by adding the following elements in JSX right below the ones we've added in the previous section:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&amp;lt;textarea
   defaultValue={&lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.generatedText}
   onChange={&lt;SPAN class="hljs-function"&gt;(&lt;SPAN class="hljs-params"&gt;e&lt;/SPAN&gt;) =&amp;gt;&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.setState({ finalMailText: e.target.value })}
   rows={&lt;SPAN class="hljs-number"&gt;10&lt;/SPAN&gt;}
   cols={&lt;SPAN class="hljs-number"&gt;40&lt;/SPAN&gt;}
 /&amp;gt;
 &amp;lt;p&amp;gt;
   &amp;lt;DefaultButton onClick={&lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.insertIntoMail&amp;gt;
     Insert into mail
   &amp;lt;&lt;SPAN class="hljs-regexp"&gt;/DefaultButton&amp;gt;
&amp;lt;/&lt;/SPAN&gt;p&amp;gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;textarea&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control in React has a property called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;defaultValue&lt;/CODE&gt;, which we can set with a text that we want to display when the component is rendered. We connect it to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generatedText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property available in the component's state. This way, once the Open API call has returned the generated text and stored it into the state, the box will automatically update itself to show it. Then, like we did with the previous box, we handle the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;onChange&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event, by saving the text typed by the user inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;finalMailText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the component's state.&lt;/P&gt;
&lt;P&gt;Finally, we have another button, which invokes a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;insertIntoMail()&lt;/CODE&gt;, which is described below:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;insertIntoMail = &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-params"&gt;()&lt;/SPAN&gt; =&amp;gt;&lt;/SPAN&gt; {
  &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; finalText = &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.finalMailText.length === &lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt; ? &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.generatedText : &lt;SPAN class="hljs-keyword"&gt;this&lt;/SPAN&gt;.state.finalMailText;
  Office.context.mailbox.item.body.setSelectedDataAsync(finalText, {
    coercionType: Office.CoercionType.Html,
  });
};
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Here we can see the Office SDK in action. First, we determine if the user has made any change to the text generated by Open AI. Then, we call the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Office.context.mailbox.item.body.setSelectedDataAsync()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method, passing as parameter the final text (the one generated by the Open API plus any edit the user might have done). This method will take care of adding the text into the body of the mail, specifically where the text cursor is placed.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="building-the-command"&gt;Building the command&lt;/H3&gt;
&lt;P&gt;Building the command requires less effort than the taskpane, since we don't have any UI. We just need to intercept the click on the button in the ribbon and manage it. The default&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;commands.ts&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file inside the commands folder includes a function called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;action()&lt;/CODE&gt;, which we can use for this purpose.&lt;/P&gt;
&lt;P&gt;First, let's create a new function that takes the body of the mail and processes it using Open AI:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;getSelectedText&lt;/SPAN&gt;(): &lt;SPAN class="hljs-title"&gt;Promise&lt;/SPAN&gt;&amp;lt;&lt;SPAN class="hljs-title"&gt;any&lt;/SPAN&gt;&amp;gt; &lt;/SPAN&gt;{
  &lt;SPAN class="hljs-keyword"&gt;return&lt;/SPAN&gt; &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Office.Promise(&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; (&lt;SPAN class="hljs-params"&gt;resolve, reject&lt;/SPAN&gt;) &lt;/SPAN&gt;{
    &lt;SPAN class="hljs-keyword"&gt;try&lt;/SPAN&gt; {
      Office.context.mailbox.item.body.getAsync(Office.CoercionType.Text, &lt;SPAN class="hljs-keyword"&gt;async&lt;/SPAN&gt; &lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; (&lt;SPAN class="hljs-params"&gt;asyncResult&lt;/SPAN&gt;) &lt;/SPAN&gt;{
        &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; configuration = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; Configuration({
          apiKey: &lt;SPAN class="hljs-string"&gt;"your-api-key"&lt;/SPAN&gt;,
        });
        &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; openai = &lt;SPAN class="hljs-keyword"&gt;new&lt;/SPAN&gt; OpenAIApi(configuration);
        &lt;SPAN class="hljs-keyword"&gt;const&lt;/SPAN&gt; response = &lt;SPAN class="hljs-keyword"&gt;await&lt;/SPAN&gt; openai.createCompletion({
          model: &lt;SPAN class="hljs-string"&gt;"text-davinci-003"&lt;/SPAN&gt;,
          prompt: &lt;SPAN class="hljs-string"&gt;"Turn the following text into a professional business mail: "&lt;/SPAN&gt; + asyncResult.value,
          temperature: &lt;SPAN class="hljs-number"&gt;0.7&lt;/SPAN&gt;,
          max_tokens: &lt;SPAN class="hljs-number"&gt;300&lt;/SPAN&gt;,
        });

        resolve(response.data.choices[&lt;SPAN class="hljs-number"&gt;0&lt;/SPAN&gt;].text);
      });
    } &lt;SPAN class="hljs-keyword"&gt;catch&lt;/SPAN&gt; (error) {
      reject(error);
    }
  });
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The main difference with the taskpane is that, in this case, we are getting the text to turn into a business mail directly from the body of the mail. To do it, we use the Office SDK and, specifically, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Office.context.mailbox.body.getAsync()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method. Being asynchronous, we receive the body in a callback, in which we implement the Open AI integration, which is the same we have seen for the taskpane. By using the Open AI library, we send a prompt followed by the text typed by the user to Open AI, by using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;createCompletion()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function and using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;text-davinci-003&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;GPT model. Once we get a response, we return to the caller the text processed by Open AI, which is stored inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;text&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the first element of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;data.choices&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;collection.&lt;/P&gt;
&lt;P&gt;Now we can implement the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;action()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-typescript hljs"&gt;&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; &lt;SPAN class="hljs-title"&gt;action&lt;/SPAN&gt;(&lt;SPAN class="hljs-params"&gt;event: Office.AddinCommands.Event&lt;/SPAN&gt;) &lt;/SPAN&gt;{
  getSelectedText().then(&lt;SPAN class="hljs-function"&gt;&lt;SPAN class="hljs-keyword"&gt;function&lt;/SPAN&gt; (&lt;SPAN class="hljs-params"&gt;selectedText&lt;/SPAN&gt;) &lt;/SPAN&gt;{
    Office.context.mailbox.item.setSelectedDataAsync(selectedText, { coercionType: Office.CoercionType.Text });
    event.completed();
  });
}
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Also, in this case we're using code we have already seen in the taskpane implementation. We call the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;getSelectedText()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function we have just created and, once we have the generated business mail, we use the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Office.context.mailbox.item.setSelectedDataAsync()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;method to copy it into the mail's body. In the end, we call&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;event.completed()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to let Office know that the command execution is completed.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="testing-and-debugging-the-add-in"&gt;Testing and debugging the add-in&lt;/H3&gt;
&lt;P&gt;Visual Studio Code makes testing the add-in easy, thanks to a series of debug profiles which are created by Yeoman. If you move to the Debug tab of Visual Studio Code, you will find different profiles, one for each Office application. The one we're interested in is called&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Outlook Desktop (Edge Chromium)&lt;/STRONG&gt;. If you select it and you press the Play button, two things will happen:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;P&gt;A terminal prompt will be launched. Inside it, Visual Studio Code will run the local server (which uses Webpack to bundle all the JavaScript) that serves the add-in content to Outlook.&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;Outlook will start and you will see a security prompt asking if you want to sideload the add-in.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P class="lia-indent-padding-left-30px"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now click on the button to compose a new mail and based on the size of your screen, you should see your add-in available in the ribbon (or, in case it doesn't fit, you'll see it by clicking on the three dots at the end of the ribbon).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;When you click on it, a panel on the left will be opened, like the one we have seen at the beginning of the post. You will also be asked if you want to connect to the debugger, make sure to click Yes to confirm. Now type a simple sentence that you want to turn into a mail. For example, something like:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-plaintext hljs"&gt;David, I'm planning to work on your garden tomorrow at 3 PM, but I might be a bit late.
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Then click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Generate text&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and, after a few seconds, you should see a more carefully crafted text being displayed in the box below:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-plaintext hljs"&gt;Dear David,

I hope this message finds you well. I wanted to let you know that I am planning to work on your garden tomorrow at 3 PM, however, I may be running a bit late. I apologize for any inconvenience this may cause.

Thank you for your understanding.

Sincerely,
[Your Name]
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Now you can make the changes you need, then click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Insert into mail&lt;/STRONG&gt;. You will find the text included inside the body of the mail, ready to be sent to your customer. In background, Visual Studio Code has attached a debugger to the WebView which is rendering the panel inside Outlook. This means that you can set breakpoints in your TypeScript code and do step-by-step debugging whenever it's needed. For example, you can set breakpoints inside the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;generateText()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function and monitor the interaction with Open AI APIs. The local server, additionally, supports live reload, so whenever you make any change, you won't have to redeploy the add-in, but they will be applied in real time.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The same testing can be done also for the command. The difference is that you must type the text to turn into a business mail directly in the mail's body, then click on the Generate business mail button in the ribbon. A loading indicator will be displayed at the top of the window, and you'll be notified once the operation is completed. Also, in this case the Visual Studio Code debugger will be attached, so you can set breakpoints in your code if needed.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="deploying-the-add-in"&gt;Deploying the add-in&lt;/H3&gt;
&lt;P&gt;Once you're done testing, you can choose to make available your plugin to a broader audience. The publishing story for Office add-ins is similar to the Teams apps ones:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;You can share the add-in for manual sideloading, which is great for testing and limited distribution.&lt;/LI&gt;
&lt;LI&gt;You can publish the add-in in your organization, so that all the employees can pick it up from the add-in store.&lt;/LI&gt;
&lt;LI&gt;You can publish the add-in in the Office store, to make it available to every Office customer around the globe.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Regardless of your choice, Office is just the "interface" for your add-in, but it doesn't host it. This is why the only required component to deploy for an Office add-in is the manifest, which includes all the information on where the web app is hosted. If you explore the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;manifest.xml&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file we have previously edited, you will find the following tag, which defines the entry point of our taskpane:&lt;/P&gt;
&lt;DIV class="code-badge"&gt;
&lt;DIV title="Copy to clipboard"&gt;&amp;nbsp;&lt;/DIV&gt;
&lt;/DIV&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;PRE class="code-badge-pre"&gt;&lt;CODE class="language-xml hljs"&gt;&lt;SPAN class="hljs-tag"&gt;&amp;lt;&lt;SPAN class="hljs-name"&gt;SourceLocation&lt;/SPAN&gt; &lt;SPAN class="hljs-attr"&gt;DefaultValue&lt;/SPAN&gt;=&lt;SPAN class="hljs-string"&gt;"https://localhost:3000/taskpane.html"&lt;/SPAN&gt;/&amp;gt;&lt;/SPAN&gt;
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Ideally, once you're ready to deploy, this URL (and the other localhost references included in the manifest) will be replaced by the real URL of the web application.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;If you want to start the process to make your add-in available to a broader audience, Visual Studio Code is still your best friend. The&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/office/dev/add-ins/publish/publish-add-in-vs-code#using-visual-studio-code-to-publish" target="_blank" rel="noopener"&gt;following document&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;will guide on how to leverage the Azure extension for Visual Studio Code to generate the distributable version of the add-in and to publish it on Azure Storage, which is a perfect match for our scenario since the add-in is made only by static web content.&lt;/P&gt;
&lt;P&gt;Once you have published your add-in, you can open any Outlook version (desktop, web, etc.), click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Get add-ins&lt;/STRONG&gt;, move to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;My add-ins&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;section and, under&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Custom addins&lt;/STRONG&gt;, click on&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Add a custom add-in&lt;/STRONG&gt;. From there, you can either pick up the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;manifest.xml&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;file of your project or specify the URL of the manifest published on Azure Storage. This way, people will be able to add it to Outlook without needing you to share any file.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this blog post we have learned how we can help users to be more productive by infusing software with the latest powerful AI models created by Open AI. Specifically, we focused on the Microsoft 365 ecosystem, by providing an Outlook "copilot" to help you write more professional business mails. And this is just the beginning! We know that new and powerful models are already available (like ChatGPT) and that Microsoft will directly offer new integrations. It's an exciting time to work in the tech space &lt;span class="lia-unicode-emoji" title=":grinning_face_with_big_eyes:"&gt;😃&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;You can find the definitive version of the add-in&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://github.com/qmatteoq/outlook-businessmails-openai" target="_blank" rel="noopener"&gt;on GitHub&lt;/A&gt;, with a few improvements that we didn't discuss in this blog post since they help to deliver a better UX, but they aren't strictly connected to the AI integration (like showing a progress indicator meanwhile Open AI processes the input text in the taskpane).&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy coding!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 07 Mar 2023 19:31:53 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/bringing-openai-into-an-outlook-add-in-a-business-mail-generator/ba-p/3743099</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-03-07T19:31:53Z</dc:date>
    </item>
    <item>
      <title>Share a link to Teams in a canvas Power App</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/share-a-link-to-teams-in-a-canvas-power-app/ba-p/3735351</link>
      <description>&lt;P&gt;Recently I worked with a customer on building a canvas Power App embedded in Microsoft Teams. The application acts as a catalogue and one of the requested features was enabling users to share an item from the catalogue to another user in Teams, with a deep link to open the application directly on the selected item. The scenario seemed quite straight forward at first, but I had to do a few research to implement it, especially for the first task, which is sharing the link over Teams. As such, I've decided to document my findings in this blog post.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Let's start!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="sharing-a-link-to-teams-from-a-power-app"&gt;Sharing a link to Teams from a Power App&lt;/H3&gt;
&lt;P&gt;Out of the box, searching on the Internet I couldn't find an easy way to perform sharing to Teams directly from an app. The two options I've explored weren't suitable:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;When the application runs inside Teams, you can leverage the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/power-apps/teams/use-teams-integration-object" target="_blank" rel="noopener"&gt;Teams connector&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to interact with the platform and perform tasks like detecting the theme, getting the name of the channel in which the app has been added, etc. Unfortunately, there isn't any available action to start sharing. You can post a message to a chat or a channel, but you must know upfront who the receiver will be. The sharing experience, instead, should allow the user to pick up the target as part of the process.&lt;/LI&gt;
&lt;LI&gt;The official documentation offers a section that explains&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/share-to-teams-overview" target="_blank" rel="noopener"&gt;how to share to Teams&lt;/A&gt;. However, both approaches (integration into a personal or tab app or in a website), are focused on a pro-developer implementation, which requires to leverage the Microsoft Teams SDK or to include a JavaScript script.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;So I started to play a bit with the second implementation, and I've discovered that the sharing experience is based on the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://teams.microsoft.com/share" target="_blank" rel="noopener"&gt;https://teams.microsoft.com/share&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;website. When you open this website in a browser, you will be authenticated with your Microsoft 365 account, which enables an auto-complete experience. As you can see from the image below, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Share To&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;field gets automatically populated with people, chats, and channels from your tenant:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;This is a good starting point! However, to support the scenario requested by the customer, I need to automatically include the link of the item to share from the catalogue in the body of the message. To better understand how the sharing website works, I created a quite simple HTML page and I followed&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/share-to-teams-from-web-apps" target="_blank" rel="noopener"&gt;the official documentation&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;to include a basic sharing experience. With this approach, in fact, you can pass to the script two parameters to define the URL and the message, which is the same goal I was trying to achieve. This simple experiment helped me to discover the parameters that are passed to the URL to pre-populate the content of the message. Clicking on the sharing button, in fact, led the browser to open a new tab with the following URL:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;https://teams.microsoft.com/share?href=https%3A%2F%2Fwww.mywebsite.com&amp;amp;msgText=This%20is%20a%20message&amp;amp;preview=false&amp;amp;s=1675706704556
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;As you can see from the URL, the sharing page accepts the two key parameters I was looking for:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;CODE&gt;href&lt;/CODE&gt;, which contains the URL to share.&lt;/LI&gt;
&lt;LI&gt;&lt;CODE&gt;msgText&lt;/CODE&gt;, which contains the text of the message.&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;Now that I have this information, it's very easy to integrate a sharing feature using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Launch()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function in Power Apps, which you can use to open an external URL. You just need to pass:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;P&gt;As&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;href&lt;/CODE&gt;, the Web URL of the Power App, which you can get by clicking on the three dots near your app in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;A href="https://make.powerapps.com/" target="_blank" rel="noopener"&gt;Power Apps portal&lt;/A&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and choosing&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Details&lt;/STRONG&gt;.&lt;BR /&gt;&lt;BR /&gt;&lt;img /&gt;&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;P&gt;As&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;msgText&lt;/CODE&gt;, a message that you want to include in the link.&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;This is an example of the function I've linked to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OnSelect()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event of a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Button&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control in my application:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;Launch("https://teams.microsoft.com/share", { href: "https://apps.powerapps.com/play/e/default-7454e082-9a4c-4adf-a685-a5f2a4cb711a/a/3bb8fb0e-5d81-4855-a950-d9415c3ad896?tenantId=7454e082-9a4c-4adf-a685-a5f2a4cb711a", msgText: "Check this application" });
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;The first parameter passed to the function is the generic Teams share URL. Then, we pass a collection of parameters we want to pass in query string, which are the ones we have just learned about (&lt;CODE&gt;msgText&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;and&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;href&lt;/CODE&gt;). If you preview your application and you press the button, a new tab in the browser will be opened with the link, the message and a link preview delivered through an Adaptive Card:&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;After you have clicked on Share, the link will be shared to the selected user or channel, as in the following image:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;img /&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now let's see how we have implemented the sharing so that the link opens the app on a specific item of the catalogue.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="supporting-a-deep-link"&gt;Supporting a deep link&lt;/H3&gt;
&lt;P&gt;A deep link is a link that doesn't open just the app on the home page, but it redirect the user to a specific content. In the web ecosystem, this scenario is typically implemented through routing or query string parameters. For example, a web application could support an&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter in the URL with the id of an item. When this URL is opened, the application automatically routes the request to the appropriate page to show the details of the selected item.&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;https://www.mywebsite.com/details?itemId=534
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;Power Apps support this mechanism as well when they are consumed using a web browser. If you open a Power App on your browser through its Web URL, you can attach to it some custom parameters that you can retrieve in the application using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Param()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function. Let's say, for example, that the URL of our Power App is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&lt;A href="https://apps.powerapps.com/play/e/default-7454e082-9a4c-4adf-a685-a5f2a4cb711a/a/3bb8fb0e-5d81-4855-a950-d9415c3ad896?tenantId=7454e082-9a4c-4adf-a685-a5f2a4cb711a" target="_blank" rel="noopener"&gt;https://apps.powerapps.com/play/e/default-7454e082-9a4c-4adf-a685-a5f2a4cb711a/a/3bb8fb0e-5d81-4855-a950-d9415c3ad896?tenantId=7454e082-9a4c-4adf-a685-a5f2a4cb711a&lt;/A&gt;&lt;/CODE&gt;. If we want to pass the information about a specific item, we can pass an extra&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter to this URL, as in the following example:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;https://apps.powerapps.com/play/e/default-7454e082-9a4c-4adf-a685-a5f2a4cb711a/a/3bb8fb0e-5d81-4855-a950-d9415c3ad896?tenantId=7454e082-9a4c-4adf-a685-a5f2a4cb711a&amp;amp;itemId=534
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;From a sharing perspective, this means that we just need to attach this parameter to our share button. We just need to make the value of the parameter dynamic, based on the selected item. For example, in my scenario, the catalogue is stored into a Dataverse table and it's displayed using a&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;STRONG&gt;Gallery&lt;/STRONG&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;control. As such, the item template includes a share button which invokes the following function:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;Launch("https://teams.microsoft.com/share", { href: "https://apps.powerapps.com/play/e/default-7454e082-9a4c-4adf-a685-a5f2a4cb711a/a/3bb8fb0e-5d81-4855-a950-d9415c3ad896?tenantId=7454e082-9a4c-4adf-a685-a5f2a4cb711a&amp;amp;itemId=" + ThisItem.ItemId, msgText: "Check this application" });
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We have added the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter and we have set, as value, the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ItemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property of the selected item, which is made accessible through the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ThisItem&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;object.&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;ItemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;is a property in the Dataverse table which contains the unique identifier of the item in the catalogue.&lt;/P&gt;
&lt;P&gt;When the link is shared and the user clicks on it, the application will be opened in the normal way. However, the extra feature we can leverage is that we can read the value of the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter in the URL through the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Param()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function. For example, if you want to display its value in a label, we can set its&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Text&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;property with the following function:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;If(Param("itemId")&amp;lt;&amp;gt;Blank(), Param("itemId"), "Parameter is missing")
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;We pass to the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;Param()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;function the name of the parameter, which is&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;in our case. If it exists (we check this condition using the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;&amp;lt;&amp;gt;Blank()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;statement) we display its value, otherwise we display a warning message.&lt;/P&gt;
&lt;P&gt;Now that you have understood how to retrieve a parameter to the URL, it's easy to build the startup experience of your application in a way that can display different content based on the parameter. For example, in the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;OnStart()&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;event of the application, you could have some code like this:&lt;/P&gt;
&lt;PRE&gt;&lt;CODE class="language-plaintext hljs"&gt;If(Param("itemId")&amp;lt;&amp;gt;Blank(), 
    Set(CurrentItem, Param("itemId"));
    Navigate(DetailScreen)
);
&lt;/CODE&gt;&lt;/PRE&gt;
&lt;P&gt;If the application is opened through sharing (so the&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;CODE&gt;itemId&lt;/CODE&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;parameter is included in the URL), we store its value in global variable (so that we can retrieve it later to query our Dataverse table, for example) and we navigate the user directly to the detail screen instead of the home screen.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;H3 id="wrapping-up"&gt;Wrapping up&lt;/H3&gt;
&lt;P&gt;In this blog post, we have learned how to implement a full "Share to Teams" experience in Power Apps, including support for deep links. In the first part, we have learned how to enable the sharing feature, which requires some extra work since Power Apps doesn't offer it out of the box. Then we have seen how we can integrate a deep link experience, which enables users to open the app directly on specific content that was shared from the application.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Happy (low) coding!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 07 Feb 2023 08:33:19 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/share-a-link-to-teams-in-a-canvas-power-app/ba-p/3735351</guid>
      <dc:creator>Matteo Pagani</dc:creator>
      <dc:date>2023-02-07T08:33:19Z</dc:date>
    </item>
    <item>
      <title>Create a 3D collaborative app for Teams Meeting using Live Share SDK, Live Share Canvas and Babylon</title>
      <link>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-a-3d-collaborative-app-for-teams-meeting-using-live-share/ba-p/3703893</link>
      <description>&lt;P&gt;&lt;A href="https://learn.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/teams-live-share-overview?tabs=javascript" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Teams Live Share&lt;/STRONG&gt;&lt;/A&gt; seamlessly integrates meetings with&amp;nbsp;&lt;A href="https://fluidframework.com/" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Fluid Framework&lt;/STRONG&gt;&lt;/A&gt;. As a recent GA SDK in Oct, 2022, Live Share provides a free, fully managed, and ready to use&amp;nbsp;&lt;A href="https://learn.microsoft.com/en-us/azure/azure-fluid-relay/" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Azure Fluid Relay&lt;/STRONG&gt;&lt;/A&gt;&lt;STRONG&gt;&amp;nbsp;&lt;/STRONG&gt;backed by the security and global scale of Teams.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;With this technology, participants in the same Teams meeting can easily interactively work together on the same content or object, for example, with real time shared annotations, operations, to exchange ideas efficiently.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Starting from&amp;nbsp;&lt;A href="https://github.com/microsoft/live-share-sdk/tree/main/samples/javascript/03.live-canvas-demo" target="_blank" rel="noopener"&gt;the simple live canvas demo&lt;/A&gt;, I verified more functions by integrating below parts together:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="https://learn.microsoft.com/en-us/microsoftteams/platform/apps-in-teams-meetings/teams-live-share-canvas?tabs=javascript" target="_blank" rel="noopener"&gt;Live Share Canvas&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://learn.microsoft.com/en-us/fluent-ui/web-components/" target="_blank" rel="noopener"&gt;Fluid UI/Web Component&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://www.babylonjs.com/" target="_blank" rel="noopener"&gt;BabyLon Js&lt;/A&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;A href="https://learn.microsoft.com/en-us/azure/azure-fluid-relay/" target="_blank" rel="noopener"&gt;Customized Fluid Relay Service&lt;/A&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;and implement a collaborative inking 2D/3D objects teams meeting extension. It also supports switching different backend Fluid Relay Services. Regarding 3D rendering, it works in Teams Web App with WebGL2 and works in Teams Desktop App with WebGL. Here is a screen snapshot that two clients in the same live share session in teams meeting (one is teams web app, another is Teams Desktop App):&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;img /&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Developers can get main ideas of how Live Share works for Teams especially with Babylon 3D and Fluid UI packages from the project. Here is the pubic github repo, I shared setup/build/test steps in it as well:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;A href="https://github.com/freistli/LiveShareCanvasBabylon" target="_blank" rel="noopener"&gt;freistli/LiveShareCanvasBabylon (github.com)&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Enjoy Teams Platform Development!&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 02 Jan 2023 02:06:05 GMT</pubDate>
      <guid>https://techcommunity.microsoft.com/t5/modern-work-app-consult-blog/create-a-3d-collaborative-app-for-teams-meeting-using-live-share/ba-p/3703893</guid>
      <dc:creator>freistli</dc:creator>
      <dc:date>2023-01-02T02:06:05Z</dc:date>
    </item>
  </channel>
</rss>

