<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ariste.info</title><description>Dynamics 365 F&amp;O, Azure DevOps and Power Platform</description><link>https://ariste.info/</link><language>en</language><item><title>Has AI taken away the joy of writing?</title><link>https://ariste.info/2026/11/has-ai-taken-away-joy-writing/</link><guid isPermaLink="true">https://ariste.info/2026/11/has-ai-taken-away-joy-writing/</guid><description>Today we have a different type of post. Rather than something technical, I&apos;m venting, or ranting, a bit. Maybe I&apos;ve already become the Simpsons &quot;Old man yells at cloud&quot; meme. The joy of writing I haven&apos;t been writing a lot for the past year. Not because there aren&apos;t new topics, or because I&apos;ve stopped finding</description><pubDate>Tue, 03 Nov 2026 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today we have a different type of post. Rather than something technical, I’m venting, or ranting, a bit. Maybe I’ve already become the Simpsons “Old man yells at cloud” meme.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/images/2026/11/OldManYellsAtCloud.PNG&quot; alt=&quot;A hand holding a newspaper with the headline &amp;quot;Old man yells at cloud&amp;quot;&quot; width=&quot;2400&quot; height=&quot;1350&quot; /&gt;&lt;figcaption&gt;Me, probably&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;The joy of writing&lt;/h2&gt;
&lt;p&gt;I haven’t been writing a lot for the past year. Not because there aren’t new topics, or because I’ve stopped finding interesting problems to write about. Oh, I have plenty of these 😁&lt;/p&gt;
&lt;p&gt;The problem for me, and I’m sure that it’s the same for other people, is that once I start drafting something I end up thinking: is any of this worth doing anymore?&lt;/p&gt;
&lt;p&gt;Why spend time writing anything when everybody can go and ask one of the AI chatbots we all know, and get exactly the answer they need?&lt;/p&gt;
&lt;p&gt;I’ve always enjoyed writing. I started my blog as a way to help myself in the future, self-documentation. And it ended up helping many other people.&lt;/p&gt;
&lt;p&gt;I also enjoy the process that leads to the article itself. Investigating and learning about a new feature, debugging an issue or building something is the fun part for me. It’s like a game.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong, I obviously also use AI. &lt;strong&gt;All day, every day&lt;/strong&gt;. I used it to move this blog from WordPress to Astro. I will use it to spellcheck this!&lt;/p&gt;
&lt;p&gt;It’s the most useful tool I’ve ever had. But it’s so inhumanly efficient that it’s taking the fun away from some things.&lt;/p&gt;
&lt;h2&gt;What’s the point in writing anything now?&lt;/h2&gt;
&lt;p&gt;This is what goes through my mind when I finish that first draft.&lt;/p&gt;
&lt;p&gt;What’s the point if people can ask an LLM a specific question, and get the exact answer without having to go through a wall of text?&lt;/p&gt;
&lt;p&gt;And as much as I enjoy writing, it also requires time and effort. And this time is usually my free time, something I started &lt;strong&gt;prioritizing and protecting A LOT&lt;/strong&gt; some years ago.&lt;/p&gt;
&lt;p&gt;For me this has sucked away the energy I need to put in the work to write something.&lt;/p&gt;
&lt;p&gt;Yes, I get the irony. I’m using plenty of tools that were trained the exact way I’m criticising.&lt;/p&gt;
&lt;p&gt;Believe me when I say &lt;strong&gt;I’ve spent hours, days, weeks, and probably months&lt;/strong&gt;, writing all the posts and guides of this site.&lt;/p&gt;
&lt;p&gt;What’s the point if I can take all of this content, feed it to Claude/ChatGPT/Copilot, so it learns my voice and tone and can produce something 95% me, just by telling it what to write about?&lt;/p&gt;
&lt;p&gt;Likewise, what’s the point of reading long articles if I can just copy-paste them and ask for a TL;DR?&lt;/p&gt;
&lt;h2&gt;The creative dichotomy&lt;/h2&gt;
&lt;p&gt;I’ve always considered the work developers do is from a creative nature. Yes, it’s definitely not the same as artistic creation, but it’s a different kind of creativity.&lt;/p&gt;
&lt;p&gt;As a developer I find it very natural to use AI tools to help me at work. We’ve been copying and pasting from the internet for years, and nobody cared about that.&lt;/p&gt;
&lt;p&gt;But I loathe other kinds of creativity where most of the work is done by an AI, because there the person behind that work is the point!&lt;/p&gt;
&lt;p&gt;What I won’t do is just go and tell Claude “hey look at the roadmap and write something”. When I wrote my book, I consciously avoided using ChatGPT because I’m VERY lazy and would’ve ended up letting it do all the work.&lt;/p&gt;
&lt;h2&gt;Everything looks the same!!!&lt;/h2&gt;
&lt;p&gt;Another awful consequence is the growing trend of &lt;strong&gt;everything looking the same&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And I’m not even talking about shitposts on LinkedIn.&lt;/p&gt;
&lt;p&gt;AI-generated images, posters, flyers. All have the same style, same fonts, same look. All look equally ugly, dull and lifeless.&lt;/p&gt;
&lt;p&gt;Worst of all? People have embraced it and settled for this!&lt;/p&gt;
&lt;p&gt;And again, I’m guilty here too. I’ve had plenty of slides created using Cowork. And my slides and everybody else’s slides looked the same, of course!&lt;/p&gt;
&lt;h2&gt;Please, create things&lt;/h2&gt;
&lt;p&gt;I beg you, please, &lt;strong&gt;keep writing, keep drawing, keep painting, keep composing and playing music&lt;/strong&gt;. Please.&lt;/p&gt;
&lt;p&gt;I don’t want to read a book from Haruki MurAIkami, or BrAIndon Sanderson. I still need them to write books and put a piece of their soul there. Damn, I’d even be at peace if Patrick Rothfuss never published the third book of The Kingkiller Chronicle if it means nobody hands the draft to an AI to finish it.&lt;/p&gt;
&lt;p&gt;I don’t want to listen to AI-generated music, or watch movies or shows packed with AI-generated content.&lt;/p&gt;
&lt;p&gt;I can be working all day with AI tools, but at the end of the day I still need to get my dose of humanity.&lt;/p&gt;
&lt;p&gt;So here’s the thing. I’ll try to keep writing, even if nobody reads it. It makes me happy, it gives me peace. When everything has to be short, have an immediate impact, and be able to be done while doing 27 other things, writing is one of the things I can actually keep my focus on while doing it. The other one would be playing World of Warcraft.&lt;/p&gt;
&lt;p&gt;Time to work on those drafts…&lt;/p&gt;</content:encoded><category>Community &amp; news</category></item><item><title>Troubleshoot Copilot Studio errors with Postman</title><link>https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/</link><guid isPermaLink="true">https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/</guid><description>Lately I’ve been testing some of the X++ features we can use to extend the Finance and Operations agent in Copilot Studio. One of these features is the X++ AI tools (formerly called AI plugins), which allow us to create Copilot Studio tools that call X++ business logic. Today’s post is a product of my</description><pubDate>Tue, 09 Sep 2025 13:59:00 GMT</pubDate><content:encoded>&lt;p&gt;Lately I’ve been testing some of the X++ features we can use to extend the Finance and Operations agent in Copilot Studio.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/12/IMG_2622-1024x1024.png&quot; alt=&quot;We&apos;re going to troubleshoot Copilot Studio errors!&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;We’re going to troubleshoot Copilot Studio errors!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;One of these features is the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-ai-plugins&quot;&gt;X++ AI tools&lt;/a&gt; (formerly called AI plugins), which allow us to create Copilot Studio tools that call X++ business logic.&lt;/p&gt;
&lt;p&gt;Today’s post is a product of my lack of focus and my superpower of ignoring yellow notifications on PPAC. Also, &lt;strong&gt;thanks to Jared Hall&lt;/strong&gt; for the hint about using Postman to troubleshoot Copilot Studio errors.&lt;/p&gt;
&lt;p&gt;If you missed any of the first three posts in the series, you can read them now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/&quot;&gt;Copilot Studio 101: Extend Dynamics 365 F&amp;amp;O Copilots&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;Build an X++ AI Tool for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/&quot;&gt;Create X++ Client Plugins for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#context-and-scenario&quot;&gt;Context and scenario&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#how-to-troubleshoot-copilot-studio-errors&quot;&gt;How to troubleshoot Copilot Studio errors&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#calling-x-ai-tools-in-the-dataverse-web-api-with-postman&quot;&gt;Calling X++ AI Tools in the Dataverse Web API with Postman&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#create-an-environment&quot;&gt;Create an environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#calling-the-dataverse-api-in-postman&quot;&gt;Calling the Dataverse API in Postman&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#your-x-ai-tool-endpoint&quot;&gt;Your X++ AI tool endpoint&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;h2&gt;Context and scenario&lt;/h2&gt;
&lt;p&gt;First, let me give a bit of context and &lt;strong&gt;clarify a bit how all of this works at a high level, and also which was my scenario&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I’m preparing a session about extending the F&amp;amp;O Copilot with X++ and Copilot Studio. For my demo, I created an X++ AI tool that runs some business logic in X++. Something simple to show how it’s done.&lt;/p&gt;
&lt;p&gt;Once we have the X++ AI tool, we deploy it to the environment, which creates a Dataverse &lt;a href=&quot;https://learn.microsoft.com/en-us/power-apps/developer/data-platform/custom-api&quot;&gt;custom API/Action&lt;/a&gt; (I’ll never stop being amazed at how much Microsoft loves name changes). This is the action that Copilot Studio uses to call the X++ logic.&lt;/p&gt;
&lt;p&gt;I wasn’t able to troubleshoot the issue, neither debugging X++ nor debugging Copilot Studio (with messages), because that’s not an option if you don’t have the generative AI orchestration enabled.&lt;/p&gt;
&lt;h3&gt;How to troubleshoot Copilot Studio errors&lt;/h3&gt;
&lt;p&gt;When I tested it with my SysAdmin user, everything was OK. But I also wanted to test it with a user whose role only provided access to the X++ AI tool’s data.&lt;/p&gt;
&lt;p&gt;And when I tested it, I got this error:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;Sorry, the bot can&apos;t talk for a while. It&apos;s something the bot&apos;s owner needs to address. Error code: AIPluginRequestFailure.&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It’s not very descriptive, right? I tested with F&amp;amp;O and Dataverse permissions, and I found that if I gave the user the System Administrator role in Dataverse, the agent worked. It had to be a Dataverse thing!&lt;/p&gt;
&lt;p&gt;After much investigating and asking, Jared suggested calling the action via the Dataverse API to troubleshoot Copilot Studio, using &lt;a href=&quot;https://insomnia.rest/&quot;&gt;Insomnia&lt;/a&gt; to see if I got any details. I used Postman because I’m more familiar with it, and I got this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-1024x172.png&quot; alt=&quot;Troubleshoot copilot studio errors with Postman&quot; width=&quot;1024&quot; height=&quot;172&quot; /&gt;&lt;figcaption&gt;The environment was in Admin mode 🥴&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I had put the environment in &lt;strong&gt;Admin mode&lt;/strong&gt; from PPAC to enable a configuration key in F&amp;amp;O and forgot to disable it.&lt;/p&gt;
&lt;p&gt;Thanks to the better details I got from the API call, I could troubleshoot the Copilot Studio error! Let’s learn how to do this.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Calling X++ AI Tools in the Dataverse Web API with Postman&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Calling the &lt;a href=&quot;https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/overview&quot;&gt;Dataverse Web API&lt;/a&gt; is similar to calling OData endpoints in F&amp;amp;O. First, you authenticate and get a token, and with that token you can call the endpoints.&lt;/p&gt;
&lt;p&gt;I’ll show this in &lt;a href=&quot;https://www.postman.com/&quot;&gt;Postman,&lt;/a&gt; but you can use any tool of your choice.&lt;/p&gt;
&lt;h3&gt;Create an environment&lt;/h3&gt;
&lt;p&gt;This step is optional; you can create a request to get the token using an AppId and secret, but I’ll show something interesting that I’ve learned from the Web API docs that show how to do this with Insomnia.&lt;/p&gt;
&lt;p&gt;Go to the &lt;strong&gt;Environments&lt;/strong&gt; tab and create a new entry for your Dataverse environment. Add these variables:&lt;/p&gt;
&lt;div&gt;
































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Variable name&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;url&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://YOUR_ENV.crmX.dynamics.com/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;clientId&lt;/td&gt;&lt;td&gt;&lt;code&gt;51f81489-12ee-4a9e-aaae-a2591f45987d&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;&lt;code&gt;9.2&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;webapiurl&lt;/td&gt;&lt;td&gt;&lt;code&gt;{{url}}/api/data/v{{version}}/&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;callback&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://localhost&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;authurl&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://login.microsoftonline.com/common/oauth2/authorize?resource={{url}}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Notice that the clientId is fixed? Am I just publishing one of my Entra ID App registrations here? Nope! This &lt;code&gt;clientId&lt;/code&gt; is Microsoft’s and is used for &lt;a href=&quot;https://learn.microsoft.com/en-us/power-apps/developer/data-platform/authenticate-oauth&quot;&gt;OAuth authentication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you have these variables configured, set the environment as active by clicking the checkbox on the right:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-2.png&quot; alt=&quot;Set environment as active in Postman&quot; width=&quot;1281&quot; height=&quot;573&quot; /&gt;&lt;figcaption&gt;Set environment as active in Postman&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;&lt;strong&gt;Calling the Dataverse API in Postman&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now, create a new request and go to the &lt;strong&gt;Authorization&lt;/strong&gt; tab. In &lt;strong&gt;Auth Type,&lt;/strong&gt; select &lt;strong&gt;OAuth 2.0&lt;/strong&gt; and give your token a name.&lt;/p&gt;
&lt;p&gt;Scroll down until you see the Configure New Token section. In &lt;strong&gt;Callback URL,&lt;/strong&gt; enter the &lt;code&gt;{{callback}}&lt;/code&gt; variable from your environment. In &lt;strong&gt;Auth URL&lt;/strong&gt;, enter &lt;code&gt;{{authurl}}&lt;/code&gt;. And in &lt;strong&gt;Client ID&lt;/strong&gt; &lt;code&gt;{{clientId}}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It should look like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-3.png&quot; alt=&quot;Configure New Token in Postman&quot; width=&quot;1016&quot; height=&quot;526&quot; /&gt;&lt;figcaption&gt;Configure New Token in Postman&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then scroll to the bottom until you see the &lt;strong&gt;Get New Access Token&lt;/strong&gt; button and click it. A Microsoft sign-in window will open. Log in with your user:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-4.png&quot; alt=&quot;Log in to get new token&quot; width=&quot;784&quot; height=&quot;535&quot; /&gt;&lt;figcaption&gt;Log in to get new token&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;A new dialog with your token will appear; click the &lt;strong&gt;Use Token&lt;/strong&gt; button:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-5.png&quot; alt=&quot;Use Token&quot; width=&quot;831&quot; height=&quot;450&quot; /&gt;&lt;figcaption&gt;Use Token&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now we’re ready to finalize the request!&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;If the token expires while making requests, you just need to click the &lt;strong&gt;Get New Access Token&lt;/strong&gt; button again.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Your X++ AI tool endpoint&lt;/h3&gt;
&lt;p&gt;So now we need to know what the endpoint name of your X++ AI tool is. That’s easy!&lt;/p&gt;
&lt;p&gt;It’s your AI tool’s &lt;strong&gt;X++ class name&lt;/strong&gt; preceded by &lt;code&gt;mserp_&lt;/code&gt;! For example, if your class is called &lt;code&gt;AASFabulousCopilotToolCustomAPI&lt;/code&gt;, your endpoint will be &lt;code&gt;mserp_AASFabulousCopilotToolCustomAPI&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So in the URL you will be &lt;code&gt;{{webapiurl}}/mserp_AASFabulousCopilotToolCustomAPI&lt;/code&gt; and the request must be a POST action.&lt;/p&gt;
&lt;p&gt;Now, your X++ AI tool probably has some input parameters, which are data members in the class. For example, my fabulous tool has two: companyId and custAccount. You have to pass these as a JSON. What are the expected names?&lt;/p&gt;
&lt;p&gt;Again, it’s easy; just add &lt;code&gt;mserp_AASFabulousCopilotToolCustomAPI_&lt;/code&gt; in front of the member names! So for &lt;strong&gt;&lt;code&gt;companyId&lt;/code&gt;&lt;/strong&gt;, it’ll be &lt;code&gt;mserp_AASFabulousCopilotToolCustomAPI_companyId&lt;/code&gt;, and for &lt;strong&gt;&lt;code&gt;custAccount&lt;/code&gt;&lt;/strong&gt;, &lt;code&gt;mserp_AASFabulousCopilotToolCustomAPI_custAccount&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You should have something like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-6.png&quot; alt=&quot;Postman call to the Dataverse Web API action&quot; width=&quot;747&quot; height=&quot;306&quot; /&gt;&lt;figcaption&gt;Postman call to the Dataverse Web API action&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click the &lt;strong&gt;Send&lt;/strong&gt; button, and your API will be called, and you should see the output.&lt;/p&gt;
&lt;p&gt;And done! Now you can also launch the debugger and test your code, making the requests from Postman.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that’s it! With these simple steps, you can troubleshoot Copilot Studio errors and test your X++ changes for Copilot Studio using Postman or any other API client of your choice!&lt;/p&gt;
&lt;p&gt;Remember, when finding vague errors in the Copilot sidecar chat, the Dataverse Web API may provide better details and help you solve the exact issue quickly. Like finding out your environment was accidentally left in &lt;strong&gt;Admin mode&lt;/strong&gt;! 🥴&lt;/p&gt;
&lt;p&gt;So next time your bot decides to be mysteriously cryptic, don’t panic; open Postman, troubleshoot Copilot Studio errors, and relax!&lt;/p&gt;
&lt;p&gt;Happy troubleshooting, and keep ignoring those yellow notifications at your own risk!&lt;/p&gt;</content:encoded><category>AI &amp; Copilot</category></item><item><title>Create X++ Client Plugins for Copilot Studio in Dynamics 365 F&amp;O</title><link>https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/</link><guid isPermaLink="true">https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/</guid><description>Part 3 of the Copilot Studio + F&amp;O extensibility series. Learn what client plugins are, when to use them, and how to build one in X++—from action scopes and parameters to implementing executeAction and wiring trigger/response topics. Includes a PurchTable example to confirm POs from the sidecar chat.</description><pubDate>Tue, 02 Sep 2025 13:59:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s the third part of the &lt;a href=&quot;https://ariste.info/copilot-studio-fo-extensibility-series/&quot;&gt;Copilot Studio and F&amp;amp;O Extensibility Series,&lt;/a&gt; and we’re going to learn about &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-client-plugins&quot;&gt;client plugins&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Remember that to follow along with the steps of this post, there are some prerequisites that you can check here: &lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#what-do-you-need-to-follow-the-articles&quot;&gt;What do you need?&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/09/CPDins.png&quot; alt=&quot;Client Plugins: don&apos;t mess with them!&quot; width=&quot;2400&quot; height=&quot;1350&quot; /&gt;&lt;figcaption&gt;Client Plugins: don’t mess with them!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you missed any of the first two posts of the series, you can read them now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/&quot;&gt;Copilot Studio 101: Extend Dynamics 365 F&amp;amp;O Copilots&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;Build an X++ AI Tool for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 4: &lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/&quot;&gt;Troubleshoot Copilot Studio errors with Postman&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#whats-a-client-plugin&quot;&gt;What’s a client plugin?&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#action-types&quot;&gt;Action types&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#input-and-output-parameters&quot;&gt;Input and output parameters&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#implement-the-executeaction-method&quot;&gt;Implement the executeAction method&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#creating-a-client-plugin&quot;&gt;Creating a client plugin&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#create-the-class&quot;&gt;Create the class&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#create-copilot-studio-topics&quot;&gt;Create Copilot Studio topics&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#hey-what-about-the-dataareaid&quot;&gt;Hey! What about the DataAreaId!?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#test-it&quot;&gt;Test it!&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/#conclusions&quot;&gt;Conclusions&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;h2&gt;What’s a client plugin?&lt;/h2&gt;
&lt;p&gt;With client plugins, you can take action in a specific form. This form will be the context in which the action is available, and then you can call functionality, operations, or the business logic in there.&lt;/p&gt;
&lt;p&gt;But hey, wait! Isn’t that EXACTLY THE SAME as the AI tools we learned about last week? That’s what I said too when AI tools were released: “Heh, then I can just use AI tools for everything”.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;In next week’s post, we’ll learn in detail the differences between using client plugins and AI tools.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Client plugins extend the &lt;strong&gt;SysCopilotChatAction&lt;/strong&gt; class using a data contract for the input and response parameters, similar to AI tools.&lt;/p&gt;
&lt;h3&gt;Action types&lt;/h3&gt;
&lt;p&gt;For client plugins, we can define three types of actions depending on the scope.&lt;/p&gt;
&lt;h4&gt;Global actions&lt;/h4&gt;
&lt;p&gt;These are client plugins that have no specific context and can run on any form in the application.&lt;/p&gt;
&lt;p&gt;To define a class as a global action, you must decorate it with the &lt;code&gt;SysCopilotChatGlobalAction&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h4&gt;Form actions&lt;/h4&gt;
&lt;p&gt;Only applicable on the specified form (or forms). Copilot Studio will only run your client plugin of this type if you’re on one of the forms that your class specifies.&lt;/p&gt;
&lt;p&gt;To define the forms, you must decorate the class with the &lt;code&gt;Export&lt;/code&gt; and &lt;code&gt;ExportMetadata&lt;/code&gt; attributes.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;To use the &lt;code&gt;Export&lt;/code&gt; and &lt;code&gt;ExportMetadata&lt;/code&gt; attributes you need to add a &lt;code&gt;using System.ComponentModel.Composition;&lt;/code&gt; to your class.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ExportMetadata(formStr(BatchGroup), identifierStr(FormName))]
[ExportMetadata(formStr(Batch), identifierStr(FormName))]
[Export(identifierStr(Microsoft.Dynamics.AX.Application.SysCopilotChatAction))]&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Runtime actions&lt;/h4&gt;
&lt;p&gt;Available only where YOU define! That’s real power! I’m joking. 😛&lt;/p&gt;
&lt;p&gt;To enable this kind of action, you must use &lt;code&gt;SysCopilotChatAction::addToCopilotRuntimeActions&lt;/code&gt; with the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;runTimeAction&lt;/strong&gt;: the class name that contains your code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;removeOnFormChange&lt;/strong&gt;: a boolean indicating whether the action is still registered when you go to another form.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;context&lt;/strong&gt;: an object containing the context to pass to the action.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To disable the action, just call the &lt;code&gt;SysCopilotChatAction::removeFromCopilotRuntimeActions&lt;/code&gt; with the &lt;strong&gt;runtimeAction&lt;/strong&gt; parameter.&lt;/p&gt;
&lt;h3&gt;Input and output parameters&lt;/h3&gt;
&lt;p&gt;For input parameters, we must decorate the parm method with the &lt;code&gt;DataMember&lt;/code&gt; and &lt;code&gt;SysCopilotChatActionInputParameter&lt;/code&gt; attributes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;SysCopilotChatActionInputParameter&lt;/code&gt; one takes two parameters, like in AI tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;description&lt;/strong&gt;: a description of the parameter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;isRequired&lt;/strong&gt;: a boolean indicating whether the parameter is mandatory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For output parameters, decorate the parm method with the &lt;code&gt;DataMember&lt;/code&gt; and &lt;code&gt;SysCopilotChatActionOutputParameter&lt;/code&gt; attributes.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;SysCopilotChatActionOutputParameter&lt;/code&gt; only takes one parameter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;description&lt;/strong&gt;: a description of the parameter.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;You can have as many input and output parameters as you need, not just one.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Implement the executeAction method&lt;/h3&gt;
&lt;p&gt;And finally, to implement the X++ logic, we need to do it in the &lt;code&gt;void executeAction(SysCopilotChatActionDefinitionAttribute _actionDefinition, Object _context)&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Here you can use the input parameters that you need in your logic and the output ones to return the values, which can be used later in Copilot Studio.&lt;/p&gt;
&lt;h2&gt;Creating a client plugin&lt;/h2&gt;
&lt;p&gt;Now let’s get to work and code a bit. I’m sure you’re wondering which incredible use case I’ve come up with.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/d8de3bbf03ad4a7e3744ff650517f662-1.gif&quot; alt=&quot;A man in a suit giving a thumbs up with a dumb face&quot; width=&quot;370&quot; height=&quot;262&quot; /&gt;&lt;figcaption&gt;Yes, sarcasm.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This time we will be using the Copilot sidecar chat to confirm a purchase order without clicking the form button, and that only works in the PurchTable form.&lt;/p&gt;
&lt;h3&gt;Create the class&lt;/h3&gt;
&lt;p&gt;As I said earlier, we need to create a class that extends the &lt;code&gt;SysCopilotChatAction&lt;/code&gt; class. I’ll start by showing the top of the class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using System.ComponentModel.Composition;

[DataContract]
[ExportMetadata(formStr(PurchTable), identifierStr(FormName))]
[Export(identifierStr(Microsoft.Dynamics.AX.Application.SysCopilotChatAction))]
[
    SysCopilotChatActionDefinition(identifierStr(MS.PA.AAS.AP.AASPurchTableConfirm),
        &apos;Confirm a purchase order&apos;,
        &apos;Confirms an open purchase order&apos;,
        menuItemActionStr(AASPurchTableConfirmAction), MenuItemType::Action)
]

public class AASPurchTableConfirmAction extends SysCopilotChatAction&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We decorate the class with the DataContract and note the &lt;code&gt;using&lt;/code&gt; on top; otherwise, the &lt;code&gt;Export&lt;/code&gt; and &lt;code&gt;ExportMetadata&lt;/code&gt; attributes won’t be available.&lt;/p&gt;
&lt;p&gt;In the ExportMetadata attribute, we pass two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The name of the form.&lt;/li&gt;
&lt;li&gt;And the &lt;strong&gt;FormName&lt;/strong&gt; identifier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want this plugin to be called from more forms, you can add as many &lt;code&gt;ExportMetadata&lt;/code&gt; lines here.&lt;/p&gt;
&lt;p&gt;Then we add the &lt;code&gt;Export&lt;/code&gt; attribute with the &lt;code&gt;identifierStr(Microsoft.Dynamics.AX.Application.SysCopilotChatAction)&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;And for the last attribute, we add SysCopilotChatActionDefinition with the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The identifier of your plugin. It must start with &lt;code&gt;MS.PA.&lt;/code&gt; and after that you can name it however you want.&lt;/li&gt;
&lt;li&gt;The name of your plugin.&lt;/li&gt;
&lt;li&gt;A description for the action it does.&lt;/li&gt;
&lt;li&gt;An action menu item that calls this class.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, we have the class members:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private PurchId purchId;
private boolean hasError;
private str errorMessage;

[   
    DataMember(&apos;purchaseOrderNumber&apos;),
    SysCopilotChatActionInputParameter(&apos;The purchase order number of the PO you want to confirm.&apos;, true)
]
public PurchId parmPurchId(PurchId _purchId = purchId)
{
    purchId = _purchId;
    return purchId;
}

[   
    DataMember(&apos;hasError&apos;),
    SysCopilotChatActionOutputParameter(&apos;Indicates whether there was an error during the confirmation process.&apos;)
]
public boolean parmHasError(boolean _hasError = hasError)
{
    hasError = _hasError;
    return hasError;
}

[   
    DataMember(&apos;errorMessage&apos;),
    SysCopilotChatActionOutputParameter(&apos;The error message if there was an error during the confirmation process.&apos;)
]
public str parmErrorMessage(str _errorMessage = errorMessage)
{
    errorMessage = _errorMessage;
    return errorMessage;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As I said, you can have as many input and output parameters. Just make sure you decorate them with the correct attributes.&lt;/p&gt;
&lt;p&gt;Finally, here are the &lt;code&gt;executeAction&lt;/code&gt; method and a helper method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void executeAction(SysCopilotChatActionDefinitionAttribute _actionDefinition, Object _context)
{
    super(_actionDefinition, _context);

    try
    {
        PurchTable purchTable = PurchTable::find(this.parmPurchId());

        if (purchTable.DocumentState != VersioningDocumentState::Approved)
        {   
            throw error(strFmt(&quot;Purchase order %1 is not in an approved state and cannot be confirmed.&quot;, this.parmPurchId()));
        }

        PurchFormLetter purchFormLetter = PurchFormLetter::construct(DocumentStatus::PurchaseOrder);

        PurchaseOrderId purchOrderDocNum = strFmt(&apos;%1-%2&apos;, this.parmPurchId(), VendPurchOrderJour::numberOfPurchaseOrderVersions(purchTable) + 1);

        purchFormLetter.update(purchTable, purchOrderDocNum);

        FormRun formRun = _context as FormRun;

        if (formRun)
        {
            formRun.dataSource(formDataSourceStr(PurchTable, PurchTable)).research(true);
        }
    }
    catch (Exception::Error)
    {
        this.parmHasError(true);
        this.parmErrorMessage(AASPurchTableConfirmAction::getInfolog());
    }
}

private static str getInfolog()
{
    SysInfologEnumerator enumerator;
    SysInfologMessageStruct msgStruct;
    Exception exception;
    str error;

    enumerator = SysInfologEnumerator::newData(infolog.copy(1, infolog.num()));

    while(enumerator.moveNext())
    {
        msgStruct = new SysInfologMessageStruct(enumerator.currentMessage());
        exception = enumerator.currentException();
        error = strFmt(&quot;%1 %2&quot;, error, msgStruct.message());
    }

    return error;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This checks that the order is in the &lt;strong&gt;Approved&lt;/strong&gt; status, posts the PO confirmation journal, and refreshes the form.&lt;/p&gt;
&lt;p&gt;As a last step, create an action menu item for this class, and we’re done on the X++ side.&lt;/p&gt;
&lt;h3&gt;Create Copilot Studio topics&lt;/h3&gt;
&lt;p&gt;We now need to create two topics in Copilot Studio. If you don’t know what topics are, I recommend reading the first article in the series: &lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/&quot;&gt;Copilot Studio 101&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Trigger action topic&lt;/h4&gt;
&lt;p&gt;The first topic will be used to trigger your action. Go to the &lt;strong&gt;Topics&lt;/strong&gt; tab and click the &lt;strong&gt;Add a topic&lt;/strong&gt; button, selecting &lt;strong&gt;From blank&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-27-1024x298.png&quot; alt=&quot;Create new topic&quot; width=&quot;1024&quot; height=&quot;298&quot; /&gt;&lt;figcaption&gt;Create new topic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Give it a short but descriptive name. I’m following kind of a naming pattern for topics, where I have an AAS prefix, a dot, and a short name. I’ve named this one AAS.ConfirmPO.&lt;/p&gt;
&lt;p&gt;In the trigger, add some phrases you’d like to use. Like confirm the order:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-28.png&quot; alt=&quot;Copilot Studio trigger&quot; width=&quot;365&quot; height=&quot;314&quot; /&gt;&lt;figcaption&gt;Copilot Studio trigger&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then add an event activity type action from the &lt;strong&gt;Advanced&lt;/strong&gt; menu:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-29.png&quot; alt=&quot;Add event activity&quot; width=&quot;598&quot; height=&quot;863&quot; /&gt;&lt;figcaption&gt;Add event activity&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Set the &lt;strong&gt;Name&lt;/strong&gt; field to the same value as the first parameter of the &lt;code&gt;SysCopilotChatActionDefinition&lt;/code&gt; attribute in the class. Mine was &lt;code&gt;MS.PA.AAS.AP.AASPurchTableConfirm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;Value&lt;/strong&gt; field, add this expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Concatenate(&quot;{&quot;&quot;purchaseOrderNumber&quot;&quot;: &quot;&quot;&quot;,Global.PA_Copilot_ServerForm_PageContext.titleField1Value, &quot;&quot;&quot;}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we’re creating a JSON variable with our &lt;code&gt;purchaseOrderNumber&lt;/code&gt; input parameter, and as its value, I’m using the first title field of the PurchTable table with the variable &lt;code&gt;Global.PA_Copilot_ServerForm_PageContext.titleField1Value&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;If the table doesn’t have a title field, you can use the &lt;code&gt;Global.PA_Copilot_ServerForm_PageContext.rootTableRecId&lt;/code&gt; variable and replace the input parameter for a RecId referencing the PurchTable record.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Finally, add an &lt;strong&gt;End current topic&lt;/strong&gt; action from the &lt;strong&gt;Topic management&lt;/strong&gt; menu:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-30.png&quot; alt=&quot;End current topic&quot; width=&quot;503&quot; height=&quot;760&quot; /&gt;&lt;figcaption&gt;End current topic&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Save it, and our trigger topic is ready.&lt;/p&gt;
&lt;h4&gt;Response topic&lt;/h4&gt;
&lt;p&gt;Now we need a topic to process the response. I’ve named this one AAS.ConfirmPOResponse.&lt;/p&gt;
&lt;p&gt;Change the trigger to the type &lt;strong&gt;A custom client event occurs&lt;/strong&gt;. Set its &lt;strong&gt;Event name&lt;/strong&gt; property to &lt;code&gt;MS.PA.AAS.AP.AASPurchTableConfirm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now we need to add a &lt;strong&gt;Parse value&lt;/strong&gt; action from the &lt;strong&gt;Variable management&lt;/strong&gt; menu:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-31.png&quot; alt=&quot;Copilot Studio topic with the add menu open on Variable management and the Parse value action highlighted&quot; width=&quot;483&quot; height=&quot;534&quot; /&gt;&lt;figcaption&gt;Parse action&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here we will parse the response from our class. Set the &lt;strong&gt;Parse value&lt;/strong&gt; field to the &lt;strong&gt;System.Activity.Text&lt;/strong&gt; variable (in the &lt;strong&gt;System&lt;/strong&gt; tab):&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-32.png&quot; alt=&quot;Activity.Text variable&quot; width=&quot;762&quot; height=&quot;346&quot; /&gt;&lt;figcaption&gt;Activity.Text variable&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then in the Data type field, select &lt;strong&gt;From sample data&lt;/strong&gt; and click the Get schema from sample JSON button. Paste this sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;isResponse&quot;: true,
    &quot;purchaseOrderNumber&quot;: &quot;PO-00001&quot;,
    &quot;hasError&quot;: true,
    &quot;errorMessage&quot;: &quot;Oh, no! An error&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is similar to parsing JSON on Power Automate. You’ll now have these four JSON fields available individually.&lt;/p&gt;
&lt;p&gt;Now add an &lt;strong&gt;Add a condition&lt;/strong&gt; control. We’ll show the user a different message depending on the outcome of the confirm operation.&lt;/p&gt;
&lt;p&gt;On the left condition, we’ll manage errors. Select the ConfirmResponse.hasError variable and check if it’s true:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-34.png&quot; alt=&quot;Error condition&quot; width=&quot;800&quot; height=&quot;414&quot; /&gt;&lt;figcaption&gt;Error condition&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then add a &lt;strong&gt;Send a message action&lt;/strong&gt;, with the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text: “The purchase order “&lt;/li&gt;
&lt;li&gt;Variable: ConfirmResponse.purchaseOrderNumber&lt;/li&gt;
&lt;li&gt;Text: ” couldn’t be confirmed, an error occurred. “&lt;/li&gt;
&lt;li&gt;Variable: ConfirmResponse.errorMessage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So it becomes human-readable when it’s displayed.&lt;/p&gt;
&lt;p&gt;In the other branch and add a &lt;strong&gt;Send a message&lt;/strong&gt; action with the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text: “Purchase order “&lt;/li&gt;
&lt;li&gt;Variable: ConfirmResponse.purchaseOrderNumber&lt;/li&gt;
&lt;li&gt;Text: ” confirmed!”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save your topic, and now click the &lt;strong&gt;Publish&lt;/strong&gt; button so it’s available on F&amp;amp;O’s sidecar chat.&lt;/p&gt;
&lt;h2&gt;Hey! What about the DataAreaId!?&lt;/h2&gt;
&lt;p&gt;Before testing the plugin, let me answer this question.&lt;/p&gt;
&lt;p&gt;If you remember from the &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;previous post about the AI tools&lt;/a&gt;, we had to explicitly specify the company in which the code was running; otherwise, it would take the default company.&lt;/p&gt;
&lt;p&gt;With client plugins, the company that the code will run in is the active one. That’s also another benefit of the &lt;strong&gt;context&lt;/strong&gt;!&lt;/p&gt;
&lt;h2&gt;Test it!&lt;/h2&gt;
&lt;p&gt;Now go to the purchase order form and select one that’s not confirmed. Open the sidecar chat and tell Copilot, “Confirm the order”. The process will run and…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-37.png&quot; alt=&quot;Order confirmed!&quot; width=&quot;1871&quot; height=&quot;1109&quot; /&gt;&lt;figcaption&gt;Order confirmed!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We have confirmed the order! Well, not we, but Copilot Studio.&lt;/p&gt;
&lt;p&gt;Now let’s try to run the other conditional branch, the one where we get an error. Just try confirming an order that is already confirmed.&lt;/p&gt;
&lt;p&gt;That works too! We get the error message in the sidecar chat!&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-38.png&quot; alt=&quot;The error appears&quot; width=&quot;1871&quot; height=&quot;1109&quot; /&gt;&lt;figcaption&gt;The error appears&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;As expected, we get the message from our &lt;code&gt;throw error&lt;/code&gt; in the function.&lt;/p&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Client plugins give Copilot superpowers &lt;em&gt;inside the form you’re on&lt;/em&gt;—perfect when you need context or guardrails.&lt;/p&gt;
&lt;p&gt;We’ve learned the essentials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Define the scope (Global, Form, Runtime).&lt;/li&gt;
&lt;li&gt;Decorate the class (&lt;code&gt;DataContract&lt;/code&gt;, &lt;code&gt;Export&lt;/code&gt;/&lt;code&gt;ExportMetadata&lt;/code&gt;, &lt;code&gt;SysCopilotChatActionDefinition&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Create inputs and outputs.&lt;/li&gt;
&lt;li&gt;Implement &lt;code&gt;executeAction()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create the trigger and response topics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reach for client plugins when the action must respect the current form/session and feel like a menu action; keep AI tools in mind for broader, cross-form logic.&lt;/p&gt;
&lt;p&gt;In the next post, we’ll put them head-to-head so you know exactly when to use each.&lt;/p&gt;
&lt;p&gt;See you in part 4!&lt;/p&gt;</content:encoded><category>AI &amp; Copilot</category></item><item><title>Build an X++ AI Tool for Copilot Studio in Dynamics 365 F&amp;O</title><link>https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/</link><guid isPermaLink="true">https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/</guid><description>Learn how to wire real F&amp;O business logic into Copilot Studio. This guide shows you how to build an X++ AI tool, set up security, auto/hand-register the Dataverse Custom API, configure inputs (including company context), create a topic, publish, and test—end to end.</description><pubDate>Tue, 26 Aug 2025 13:59:00 GMT</pubDate><content:encoded>&lt;p&gt;Welcome to article 2 in the Copilot Studio series! Want Copilot Studio to run real F&amp;amp;O business logic? Build a simple X++ AI tool and wire it in.&lt;/p&gt;
&lt;p&gt;If you missed other articles in the series, read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/&quot;&gt;Copilot Studio 101: Extend Dynamics 365 F&amp;amp;O Copilots&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/&quot;&gt;Create X++ Client Plugins for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 4: &lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/&quot;&gt;Troubleshoot Copilot Studio errors with Postman&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s the idea: Copilot Studio gets truly useful when it can run your real F&amp;amp;O business logic on demand. The fastest way to do that today is with an &lt;strong&gt;X++ AI tool&lt;/strong&gt;: a small data contract class exposed as a &lt;strong&gt;custom API&lt;/strong&gt; in Dataverse that can be called from Copilot Studio.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/dins-1024x1024.png&quot; alt=&quot;a cartoon of a knife with a face mask&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;An AI tool, part of the F&amp;amp;O Swiss Army knife gadgets for your AI endeavors!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In this walkthrough, we’ll build a minimal example (look up a customer’s name by account number) to show the pattern end-to-end. You’ll see how to create the class and attributes, set up the right security (menu item, privilege, role), deploy to Dataverse, and configure the tool in Copilot Studio.&lt;/p&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#whats-an-x-ai-tool&quot;&gt;What’s an X++ AI tool?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#prerequisites&quot;&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#create-the-ai-tool&quot;&gt;Create the AI tool&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#create-a-class&quot;&gt;Create a class&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#create-the-security-objects&quot;&gt;Create the security objects&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#deploy-the-model-to-dataverse&quot;&gt;Deploy the model to Dataverse&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#create-the-dataverse-api&quot;&gt;Create the Dataverse API&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#using-the-ai-tool-in-copilot-studio&quot;&gt;Using the AI tool in Copilot Studio&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#configure-inputs&quot;&gt;Configure inputs&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#create-a-topic&quot;&gt;Create a topic&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#add-a-new-topic&quot;&gt;Add a new topic&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#publish-the-agent&quot;&gt;Publish the agent&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#testing-the-agent&quot;&gt;Testing the agent&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;h2&gt;What’s an X++ AI tool?&lt;/h2&gt;
&lt;p&gt;X++ &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-ai-plugins&quot;&gt;AI tools&lt;/a&gt; are used to extend the capabilities of agents in Copilot Studio.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;These AI tools are quite useful… if you have a good use case 😉. Right now this is the hardest part for me. I understand and see their value, but finding a good use case is not easy.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In X++, an AI tool is simply a data contract class! Alright, let’s learn how to create one.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;To follow the steps described in this article, I suggest using a &lt;a href=&quot;https://ariste.info/dynamics365almguide/deploy-ude-environment-ppac/&quot;&gt;UDE&lt;/a&gt; (Unified Developer Experience) environment. You won’t be able to test this on a local VHD-based VM.&lt;/p&gt;
&lt;p&gt;Your Dataverse environment needs the following solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finance and Operations Virtual Entity&lt;/li&gt;
&lt;li&gt;Copilot for finance and operations apps&lt;/li&gt;
&lt;li&gt;Copilot in Microsoft Dynamics 365 Finance or Copilot in Microsoft Dynamics 365 Supply Chain Management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In your Dynamics 365 F&amp;amp;O environment, you must enable the &lt;strong&gt;Custom API Generation&lt;/strong&gt; feature if it’s not enabled by default.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Caution&lt;/p&gt;
&lt;p&gt;As of August 2025, the documentation on Microsoft Learn is outdated, and you don’t need to take any manual steps after creating the AI tool. You can ignore everything in the section &lt;strong&gt;Create the Dataverse custom API&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Create the AI tool&lt;/h2&gt;
&lt;p&gt;For this example, I’ll be creating a class that runs a stupid functionality: return the customer’s name based on their customer account number. I know it’s not the craziest example of using cutting-edge AI technology.&lt;/p&gt;
&lt;p&gt;I just want to show you the basics of creating an AI tool in X++, and then you can use it with your scenario!&lt;/p&gt;
&lt;h3&gt;Create a class&lt;/h3&gt;
&lt;p&gt;So first create a class. I’ll name mine &lt;code&gt;AASCustomerNameCustomAPI&lt;/code&gt;. This class needs to implement the &lt;strong&gt;&lt;code&gt;ICustomAPI&lt;/code&gt;&lt;/strong&gt; interface.&lt;/p&gt;
&lt;p&gt;We also must decorate it with these attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CustomAPI&lt;/strong&gt;: this sets the name and description shown in Copilot Studio. Mine is &lt;code&gt;[CustomAPI(&apos;Get the customer name&apos;, &apos;Returns the name of the customer based on their customer account number&apos;)]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AIPluginOperation&lt;/strong&gt;: nothing special, just use &lt;code&gt;[AIPluginOperation]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DataContract&lt;/strong&gt;: and also nothing special, use &lt;code&gt;[DataContract]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next: input and output parameters. When we ask Copilot for something, it usually needs input data. In our case it’s the customer account number. So I’ll create a custAccount variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private CustAccount custAccount;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we expect a response from the bot. That’s our output parameter, and because for this example it’s the name, I’ll create a custName variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private Name custName;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great! We need one more thing: a &lt;code&gt;CompanyId&lt;/code&gt; variable! Why? As in many Dataverse/Power Platform integrations, the default company from your user info is used. Fortunately, we can get the current company where the request is being done from Copilot Studio, but we need a variable to then change the company if it’s not the same as the default one you have.&lt;/p&gt;
&lt;p&gt;That’s why I also create a companyId member:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private CompanyId companyId;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you may already be thinking, if the class is a data contract, these are our data members. And what do data members need? Their parm methods!&lt;/p&gt;
&lt;p&gt;And because they’re AI tool data members, we need to decorate them with the &lt;code&gt;CustomAPIRequestParameter&lt;/code&gt; for the request and &lt;code&gt;CustomAPIResponseProperty&lt;/code&gt; for the response. They’ll look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    CustomAPIRequestParameter(&apos;The company of the customer&apos;, true),
    DataMember(&quot;companyId&quot;)
]
public CompanyId parmCompanyId(CompanyId _companyId = companyId)
{
    companyId = _companyId;
    return companyId;
}    

[
    CustomAPIRequestParameter(&apos;The customer account number&apos;, false),
    DataMember(&quot;custAccount&quot;)
]
public CustAccount parmCustAccount(CustAccount _custAccount = custAccount)
{
    custAccount = _custAccount;
    return custAccount;
}

[
    CustomAPIResponseProperty(&apos;The customer name&apos;),
    DataMember(&quot;custName&quot;)
]
public Name parmCustName(Name _custName = custName)
{
    custName = _custName;
    return custName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;CustomAPIRequestParameter&lt;/code&gt;&lt;/strong&gt; attribute expects two parameters: the description of the field and a boolean indicating if the field is optional. You can see I’ve set the one for the companyId as true, meaning optional.&lt;/p&gt;
&lt;p&gt;If we don’t provide a companyId value, the process will fail, but we’ll make sure that doesn’t happen when using the tool in Copilot Studio.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;&lt;code&gt;CustomAPIResponseProperty&lt;/code&gt;&lt;/strong&gt; attribute only expects one parameter: a description.&lt;/p&gt;
&lt;p&gt;Now override the &lt;code&gt;run()&lt;/code&gt; method that comes from the interface. This method contains the business logic that’s being triggered. Getting a customer name, that’s easy, right?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Hookable(false)]
public void run(Args _args)
{
    changecompany(this.parmCompanyId())
    {
        CustTable custTable = CustTable::find(this.parmCustAccount());

        if (custTable)
        {
            this.parmCustName(custTable.name());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build the class, and it’s ready. But there’s more to do!&lt;/p&gt;
&lt;h3&gt;Create the security objects&lt;/h3&gt;
&lt;p&gt;For X++ AI tools to work properly, we also need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;action menu item&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;security privilege&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;role&lt;/strong&gt; for the privilege (and a duty if you prefer to use that). We &lt;strong&gt;must&lt;/strong&gt; assign this role to the user &lt;strong&gt;chatting to&lt;/strong&gt; the agent; otherwise, they won’t be able to access it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the action menu item, set its &lt;strong&gt;Object Type&lt;/strong&gt; property to Class, and the &lt;strong&gt;Object&lt;/strong&gt; property to the name of the class we created before.&lt;/p&gt;
&lt;p&gt;Create a new security privilege; I’ve called mine AASCustomerNameAPIAccess. Add the action menu item to the &lt;strong&gt;Entry Points&lt;/strong&gt; node, and set its &lt;strong&gt;Access Level&lt;/strong&gt; property to &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I’m creating a new role, also named AASCustomerNameAPIAccess, that has the AASCustomerNameAPIAccess security privilege in the &lt;strong&gt;Privileges&lt;/strong&gt; node.&lt;/p&gt;
&lt;p&gt;Your VS project should look like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-8.png&quot; alt=&quot;Project objects&quot; width=&quot;401&quot; height=&quot;227&quot; /&gt;&lt;figcaption&gt;Project objects&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Deploy the model to Dataverse&lt;/h3&gt;
&lt;p&gt;And because I’m in a UDE environment, I’m going to deploy the code to Dataverse.&lt;/p&gt;
&lt;p&gt;Once the deployment completes, you can go to F&amp;amp;O, &lt;code&gt;System administration &amp;gt; Setup &amp;gt; Synchronize Dataverse Custom APIs&lt;/code&gt;, and look for your API:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-9.png&quot; alt=&quot;Custom API created&quot; width=&quot;1832&quot; height=&quot;500&quot; /&gt;&lt;figcaption&gt;Custom API created&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If it’s there, you’re all set. If it isn’t, please verify that you’ve created and configured the menu item, privilege, and role or duty correctly. Also check that you haven’t missed adding any attribute in the data contract class or members!&lt;/p&gt;
&lt;h3&gt;Create the Dataverse API&lt;/h3&gt;
&lt;p&gt;Once we have deployed the code, we must create a Custom API and parameters in Dataverse.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;This step wasn’t needed until recent updates of 10.0.44 and 10.0.45. It’s been enforced to have better ALM when moving these between environments.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Go to the &lt;a href=&quot;https://make.powerapps.com/&quot;&gt;Power Apps maker portal&lt;/a&gt;, and create a new solution.&lt;/p&gt;
&lt;p&gt;First, click New &amp;gt; More &amp;gt; Other &amp;gt; Custom API:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-41.png&quot; alt=&quot;Dataverse Custom APIs&quot; width=&quot;1221&quot; height=&quot;782&quot; /&gt;&lt;figcaption&gt;Dataverse Custom APIs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the new window, enter the following parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unique Name&lt;/strong&gt;: aas_AASCustomerNameCustomAPI (that’s your prefix + class name).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: Customer name agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Display Name&lt;/strong&gt;: Customer name agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;: Returns the customer name based on their customer account number.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binding Type&lt;/strong&gt;: Global.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugin Type&lt;/strong&gt;: Microsoft.Dynamics.Fno.Copilot.Plugins.InvokeFnoCustomAPI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click &lt;strong&gt;Save &amp;amp; Close&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Then we must create a &lt;strong&gt;Custom API Request Parameter&lt;/strong&gt; for the parm methods decorated with the &lt;strong&gt;CustomAPIRequestParameter&lt;/strong&gt; attribute.&lt;/p&gt;
&lt;p&gt;You must do this for each input parameter you have! I’ll show this for the companyId parameter.&lt;/p&gt;
&lt;p&gt;Click New &amp;gt; More &amp;gt; Other &amp;gt; Custom API Request Parameter and set these values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Custom API&lt;/strong&gt;: Customer name agent (this is the name you gave the Custom API in the previous step).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unique Name&lt;/strong&gt;: aas_AASCustomerNameCustomAPI_companyId (this is your publisher prefix + class name + parameter name).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: companyId.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Display Name&lt;/strong&gt;: companyId.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;: the company in which to find the customer’s name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type&lt;/strong&gt;: String.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IsOptional&lt;/strong&gt;: No.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click &lt;strong&gt;Save &amp;amp; Close&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Do the same for the custAccount member of the class.&lt;/p&gt;
&lt;p&gt;And finally, create the parameter for the parm methods decorated with the &lt;strong&gt;CustomAPIResponseProperty&lt;/strong&gt; attribute.&lt;/p&gt;
&lt;p&gt;Click New &amp;gt; More &amp;gt; Other &amp;gt; Custom API Response Parameter and set these values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Custom API&lt;/strong&gt;: Customer name agent (this is the name you gave the Custom API in the previous step).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unique Name&lt;/strong&gt;: aas_AASCustomerNameCustomAPI_custName (this is your publisher prefix + class name + parameter name).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: custName.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Display Name&lt;/strong&gt;: custName.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description&lt;/strong&gt;: the name of the customer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type&lt;/strong&gt;: String.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click &lt;strong&gt;Save &amp;amp; Close&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Using the AI tool in Copilot Studio&lt;/h2&gt;
&lt;p&gt;Once the tool is ready, go to &lt;a href=&quot;https://copilotstudio.microsoft.com/&quot;&gt;Copilot Studio&lt;/a&gt; to add it to our &lt;strong&gt;Copilot for finance and operations apps&lt;/strong&gt; agent.&lt;/p&gt;
&lt;p&gt;Make sure you select the correct environment, open the agent, and go to the &lt;strong&gt;Tools&lt;/strong&gt; tab:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-10.png&quot; alt=&quot;Tools tab in Copilot for finance and operations agent&quot; width=&quot;1566&quot; height=&quot;623&quot; /&gt;&lt;figcaption&gt;Tools tab in Copilot for finance and operations agent&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click the &lt;strong&gt;Add a tool&lt;/strong&gt; button, then select &lt;strong&gt;Microsoft Dataverse&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-42.png&quot; alt=&quot;Add your AI tool in Copilot Studio&quot; width=&quot;1653&quot; height=&quot;1030&quot; /&gt;&lt;figcaption&gt;Add your AI tool in Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the next screen, select &lt;strong&gt;Perform an unbound action in selected environment&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-43-1024x1024.png&quot; alt=&quot;Perform an unbound action in selected environment&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Perform an unbound action in selected environment&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Create a connection, either using &lt;strong&gt;Oauth&lt;/strong&gt; with your account or a &lt;strong&gt;Service Principal&lt;/strong&gt; with an Entra ID App registration, up to you which one to use.&lt;/p&gt;
&lt;p&gt;Click the &lt;strong&gt;Add and configure&lt;/strong&gt; button.&lt;/p&gt;
&lt;p&gt;Change the &lt;strong&gt;Name&lt;/strong&gt; and &lt;strong&gt;Description&lt;/strong&gt; fields. I’ve used “AAS.Get the customer name” as the Name and “Returns the name of the customer based on the customer account and legal entity” as Description.&lt;/p&gt;
&lt;h3&gt;Configure inputs&lt;/h3&gt;
&lt;p&gt;In the &lt;strong&gt;Inputs&lt;/strong&gt; section change the Environment input to “Custom value”, and as the value select “(Current)”.&lt;/p&gt;
&lt;p&gt;Then for the Action Name input, change it to custom too, and in the value enter the name of the Custom API we’ve created. It should be in the lookup.&lt;/p&gt;
&lt;p&gt;Next, click the &lt;strong&gt;Add input&lt;/strong&gt; button:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-44.png&quot; alt=&quot;Add inputs&quot; width=&quot;1653&quot; height=&quot;1030&quot; /&gt;&lt;figcaption&gt;Add inputs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Select both parameters, one at a time.&lt;/p&gt;
&lt;p&gt;By default, inputs are set to &lt;strong&gt;Dynamically fill with AI&lt;/strong&gt;, which means that the agent will decide which values go there. We can also select &lt;strong&gt;Custom value&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-13.png&quot; alt=&quot;Input values&quot; width=&quot;830&quot; height=&quot;270&quot; /&gt;&lt;figcaption&gt;Input values&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Let’s do this for the companyId field. Remember I said that it takes the default company on user info? We’re going to change this and select the company in which the user was talking to the bot.&lt;/p&gt;
&lt;p&gt;Click the three dots in the value field, and look for dataAreaId in the &lt;strong&gt;Custom&lt;/strong&gt; tab. You should find only one result: &lt;strong&gt;PA_Copilot_ServerForm_UserContext.dataAreaId&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This variable contains the company in which the user was when they asked the agent.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;If you would like to learn more about how this is populated, open the &lt;strong&gt;SysCopilotChatUserContext&lt;/strong&gt; class in Visual Studio.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Leave the custAccount parameter to &lt;strong&gt;Dynamically fill with AI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Don’t forget to click the &lt;strong&gt;Save&lt;/strong&gt; button before leaving this page!&lt;/p&gt;
&lt;h2&gt;Create a topic&lt;/h2&gt;
&lt;p&gt;Now the action is there, and we need the agent to be able to know when to use it. That’s what topics are used for.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Caution&lt;/p&gt;
&lt;p&gt;Copilot Studio includes a &lt;strong&gt;Generative AI Orchestration&lt;/strong&gt; option.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-14.png&quot; alt=&quot;Gen AI orchestration in Copilot Studio&quot; width=&quot;946&quot; height=&quot;413&quot; /&gt;&lt;figcaption&gt;Gen AI orchestration in Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If this is enabled and supported for your F&amp;amp;O agent, the agent can decide when to invoke the tool automatically, and you &lt;strong&gt;don’t need to create a manual topic trigger&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As of September 2025, this orchestration isn’t supported for the F&amp;amp;O built-in Copilot agent, so create a topic manually.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Add a new topic&lt;/h3&gt;
&lt;p&gt;Go to the &lt;strong&gt;Topics&lt;/strong&gt; tab and click the &lt;strong&gt;Add a topic&lt;/strong&gt; button, selecting the &lt;strong&gt;From blank&lt;/strong&gt; option. Give the new topic a name.&lt;/p&gt;
&lt;p&gt;You can see the topic designer looks a bit like the Power Automate one, and it also starts with a trigger:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-15-1024x551.png&quot; alt=&quot;Topic trigger&quot; width=&quot;1024&quot; height=&quot;551&quot; /&gt;&lt;figcaption&gt;Topic trigger&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here we need to introduce the phrases that will start our AI tool action. For example, “What’s the name of the customer”, and plenty of variations.&lt;/p&gt;
&lt;p&gt;How can you get most of them? With AI! What I do is just ask ChatGPT for variations of one or two examples and use those, and then ask it to put the variations in a file with no quotes. Then I paste it all in Copilot Studio.&lt;/p&gt;
&lt;p&gt;Then click the + button below the trigger, go to &lt;strong&gt;Add a tool&lt;/strong&gt;, select the &lt;strong&gt;Tool&lt;/strong&gt; tab and choose yours:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-16.png&quot; alt=&quot;AI action in Copilot Studio&quot; width=&quot;400&quot; height=&quot;750&quot; /&gt;&lt;figcaption&gt;AI action in Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Done! Don’t forget to click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Publish the agent&lt;/h2&gt;
&lt;p&gt;Even if we clicked &lt;strong&gt;Save&lt;/strong&gt; a thousand times in Copilot Studio, if we go to the F&amp;amp;O sidecar chat and try to run our tool, it will run nothing.&lt;/p&gt;
&lt;p&gt;To apply the changes, we need to click the &lt;strong&gt;Publish&lt;/strong&gt; button in the top right area, near the &lt;strong&gt;Test&lt;/strong&gt; one:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-20-1024x550.png&quot; alt=&quot;Publish on Copilot Studio&quot; width=&quot;1024&quot; height=&quot;550&quot; /&gt;&lt;figcaption&gt;Publish on Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;Testing the agent&lt;/h2&gt;
&lt;p&gt;Everything is ready! Now click the &lt;strong&gt;Test&lt;/strong&gt; button in the top right corner. A new pane will open. Here we’ll test the bot, asking, “What’s the name of customer US-001?” and we’ll get it!&lt;/p&gt;
&lt;p&gt;Sure? Well, no. Remember we set that the company field would come from the user context in F&amp;amp;O? This is what happens:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-17.png&quot; alt=&quot;Copilot Studio error&quot; width=&quot;847&quot; height=&quot;160&quot; /&gt;&lt;figcaption&gt;Copilot Studio error&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It gets an empty &lt;code&gt;companyId&lt;/code&gt;, and when it calls the X++ code, the &lt;code&gt;changecompany&lt;/code&gt; method is called with an empty value, and it fails. Also because I added no try-catch block, but that’s a different story.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;Copilot Studio’s test panel doesn’t include F&amp;amp;O user context. Test context-dependent tools in the F&amp;amp;O side panel or temporarily use custom values while testing in Studio.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Go to F&amp;amp;O, and ask the same question in the sidecar chat:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-21.png&quot; alt=&quot;Copilot in F&amp;amp;O knows!&quot; width=&quot;1476&quot; height=&quot;593&quot; /&gt;&lt;figcaption&gt;Copilot in F&amp;amp;O knows!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;But wait, every time I want to test something that uses the context, I have to publish the agent and go to F&amp;amp;O? Well, what you can do is set all the inputs in the tool to use a &lt;strong&gt;dynamically filled value&lt;/strong&gt; while testing it inside Copilot Studio and change it before publishing.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And we’re done! We’ve seen how to create and deploy an X++ AI tool from scratch, configure its inputs, set up security, and integrate it into Copilot Studio.&lt;/p&gt;
&lt;p&gt;My example was intentionally simple, but I wanted to show all the fundamental steps needed to build more advanced AI integrations.&lt;/p&gt;
&lt;p&gt;Finding the perfect use case will be the trickiest part of the process, but now you’ve got the basics down, and I’m sure you’ll discover many valuable scenarios to use this.&lt;/p&gt;
&lt;p&gt;You can read part 3 now: &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;Create X++ Client Plugins for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI &amp; Copilot</category></item><item><title>Copilot Studio 101: Extend Dynamics 365 F&amp;O Copilots</title><link>https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/</link><guid isPermaLink="true">https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/</guid><description>Kick off a hands-on series on Copilot Studio for Dynamics 365 F&amp;O. See what it is, the setup you need, how it uses Dataverse, built-in copilots, and two X++ extensibility paths: client plugins and AI tools.</description><pubDate>Tue, 19 Aug 2025 13:59:00 GMT</pubDate><content:encoded>&lt;p&gt;With this post I’m starting a series of articles about extending the Dynamics 365 Finance and Operations agent in Copilot Studio. Yes… I’ve also finally fallen into the AI whirlwind 😅.&lt;/p&gt;
&lt;p&gt;This introductory post covers the basics of Copilot Studio and its extensibility options. After this, we’ll see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 2: &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;Create AI tools (plugins) in X++&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Part 3: &lt;a href=&quot;https://ariste.info/2025/09/create-client-plugins-copilot-studio-d365-fo/&quot;&gt;Create client plugins in X++.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 4: &lt;a href=&quot;https://ariste.info/2025/09/troubleshoot-copilot-studio-errors-postman/&quot;&gt;Troubleshooting Copilot Studio agents using Postman&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;When to use a client plugin vs. AI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/IMG_2689.png&quot; alt=&quot;Driving with Copilot Studio&quot; width=&quot;2400&quot; height=&quot;1350&quot; /&gt;&lt;figcaption&gt;Driving with Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The list will grow as Microsoft ships new features. For example, once the &lt;strong&gt;Dynamics 365 ERP MCP server&lt;/strong&gt; becomes extensible, I’ll add an article for that as well.&lt;/p&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#what-is-copilot-studio&quot;&gt;What is Copilot Studio?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#before-you-start&quot;&gt;Before you start&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#licensing&quot;&gt;Licensing&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#copilot-studio--fo&quot;&gt;Copilot Studio &amp;amp; F&amp;amp;O&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#architecture-of-the-fo-copilot&quot;&gt;Architecture of the F&amp;amp;O Copilot&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#the-finance-and-operations-copilots&quot;&gt;The Finance and Operations Copilots&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#platform&quot;&gt;Platform&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#finance&quot;&gt;Finance&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#supply-chain-management&quot;&gt;Supply Chain Management&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#project-operations&quot;&gt;Project Operations&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#commerce&quot;&gt;Commerce&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#copilot-studio-ui&quot;&gt;Copilot Studio UI&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#extending-copilots-in-x&quot;&gt;Extending Copilots in X++&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#mcp-server-for-dynamics-365-erp&quot;&gt;MCP server for Dynamics 365 ERP&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2025/08/copilot-studio-extend-d365-fo-copilot/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;h2&gt;What is Copilot Studio?&lt;/h2&gt;
&lt;p&gt;If you’ve been working with F&amp;amp;O or the Power Platform for some time you might remember &lt;strong&gt;Power Virtual Agents&lt;/strong&gt; (PVA).&lt;/p&gt;
&lt;p&gt;PVA was Microsoft’s low-code platform for building chatbots. They were part of the Power Platform and allowed you to create, test, and publish bots through a visual, no-code interface, connect to data and services via Power Automate, and deploy them to Microsoft Teams, websites, etc.&lt;/p&gt;
&lt;p&gt;In 2023 Microsoft announced the release of &lt;strong&gt;Copilot Studio&lt;/strong&gt;, and that PVA was becoming part of it. Yes, new features were added, but the foundation of Copilot Studio was Power Virtual Agents.&lt;/p&gt;
&lt;p&gt;With all the AI advancements since then, Copilot Studio has evolved not just into a low‑code chatbot creation tool but into a complete suite that also extends existing copilots that Microsoft has released for their products, like the Copilot for Finance and Operations apps agent.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;h2&gt;Before you start&lt;/h2&gt;
&lt;p&gt;If you want to follow the examples that I will show in the articles, you will need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unified Developer Experience (UDE) development environment&lt;/strong&gt;: creating client plugins and AI tools requires a UDE environment. I haven’t tested this on a cloud-hosted environment linked to Dataverse, and the documentation explicitly asks for a UDE environment. You also need to be at least on version 10.0.40.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System administrator on the F&amp;amp;O environment&lt;/strong&gt;: probably you could do with less, but if the UDE environment is yours, you’re most likely the administrator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System customizer on Dataverse&lt;/strong&gt;: this is the least access role you need to be able to install the Copilot solutions on the Dataverse environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dataverse solutions&lt;/strong&gt;: you need the following solutions in the UDE environment:
&lt;ul&gt;
&lt;li&gt;Finance and Operations Virtual Entity: to enable Copilot Studio to talk to F&amp;amp;O.&lt;/li&gt;
&lt;li&gt;Copilot for finance and operations apps: to add the F&amp;amp;O agent to Copilot Studio.&lt;/li&gt;
&lt;li&gt;Copilot in Microsoft Dynamics 365 Finance: to add the finance-related agents.&lt;/li&gt;
&lt;li&gt;Copilot in Microsoft Dynamics 365 Supply Chain Management: to add the SCM-related agents.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visual Studio&lt;/strong&gt;: you need Visual Studio to follow the code examples and to apply them to the UDE environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Licensing&lt;/h3&gt;
&lt;p&gt;I’ve left a separate section in the requirements for licenses because… you know, licensing. Copilot Studio has its own &lt;a href=&quot;https://go.microsoft.com/fwlink/?linkid=2320995&quot;&gt;licensing guide&lt;/a&gt;, yay!&lt;/p&gt;
&lt;p&gt;There’s no entitlement for Copilot Studio in the F&amp;amp;O licenses. First, you need to get one of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up &lt;strong&gt;pay-as-you-go&lt;/strong&gt; for Copilot Studio.&lt;/li&gt;
&lt;li&gt;Copilot Studio &lt;strong&gt;credit pack subscription&lt;/strong&gt;: 25,000 monthly credits. Previously called message pack subscription and messages.&lt;/li&gt;
&lt;li&gt;Some &lt;strong&gt;Microsoft 365 Copilot plans&lt;/strong&gt; have limited use rights, but only related to the M365 agent. &lt;strong&gt;Not our thing&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And to build and manage the agents, you need the &lt;strong&gt;Copilot Studio author role&lt;/strong&gt; or &lt;strong&gt;Copilot Studio user license&lt;/strong&gt;. Both are free!&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A clarification about licenses&lt;/strong&gt;: users interacting with the agent don’t need a Copilot Studio user license. Only the users customizing the agents in Copilot Studio need them.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Copilot Studio &amp;amp; F&amp;amp;O&lt;/h2&gt;
&lt;p&gt;Copilot Studio uses F&amp;amp;O &lt;strong&gt;Virtual Entities&lt;/strong&gt; (a Dataverse representation of F&amp;amp;O data) and Dataverse &lt;strong&gt;actions&lt;/strong&gt; to trigger X++ business logic in F&amp;amp;O. In the end, Copilot Studio acts as an orchestration layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start conversation&lt;/strong&gt;: a user starts a chat with the Copilot sidecar chat in F&amp;amp;O.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intent resolution&lt;/strong&gt;: Copilot Studio identifies the topic (or “action”) that meets the intent and selects the right tool to run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access data or logic&lt;/strong&gt;: Copilot Studio will trigger F&amp;amp;O business logic or retrieve data using Dataverse actions or virtual tables, respectively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response:&lt;/strong&gt; Copilot Studio finally mixes the output with AI and responds back to the sidecar chat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the Copilot Studio chatbot is the single entry point, all happens in Power Platform: managed solutions move your tools, topics, and environment variables through dev -&amp;gt; test -&amp;gt; prod. Only the X++ assemblies travel with your F&amp;amp;O deployable package.&lt;/p&gt;
&lt;h3&gt;Architecture of the F&amp;amp;O Copilot&lt;/h3&gt;
&lt;p&gt;As I said, the Copilot for F&amp;amp;O is Dataverse-based. In this diagram I stole from &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-architecture&quot;&gt;Microsoft Learn&lt;/a&gt;, you can see all the components involved:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-26.png&quot; alt=&quot;Architecture of Copilot in F&amp;amp;O&quot; width=&quot;1314&quot; height=&quot;626&quot; /&gt;&lt;figcaption&gt;Architecture of Copilot in F&amp;amp;O&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;As shown in the diagram, the AI logic, data, and connectors are hosted in Dataverse. Copilot Studio orchestrates communication between the two, consuming F&amp;amp;O data via &lt;a href=&quot;https://ariste.info/2020/09/dynamics-odata-dataverse-virtual-entities/&quot;&gt;virtual entities&lt;/a&gt; and invoking business logic via Dataverse actions, then composing an AI-generated response.&lt;/p&gt;
&lt;h2&gt;The Finance and Operations Copilots&lt;/h2&gt;
&lt;p&gt;In Dynamics 365 F&amp;amp;O, we can find three Copilot “flavors”, depending on where the users access them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sidecar&lt;/strong&gt;: the side chat that you can open with the Copilot button on the top menu bar. It provides natural language chatting capabilities, like ChatGPT.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded&lt;/strong&gt;: displayed on some forms, for example, the summaries on the customer or vendor forms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Outside&lt;/strong&gt;: Copilot experiences outside the F&amp;amp;O application, for example, using F&amp;amp;O data within Microsoft 365 Copilot or an agent accessed through Microsoft Teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we have several copilots that are available out-of-the-box across all the different F&amp;amp;O apps (Platform), and then for every specific F&amp;amp;O app (Finance, SCM, …) there are specific business-related copilots.&lt;/p&gt;
&lt;h3&gt;Platform&lt;/h3&gt;
&lt;p&gt;These are available across all the different F&amp;amp;O apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/copilot/copilot-generative-help&quot;&gt;Generative help and guidance with Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/organization-administration/workflow-history-summary&quot;&gt;Workflow history summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/enable-copilot-feedback&quot;&gt;Enhanced user feedback for Copilot and related experiences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/chat-with-fno-data&quot;&gt;Chat with finance and operations data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/copilot/immersive-home&quot;&gt;Immersive Home (production-ready preview)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Finance&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/finance/accounts-receivable/collectionscoordinatorsummary&quot;&gt;Collections coordinator summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/finance/accounts-receivable/customerpagesummary&quot;&gt;Customer page summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Supply Chain Management&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/supply-chain/get-started/copilot-summaries-overview&quot;&gt;AI summaries with Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/supply-chain/demand-planning/demand-planning-copilot&quot;&gt;Analyze demand plans with Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/supply-chain/procurement/purchase-order-changes-after-confirmation&quot;&gt;Review and accept changes to confirmed purchase orders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/supply-chain/warehousing/warehouse-management-mobile-app-insights&quot;&gt;Workload insights with Copilot in the Warehouse Management mobile app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/supply-chain/procurement/supplier-com-agent-overview&quot;&gt;Supplier Communications Agent overview (production-ready preview)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Project Operations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/project-operations/project-management/copilot-features&quot;&gt;Copilot for project overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/project-operations/time/copilot-in-time-entry&quot;&gt;Use Copilot in time entry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Commerce&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/commerce/copilot-pos-customer-insights&quot;&gt;Customer insights by Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/commerce/copilot-pos-report-insights&quot;&gt;Store report insights by Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/commerce/copilot-statement-summaries&quot;&gt;Enable Copilot statement posting summaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/commerce/copilot-based-merch-insights&quot;&gt;Streamline your merchandising process with Copilot-based insights&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/commerce/copilot-site-builder&quot;&gt;Use Copilot in site builder to enrich product detail pages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Copilot Studio UI&lt;/h2&gt;
&lt;p&gt;Now let’s learn a bit about the elements of Copilot Studio. When you open &lt;a href=&quot;https://copilotstudio.microsoft.com/&quot;&gt;Copilot Studio,&lt;/a&gt; you will find a somewhat familiar screen because its UI is built like other Power Platform components:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-23.png&quot; alt=&quot;Copilot Studio UI&quot; width=&quot;1264&quot; height=&quot;406&quot; /&gt;&lt;figcaption&gt;Copilot Studio UI&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here we have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;: the environment in which you’re creating or editing the agent. This should be the same as your UDE environment.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/microsoft-copilot-studio/authoring-first-bot?tabs=web&quot;&gt;&lt;strong&gt;Agents&lt;/strong&gt;&lt;/a&gt;: here we can create, edit, or delete the agents.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/microsoft-copilot-studio/flows-overview&quot;&gt;&lt;strong&gt;Flows&lt;/strong&gt;&lt;/a&gt;: similar to Power Automate flows, but with agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/microsoft-copilot-studio/library-add-actions&quot;&gt;Tools&lt;/a&gt;&lt;/strong&gt;: to create tools that extend and improve your agents. These can be reused across different agents.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next I want to focus on the &lt;strong&gt;Agents&lt;/strong&gt; tab. Here you’ll see all the agents available for your environment:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-24.png&quot; alt=&quot;Agents in Copilot Studio&quot; width=&quot;1529&quot; height=&quot;793&quot; /&gt;&lt;figcaption&gt;Agents in Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here you can create a new one or modify the existing ones. And if you click on the F&amp;amp;O one, you’ll see several tabs and the agent description:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2025/08/image-25.png&quot; alt=&quot;F&amp;amp;O agent in Copilot Studio&quot; width=&quot;1702&quot; height=&quot;895&quot; /&gt;&lt;figcaption&gt;F&amp;amp;O agent in Copilot Studio&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;What is each tab for?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Overview&lt;/strong&gt;: a quick view of your agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge&lt;/strong&gt;: define what our agent knows and where it takes it from.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt;: actions that your agent can run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt;: other Copilot agents that you can connect to yours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Topics&lt;/strong&gt;: create conversation flows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Activity&lt;/strong&gt;: shows an activity map of the currently running agent. Only works when generative AI orchestration is enabled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analytics&lt;/strong&gt;: shows usage statistics of your agent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channels&lt;/strong&gt;: decide where your agent is available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other than this, we have the &lt;strong&gt;Publish&lt;/strong&gt; button that will push the changes to the live bot. And also the &lt;strong&gt;Settings&lt;/strong&gt; button that shows additional setup for the bot.&lt;/p&gt;
&lt;p&gt;We will see most of these elements in the future posts.&lt;/p&gt;
&lt;h2&gt;Extending Copilots in X++&lt;/h2&gt;
&lt;p&gt;And what about extending the agents using X++ code? That’s possible too, and right now we have two ways of doing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-client-plugins&quot;&gt;Client plugins&lt;/a&gt;&lt;/strong&gt;: &lt;em&gt;Client plugins&lt;/em&gt;, or client actions, are Copilot plugins that invoke client code and are available for users in the context of client experiences for F&amp;amp;O apps. Developers can extend the Copilot chat capabilities by defining plugins that convert the functionality, operations, and business logic of the X++ code base into actions that users can invoke through natural language.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-ai-plugins&quot;&gt;AI tools&lt;/a&gt;&lt;/strong&gt;: currently in prerelease and previously called AI plugins, these can be added to the in-app Copilot for F&amp;amp;O apps, other Microsoft copilots or agents, or custom agents and can use the business logic. They don’t require specific context in the F&amp;amp;O client compared to client plugins.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In short, &lt;strong&gt;client plugins&lt;/strong&gt; are for UI-context actions, and &lt;strong&gt;AI tools&lt;/strong&gt; are background operations available globally. We will learn more details about both options in upcoming posts!&lt;/p&gt;
&lt;h2&gt;MCP server for Dynamics 365 ERP&lt;/h2&gt;
&lt;p&gt;And we also have the Model Context Protocol (MCP) server, which adds more capabilities to interact with F&amp;amp;O data. Currently it adds 12 tools that you can check &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/copilot/copilot-mcp&quot;&gt;in the Microsoft Learn article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As of August 2025, it’s not extensible, but Microsoft may allow custom MCP tools in the future.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;For the MCP server to show up on your agent in Copilot Studio you need the F&amp;amp;O version to be at least &lt;strong&gt;10.0.2263.17&lt;/strong&gt;. The version of the &lt;strong&gt;Copilot in Microsoft Dynamics 365 Finance&lt;/strong&gt; solution must be at least &lt;strong&gt;1.0.3049.1&lt;/strong&gt;. And the version of the &lt;strong&gt;Copilot in Microsoft Dynamics 365 Supply Chain Management&lt;/strong&gt; solution must be at least &lt;strong&gt;1.1.03046.2&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Copilot Studio is quickly becoming the centerpiece for bringing generative-AI experiences into Dynamics 365 Finance &amp;amp; Operations. In this first post we’ve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seen how PVA turned into Copilot Studio—and where it plugs into the F&amp;amp;O stack.&lt;/li&gt;
&lt;li&gt;Learned about prerequisites.&lt;/li&gt;
&lt;li&gt;Met the ready-made copilots you already get in Finance, SCM, ProjOps, and Commerce.&lt;/li&gt;
&lt;li&gt;Clicked around the Copilot Studio UI: agents, tools, topics, channels, and everything else.&lt;/li&gt;
&lt;li&gt;Teased the two ways to add X++ &lt;em&gt;magic&lt;/em&gt; in: client plug-ins and AI tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next article, we’ll roll up our sleeves and build a client plug-in in X++. Until then, make sure your UDE environment is ready, the required Dataverse solutions are installed, and your licenses are in place to follow along line-by-line!&lt;/p&gt;
&lt;p&gt;You can read part 2 of the series now: &lt;a href=&quot;https://ariste.info/2025/08/ai-tool-copilot-studio-dynamics-365-fo/&quot;&gt;Build an X++ AI Tool for Copilot Studio in Dynamics 365 F&amp;amp;O&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>AI &amp; Copilot</category></item><item><title>Ready to master ALM? The New Dynamics 365 ALM Guide is Here!</title><link>https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/</link><guid isPermaLink="true">https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/</guid><description>Today I’m announcing the first part of the new Dynamics 365 ALM guide for Finance and Operations (F&amp;O) is now available! This guide focuses on the unified development and administration experiences and the significant changes coming in the next few months. I’ve completely rewritten it from scratch, ensuring that all screenshots are up to date</description><pubDate>Sun, 29 Dec 2024 12:13:14 GMT</pubDate><content:encoded>&lt;p&gt;Today I’m announcing the first part of the new &lt;strong&gt;Dynamics 365 ALM&lt;/strong&gt; guide for Finance and Operations (F&amp;amp;O) is now available!&lt;/p&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#whats-new-in-the-dynamics-365-alm-guide&quot;&gt;What’s New in the Dynamics 365 ALM Guide?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#whats-now-available&quot;&gt;What’s now available?&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#welcome-to-the-new-alm-guide&quot;&gt;Welcome to the new ALM guide&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#create-and-prepare-azure-devops-organization-and-project&quot;&gt;Create and prepare Azure DevOps organization and project&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#unified-development-environment&quot;&gt;Unified development environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#using-visual-studio&quot;&gt;Using Visual Studio&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/12/ready-master-alm-new-dynamics-365-alm-guide/#whats-planned-next&quot;&gt;What’s Planned Next?&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/12/IMG_2622-1024x1024.png&quot; alt=&quot;With the new Dynamics 365 ALM Guide you have all the tools!&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;With the new Dynamics 365 ALM Guide you have all the tools!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This guide focuses on the unified development and administration experiences and the significant changes coming in the next few months. I’ve completely rewritten it from scratch, ensuring that all screenshots are up-to-date to make the process easier to follow.&lt;/p&gt;
&lt;p&gt;Over time, countless changes have impacted ALM for Dynamics 365. After writing the original ALM posts, I realized it was more efficient to create a new guide rather than amend the old content. Believe me, it’s easier to write from the beginning than reviewing what I already have…&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://ariste.info/dynamics365almguide/welcome-2/&quot;&gt;previous guide&lt;/a&gt; covered everything essential for maintaining a healthy &lt;strong&gt;Dynamics 365 ALM&lt;/strong&gt; process for Finance and Operations projects. While much of that information remains relevant, some updates—especially with Azure DevOps—reflect the latest advancements.&lt;/p&gt;
&lt;p&gt;One of the biggest shifts is the deprecation of Lifecycle Services (LCS) and the transition to the Power Platform admin center (PPAC). This change introduces new tools and workflows we need to adapt to for effective ALM.&lt;/p&gt;
&lt;h2&gt;What’s New in the Dynamics 365 ALM Guide?&lt;/h2&gt;
&lt;p&gt;With the new Unified &lt;a href=&quot;https://learn.microsoft.com/power-platform/developer/unified-experience/finance-operations-dev-overview?WT.mc_id=DX-MVP-5003976&quot;&gt;development&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview?WT.mc_id=DX-MVP-5003976&quot;&gt;administration&lt;/a&gt; experiences, here’s a sneak peek at what’s coming:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PPAC (say goodbye to LCS)&lt;/li&gt;
&lt;li&gt;Power Platform tools for DevOps&lt;/li&gt;
&lt;li&gt;The new “local” development experience&lt;/li&gt;
&lt;li&gt;Use Git in a very straightforward way&lt;/li&gt;
&lt;li&gt;Deploy to production from a release pipeline… yes, you read well, “deploy to production”, that’s correct 😁&lt;/li&gt;
&lt;li&gt;Automating operations with PAC CLI or PowerShell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The new guide will offer step-by-step instructions for implementing these tools and workflows. Initially, the focus will be on Dynamics 365 Finance and Operations, but my goal is to expand it to cover a fully unified ALM strategy that includes both ERP and Dataverse projects.&lt;/p&gt;
&lt;p&gt;I can’t wait to dive into this journey with you. Let’s embrace the new Dynamics 365 ALM era together!&lt;/p&gt;
&lt;h2&gt;What’s now available?&lt;/h2&gt;
&lt;p&gt;Currently, the Dynamics 365 ALM guide is a work-in-progress. The first chapters have been written, and I’ve got drafts of the next ones.&lt;/p&gt;
&lt;p&gt;Here’s what’s available now:&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://ariste.info/dynamics365almguide/welcome-to-the-new-alm-guide/#&quot;&gt;Welcome to the new ALM guide&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An introduction to the new Dynamics 365 ALM guide and the prerequisites needed to follow along.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://ariste.info/dynamics365almguide/welcome-to-the-new-alm-guide/#&quot;&gt;Create and prepare Azure DevOps organization and project&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This chapter explains how to create a DevOps organization and project from scratch. If your company doesn’t have a DevOps organization, you’ll learn how to create one and add a project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enable TFVC in a new organization (since Microsoft defaults to Git now).&lt;/li&gt;
&lt;li&gt;Configure pipeline agents with an Azure subscription to add more parallel jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a href=&quot;https://ariste.info/dynamics365almguide/welcome-to-the-new-alm-guide/#&quot;&gt;Unified development environment&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Introducing one of the most exciting features: the unified developer experience (UDE). Learn how to deploy a new development environment in Dataverse using PowerShell or the PPAC UI.&lt;/p&gt;
&lt;p&gt;We’ll also discuss the transition to the capacity-based model. 🤐&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://ariste.info/dynamics365almguide/welcome-to-the-new-alm-guide/#&quot;&gt;Using Visual Studio&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This chapter covers using Visual Studio 2022 to develop X++ code, pushing changes to Dataverse, and debugging. (Still a work in progress.)&lt;/p&gt;
&lt;p&gt;This is the currently available content in the new Dynamics 365 ALM guide, but I’m preparing many more things!&lt;/p&gt;
&lt;h2&gt;What’s Planned Next?&lt;/h2&gt;
&lt;p&gt;The missing parts for the Dynamics 365 ALM guide and what I have in my mind are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git basics&lt;/strong&gt;: just some words on Git. Using it in our projects will be much easier with the UDEs, and it’s worth learning how to create branches, change between branches, or other basic operations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipelines&lt;/strong&gt;: CI and CD pipelines. Including deploying to production using the Power Platform tasks in releases😍 Oh, and forget about the LCS connector with a username and password… say hello to service principals!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Further automation&lt;/strong&gt;: like in the old guide, I will describe how we can use pipelines to automate certain processes like copying environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PPAC-focused topics&lt;/strong&gt;: with the move to PPAC we must learn plenty of new things. How to check the capacity, manage environments, provide access to users, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide has been long in the making, but I hope it will be just as useful as the first one was. Stay tuned for more updates!&lt;/p&gt;</content:encoded><category>ALM &amp; DevOps</category></item><item><title>ISVLicenseGenerator v0.9: a farewell?</title><link>https://ariste.info/2024/09/isvlicensegenerator-v0-9-farewell/</link><guid isPermaLink="true">https://ariste.info/2024/09/isvlicensegenerator-v0-9-farewell/</guid><description>I’ve been working on the ISVLicenseGenerator open source tool for some years now, almost five. It all started when I ordered a code signing certificate to sign ISV solutions, and I got a USB token instead of a software certificate. ISVLicenseGenerator is not needed anymore and won’t be maintained. As of version 10.0.43, and since</description><pubDate>Wed, 18 Sep 2024 04:47:58 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been working on the ISVLicenseGenerator open source tool for some years now, almost five. &lt;a href=&quot;https://ariste.info/2019/12/create-an-isv-license-from-a-cryptographic-usb-token/&quot;&gt;It all started&lt;/a&gt; when I ordered a code-signing certificate to sign ISV solutions, and I got a USB token instead of a software certificate.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ISVLicenseGenerator is not needed anymore and won’t be maintained.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As of version 10.0.43, and since 10.0.38, it’s possible to generate a license file using AXUtil using a HSM-backed certificate: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&quot;&gt;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/09/isvlicensegenerator-v0-9-farewell/#whats-new-in-isvlicensegenerator-v09&quot;&gt;What’s new in ISVLicenseGenerator v0.9?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/09/isvlicensegenerator-v0-9-farewell/#why-this-could-be-the-last-isvlicensegenerator-version&quot;&gt;Why this could be the last ISVLicenseGenerator version?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/09/isvlicensegenerator-v0-9-farewell/#if-its-the-end-so-be-it&quot;&gt;If it’s the end, so be it!&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;p&gt;After many years of Microsoft not offering support for these devices to sign the license files, it looks like with the changes introduced to AXUtil in the version 10.0.37, ISVLicenseGenerator will not be needed anymore…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/09/RIP-ISVLicenseGenerator.png&quot; alt=&quot;RIP ISVLicenseGenerator&quot; width=&quot;2048&quot; height=&quot;1294&quot; /&gt;&lt;figcaption&gt;RIP ISVLicenseGenerator&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you want to learn more about ISVLicenseGenerator you can read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing&quot;&gt;Microsoft learn: Independent software vendor (ISV) licensing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2019/12/create-an-isv-license-from-a-cryptographic-usb-token/&quot;&gt;Create an ISV license from a cryptographic USB token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2020/08/isv-license-generator-sha-2-support/&quot;&gt;ISV License Generator v0.3 with SHA-2 support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2023/03/isv-license-generator-v0-6-released/&quot;&gt;ISV License Generator v0.6 released&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/02/isvlicensegenerator-azure-key-vault/&quot;&gt;Azure Key Vault support in ISVLicenseGenerator v0.7&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What’s new in ISVLicenseGenerator v0.9?&lt;/h2&gt;
&lt;p&gt;In version 10.0.38 a new parameter to the command-line version of AxUtil was added: AllowCrossDomainInstallation. From the docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This parameter provides ISVs (Independent Software Vendors) with the ability to generate licenses that can be used across different environments for the same tenant (customer). The default value is set to &lt;strong&gt;false&lt;/strong&gt;, which means the tenant can’t use the same ISV license across different environments or reuse the same ISV license within the same environment when the admin domain name changes. When the value is set to &lt;strong&gt;true&lt;/strong&gt;, the customer can install the same ISV license across different environments associated with the same tenant or when the customer changes the admin domain name of the environment. This parameter is available in Dynamics 365 Finance version 10.0.38 and higher.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is what I’ve added, here you can see the option in the UI:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/09/image-15.png&quot; alt=&quot;AllowCrossDomainInstallation parameter in ISVLicenseGenerator&quot; width=&quot;579&quot; height=&quot;645&quot; /&gt;&lt;figcaption&gt;AllowCrossDomainInstallation parameter&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;When this is selected, the string &lt;em&gt;allowcrossdomaininstallation=”true”&lt;/em&gt; is added to the text of the license file, and it’s also added to the string that’s used to generate the signature. That’s all.&lt;/p&gt;
&lt;p&gt;Then when the license is loaded using a deployable package, the code in the standard checks if that field is set to true and sets it in the environment. &lt;strong&gt;You can see this in the &lt;em&gt;SysLicenseCodeReadFile&lt;/em&gt; class, method &lt;em&gt;codeStatus&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You can &lt;strong&gt;download ISVLicenseGenerator v0.9&lt;/strong&gt; from the releases’ page on GitHub: &lt;a href=&quot;https://github.com/aariste/ISVLicenseGenerator/releases/tag/0.9&quot;&gt;https://github.com/aariste/ISVLicenseGenerator/releases/tag/0.9&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why this could be the last ISVLicenseGenerator version?&lt;/h2&gt;
&lt;p&gt;If you’ve taken a look at the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing&quot;&gt;Microsoft learn documentation on ISV licensing&lt;/a&gt; lately, you maybe have noticed that there are two more optional parameters that can be used on the command line: &lt;em&gt;subjectname&lt;/em&gt; and &lt;em&gt;thumbprint&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;What are they used for? First, let’s see how we use the command-line tool without them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\AOSService\PackagesLocalDirectory\Bin\axutil genlicense /file:c:\templicense.txt /certificatepath:c:\tempisvcert.pfx /licensecode:ISVLicenseCode /customer:TAEOfficial.ccsctp.net /serialnumber:4dbfcf74-c5a6-4727-b638-d56e51d1f381 /password:yourPassword&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we’re using the &lt;em&gt;certificatepath&lt;/em&gt; and &lt;em&gt;password&lt;/em&gt; parameters to access the PFX file that contains the private key that’s used to generate the license file.&lt;/p&gt;
&lt;p&gt;If we want to use the &lt;em&gt;subjectname&lt;/em&gt; and &lt;em&gt;thumbprint&lt;/em&gt; parameters, we must skip using &lt;em&gt;certificatepath&lt;/em&gt; and &lt;em&gt;password&lt;/em&gt;. The command would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\AOSService\PackagesLocalDirectory\Bin\axutil genlicense /file:c:\templicense.txt /licensecode:ISVLicenseCode /customer:TAEOfficial.ccsctp.net /serialnumber:4dbfcf74-c5a6-4727-b638-d56e51d1f381 /subjectName:&quot;ISVCert&quot; /thumbprint:certificateThumbprint&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We do it like that because AxUtil will use the &lt;em&gt;subjectname&lt;/em&gt; and &lt;em&gt;thumbprint&lt;/em&gt; parameters to get our certificate from the certificate store in our computer. And as far as I know, &lt;strong&gt;because I don’t have a USB cryptographic token anymore&lt;/strong&gt;, it should also work with it.&lt;/p&gt;
&lt;p&gt;When you receive the USB token, you can install the public key certificate in your PC.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;It’s not possible to export the private key from a USB token, because their purpose is to protect it.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Once it’s installed, go to “Manage user certificates”, and select the node under &lt;em&gt;Personal&lt;/em&gt;, and &lt;em&gt;Certificates&lt;/em&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/09/image-16.png&quot; alt=&quot;Windows certificate manager&quot; width=&quot;269&quot; height=&quot;633&quot; /&gt;&lt;figcaption&gt;Windows certificate manager&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then on the right pane look for your certificate and double click it, a dialog will open, go to the details tab and scroll down:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/09/image-17.png&quot; alt=&quot;Subject and Thumbprint on certificate details&quot; width=&quot;405&quot; height=&quot;515&quot; /&gt;&lt;figcaption&gt;Subject and Thumbprint on certificate details&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Those are the values that will be used in the command line, and if your certificate’s in a crypto USB token, the built-in functionality on Windows should open the certificate selector and then your provider software should ask for the password to unlock the private key… I guess 🤣 because I couldn’t try. But that’s how ISVLicenseGenerator works, it lets you select the certificate from the list, then loads it, and the certificate management software from the certificate provider asks you for the password, unlocks the private key, and uses it to generate the license file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In theory&lt;/strong&gt;, IN THEORY, theoretically, you can also use the &lt;em&gt;subjectname&lt;/em&gt; and &lt;em&gt;thumbprint&lt;/em&gt; parameters with an HSM-backed certificate. Using the full address where your certificate is. But I also haven’t tested this 😝&lt;/p&gt;
&lt;h2&gt;If it’s the end, so be it!&lt;/h2&gt;
&lt;p&gt;I’m releasing this last version as some kind of farewell to it. I created it out of a mistake, order a USB certificate instead of software. I’ve enjoyed creating and maintaining it, because that’s what I like the most about this job, being able to investigate (decompile 😂) things, and understand how they work. There have been a lot of “aha moments” while I understood the code in the standard AXUtil library and the X++ classes that enable the license files.&lt;/p&gt;
&lt;p&gt;And when I got it working the first time, I was really happy (and also relieved) to see I could keep the USB and didn’t have to order another certificate. But then, thanks to Microsoft ignoring their customer’s requests it’s been a useful tool for many other people, and I’m also happy about that.&lt;/p&gt;
&lt;p&gt;So if I had to make a toast I’d say: to problems (that can be solved), and its solutions that make me happy!&lt;/p&gt;</content:encoded><category>Development</category></item><item><title>APIOps: ALM for Azure API Management</title><link>https://ariste.info/2024/08/apiops-alm-for-azure-api-management/</link><guid isPermaLink="true">https://ariste.info/2024/08/apiops-alm-for-azure-api-management/</guid><description>In this post, I’m going to talk about APIOps, the DevOps techniques applied to Azure API Management, that allow us to automate the promotion of changes between API Management instances. It would be possible to attain a certain degree of automation using Bicep, but it wouldn’t apply to many elements of the APIM, and that’s</description><pubDate>Tue, 27 Aug 2024 06:44:09 GMT</pubDate><content:encoded>&lt;p&gt;In this post, I’m going to talk about &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/architecture/example-scenario/devops/automated-api-deployments-apiops&quot;&gt;APIOps&lt;/a&gt;, the DevOps techniques applied to Azure API Management, that allow us to automate the promotion of changes between API Management instances.&lt;/p&gt;
&lt;p&gt;It would be possible to attain a certain degree of automation using &lt;a href=&quot;https://ariste.info/2022/09/bicep-azure-api-management-architecture/&quot;&gt;Bicep&lt;/a&gt;, but it wouldn’t apply to many elements of the APIM, and that’s why we need and like APIOps!&lt;/p&gt;
&lt;nav&gt;&lt;p&gt;Table of contents&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#why-use-apiops&quot;&gt;Why use APIOps?&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#how-does-apiops-work&quot;&gt;How does APIOps work?&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#two-supported-scenarios&quot;&gt;Two supported scenarios&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#configuring-apiops&quot;&gt;Configuring APIOps&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#starting-scenario&quot;&gt;Starting scenario&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#upload-base-files&quot;&gt;Upload base files&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-a-service-connection&quot;&gt;Create a service connection&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-variable-group&quot;&gt;Create variable group&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-environment&quot;&gt;Create environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-config-file&quot;&gt;Create config file&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-the-extractor-pipeline&quot;&gt;Create the extractor pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#create-the-publisher-pipeline&quot;&gt;Create the publisher pipeline&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/#merge-the-pull-request&quot;&gt;Merge the Pull Request&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/08/IMG_2543-1024x1024.png&quot; alt=&quot;APIOps is like riding the master of automation&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;APIOps is like riding the master of automation&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you want to learn more about Azure API Management, you can read my previous posts and video:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2022/02/azure-api-management-dynamics-365-fo/&quot;&gt;Azure API Management for Dynamics 365 F&amp;amp;O&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2022/09/azure-api-management-architecture/&quot;&gt;Azure API Management for your integrations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2022/09/bicep-azure-api-management-architecture/&quot;&gt;IaC with Bicep: deploy Azure API Management Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7F0zloYriIE&quot;&gt;Empower Your F&amp;amp;O Integrations with Azure API Management: Unlocking Superpowers!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why use APIOps?&lt;/h2&gt;
&lt;p&gt;The biggest benefit from using APIOps is &lt;strong&gt;automation&lt;/strong&gt;. With &lt;a href=&quot;https://ariste.info/dynamics365almguide/&quot;&gt;DevOps&lt;/a&gt; we can automate the deployments of updated code to sandbox environments thanks to CD pipelines, and we can have CI pipelines to make sure our code builds correctly.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ariste.info/dynamics365almguide/&quot;&gt;You can read my complete ALM guide on Microsoft Dynamics 365 for Finance &amp;amp; Operations and Azure DevOps&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Also, automation brings us many other benefits. By applying the principles of Application Lifecycle Management (ALM) to APIs, APIOps makes the entire APIM lifecycle much easier on us. This automation reduces the risk of errors when moving changes between development, UAT or production APIMs. It also accelerates the deployment process thanks to the publishing pipeline that moves the changes between environments/instances.&lt;/p&gt;
&lt;h2&gt;How does APIOps work?&lt;/h2&gt;
&lt;p&gt;Later we’ll learn how it works in more detail, but now let’s see a high-level solution of how APIOps works. The current solution, which is provided by Microsoft, is based on two pipelines and some extra tools hosted on their &lt;a href=&quot;https://github.com/Azure/apiops&quot;&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is an &lt;strong&gt;extractor pipeline&lt;/strong&gt; that will “read” your source APIM instance and generate several files used to define the features and characteristics of your APIM instance.&lt;/p&gt;
&lt;p&gt;The extractor pipeline &lt;strong&gt;creates a Pull Request&lt;/strong&gt; in your Azure DevOps repo. When we merge this PR into the main branch, the &lt;strong&gt;publisher pipeline&lt;/strong&gt; runs.&lt;/p&gt;
&lt;p&gt;The publisher pipeline takes the setup of your source APIM instance from the files created by the extractor pipeline.&lt;/p&gt;
&lt;p&gt;First, the publisher pipeline will publish the changes to the same source APIM instance so you can do some testing.&lt;/p&gt;
&lt;p&gt;Then the changes are deployed to the UAT pipeline (if you have one), but &lt;strong&gt;only after manual approval&lt;/strong&gt;. And finally, and also after manual approval, they move to the production APIM instance.&lt;/p&gt;
&lt;h3&gt;Two supported scenarios&lt;/h3&gt;
&lt;p&gt;APIOps supports two ways of working: users that build their APIs from the Azure portal and users that build their APIs from an IDE (like VS). In this post, I’ll be showing the first way, from the Azure portal.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;first scenario&lt;/strong&gt; (from Azure portal), the workflow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make all changes to the APIM in the Azure portal.&lt;/li&gt;
&lt;li&gt;Manually run the extractor pipeline, which will automatically create a PR with all the changes.&lt;/li&gt;
&lt;li&gt;Approve and merge the PR into the main branch.&lt;/li&gt;
&lt;li&gt;The publisher pipeline will be triggered and publish the changes to the current “dev” environment.&lt;/li&gt;
&lt;li&gt;Once the changes are reviewed and validated, there are manual approvals to promote the changes to higher environments like UAT or production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the second scenario (from an IDE), the workflow is slightly different:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do the changes in the IDE and commit them to a dev branch.&lt;/li&gt;
&lt;li&gt;Manually create a PR to the main branch.&lt;/li&gt;
&lt;li&gt;Approve the PR and merge it to the main branch&lt;/li&gt;
&lt;li&gt;The publisher pipeline will be triggered and publish the changes to the current “dev” environment.&lt;/li&gt;
&lt;li&gt;Once the changes are reviewed and validated, there are manual approvals to promote the changes to higher environments like UAT or production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can see the difference between both scenarios is in the first steps, and also that when using an IDE the extractor pipeline isn’t used because we’re doing the changes in code that’s checked-in to the dev branch.&lt;/p&gt;
&lt;h2&gt;Configuring APIOps&lt;/h2&gt;
&lt;p&gt;Now that we know how APIOps works, it’s time to configure everything. But first, here are some links that contain all the information and we’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Azure/apiops&quot;&gt;APIOps GitHub project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://azure.github.io/apiops/&quot;&gt;APIOps documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The scenario I’ll describe only has a development and production APIM instance, but I’ll explain how to also add a UAT instance between dev and prod.&lt;/p&gt;
&lt;h3&gt;Starting scenario&lt;/h3&gt;
&lt;p&gt;For this example, we’ll have a “source” APIM instance with a single API called customers that has an update method:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-2.png&quot; alt=&quot;APIM instance with the customer API&quot; width=&quot;1439&quot; height=&quot;804&quot; /&gt;&lt;figcaption&gt;APIM instance with the customer API&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The APIM is using a policy to authenticate to Dynamics 365 F&amp;amp;O. We want all of these to move into the production APIM!&lt;/p&gt;
&lt;h3&gt;Upload base files&lt;/h3&gt;
&lt;p&gt;The first step will be preparing the Git repo in Azure DevOps, so head to the &lt;a href=&quot;https://github.com/Azure/apiops/releases&quot;&gt;releases page&lt;/a&gt; of APIOps’ GitHub project and look for the latest version. Beware of not using one of the many alpha versions! For this post, I’m using v5.1.4, which is the latest non-alpha version.&lt;/p&gt;
&lt;p&gt;Download the Azure_DevOps.zip file from there, unzip it and upload the contents to &lt;strong&gt;your Git repo in Azure DevOps&lt;/strong&gt;. Once uploaded, your repo should look like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-6.png&quot; alt=&quot;APIOps pipelines&quot; width=&quot;838&quot; height=&quot;323&quot; /&gt;&lt;figcaption&gt;APIOps pipelines&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This ZIP file contains the YAML files for the extractor and publisher pipelines.&lt;/p&gt;
&lt;h3&gt;Create a service connection&lt;/h3&gt;
&lt;p&gt;We need to create a service connection in Azure DevOps, pointing to the Azure that contains the APIM resources. Go to your project settings and select “Service connections” in the left menu under pipelines.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/08/image.png&quot; alt=&quot;Create the service connection&quot; width=&quot;1524&quot; height=&quot;863&quot; /&gt;&lt;figcaption&gt;Create the service connection&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Create a new one, select “Azure Resource Manager”, then “Workload Identity federation (automatic)”, and in the next step you must choose your subscription, resource group where the resources are and give the connection a name. You can use the subscription name plus resource group to identify it easily.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;Check the “Grant access permission to all pipelines” checkbox. Otherwise you’ll have to authorize it when running the extractor and publisher pipelines later.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Create variable group&lt;/h3&gt;
&lt;p&gt;Go to the Pipelines menu, then Library and create a new variable group called &lt;strong&gt;apim-automation&lt;/strong&gt; and the following variables: &lt;strong&gt;SERVICE_CONNECTION_NAME&lt;/strong&gt;, &lt;strong&gt;APIM_NAME&lt;/strong&gt;, &lt;strong&gt;RESOURCE_GROUP_NAME&lt;/strong&gt;, &lt;strong&gt;RESOURCE_GROUP_NAME_Prod&lt;/strong&gt; and &lt;strong&gt;apiops_release_version&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/08/image-4-1024x741.png&quot; alt=&quot;Variable group&quot; width=&quot;1024&quot; height=&quot;741&quot; /&gt;&lt;figcaption&gt;Variable group&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now for the values use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SERVICE_CONNECTION_NAME&lt;/strong&gt;: the name of the connection you created in the previous step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;APIM_NAME&lt;/strong&gt;: the name of your &lt;strong&gt;dev APIM&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RESOURCE_GROUP_NAME&lt;/strong&gt;: the azure resource group name where your dev APIM is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RESOURCE_GROUP_NAME_Prod&lt;/strong&gt;: the azure resource group name where your production APIM is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;apiops_release_version&lt;/strong&gt;: the version of the release from the APIOps you’re using. In my case, it is v6.0.1.1. You need to specify it with the points, so it would be “v6.0.1.1”.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Create environment&lt;/h3&gt;
&lt;p&gt;Now, in the left menu, still in the Pipelines section, go to “Environments”, and create a new one called “Prod”. We will use this in the approval steps of the pipelines. Change to the “Approvals and checks” tab on top, and add a new check of the type “Approvals”:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-12.png&quot; alt=&quot;Environment approval&quot; width=&quot;1382&quot; height=&quot;654&quot; /&gt;&lt;figcaption&gt;Environment approval&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Add someone as the approver, I’ll add myself for the example.&lt;/p&gt;
&lt;h3&gt;Create config file&lt;/h3&gt;
&lt;p&gt;Now go back to your repo and create a new file in the root. Call it configuration.prod.yaml with this contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apimServiceName: apim-aristeinfo
namedValues:
loggers:
diagnostics:
backends:
apis: customers&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the apimServiceName element add your production’s APIM name. And to the apis element your API name.&lt;/p&gt;
&lt;p&gt;The config file is used to replace the values of the dev APIM for the ones that the prod instance will have. like named values that use Key Vaults, loggers etc. You can learn all about the config file &lt;a href=&quot;https://github.com/Azure/apiops/wiki/APIM-resources&quot;&gt;in the APIOps wiki&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Now, also in the root of the repo, create a folder called artifacts. This folder will be used to store the extracted configuration of your dev APIM instance.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Note&lt;/p&gt;
&lt;p&gt;If you give this folder a different name other than artifacts, you must update the run-publisher.yaml file, in the parameters section, because it specifically looks for the “artifacts” folder.&lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;Create the extractor pipeline&lt;/h3&gt;
&lt;p&gt;Now it’s time to create two pipelines from the YAML files we uploaded in the beginning, an &lt;strong&gt;extractor&lt;/strong&gt; and a &lt;strong&gt;publisher&lt;/strong&gt; pipeline. Go to the pipelines menu and create a new one.&lt;/p&gt;
&lt;p&gt;We start with the extractor pipeline. Select “Azure Repos Git”:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-14.png&quot; alt=&quot;Azure Repos Git&quot; width=&quot;885&quot; height=&quot;645&quot; /&gt;&lt;figcaption&gt;Azure Repos Git&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then select your repo, and in the next step “Existing Azure Pipelines YAML file”:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-16.png&quot; alt=&quot;Existing YAML pipeline&quot; width=&quot;920&quot; height=&quot;665&quot; /&gt;&lt;figcaption&gt;Existing YAML pipeline&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And select the run-extractor.yml file:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-17.png&quot; alt=&quot;Extractor pipeline&quot; width=&quot;496&quot; height=&quot;264&quot; /&gt;&lt;figcaption&gt;Extractor pipeline&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Save it, don’t run it yet, and then go to pipelines, select it and click “Run pipeline”. We will get some fields to fill in:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-19-503x1024.png&quot; alt=&quot;Run pipeline panel of the extractor with required fields for the APIM instance name, resource group, repository and folder, and the target branch set to main&quot; width=&quot;503&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Extractor pipeline run&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We must fill in these fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;APIM instance name&lt;/strong&gt;: the name of your dev APIM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;APIM instance resource group name&lt;/strong&gt;: the resource group name where your dev APIM is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;APIM repository for pull request&lt;/strong&gt;: the name of your Azure DevOps repo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folder where you want to extract the artifacts&lt;/strong&gt;: the folder we created in the root of our repo, which we named &lt;strong&gt;artifacts&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now just click run… and there’s a chance your pipeline won’t start running! Click the first stage in the API and check whether you’re getting this message: “This pipeline needs permission to access 2 resources before this run can continue to Create artifacts from portal”.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-21.png&quot; alt=&quot;This pipeline needs permission to access 2 resources before this run can continue to Create artifacts from portal&quot; width=&quot;1275&quot; height=&quot;694&quot; /&gt;&lt;figcaption&gt;Pipeline needs permission&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click view and “Permit” if you have anything to allow. If you didn’t select the “Grant access permission to all pipelines” checkbox when you created the service connection you will get these two items to permit:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/Val.png&quot; alt=&quot;Permit&quot; width=&quot;523&quot; height=&quot;271&quot; /&gt;&lt;figcaption&gt;Permit&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you did select the checkbox, you will only get the variable group permit. Now let’s see if the pipeline run works…&lt;/p&gt;
&lt;p&gt;Most likely you’ll see a successful (with a warning) first stage, and a failed second stage, the one called “Create artifacts pull request”. This is due to missing permissions. Click the second stage, and go to the step with the error. You will see a line that says: “You need the Git ‘GenericContribute’ permission to perform this action.”, followd bu an identity like Build\3d382728- and more numbers 🙂&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-23.png&quot; alt=&quot;You need the Git &apos;GenericContribute&apos; permission to perform this action&quot; width=&quot;1374&quot; height=&quot;694&quot; /&gt;&lt;figcaption&gt;You need the Git ‘GenericContribute’ permission to perform this action&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Copy that GUID, go to your project settings, then repositories, the security tab on top, and paste it in the search box:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-25.png&quot; alt=&quot;Give permissions to the build service &quot; width=&quot;774&quot; height=&quot;440&quot; /&gt;&lt;figcaption&gt;Give permissions to the build service&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And set to “Allow” the following permissions: Contribute, Contribute to pull requests, Create branch and Create tag.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-26.png&quot; alt=&quot;Permissions&quot; width=&quot;1463&quot; height=&quot;810&quot; /&gt;&lt;figcaption&gt;Permissions&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now go back to the pipelines, enter the failed run, and rerun the failed job in the second step:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-28.png&quot; alt=&quot;Pipeline run where the Create template branch stage failed, with the Rerun failed jobs button highlighted&quot; width=&quot;602&quot; height=&quot;397&quot; /&gt;&lt;figcaption&gt;Rerun failed jobs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now hopefully the pipeline will create a pull request. So go to Repos, Pull Requests, switch to the active tab and yours should be there. But leave it there for now.&lt;/p&gt;
&lt;h3&gt;Create the publisher pipeline&lt;/h3&gt;
&lt;p&gt;First, install the “&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens&quot;&gt;Replace Tokens&lt;/a&gt;” extension for Azure DevOps to your organization. It’s required and the publisher pipeline will fail otherwise.&lt;/p&gt;
&lt;p&gt;Now we have to create the publisher pipeline. We’ll do the same as with the extractor one, create a new one, from an existing YAML file, but this time select the “/tools/pipelines/run-publisher.yaml” file.&lt;/p&gt;
&lt;p&gt;This one is a CI pipeline, that will run after every commit or merge to the main branch.&lt;/p&gt;
&lt;p&gt;We’re done here. Yes, that was all. Now back to the Pull Request.&lt;/p&gt;
&lt;h3&gt;Merge the Pull Request&lt;/h3&gt;
&lt;p&gt;Open the pull request and go to the “Files” tab. You will see that it has created a structure of folders and files with the definition of your dev APIM.&lt;/p&gt;
&lt;p&gt;Also, if you go to the files in your repo, and click the branches, you’ll see that the pull request has been created in a different branch:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-30-1024x400.png&quot; alt=&quot;New branch&quot; width=&quot;1024&quot; height=&quot;400&quot; /&gt;&lt;figcaption&gt;New branch&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And that’s why we must merge it to main. Go to the PR again and approve it, or just complete it, it’s your choice 🤪&lt;/p&gt;
&lt;p&gt;Once the PR is completed, the publisher pipeline will start… to ask for more permissions! Like the extractor one, we have to permit changes to the variable group and service connection:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/06/image-32.png&quot; alt=&quot;More permissions needed&quot; width=&quot;1188&quot; height=&quot;601&quot; /&gt;&lt;figcaption&gt;More permissions needed&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now the first stage will publish the changes to the dev APIM instance. At this point, you can go to your dev APIM to validate and test the changes.&lt;/p&gt;
&lt;p&gt;Remember we created an environment with an approval before? It will be used now before publishing the changes to the prod APIM instance:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/08/image-5.png&quot; alt=&quot;Approval in the pipeline&quot; width=&quot;1140&quot; height=&quot;952&quot; /&gt;&lt;figcaption&gt;Approval in the pipeline&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click Review, and approve it, and the publisher pipeline will apply the changes to the production pipeline.&lt;/p&gt;
&lt;p&gt;And that’s all, I hope this post has been useful to start “playing” with the APIOps concept and automate you APIs lifecycle even more! This is quite “basic” and I’d like to write some more content about the config file and how to replace some values in policies using named values for example. I’ll leave that for another post!&lt;/p&gt;</content:encoded><category>ALM &amp; DevOps</category></item><item><title>Error querying Synapse Link</title><link>https://ariste.info/2024/03/error-querying-synapse-link/</link><guid isPermaLink="true">https://ariste.info/2024/03/error-querying-synapse-link/</guid><description>A short one for today! I stumbled upon this while configuring the new Azure Synapse Link for Dataverse feature for an environment. Most of the time, when I’m configuring resources on Azure, I do it as a subscription or resource group owner. This means I’m working in god mode all the time, and I won’t</description><pubDate>Tue, 19 Mar 2024 09:49:54 GMT</pubDate><content:encoded>&lt;p&gt;A short one for today! I stumbled upon this while configuring the new Azure Synapse Link for Dataverse feature for an environment.&lt;/p&gt;
&lt;p&gt;Most of the time, when I’m configuring resources on Azure, I do it as a subscription or resource group owner. This means I’m working in god-mode all the time, and I won’t be finding out some issues that users with less access rights do.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/03/IMG_2528-1024x1024.png&quot; alt=&quot;Fixing Synapse Link&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Fixing Synapse Link&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;What’s Azure Synapse Link for Dataverse?&lt;/h2&gt;
&lt;p&gt;If you’re currently using the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/finance-data-azure-data-lake&quot;&gt;Export to Data Lake&lt;/a&gt; feature using the add-in in LCS, you must know that it’s been deprecated since October 15, 2023, and that after November 1, 2024, it will stop working. Azure Synapse Link for Dataverse is replacing it, and you can learn how to migrate in the &lt;a href=&quot;https://aka.ms/TransitionToSynapseLink&quot;&gt;Synapse Link transition guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The new Synapse Link feature is configured from Power Platform’s maker portal, and it still supports standard and custom data entities and tables. But if you can learn more about it, and how to configure it, you can read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aurélien Clere’s blog post &lt;a href=&quot;https://www.powerazure365.com/blog-1/one-dynamics-one-platform-analytics-delta-lake-synapse-link&quot;&gt;One Dynamics One Platform : Analytics – Delta Lake Synapse Link&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Chapter 8 of my book “&lt;a href=&quot;https://ariste.info/get-my-book/&quot;&gt;Extending Dynamics 365 Finance and Operations Apps with Power Platform&lt;/a&gt;“.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The issue: blob access rights&lt;/h2&gt;
&lt;p&gt;When we configure Synapse Link with a Synapse workspace, we have the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage account&lt;/strong&gt;: here’s where data from the ERP is stored in data/delta lake format, depending on how you configure Synapse Link.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synapse Analytics workspace&lt;/strong&gt;: here we can explore data thanks to the built-in serverless SQL instance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spark pool&lt;/strong&gt;: used for data processing and transformation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the requisites to configure all of this is that we need to add the Synapse Analytics workspace as a “Storage Blob Data Contributor” on the storage account so it’s able to read data.&lt;/p&gt;
&lt;p&gt;When you do all of this and access the serverless SQL pool in the Synapse Analytics workspace, you can query data without issues. And the same if you try accessing it from SQL Server Management Studio (SSMS) with your user that’s an owner on Azure.&lt;/p&gt;
&lt;p&gt;But what if you create a Microsoft Entra ID app registration that will be used as a service principal to access the serverless SQL instance? We create the app registration, add it as a service principal with the role “Synapse SQL Administrator” inside the workspace, then access the SQL instance with SSMS, try to query the CustTable and…&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content of directory on path &apos;https://your_storage_account_name.dfs.core.windows.net/dataverse-instance-name/deltalake/custtable_partitioned/_delta_log/*.*&apos; cannot be listed.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why is that!? Remember you added the Synapse Analytics workspace as a “Storage Blob Data Contributor” to the storage account? Well, we need to do the same with the service principal we’ve created and that will be using to read data.&lt;/p&gt;
&lt;p&gt;So go to your storage account, and then add the role assignment:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/03/image-1024x742.png&quot; alt=&quot;Add role assignments to storage account&quot; width=&quot;1024&quot; height=&quot;742&quot; /&gt;&lt;figcaption&gt;Add role assignments to storage account&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We can use “Storage Blob Data Contributor” or “Storage Blob Data Reader” which has less access rights but will be enough to query data.&lt;/p&gt;
&lt;p&gt;Now just wait for a few minutes and try running the query again. And problem solved!&lt;/p&gt;</content:encoded><category>Power Platform &amp; Dataverse</category></item><item><title>Azure Key Vault support in ISVLicenseGenerator v0.7</title><link>https://ariste.info/2024/02/isvlicensegenerator-azure-key-vault/</link><guid isPermaLink="true">https://ariste.info/2024/02/isvlicensegenerator-azure-key-vault/</guid><description>I’m once again back with a new version of the ISVLicenseGenerator tool, the piece of software I created with a modified version of the standard AXUtil.dll library that lets us sign license files using a cryptographic USB token. ISVLicenseGenerator is not needed anymore and won’t be maintained. As of version 10.0.43, and since 10.0.38, it’s</description><pubDate>Thu, 22 Feb 2024 15:49:09 GMT</pubDate><content:encoded>&lt;p&gt;I’m once again back with a new version of the ISVLicenseGenerator tool, the piece of software I created with a modified version of the standard AXUtil.dll library that lets us sign license files using a cryptographic USB token.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ISVLicenseGenerator is not needed anymore and won’t be maintained.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As of version 10.0.43, and since 10.0.38, it’s possible to generate a license file using AXUtil using a HSM-backed certificate: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&quot;&gt;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In the &lt;a href=&quot;https://github.com/aariste/ISVLicenseGenerator/releases/tag/0.7&quot;&gt;new version (v0.7)&lt;/a&gt; I’ve added the possibility of signing the license file using a certificate stored on an Azure Key Vault.&lt;/p&gt;
&lt;p&gt;And this time I come with a request from anyone reading this post. &lt;strong&gt;If you have an HSM-protected key for your code-signing certificate, and are storing it in a Key Vault, I’d love you to try this new version out!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why am I asking this? Well, I swear there are no hidden intentions here! The main reason is that I don’t have access to HSM keys, and I can’t test the new functionality with one of those. So if your certificate provider has a cloud HSM, and provides some sort of Azure Key Vault integration to store the keys in it, and you want to try it… I’ll be very happy to try to fix things if they don’t work!&lt;/p&gt;
&lt;h2&gt;What’s going on with ISV licensing?&lt;/h2&gt;
&lt;p&gt;In February 2024 AXUtil will stop allowing to use SHA1 as the signing algorithm. Something I did on &lt;a href=&quot;https://ariste.info/2023/03/isv-license-generator-v0-6-released/&quot;&gt;ISVLicenseGenerator v0.6&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There’s something that, as far as I know, Microsoft still hasn’t solved, which is offering a solution for certificates that are delivered in a cryptographic USB token. Yes, my tool covers that, but I’d totally understand if some companies wouldn’t trust a community-made tool instead of an official Microsoft tool 🤷‍♂️&lt;/p&gt;
&lt;h2&gt;What’s new in version 0.7?&lt;/h2&gt;
&lt;p&gt;If you open the tool and see the UI, you’ll see it keeps the &lt;em&gt;beautiful&lt;/em&gt; appearance, but with a few new fields on top:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image.png&quot; alt=&quot;ISVLicenseGenerator v0.7&quot; width=&quot;579&quot; height=&quot;608&quot; /&gt;&lt;figcaption&gt;ISVLicenseGenerator v0.7&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now you can select between signing the license file using the USB token or local certificate (that’s the existing functionality), or use a certificate stored on Azure Key Vault!&lt;/p&gt;
&lt;p&gt;If you choose it as the certificate location, you’ll have to provide details in five more fields:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-1.png&quot; alt=&quot;Azure Key Vault certificate additional fields&quot; width=&quot;579&quot; height=&quot;608&quot; /&gt;&lt;figcaption&gt;Azure Key Vault certificate additional fields&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key Vault DNS&lt;/strong&gt;: this can be found in the overview tab of your Key Vault instance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key name&lt;/strong&gt;: is the name of the &lt;strong&gt;certificate&lt;/strong&gt; in Azure Key Vault where you’ve stored the code-signing certificate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entra ID Tenant Id&lt;/strong&gt;: can be found on the main page of Entra ID in the Azure portal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AppId&lt;/strong&gt;: we’ll need an app registration to access the Key Vault from the application as a service principal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secret&lt;/strong&gt;: the secret in the app registration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;Configuring Azure Key Vault&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Let’s quickly see how to configure the Key Vault, store the certificate and add the app registration as a service principal.&lt;/p&gt;
&lt;p&gt;First deploy an Azure Key Vault, it can be from the standard tier.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-2.png&quot; alt=&quot;Azure Key Vault&quot; width=&quot;987&quot; height=&quot;481&quot; /&gt;&lt;figcaption&gt;Azure Key Vault&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the key vault you’ll see the DNS, which is the Vault URI field (and which I’ll have deleted once I’m done writing this post), and you also have the tenant Id in the Directory ID field below the DNS.&lt;/p&gt;
&lt;p&gt;Now upload the certificate under the “Certificates” menu on the sidebar:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-9.png&quot; alt=&quot;Import private key&quot; width=&quot;753&quot; height=&quot;461&quot; /&gt;&lt;figcaption&gt;Import private key&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Remember here you need to upload your &lt;strong&gt;private key&lt;/strong&gt;! The public key is used as a resource in Visual Studio when you create the configuration key used to lock your ISV.&lt;/p&gt;
&lt;p&gt;The next step will be creating an App registration on Entra ID:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-3.png&quot; alt=&quot;App registration&quot; width=&quot;764&quot; height=&quot;464&quot; /&gt;&lt;figcaption&gt;App registration&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Here you will get the App Id in the Application (client) ID, and can get the tenant ID again.&lt;/p&gt;
&lt;p&gt;Create a secret in the app registration and keep it somewhere safe.&lt;/p&gt;
&lt;p&gt;Now we need to give this app registration rights on the Key Vault to access the certificate we’ll upload, and to sign the license. Go to the Key Vault and under Access management (IAM) on the sidebar we’ll add two roles.&lt;/p&gt;
&lt;p&gt;Why two? We could accomplish this with a single role, but I want to reduce the amount of access rights for this app registration! First click the Add button on top and select “Add role assignment”. The first role we’ll add is the “Key Vault Certificate User” role:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-4.png&quot; alt=&quot;Azure Add role assignment page with the Key Vault Certificate User role highlighted in the list of built-in roles&quot; width=&quot;988&quot; height=&quot;676&quot; /&gt;&lt;figcaption&gt;Key Vault Certificate User&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click the next button, and click the “+ Select members” link, and look for the name of your app registration:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-5.png&quot; alt=&quot;Add app registration&quot; width=&quot;988&quot; height=&quot;677&quot; /&gt;&lt;figcaption&gt;Add app registration&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click, select, then “Review + assign” twice.&lt;/p&gt;
&lt;p&gt;Now repeat the steps for the “Key Vault Crypto User” role:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-6.png&quot; alt=&quot;Key Vault Crypto User&quot; width=&quot;988&quot; height=&quot;642&quot; /&gt;&lt;figcaption&gt;Key Vault Crypto User&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Once done we can go to ISVLicenseGenerator, fill in all the fields and click generate:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-7.png&quot; alt=&quot;ISVLicenseGenerator v0.7&quot; width=&quot;579&quot; height=&quot;608&quot; /&gt;&lt;figcaption&gt;ISVLicenseGenerator v0.7&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If all the information is correct you’ll get this nice success screen:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-8.png&quot; alt=&quot;Success&quot; width=&quot;301&quot; height=&quot;146&quot; /&gt;&lt;figcaption&gt;Success&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And you’ll have a working license to unlock your ISV!&lt;/p&gt;
&lt;h2&gt;What happens with the HSM-protected keys?&lt;/h2&gt;
&lt;p&gt;Well, that’s the question I’d also like to know the answer to! Unfortunately, I don’t have access to any certificate provider, and can’t check their HSM capabilities or if they have an integration for code-signing certificates to be stored in Azure Key Vault.&lt;/p&gt;
&lt;p&gt;If this were possible, we could use the HSM capabilities of the Premium tier of Azure Key Vault, where we can create a Key Encryption Key (KEK) that’s later used to request a certificate from a certificate provider:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/02/image-10.png&quot; alt=&quot;KEK generation&quot; width=&quot;720&quot; height=&quot;216&quot; /&gt;&lt;figcaption&gt;KEK generation&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you want to give it a try you can fork &lt;a href=&quot;https://github.com/aariste/ISVLicenseGenerator&quot;&gt;the repo on GitHub&lt;/a&gt;, and if you need any assistance from me, feel free to contact me. I’d love to add this feature to the tool.&lt;/p&gt;</content:encoded><category>Development</category></item><item><title>I wrote a book! Extending Dynamics 365 Finance and Operations Apps with Power Platform</title><link>https://ariste.info/2024/01/book-dynamics-365-finance-power-platform/</link><guid isPermaLink="true">https://ariste.info/2024/01/book-dynamics-365-finance-power-platform/</guid><description>I guess that most of my readers will already be aware of this, as I’ve been spamming LinkedIn with the news: I wrote a book! The book is called like this post’s title: “Extending Dynamics 365 Finance and Operations Apps with Power Platform”. It covers some practical scenarios where I show how to use Power</description><pubDate>Mon, 15 Jan 2024 08:29:07 GMT</pubDate><content:encoded>&lt;p&gt;I guess that most of my readers will already be aware of this, as I’ve been spamming LinkedIn with the news: I wrote a book!&lt;/p&gt;
&lt;p&gt;The book is called like this post’s title: “Extending Dynamics 365 Finance and Operations Apps with Power Platform”. It covers some practical scenarios where I show how to use Power Apps, Power Automate or AI Builder with F&amp;amp;O data.&lt;/p&gt;
&lt;p&gt;It also offers an introduction and overview into the Power Platform integration and F&amp;amp;O: linked environments, Dual-write, Virtual tables, Dataverse, PPAC, etc.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2024/01/cover_image-scaled.jpg&quot; alt=&quot;Extending Dynamics 365 Finance and Operations Apps with Power Platform cover&quot; width=&quot;2076&quot; height=&quot;2560&quot; /&gt;&lt;figcaption&gt;Cover of the book&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;Who is the book’s target?&lt;/h2&gt;
&lt;p&gt;Well I’m a technical person, so if you expect is a purely technical book you’re ri… WRONG!&lt;/p&gt;
&lt;p&gt;Of course, I will always have a bias when doing things, because, as I just said, I’m a technical person and I LOVE solving technical issues. That’s what drives me, to be honest.&lt;/p&gt;
&lt;p&gt;But the book is targeted at all profiles, technical and functional consultants, architects, that maybe haven’t had enough exposure to Dataverse or the Power Platform. The first chapters will show you the basic concepts and I think that’ll be very helpful.&lt;/p&gt;
&lt;p&gt;And I think that also people from the F&amp;amp;O or the Dataverse sides will learn something from their respective “other side” 😝&lt;/p&gt;
&lt;h2&gt;What’s in the book?&lt;/h2&gt;
&lt;p&gt;This is the table of contents and a brief description of the content:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Dynamics 365 F&amp;amp;O and Low-Code Development&lt;/strong&gt;, discusses how development has been done traditionally in F&amp;amp;O and how low-code tools like those in Power Platform can help us speed it up. An overview of Power Platform and Dataverse is also provided.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dual-Write and Virtual Tables&lt;/strong&gt;, shows how we can integrate the data from F&amp;amp;O into Dataverse and the benefits and issues that come with both methods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power Automate Flows in Dynamics 365&lt;/strong&gt;, provides an overview of Power Automate, its connectors, and the triggers and actions we can have inside a flow. We also compare and learn about the differences between using the Dynamics 365 F&amp;amp;O connector and the Dataverse one with virtual tables in flows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replacing F&amp;amp;O Processes with Power Automate&lt;/strong&gt;, discusses how we can extend the workflow functionality in F&amp;amp;O using Power Automate and have users approve or reject flows from Teams or emails.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building Automations and Integrations&lt;/strong&gt;, shows how we can use a Power Automate flow instead of X++ to connect to a FTP/SFTP server to retrieve a file and update data inside F&amp;amp;O.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power Apps: What’s in It for Finance and Operations Consultants&lt;/strong&gt;, serves as an introduction to Power Apps and how we can embed them in existing F&amp;amp;O forms.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extend F&amp;amp;O Apps with Power Apps&lt;/strong&gt;, shows an example of building a canvas Power App in which we display F&amp;amp;O data, create filters, and update the data back in F&amp;amp;O from the canvas app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power BI reporting for Dynamics 365 F&amp;amp;O Apps&lt;/strong&gt;, shows how we can export data from F&amp;amp;O into Azure Data Lake Storage thanks to the Synapse Link functionality and virtual tables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrating AI Builder&lt;/strong&gt;, introduces us to AI Builder and its custom and pre-trained models, and shows examples of using those models inside a Power App and from Power Automate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment Management&lt;/strong&gt;, serves as an introduction to the Power Platform admin center and Dataverse environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solution Management&lt;/strong&gt;, serves as an introduction to the Application Lifecycle Management (ALM) concept and shows the differences between managed and unmanaged solutions, along with examining how we can move the components we create between environments.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Where can you get the book?&lt;/h2&gt;
&lt;p&gt;Depending on where you live, you can buy the book from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.packtpub.com/product/extending-dynamics-365-finance-and-operations-apps-with-power-platform/9781801811590&quot;&gt;Packt’s site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.com&lt;/a&gt; (US)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.co.uk/Extending-Finance-Operation-Power-Platform/dp/1801811598&quot;&gt;Amazon.co.uk&lt;/a&gt; (UK)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.es/dp/1801811598&quot;&gt;Amazon.es&lt;/a&gt; (Spain)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.it/dp/1801811598&quot;&gt;Amazon.it&lt;/a&gt; (Italy)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.de/-/en/Adri%C3%A0-Ariste-Santacreu/dp/1801811598&quot;&gt;Amazon.de&lt;/a&gt; (Germany)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.fr/-/en/Adri%C3%A0-Ariste-Santacreu/dp/1801811598/&quot;&gt;Amazon.fr&lt;/a&gt; (France)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.co.jp/-/en/Adri%C3%A0-Ariste-Santacreu-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.co.jp&lt;/a&gt; (Japan)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.in/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4?ref_=ast_author_mpb&quot;&gt;Amazon.in&lt;/a&gt; (India)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com.br/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.br&lt;/a&gt; (Brazil)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.ca/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.ca&lt;/a&gt; (Canada)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com.mx/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.com.mx&lt;/a&gt; (Mexico)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com.au/Extending-Finance-Operation-Power-Platform-ebook/dp/B0CJKN87R4&quot;&gt;Amazon.com.au&lt;/a&gt; (Australia)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.barnesandnoble.com/w/extending-dynamics-365-finance-and-operations-apps-with-power-platform-adri-ariste-santacreu/1144610737?ean=9781801811590&quot;&gt;Barnes&amp;amp; Noble&lt;/a&gt; (US)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kriso.ee/extending-d365-finance-operation-apps-power-db-9781801811590.html&quot;&gt;Kriso&lt;/a&gt; (Estonia and Lithuania)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Drop me an email at adria(at)ariste(dot)info if you’re having trouble finding it.&lt;/p&gt;</content:encoded><category>Community &amp; news</category></item><item><title>Microsoft Dev Box and Dynamics 365 F&amp;O local development</title><link>https://ariste.info/2023/12/microsoft-dev-box-dynamics-365-development/</link><guid isPermaLink="true">https://ariste.info/2023/12/microsoft-dev-box-dynamics-365-development/</guid><description>After a long, long, long, long, long time waiting for it, the Dynamics 365 F&amp;O local development feature is finally here… in public preview. It’s a bit different since the first time we heard about this at the MBAS in 2019… but it opens up a new scenario for developers. I won’t go into much</description><pubDate>Thu, 14 Dec 2023 07:15:19 GMT</pubDate><content:encoded>&lt;p&gt;After a long, long, long, long, long time waiting for it, the Dynamics 365 F&amp;amp;O local development feature is finally here… in public preview. It’s a bit different since &lt;a href=&quot;https://ariste.info/2019/06/microsoft-business-applications-summit-2019/&quot;&gt;the first time we heard about this at the MBAS in 2019&lt;/a&gt;… but it opens up a new scenario for developers.&lt;/p&gt;
&lt;p&gt;I won’t go into much detail about the new unified developer experience, which is its name now, but if you want to know what you need to use it and how to configure it, you can read this blog post from &lt;strong&gt;&lt;a href=&quot;https://www.linkedin.com/in/aurelien-clere/&quot;&gt;Aurélien Clere&lt;/a&gt;&lt;/strong&gt;: &lt;a href=&quot;https://www.powerazure365.com/blog-1/dynamics-365-finops-unified-developer-experience&quot;&gt;Dynamics 365 FinOps Unified developer experience&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Today I’ll explain what &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/dev-box/&quot;&gt;Microsoft Dev Box&lt;/a&gt; is and how we can use it with the Dynamics 365 F&amp;amp;O local development tools. We will also learn about the cost, and in which scenarios we could benefit from Microsoft Dev Box as our development environment. You know, the best of both worlds, or the worst… 🤣&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/IMG_2490-1024x1024.png&quot; alt=&quot;Mixing the cloud and local tools...&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Mixing the cloud and local tools…&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;But first…&lt;/p&gt;
&lt;h2&gt;What is Microsoft Dev Box?&lt;/h2&gt;
&lt;p&gt;Microsoft Dev Box is an Azure service that we can use to deploy ready to use virtual machines for development. For X++ development? No, it’s targeted to… well to anything that Visual Studio has out-of-the-box like .NET, Azure development, Python, etc. But that doesn’t mean we can’t use it for X++ development! We’ll see that later.&lt;/p&gt;
&lt;p&gt;What can we do with Microsoft Dev Box?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless Setup and Configuration&lt;/strong&gt;: Microsoft Dev Box allows teams to quickly set up development environments that are pre-configured with the necessary tools, libraries, and settings. This reduces the time spent on environment setup and troubleshooting, allowing developers to focus more on coding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collaboration and Consistency&lt;/strong&gt;: Since Microsoft Dev Boxes are cloud-based, teams across different locations can work on a consistent environment. This ensures that everyone is working with the same set of tools and configurations, reducing the “it works on my machine” problem.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Powerful and Scalable Resources&lt;/strong&gt;: Microsoft Dev Box provides access to scalable cloud resources, which means developers can leverage high-performance computing power as needed. This is particularly useful for resource-intensive tasks like compiling large codebases, running tests, or using machine learning algorithms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enhanced Security&lt;/strong&gt;: Being a cloud-based solution, Dev Box benefits from Microsoft’s robust security protocols. This means your code and development environment are protected with industry-leading security measures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integration with Existing Tools&lt;/strong&gt;: Dev Box integrates seamlessly with other Microsoft services and many third-party tools, offering a comprehensive development experience. Whether you’re using GitHub, Azure, or other CI/CD tools, Dev Box fits into your existing workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility and Customization&lt;/strong&gt;: Teams can customize their Dev Boxes to match their specific project needs. Whether you’re developing a mobile app, a web service, or a machine learning model, you can tailor your environment accordingly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Efficient Resource Management&lt;/strong&gt;: With Dev Box, organizations can manage resources more efficiently. Development environments can be spun up or down as needed, ensuring that you only pay for what you use.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And speaking of costs… how much does Microsoft Dev Box cost? As everything in Azure it depends on the SKU of the resources used. We can choose between three CPU and RAM setups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8 vCPU and 32 GB RAM&lt;/li&gt;
&lt;li&gt;16 vCPU and 64 GB RAM&lt;/li&gt;
&lt;li&gt;32 vCPU and 128 GB RAM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And we can deploy each of these sizes with 256, 512, 1024 or 2048 GB of storage.&lt;/p&gt;
&lt;p&gt;So, for example, a 8 vCPU with 32 GB of RAM and 256 GB of SSD storage costs, deployed in West Europe:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt;: 18.69€/20.52$ per month&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compute&lt;/strong&gt;: 1.47€/1.61$ per hour&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maximum monthly price&lt;/strong&gt;: 135.93€/149.26$ per month&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/dev-box/&quot;&gt;Microsoft Dev Box pricing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s not that expensive, if we use a dev VM for 8 hours each day, and 20 days per month that’s 20*8*1.47 + 18.69, which is 253.89€ per month. &lt;strong&gt;Is it? No it isn’t!&lt;/strong&gt; You can see we have a maximum monthly price, and &lt;strong&gt;when the usage of an instance goes over that maximum price, that’s all you will pay that month&lt;/strong&gt;! The compute cost is capped at 80 hours.&lt;/p&gt;
&lt;h3&gt;Licenses everywhere&lt;/h3&gt;
&lt;p&gt;Before continuing, let me tell you something about licenses. Because it’s always licensing! And let me quote Microsoft on this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To use Microsoft Dev Box, each user must be licensed for Windows 11 Enterprise or Windows 10 Enterprise, Microsoft Endpoint Manager, and Azure Active Directory P1.&lt;br /&gt;
In addition to being available independently, these licenses are included in Microsoft 365 F3, Microsoft 365 E3, Microsoft 365 E5, Microsoft 365 A3, Microsoft 365 A5, Microsoft 365 Business Premium, and Microsoft 365 Education Student Use Benefit subscriptions.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/dev-box/&quot;&gt;https://azure.microsoft.com/en-us/pricing/details/dev-box/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s not that bad, right? Most of us working at partners, or customers implementing Dynamics 365, have a Microsoft E3 or E5 license assigned, which would fulfill the base requirements. Just keep that in mind.&lt;/p&gt;
&lt;p&gt;Enough licensing, now let’s get back to dev boxes and pricing!&lt;/p&gt;
&lt;p&gt;Having a maximum monthly price means that if you use a Microsoft Dev Box just for some hours a month, you will pay its hourly price, but if you use it a lot, when you go over the max price it will switch to the max monthly price &lt;strong&gt;automatically&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;That’s regarding compute, which only counts while the dev box is running (not shut down or hibernated). &lt;strong&gt;But what about storage?&lt;/strong&gt; Well, as like many other Azure services, you pay for storage as long as the storage resource exists, so if you kept a Microsoft Dev Box turned off for a whole month you’d only pay its storage costs.&lt;/p&gt;
&lt;p&gt;This looks cheaper than a Cloud-hosted environment VM to me… of course we can’t compare both because if we need to enable the Power Platform integration and have a Dataverse environment linked to your F&amp;amp;O dev instance we have to deploy it as a CHE. Do we?&lt;/p&gt;
&lt;p&gt;Well, here’s where the new unified development experience takes the spotlight!&lt;/p&gt;
&lt;h2&gt;One Dynamics One Platform&lt;/h2&gt;
&lt;p&gt;As you may know, we’re on a trip to have a unified admin and developer experience for Dynamics 365 F&amp;amp;O and Dataverse.&lt;/p&gt;
&lt;p&gt;The new developer experience starts when we deploy a sandbox environment in Power Platform admin center instead of LCS. And this means that we no longer have to manually configure the Power Platform integration, it will be on by default.&lt;/p&gt;
&lt;p&gt;So we don’t really need a CHE if we want to build things that use both F&amp;amp;O and Dataverse. Let me show you something, this is how a unified development environment looks like on PPAC.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image.png&quot; alt=&quot;Unified development environment in PPAC&quot; width=&quot;968&quot; height=&quot;563&quot; /&gt;&lt;figcaption&gt;Unified development environment in PPAC&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Do you notice something different? It has a Dataverse environment URL and… a Finance and Operations URL too! This is not something new and exclusive about the unified development experience, we’ve actually had it for some time, but it was a bit different. Let me explain.&lt;/p&gt;
&lt;p&gt;When we configured the Power Platform integration &lt;strong&gt;from LCS,&lt;/strong&gt; a Dataverse environment was deployed and showed the F&amp;amp;O URL too. But in this case we’re deploying a Dataverse environment &lt;strong&gt;from PPAC&lt;/strong&gt; with F&amp;amp;O installed as a Dynamics 365 app!&lt;/p&gt;
&lt;p&gt;If this sounds a bit confusing, I’d recommend you to get familiar with PPAC and the &lt;a href=&quot;https://learn.microsoft.com/en-us/power-platform/admin/unified-experience/finance-operations-apps-overview&quot;&gt;new unified admin experience in PPAC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And this is why I think that in some scenarios we can replace our CHE for a Microsoft Dev Box with the local dev tools.&lt;/p&gt;
&lt;h2&gt;Unified developer experience and Microsoft Dev Box&lt;/h2&gt;
&lt;p&gt;Let me make something clear first: this is not a blueprint, a one-size-fits-all solution. Depending on your team, your processes, your customer’s requirements and other factors, you may still benefit from using cloud hosted environments. It’s up to you to find that out!&lt;/p&gt;
&lt;p&gt;You might be thinking, “but weren’t the local dev tools meant to be used locally?”. Yes, you can install them on your local laptop and start working!&lt;/p&gt;
&lt;p&gt;But what if your current laptop doesn’t meet the requirements? What if because of company policies, you can’t install all the required components? What if you just don’t want to install anything on your laptop? What if, what if…?&lt;/p&gt;
&lt;p&gt;So whatever is your reason, I will show you an alternative to use the local dev tools!&lt;/p&gt;
&lt;h3&gt;Deploying a dev center&lt;/h3&gt;
&lt;p&gt;The first thing we need to do is create a Dev center in our Azure subscription:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-1.png&quot; alt=&quot;Create a dev center on Azure&quot; width=&quot;833&quot; height=&quot;593&quot; /&gt;&lt;figcaption&gt;Create a dev center on Azure&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Select the region which is the nearest to you or your development team, in my case I go with West Europe. If you have a distributed team of developers, you can create several dev centers for the different regions your team is.&lt;/p&gt;
&lt;p&gt;Next we need to create a project inside the dev center:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-2.png&quot; alt=&quot;Create a project in dev center&quot; width=&quot;722&quot; height=&quot;796&quot; /&gt;&lt;figcaption&gt;Create a project in dev center&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We just need to select the dev center and give the project a name:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-3.png&quot; alt=&quot;New project in dev center&quot; width=&quot;772&quot; height=&quot;643&quot; /&gt;&lt;figcaption&gt;New project in dev center&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now we will go back to the dev center and will create a dev box definition:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-4.png&quot; alt=&quot;Create a dev box definition&quot; width=&quot;870&quot; height=&quot;668&quot; /&gt;&lt;figcaption&gt;Create a dev box definition&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Complete the required fields with your selected VM and storage sizes:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-6.png&quot; alt=&quot;Dev box definition&quot; width=&quot;762&quot; height=&quot;754&quot; /&gt;&lt;figcaption&gt;Dev box definition&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I recommend giving it a meaningful name, which hints at the both the size of the VM, storage and Visual Studio and Windows versions.&lt;/p&gt;
&lt;p&gt;Now go inside the project we created earlier, and create a new dev box pool:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-7.png&quot; alt=&quot;New dev box pool&quot; width=&quot;554&quot; height=&quot;682&quot; /&gt;&lt;figcaption&gt;New dev box pool&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And now we will complete all the fields:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-8-702x1024.png&quot; alt=&quot;Dev box pool details&quot; width=&quot;702&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Dev box pool details&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I’d also use the same name as in the definition, just out of convenience. Complete all other fields as per your requirements and select the licensing checkbox.&lt;/p&gt;
&lt;p&gt;Finally, in the project, we need to assign the role &lt;em&gt;DevCenter Dev Box User&lt;/em&gt; to the users that will create and use the dev boxes. &lt;strong&gt;You have to do this even if you’re the owner of the Azure resources!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’re done with the dev center, now it’s time to access the dev boxes…&lt;/p&gt;
&lt;h3&gt;Accessing the Microsoft dev box&lt;/h3&gt;
&lt;p&gt;In order to create and access these dev boxes, we need to go to Microsoft developer portal at &lt;a href=&quot;https://devportal.microsoft.com/&quot;&gt;https://devportal.microsoft.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you get this screen:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-9.png&quot; alt=&quot;Dev portal is not ready!&quot; width=&quot;762&quot; height=&quot;453&quot; /&gt;&lt;figcaption&gt;Dev portal is not ready!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This means that either the Azure resources are not deployed yet or that you’re missing the &lt;em&gt;DevCenter Dev Box User&lt;/em&gt; role! Once that is fixed you should see this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-10.png&quot; alt=&quot;Dev portal is ready!&quot; width=&quot;783&quot; height=&quot;546&quot; /&gt;&lt;figcaption&gt;Dev portal is ready!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;So just click the “New dev box” button:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-11.png&quot; alt=&quot;New dev box creation&quot; width=&quot;803&quot; height=&quot;603&quot; /&gt;&lt;figcaption&gt;New dev box creation&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In case you had several dev box pools, for example for different Visual Studio versions, you would get a dropdown in the “Dev box pool” field, and you’d be able to select in which pool you’re deploying the dev box.&lt;/p&gt;
&lt;p&gt;Finally, click the create button and wait around 25 minutes.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-12.png&quot; alt=&quot;Dev box being created!&quot; width=&quot;827&quot; height=&quot;795&quot; /&gt;&lt;figcaption&gt;Dev box being created!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Configuring the dev box for F&amp;amp;O&lt;/h3&gt;
&lt;p&gt;Once the Microsoft dev box is ready you will receive an email and be able to connect to it using the RDP client or your browser like &lt;a href=&quot;https://ariste.info/2023/01/configure-azure-bastion-dynamics-365-vm/&quot;&gt;Bastion&lt;/a&gt; does.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-13.png&quot; alt=&quot;Microsoft dev box is ready&quot; width=&quot;827&quot; height=&quot;795&quot; /&gt;&lt;figcaption&gt;Microsoft dev box is ready&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Choose your preferred method and log into the dev box using your Azure ADMicrosoft Entra ID credentials!&lt;/p&gt;
&lt;p&gt;The VM has everything needed to work with the F&amp;amp;O local dev tools, including LocalDB.&lt;/p&gt;
&lt;p&gt;Once you’ve logged in you basically have to follow Aurelién’s post linked at the beginning of the post. I’ll configure everything quickly without going into much detail and I’ll just focus on the issues I’ve found.&lt;/p&gt;
&lt;p&gt;The first step is running Visual Studio and logging into your account. Then we will install the Power Platform tools extension.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-14.png&quot; alt=&quot;Power Platform Tools install&quot; width=&quot;929&quot; height=&quot;643&quot; /&gt;&lt;figcaption&gt;Power Platform Tools install&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Close Visual Studio and the extension will install. Once done open Visual Studio again and connect to your F&amp;amp;O development environment in Dataverse. We do that in the Tools menu:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-15.png&quot; alt=&quot;Connect to Dataverse&quot; width=&quot;672&quot; height=&quot;247&quot; /&gt;&lt;figcaption&gt;Connect to Dataverse&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Enter your credentials, and select your environment in the list and select the solution. The a dialog will appear asking if we want to download the F&amp;amp;O assets:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-17.png&quot; alt=&quot;F&amp;amp;O assets download&quot; width=&quot;436&quot; height=&quot;169&quot; /&gt;&lt;figcaption&gt;F&amp;amp;O assets download&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This will download several things to the &lt;em&gt;C:\Users\YOUR_USER_NAME\AppData\Local\Microsoft\Dynamics365\10.0.1627.73&lt;/em&gt; folder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;F&amp;amp;O visual studio extension&lt;/li&gt;
&lt;li&gt;Cross-references database BAK&lt;/li&gt;
&lt;li&gt;PackagesLocalDirectory ZIP file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first thing we will do is unzip the ZIP file. The documentation suggests to use 7-Zip to do that, and I do too because using the Windows extraction tool it took over an hour for me to unzip the file, while when using 7-Zip it took less than 10 minutes. You can uncompress the AOT objects wherever you want. For example, I do it at C:\AOSService\PackagesLocalDirectory because… I’m used to that.&lt;/p&gt;
&lt;p&gt;While the ZIP file is uncompressed install the VS extension for F&amp;amp;O. And one thing: &lt;strong&gt;don’t move the BAK file from there&lt;/strong&gt;! When we configure the environment it expects the BAK file to be there, if we delete it, cut it somewhere else or whatever, we won’t be able to configure the local development tools!&lt;/p&gt;
&lt;p&gt;When the ZIP file has finished being extracted and the extension is installed we can continue.&lt;/p&gt;
&lt;p&gt;Open Visual Studio again, go to Extensions, hover on the Dynamics 365 menu item and… a dialog will appear!&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-18.png&quot; alt=&quot;Local XPP configuration&quot; width=&quot;1505&quot; height=&quot;916&quot; /&gt;&lt;figcaption&gt;Local XPP configuration&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We need to complete all the fields. Give it a name and description as you like, as the xRef database server use (localdb)\., and a name for the xRef DB that will be restored using the BAK file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IMPORTANT&lt;/strong&gt;: click on the version number. I went nuts until I noticed you had to actually click and select it, it’s not automatically selected!&lt;/p&gt;
&lt;p&gt;Finally, change the paths to your AOSService folders and click Save. It will fail, because we have to select the configuration as active by enabling the “Current” check next to the configuration name on the list. Now click Save and it will start restoring the xRef DB, you will see a message on the dialog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you have a database server error&lt;/strong&gt; you might have to start the localdb instance. You can do that opening a command prompt and running this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqllocaldb start mssqllocaldb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now create a runnable class with a hello world, deploy the model to the environment, run it and…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-19-870x1024.png&quot; alt=&quot;Runnable class in the cloud!&quot; width=&quot;870&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Runnable class in the cloud!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It works!&lt;/p&gt;
&lt;p&gt;Once you’re done for the day, you can head back to the developer portal and choose several options:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/12/image-20.png&quot; alt=&quot;Dev center shut down&quot; width=&quot;601&quot; height=&quot;659&quot; /&gt;&lt;figcaption&gt;Dev center shut down&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You can hibernate or shut down the box, which will stop the computing costs (but not the storage ones). We can also restart it, or delay the scheduled shutdown if it’s configured. And of course, we can also delete it.&lt;/p&gt;
&lt;h2&gt;What about performance?&lt;/h2&gt;
&lt;p&gt;Probably performance is one of the things we all thought about when the local developer experience was released.&lt;/p&gt;
&lt;p&gt;And how is performance of the local tools on a cloud box? Well, it’s the same as in my laptop. I think the bottleneck for performance here are the actual F&amp;amp;O development tools and not hardware, local or in the cloud.&lt;/p&gt;
&lt;p&gt;You still get some VS freezes and the warnings of the extension, but it’s definitely not worse than working on your own PC.&lt;/p&gt;
&lt;h2&gt;What about Visual Studio 2022?&lt;/h2&gt;
&lt;p&gt;If you want to use a Microsoft dev box with Visual Studio 2022 or Windows 11, go ahead! Since version 10.0.36 the &lt;a href=&quot;https://devblogs.microsoft.com/powerplatform/the-visual-studio-2022-add-in-is-now-available-for-dynamics-365-for-financials-and-operations/&quot;&gt;Visual Studio extension for the F&amp;amp;O tools are included in the deployable packages used for the version upgrades&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;This is a small proof of concept, and while I think most people will use the local development tools on their local PC, there’s some cases where we could benefit from Microsoft Dev Box.&lt;/p&gt;
&lt;p&gt;From a cost perspective we see that Microsoft Dev Box can cost much less than a classic LCS-deployed virtual machine.&lt;/p&gt;
&lt;p&gt;I wouldn’t use this if I wanted to deploy and delete the dev box daily, because configuring the dev tools every day would be quite a loss of time. However it’s possible to use custom images to create dev boxes. Something I haven’t tried but maybe could be explored to have an image pre-configured.&lt;/p&gt;
&lt;p&gt;Finally, this is something that may work for me, but not for others, and as always we need to evaluate what’s the best solution for us: the local VHD, cloud-hosted environments or local development.&lt;/p&gt;
&lt;p&gt;But hey, I enjoyed trying this!&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.powerazure365.com/blog-1/dynamics-365-finops-unified-developer-experience&quot;&gt;Dynamics 365 FinOps Unified developer experience&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/powerplatform/the-public-preview-for-the-unified-experience-is-live/&quot;&gt;The Public Preview for the Unified Experience is live&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/power-platform/admin/unified-experience/finance-operations-apps-overview&quot;&gt;Unified admin experience for finance and operations apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/power-platform/admin/unified-experience/tutorial-deploy-new-environment-with-erp-template?tabs=PPAC&quot;&gt;Tutorial: Provision a new environment with an ERP-based template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/power-platform/developer/unified-experience/finance-operations-dev-overview&quot;&gt;Unified developer experiece for finance and operations apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/powerplatform/the-public-preview-for-the-unified-experience-is-live/&quot;&gt;The Public Preview for the Unified Experience is live!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/powerplatform/the-visual-studio-2022-add-in-is-now-available-for-dynamics-365-for-financials-and-operations/&quot;&gt;The Visual Studio 2022 add-in is now available for Dynamics 365 for Financials and Operations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>Azure</category></item><item><title>Microsoft Sentinel for Dynamics 365 F&amp;O</title><link>https://ariste.info/2023/07/microsoft-sentinel-for-dynamics-365-fo/</link><guid isPermaLink="true">https://ariste.info/2023/07/microsoft-sentinel-for-dynamics-365-fo/</guid><description>Today I’m bringing you a post that’s all about Dynamics 365 F&amp;O, but the main focus is on security, specifically Microsoft Sentinel. Some days ago it was announced a Microsoft Sentinel Solution for F&amp;O had been published and is currently in preview . Let’s learn a bit about it! Sometimes we overlook the security aspects</description><pubDate>Wed, 19 Jul 2023 05:51:04 GMT</pubDate><content:encoded>&lt;p&gt;Today I’m bringing you a post that’s all about Dynamics 365 F&amp;amp;O, but the main focus is on security, specifically &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/sentinel/overview&quot;&gt;Microsoft Sentinel&lt;/a&gt;. Some days ago it was announced a &lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-sentinel-blog/what-s-new-microsoft-sentinel-solution-for-dynamics-365-finance/ba-p/3856210&quot;&gt;Microsoft Sentinel Solution for F&amp;amp;O had been published&lt;/a&gt; &lt;strong&gt;and is currently in preview&lt;/strong&gt;. Let’s learn a bit about it!&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/IMG_2461-1024x1024.png&quot; alt=&quot;That’s how Microsoft Sentinel really works&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;That’s how Microsoft Sentinel really works&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Sometimes we overlook the security aspects of the things that are not directly related to F&amp;amp;O, specially regarding resources like networking, storage accounts, dev VMs, Microsoft Entra ID (this is &lt;a href=&quot;https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/azure-ad-is-becoming-microsoft-entra-id/ba-p/2520436&quot;&gt;Azure AD’s new name&lt;/a&gt;!) or &lt;a href=&quot;https://ariste.info/2023/01/configure-azure-bastion-dynamics-365-vm/&quot;&gt;using Bastion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And we don’t do that because we just don’t care about security, but because we’re Dynamics 365 people, and sometimes we might lack the knowledge in other things. If you’re lucky, you’ll have an in-house security team that’ll take care of that, otherwise we need to train ourselves a bit.&lt;/p&gt;
&lt;p&gt;This is the first time I’ve used Microsoft Sentinel, and I’m for sure missing on a lot of things and features. Time to learn!&lt;/p&gt;
&lt;h2&gt;What is Microsoft Sentinel?&lt;/h2&gt;
&lt;p&gt;Let me repeat: &lt;strong&gt;this feature is in preview&lt;/strong&gt; as of July 2023.&lt;/p&gt;
&lt;p&gt;Microsoft Learn defines Sentinel as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Microsoft Sentinel is a scalable, cloud-native solution that provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Security information and event management (SIEM)&lt;/li&gt;
&lt;li&gt;Security orchestration, automation, and response (SOAR)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Microsoft Sentinel delivers intelligent security analytics and threat intelligence across the enterprise. With Microsoft Sentinel, you get a single solution for attack detection, threat visibility, proactive hunting, and threat response.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/sentinel/overview&quot;&gt;https://learn.microsoft.com/en-us/azure/sentinel/overview&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft Sentinel makes it easy to collect security data across your organization, not only Dynamics 365 F&amp;amp;O! From devices, to users, to apps, to servers on any cloud. It uses the power of artificial intelligence to ensure you are identifying real threats quickly.&lt;/p&gt;
&lt;p&gt;Microsoft Sentinel provides several capabilities, including but not limited to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Collect data at cloud scale&lt;/strong&gt; across all users, devices, applications, and infrastructure, both on-premises and in multiple clouds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detect previously undetected threats&lt;/strong&gt;, and &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/sentinel/false-positives&quot;&gt;minimize false positives&lt;/a&gt; using Microsoft’s analytics and unparalleled threat intelligence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Investigate threats with artificial intelligence&lt;/strong&gt;, and hunt for suspicious activities at scale, tapping into years of cybersecurity work at Microsoft.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Respond to incidents rapidly&lt;/strong&gt; with built-in orchestration and automation of common tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configuring Microsoft Sentinel for F&amp;amp;O&lt;/h2&gt;
&lt;p&gt;The process to configure Microsoft Sentinel for F&amp;amp;O is pretty straightforward and if you follow the official Microsoft Learn tutorial to &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/sentinel/dynamics-365/deploy-dynamics-365-finance-operations-solution&quot;&gt;deploy Microsoft Sentinel solution for Dynamics 365 Finance and Operations&lt;/a&gt;, you should have no issues, but I’ll follow the same steps.&lt;/p&gt;
&lt;h3&gt;Create a new Log Analytics Workspace&lt;/h3&gt;
&lt;p&gt;Go to the Azure portal and look for Microsoft Sentinel, click the “Create Microsoft Sentinel” button:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel1.png&quot; alt=&quot;Microsoft Sentinel workspaces in your Azure subscription&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Microsoft Sentinel workspaces in your Azure subscription&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Microsoft Sentinel sort-of runs on top of a Log Analytics workspace. We use the Log Analytics workspace to ingest the data from our resources, then Microsoft Sentinel analyses that data. The create button will show the Log Analytics workspaces, if you have any:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel2.png&quot; alt=&quot;Log Analytics workspaces&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Log Analytics workspaces&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you don’t have any, or want to create a new one, you must create a new one:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel3.png&quot; alt=&quot;Create Log Analytics workspace&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Create Log Analytics workspace&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Give it a meaningful name, you can see in the image I’m calling it onefin-sentinel, but a better name would be onefin-sentinel-loganalytics.&lt;/p&gt;
&lt;h3&gt;Add Microsoft Sentinel to the workspace&lt;/h3&gt;
&lt;p&gt;Once it’s created you will see it in the previous screen, if you don’t see it click the refresh button. Finally, select it and click the “Add” button in the bottom left corner.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel4.png&quot; alt=&quot;Add Microsoft Sentinel to a Log Analytics workspace&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Add Microsoft Sentinel to a Log Analytics workspace&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Install the F&amp;amp;O solution&lt;/h3&gt;
&lt;p&gt;When this is done, it will take you to the Microsoft Sentinel main screen. Now we need to install the F&amp;amp;O solution. In the left sidebar, look for “Content hub”, and then in the search bar look for Dynamics 365, you should see the one we want as “Dynamics 365 Finance and Operations”. Click the “Install” button in the lower right corner:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel5.png&quot; alt=&quot;Install the Dynamics 365 Finance and Operations solution in Microsoft Sentinel&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Install the Dynamics 365 Finance and Operations solution in Microsoft Sentinel&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Install the F&amp;amp;O data connector&lt;/h3&gt;
&lt;p&gt;When the solution has finished installing, we need to add the data connector to F&amp;amp;O. In the left menu, go to the “Data connectors” section. You should already see the “Dynamics 365 F&amp;amp;O (using Azure Functions)” connector there. If it’s not there, use the search bar or try cleaning the filters:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel6.png&quot; alt=&quot;Microsoft Sentinel data connector for Dynamics 365 F&amp;amp;O&quot; width=&quot;1146&quot; height=&quot;687&quot; /&gt;&lt;figcaption&gt;Microsoft Sentinel data connector for Dynamics 365 F&amp;amp;O&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click the “Open connector page” button in the lower right corner. You will see this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/SentinelA.png&quot; alt=&quot;Data connector for F&amp;amp;O&quot; width=&quot;1146&quot; height=&quot;930&quot; /&gt;&lt;figcaption&gt;Data connector for F&amp;amp;O&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The data collection is done using an Azure Function. Don’t worry, it will be deployed using an ARM template. We just need to click the blue “Deploy to Azure” button, a new tab will open:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-13.png&quot; alt=&quot;Azure Function deployment using ARM template&quot; width=&quot;1146&quot; height=&quot;930&quot; /&gt;&lt;figcaption&gt;Azure Function deployment using ARM template&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We need to complete the information in the template fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Function App Name: the name of the Azure Function app, remember that it should be meaningful and describe what it is. Like d365fo-sentinel-app.&lt;/li&gt;
&lt;li&gt;Workspace Name: the Log Analytics workspace name we created in the first steps.&lt;/li&gt;
&lt;li&gt;Finance Operations Api Host: the URL of your Dynamics 365 F&amp;amp;O instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click the “Review + create” button, and once everything has been validated, the “Create” button.&lt;/p&gt;
&lt;p&gt;This will take a bit longer than the previous steps, but not much, I’d say less than 5 minutes.&lt;/p&gt;
&lt;h3&gt;Get the function Managed Identity&lt;/h3&gt;
&lt;p&gt;Head to the Azure AD section in the Azure portal. Yes, the name has changed, but not all the branding is ready yet. Navigate to the “Enterprise Applications” section. We have to remove the filter on the image below:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-14.png&quot; alt=&quot;Remove the Enterprise Applications filter&quot; width=&quot;1146&quot; height=&quot;610&quot; /&gt;&lt;figcaption&gt;Remove the Enterprise Applications filter&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And then add a new filter to display only the managed identities apps:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-15.png&quot; alt=&quot;Filter on managed identities&quot; width=&quot;274&quot; height=&quot;295&quot; /&gt;&lt;figcaption&gt;Filter on managed identities&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the search bar look for your Azure Function app name:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-16.png&quot; alt=&quot;Azure Function in AD&quot; width=&quot;1350&quot; height=&quot;610&quot; /&gt;&lt;figcaption&gt;Azure Function in AD&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And finally, copy its Application ID. Now we’re headed to the F&amp;amp;O interface.&lt;/p&gt;
&lt;h2&gt;Create a new security role&lt;/h2&gt;
&lt;p&gt;Go to System administration -&amp;gt; Security -&amp;gt; Security configuration, and create a new role.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/Sentinel7-1024x562.png&quot; alt=&quot;New role&quot; width=&quot;1024&quot; height=&quot;562&quot; /&gt;&lt;figcaption&gt;New role&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now click on Privileges under the references grid, and click the “Add references” button:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-17.png&quot; alt=&quot;Add privilege to role&quot; width=&quot;1146&quot; height=&quot;795&quot; /&gt;&lt;figcaption&gt;Add privilege to role&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the list, filter on the name and look for “Database log”. Add the “Database log Entity View” privilege.&lt;/p&gt;
&lt;p&gt;Finally, publish the security objects. Go to the “Unpublished objects” tab, select the new role we just created, and click the “Publish selection” button. You can use the “Publish all” option if you wish and have no other pending changes:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-18.png&quot; alt=&quot;Publish objects&quot; width=&quot;809&quot; height=&quot;442&quot; /&gt;&lt;figcaption&gt;Publish objects&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; if you’re not doing this for the production environment, it’s better to create the role and add the privilege in Visual Studio. If a DB refresh is done to the environment you’re using, the role created from the Dynamics 365 F&amp;amp;O UI will be gone.&lt;/p&gt;
&lt;h3&gt;Create a new user&lt;/h3&gt;
&lt;p&gt;Or use an existing one if you want. This user will be assigned the role we’ve created and used in the Azure Active Directory applications form.&lt;/p&gt;
&lt;p&gt;If you use an existing user, make sure it’s not a superuser or system administrator.&lt;/p&gt;
&lt;h3&gt;Add a new Azure AD application&lt;/h3&gt;
&lt;p&gt;Now, using the Application ID we copied before from the Azure Function managed identity, create a new record in the Azure Active Directory applications form, and use the new user we’ve just created as the User ID.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-19-1024x389.png&quot; alt=&quot;New Azure AD App&quot; width=&quot;1024&quot; height=&quot;389&quot; /&gt;&lt;figcaption&gt;New Azure AD App&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We’ve configured everything. Microsoft Sentinel is ready to ingest and analyze data from our Dynamics 365 F&amp;amp;O instance. But which data is monitored? Now, this is going to be interesting…&lt;/p&gt;
&lt;h3&gt;Now, the part that I don’t like…&lt;/h3&gt;
&lt;p&gt;You might’ve already noticed and already know where I’m going. When we created the new role, we added the privilege to read the database log entity, remember?&lt;/p&gt;
&lt;p&gt;It’s not a surprise, the monitoring in Microsoft Sentinel for F&amp;amp;O is using the database log!! And what is wrong with the database log? &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/sysadmin/configure-manage-database-log#database-logging-and-performance&quot;&gt;Possible performance issues&lt;/a&gt;!!&lt;/p&gt;
&lt;p&gt;So, in my opinion, this restricts a bit what we can do with Microsoft Sentinel to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parameter tables&lt;/li&gt;
&lt;li&gt;Master data tables&lt;/li&gt;
&lt;li&gt;Other tables during a short period of time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And of course, we can forget about transaction tables. At least, it’s not possible to add new tables to the database log until you configure the cleanup log batch job.&lt;/p&gt;
&lt;p&gt;Add whichever table you want to monitor. I’ve chosen the Bank Accounts as in the Learn tutorial. When the Azure Function app triggers, which does every 10 minutes, we will see the changes in the Logs section:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-20.png&quot; alt=&quot;Microsoft Sentinel Logs query on the FinanceOperationsActivity_CL table, returning three Update records for the BankAccountTable&quot; width=&quot;1610&quot; height=&quot;713&quot; /&gt;&lt;figcaption&gt;F&amp;amp;O Logs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And in the query editor we look for the FinanceOperationsActivity_CL logs as in the image above, we will see the records for the changes done. And inside the “FormattedData” node we can see all the fields of the table that we’re logging, with their old and new value:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-21.png&quot; alt=&quot;Changes in the Bank Accounts&quot; width=&quot;757&quot; height=&quot;541&quot; /&gt;&lt;figcaption&gt;Changes in the Bank Accounts&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;What does Microsoft Sentinel offers out-of-the-box for F&amp;amp;O?&lt;/h2&gt;
&lt;p&gt;The solution we installed in the first steps includes a small set of rule templates that can be enabled and used with no other setup needed:&lt;/p&gt;
&lt;div&gt;




















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Rule name&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F&amp;amp;O – Non-interactive account mapped to self or sensitive privileged user&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Identifies changes to Azure AD Client Apps registered for Finance &amp;amp; Operations, specifically when a new client is mapped to a predefined list of sensitive privileged user accounts, or when a user associates a client app with their own account.   To modify the list of sensitive privileged accounts, change the “priviliged_user_accounts” variable in the rule query.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;(Refer to the example in the rule query)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;---&lt;/td&gt;&lt;td&gt;---&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F&amp;amp;O – Mass update or deletion of user account records&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Identifies large delete or update operations on Finance and Operations user records based on predefined thresholds.   Default update threshold: &lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Default delete threshold: &lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;---&lt;/td&gt;&lt;td&gt;---&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F&amp;amp;O – Bank account change following network alias reassignment&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Identifies updates to bank account number by a user account which his alias was recently modified to a new value.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;---&lt;/td&gt;&lt;td&gt;---&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F&amp;amp;O – Reverted bank account number modifications&lt;/strong&gt;  &lt;/td&gt;&lt;td&gt;Identifies changes to bank account numbers in Finance &amp;amp; Operations, whereby a bank account number is modified but then subsequently reverted a short time later.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;---&lt;/td&gt;&lt;td&gt;---&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;F&amp;amp;O – Unusual sign-in activity using single factor authentication&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Identifies successful sign-in events to Finance &amp;amp; Operations and Lifecycle Services using single factor/password authentication. Sign-in events from tenants not using MFA, coming from an Azure AD trusted network location, or from geolocations seen previously in the last 14 days are excluded.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;This detection uses logs ingested from Azure Active Directory. Therefore, you should enable the Azure Active Directory data connector. |
| --- | --- |&lt;/p&gt;
&lt;p&gt;These alert templates can be found under the Analytics menu, in the Rule templates tab:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-24-1024x557.png&quot; alt=&quot;Alert templates&quot; width=&quot;1024&quot; height=&quot;557&quot; /&gt;&lt;figcaption&gt;Alert templates&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This alerts can create incidents, which for example we can later use in a Logic App (there’s no Microsoft Sentinel connector for Power Automate), or can send notifications from Azure to administrators.&lt;/p&gt;
&lt;h2&gt;What happens behind the scenes?&lt;/h2&gt;
&lt;p&gt;I’ll provide a short explanation as a high-level overview of how all the elements we’ve deployed and configured work.&lt;/p&gt;
&lt;p&gt;As we’ve seen, all of this depends on the database log functionality in Dynamics 365 Finance and Operations. Then there’s an Azure Function that runs on a schedule and will query the OData endpoint for the database log entity. That’s why we need to configure the managed identity of the Azure Function in the AD applications form.&lt;/p&gt;
&lt;p&gt;And it’s the Azure Function the one that’s responsible to send the data to the Log Analytics Workspace, which is later analized by Microsoft Sentinel.&lt;/p&gt;
&lt;h2&gt;Some final thoughts&lt;/h2&gt;
&lt;p&gt;I do see the value of Microsoft Sentinel in securing your Azure AD Microsoft Entra ID. For F&amp;amp;O, with the functionality I’ve described here, and what I’ve been able to test… to me, it feels like, &lt;strong&gt;in simple scenarios&lt;/strong&gt;, we’re using a sledgehammer to crack a nut. If you only want to track field changes, you can use the &lt;a href=&quot;https://ariste.info/2023/07/fo-alert-emails-power-automate/&quot;&gt;standard alerts’ functionality&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, &lt;strong&gt;for complex scenarios&lt;/strong&gt;, Microsoft Sentinel offers more advanced rules, like checking for mass updates, or field values being changed after a user has an updated network alias and therefore a different login. This is something that &lt;strong&gt;really does add a value to your security strategy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As I said, this is the first time I’ve used Microsoft Sentinel, so I’m probably missing on something, I promise I’ll keep on investigating.&lt;/p&gt;</content:encoded><category>Azure</category></item><item><title>F&amp;O alert emails in Power Automate</title><link>https://ariste.info/2023/07/fo-alert-emails-power-automate/</link><guid isPermaLink="true">https://ariste.info/2023/07/fo-alert-emails-power-automate/</guid><description>There’s a nice feature in Dynamics 365 F&amp;O that lets us emit notifications when data is changed, created or deleted: the alert framework. In this post, I will show you how to send the alerts as Business events and send emails notifying the users using a Power Automate flow. When I was almost done writing</description><pubDate>Mon, 10 Jul 2023 06:02:31 GMT</pubDate><content:encoded>&lt;p&gt;There’s a nice feature in Dynamics 365 F&amp;amp;O that lets us emit notifications when data is changed, created or deleted: the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/get-started/alerts-overview&quot;&gt;alert framework&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/IMG_2459-1024x1024.png&quot; alt=&quot;Cartoon character wearing a flashing red alarm light on its head&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;In this post, I will show you how to send the alerts as Business events and send emails notifying the users using a Power Automate flow.&lt;/p&gt;
&lt;p&gt;When I was almost done writing this blog post, I found this &lt;a href=&quot;https://dynamicspost.com/using-dynamics-365-finance-and-operations-alerts-to-trigger-power-automate-flows/&quot;&gt;blog post&lt;/a&gt; and video from Scott Gaines where he showed this scenario. Take a look at it for a clearer step-by-step guide: &lt;a href=&quot;https://www.youtube.com/watch?v=Ud7AK9Z_bnc&quot;&gt;Using Dynamics 365 Finance and Operations Alerts to Trigger Power Automate Flows&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Why? The standard does that!&lt;/h2&gt;
&lt;p&gt;You’re 100% right. There’s a standard functionality that already does that, you can configure an alert and select a recipient for the email and add a message:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-1.png&quot; alt=&quot;Create an alert dialog in F&amp;amp;O&quot; width=&quot;1117&quot; height=&quot;905&quot; /&gt;&lt;figcaption&gt;Create an alert dialog in F&amp;amp;O&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;So why would anyone duplicate that functionality outside of Dynamics 365 F&amp;amp;O? It’s just my opinion, but the email that the standard sends is terribly awful! Look at this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image.png&quot; alt=&quot;Standard alert email&quot; width=&quot;850&quot; height=&quot;398&quot; /&gt;&lt;figcaption&gt;Standard alert email&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Plus, you can’t customize it! Not even brand it with your company logo. Other than adding a fixed message in the Message field when creating the alert, you can’t add template placeholders as we do in the email templates for example.&lt;/p&gt;
&lt;p&gt;In the past, I raised a question about this in the &lt;a href=&quot;https://experience.dynamics.com/insider&quot;&gt;Dynamics Insider Program&lt;/a&gt; group and was told to create an idea, so I did that, &lt;a href=&quot;https://experience.dynamics.com/ideas/idea/?ideaid=db827452-84ce-e911-b083-0003ff689d13&quot;&gt;here’s the idea&lt;/a&gt;. But after almost 4 years, I’m not very confident this will be implemented, specially seeing we can do it using Power Automate.&lt;/p&gt;
&lt;h2&gt;Alerts Business events&lt;/h2&gt;
&lt;p&gt;We have two batches for the alerts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change-based alerts: these include create, delete or update events.&lt;/li&gt;
&lt;li&gt;Due date alerts: triggered by a date going over a limit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both types are also available as Business events under the “Alerts” category. When an alert is triggered, it will emit a Business event of the same type as the alert.&lt;/p&gt;
&lt;p&gt;However, in order for this to work, you need to mark the “Send externally” field when configuring the alert.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-3.png&quot; alt=&quot;Alert configured with the send externally field&quot; width=&quot;490&quot; height=&quot;865&quot; /&gt;&lt;figcaption&gt;Alert configured with the send externally field&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Before continuing, let’s create an alert on the customer groups form. Select the field “Terms of payment”, the event “has changed”, and in the “Alert me with” tab enable the “Send externally” field. Just like in the image above.&lt;/p&gt;
&lt;h2&gt;Power automate&lt;/h2&gt;
&lt;p&gt;Now we’ll configure the flow. Start by creating a new automated cloud flow, and in the dialog that will open, press the skip button.&lt;/p&gt;
&lt;p&gt;As a trigger, search for “Fin &amp;amp; Apps” and you should see the “When a Business Event occurs” trigger. Select your instance, Alerts in the category field, When an alert rule is triggered in the business event field, and the company where you configured the alert in the Legal entity field. In my case, that’s Contoso’s USMF.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-4.png&quot; alt=&quot;When a business event occurs&quot; width=&quot;609&quot; height=&quot;221&quot; /&gt;&lt;figcaption&gt;When a business event occurs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This trigger will return a JSON body, we need to parse it to be able to have all the JSON members as a separate field in the dynamic fields’ dialog in the flow designer.&lt;/p&gt;
&lt;p&gt;Add a Parse JSON action, and as the Content field, select the body that’s returning the trigger. Now we must define the schema. The schema is a definition that tells the parse block which fields are expected, and its type. Instead of doing this by hand, we will use the “Generate from sample” button.&lt;/p&gt;
&lt;p&gt;Save the flow as it is, and go back to the Business Events workspace. Select the BusinessEventsAlertEvent business event and click the “Download schema” link on the right pane:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-5.png&quot; alt=&quot;Download F&amp;amp;O business event schema&quot; width=&quot;749&quot; height=&quot;579&quot; /&gt;&lt;figcaption&gt;Download F&amp;amp;O business event schema&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This will download a JSON file with sample data. Open it and copy the content. Then go back to the Power Automate designer, click the “Generate from sample” button and paste the content of the file into the dialog that opens, and finally click the Done button.&lt;/p&gt;
&lt;p&gt;This will generate the schema. Now we need to design a nice email, because that was what I didn’t like in the first place, right? And how will we do this? With our old friends &lt;a href=&quot;https://ariste.info/2020/09/workflow-approval-teams-adaptive-cards/&quot;&gt;adaptive card&lt;/a&gt;s!&lt;/p&gt;
&lt;p&gt;Go to the &lt;a href=&quot;https://adaptivecards.io/designer/&quot;&gt;adaptive cards designer&lt;/a&gt;, select “Outlook Actionable Messages”and design the one that makes you happy.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-6.png&quot; alt=&quot;Outlook email adaptive card&quot; width=&quot;958&quot; height=&quot;508&quot; /&gt;&lt;figcaption&gt;Outlook email adaptive card&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In my case I’ve added a logo in the top left corner, a title in bold large size, a text field and an OpenUrl button.&lt;/p&gt;
&lt;p&gt;In the adaptive cards designer, you can copy and paste the JSON structure of the card from the “Card payload editor” on the lower part of the page. We will use that later, but first we will create the text that will be displayed on the card.&lt;/p&gt;
&lt;p&gt;Add a new compose block after the Parse JSON action, and write what you want to display in the email. In my case, I’ve done this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-7.png&quot; alt=&quot;Adaptive card text in a power automate compose block&quot; width=&quot;600&quot; height=&quot;178&quot; /&gt;&lt;figcaption&gt;Adaptive card text in a power automate compose block&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The TableLabel field (which comes from the Parse JSON action) will display the name of the table. The KeyValue1 and KeyValue2 fields display what we define in the TitleField1 and TitleField2 of the table, in the case of the CustGroup table the CustGroup and Name fields.&lt;/p&gt;
&lt;p&gt;Next, add another compose block. Here we will use the JSON content of the adaptive card. But wait a second, we can’t just paste it! In order for the email to display the content correctly, we must wrap the JSON content inside this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;application/adaptivecard+json&quot;&amp;gt;
JSON CONTENT
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we should have this in this compose block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;application/adaptivecard+json&quot;&amp;gt;
{
    &quot;type&quot;: &quot;AdaptiveCard&quot;,
    &quot;$schema&quot;: &quot;http://adaptivecards.io/schemas/adaptive-card.json&quot;,
    &quot;version&quot;: &quot;1.4&quot;,
    &quot;body&quot;: [
        {
            &quot;type&quot;: &quot;ImageSet&quot;,
            &quot;images&quot;: [
                {
                    &quot;type&quot;: &quot;Image&quot;,
                    &quot;size&quot;: &quot;Small&quot;,
                    &quot;url&quot;: &quot;URL_OF_LOGO&quot;
                }
            ],
            &quot;imageSize&quot;: &quot;Small&quot;
        },
        {
            &quot;type&quot;: &quot;Container&quot;,
            &quot;items&quot;: [
                {
                    &quot;type&quot;: &quot;TextBlock&quot;,
                    &quot;text&quot;: &quot;SUBJECT_FIELD_PARSE_JSON&quot;,
                    &quot;wrap&quot;: true,
                    &quot;id&quot;: &quot;Title&quot;,
                    &quot;weight&quot;: &quot;Bolder&quot;,
                    &quot;size&quot;: &quot;Large&quot;,
                    &quot;fontType&quot;: &quot;Default&quot;
                }
            ]
        },
        {
            &quot;type&quot;: &quot;Container&quot;,
            &quot;items&quot;: [
                {
                    &quot;type&quot;: &quot;TextBlock&quot;,
                    &quot;text&quot;: &quot;OUTPUT_FROM_TEXT&quot;,
                    &quot;wrap&quot;: true,
                    &quot;id&quot;: &quot;Text&quot;
                }
            ]
        },
        {
            &quot;type&quot;: &quot;Container&quot;,
            &quot;items&quot;: [
                {
                    &quot;type&quot;: &quot;ActionSet&quot;,
                    &quot;actions&quot;: [
                        {
                            &quot;type&quot;: &quot;Action.OpenUrl&quot;,
                            &quot;title&quot;: &quot;Go to record&quot;,
                            &quot;id&quot;: &quot;OpenBtn&quot;,
                            &quot;url&quot;: &quot;LINK_FIELD_PARSE_JSON&quot;
                        }
                    ],
                    &quot;id&quot;: &quot;OpenBtn&quot;
                }
            ]
        }
    ]
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are 4 places where you should change the values:&lt;/p&gt;
&lt;p&gt;The URL of your logo:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-8.png&quot; alt=&quot;Logo URL in adaptive card&quot; width=&quot;631&quot; height=&quot;479&quot; /&gt;&lt;figcaption&gt;URL&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The Subject field that returns the Parse JSON step:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-10.png&quot; alt=&quot;The Subject field that returns the Parse JSON step&quot; width=&quot;604&quot; height=&quot;342&quot; /&gt;&lt;figcaption&gt;Mail subject&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The output of the compose action where you wrote the body of the email:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-9.png&quot; alt=&quot;The output of the compose action where you wrote the body of the email&quot; width=&quot;605&quot; height=&quot;266&quot; /&gt;&lt;figcaption&gt;Mail body&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And finally, the Link field from the Parse JSON step:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-11.png&quot; alt=&quot;The Link field from the Parse JSON step&quot; width=&quot;615&quot; height=&quot;402&quot; /&gt;&lt;figcaption&gt;Link to record&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Finally, add a “Send an email (V2)” action and complete the To field with the people that should receive the email. This value doesn’t come in the JSON output from the Business event, you must add the recipients by hand. This might seem worse than adding the emails in the alert inside F&amp;amp;O, but you can always use distribution lists or email groups instead of adding all the addresses.&lt;/p&gt;
&lt;p&gt;Then in the Subject field select the Subject field from the Parse JSON action, and in the Body field add the Outputs of the adaptive card compose action, the last one we did.&lt;/p&gt;
&lt;p&gt;Save the flow, go to the customer groups form and change the terms of payment value of any record. The flow will trigger, and you should get an email that looks like this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/07/image-12.png&quot; alt=&quot;Adaptive card email&quot; width=&quot;849&quot; height=&quot;424&quot; /&gt;&lt;figcaption&gt;Alert email&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I know, it’s not much nicer than the standard one, but thanks to adaptive cards we can make it even better. It’s just that I’ve not done it better.&lt;/p&gt;
&lt;p&gt;In conclusion, using Power Automate along the alert framework in Dynamics 365 F&amp;amp;O not only enhances the versatility and utility of alerts, but also provides opportunities to create more personalized and visually pleasing notifications.&lt;/p&gt;</content:encoded><category>Power Platform &amp; Dataverse</category></item><item><title>ISV License Generator v0.6 released</title><link>https://ariste.info/2023/03/isv-license-generator-v0-6-released/</link><guid isPermaLink="true">https://ariste.info/2023/03/isv-license-generator-v0-6-released/</guid><description>Yesterday I released a new version of ISV License Generator with full support for the SHA 256 hash function after fixing an issue with it. ISVLicenseGenerator is not needed anymore and won’t be maintained. As of version 10.0.43, and since 10.0.38, it’s possible to generate a license file using AXUtil using a HSM backed certificate:</description><pubDate>Thu, 16 Mar 2023 09:33:31 GMT</pubDate><content:encoded>&lt;p&gt;Yesterday I released a new version of &lt;a href=&quot;https://github.com/aariste/ISVLicenseGenerator&quot;&gt;ISV License Generator&lt;/a&gt; with full support for the SHA-256 hash function after fixing an issue with it.&lt;/p&gt;
&lt;div&gt;&lt;p&gt;Warning&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ISVLicenseGenerator is not needed anymore and won’t be maintained.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As of version 10.0.43, and since 10.0.38, it’s possible to generate a license file using AXUtil using a HSM-backed certificate: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&quot;&gt;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/isv-licensing#create-a-package-and-generate-a-customer-specific-license&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;What’s new in ISV License Generator?&lt;/h2&gt;
&lt;p&gt;In this new version the support to generate Dynamics 365 Finance and Operations licenses using the SHA-1 hash has been removed, and it will use SHA-256 by default as the AXUtil tool does.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/03/image.png&quot; alt=&quot;ISV License Generator v0.6&quot; width=&quot;582&quot; height=&quot;341&quot; /&gt;&lt;figcaption&gt;ISV License Generator&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Remember this is an open-source tool released under the MIT License which uses a custom version of the AXUtil library to be able to use it from a Windows application.&lt;/p&gt;
&lt;p&gt;If you want to learn more about this tool, you can read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2019/12/create-an-isv-license-from-a-cryptographic-usb-token/&quot;&gt;Create an ISV license from a cryptographic USB token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2020/08/isv-license-generator-sha-2-support/&quot;&gt;ISV License Generator v0.3 with SHA-2 support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why is this important?&lt;/h2&gt;
&lt;p&gt;Due to the &lt;a href=&quot;https://cabforum.org/2022/04/06/ballot-csc-13-update-to-subscriber-key-protection-requirements/&quot;&gt;Ballot CSC-13&lt;/a&gt; of the CA/B Forum, code signing certificates will stop being shipped as software certificates, and all certificates will have to be store in a hardware crypto device which is FIPS compliant.&lt;/p&gt;
&lt;p&gt;In the past we could use software certificates and the AXUtil tool to generate licenses, but if we had our code signing certificates in a cryptographic USB, we could not use it. And that’s why I created this tool.&lt;/p&gt;
&lt;p&gt;So if you’re currently signing licenses for your ISVs with a software certificate, you can still use the AXUtil tool. But when it expires, and you have to renew it, you’ll get it in a cryptographic hardware device, and then you can use ISV License Generator to create your licenses!!&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://www.linkedin.com/in/jatomas/&quot;&gt;Juan Antonio Tomás&lt;/a&gt; for making me notice about this change in signing certificates!&lt;/p&gt;
&lt;p&gt;You can download the latest version of &lt;a href=&quot;https://github.com/aariste/ISVLicenseGenerator/releases/tag/0.6&quot;&gt;ISVLicenseGenerator v0.6&lt;/a&gt; on GitHub.&lt;/p&gt;</content:encoded><category>Development</category></item><item><title>Create a Dynamics 365 F&amp;O encrypted field</title><link>https://ariste.info/2023/03/create-dynamics-365-encrypted-field/</link><guid isPermaLink="true">https://ariste.info/2023/03/create-dynamics-365-encrypted-field/</guid><description>Today we have a quick and easy X++ post where we’ll learn how to create an encrypted field in Dynamics 365 Finance and Operations. Creating an encrypted field in Dynamics 365 Finance and Operations is a simple process that can help secure sensitive information in your application. By using an encryption key to encrypt the</description><pubDate>Wed, 08 Mar 2023 12:02:53 GMT</pubDate><content:encoded>&lt;p&gt;Today we have a quick and easy X++ post where we’ll learn how to create an encrypted field in Dynamics 365 Finance and Operations.&lt;/p&gt;
&lt;p&gt;Creating an encrypted field in Dynamics 365 Finance and Operations is a simple process that can help secure sensitive information in your application. By using an encryption key to encrypt the data, it ensures that the data remains safe even if it is accessed by unauthorized users.&lt;/p&gt;
&lt;p&gt;By encrypted field I mean what you see, for example, in the Email parameters form for the SMTP password:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-16.png&quot; alt=&quot;Email parameters encrypted field&quot; width=&quot;825&quot; height=&quot;335&quot; /&gt;&lt;figcaption&gt;Email parameters encrypted field&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;Encrypted fields&lt;/h2&gt;
&lt;p&gt;These encrypted fields aren’t stored as plain text in the database, instead they’re encrypted (as its name suggests) using a key and that value is saved.&lt;/p&gt;
&lt;p&gt;It’s worth mentioning that each environment has a different encryption key, so when you &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/database/database-refresh?WT.mc_id=BA-MVP-5003976#these-elements-are-removed-for-all-database-refresh-operations&quot;&gt;refresh a sandbox or development environment with data from another environment, the values of encrypted fields are lost&lt;/a&gt; because they can’t be unencrypted with a different key.&lt;/p&gt;
&lt;h2&gt;Creating it&lt;/h2&gt;
&lt;p&gt;You need a new field in a form, you know that the first step is creating it in a table. For this example, I’ll be adding a field to the CustParameters table and form.&lt;/p&gt;
&lt;p&gt;So extend the table in your &lt;a href=&quot;https://ariste.info/2021/11/package-model-planning/&quot;&gt;model&lt;/a&gt; and go to the EDTs in the AOT and drag and drop the EncryptedField EDT into the CustParameters table extension. Give it the name you want, I’ll name mine AASEncryptedField, and let’s continue.&lt;/p&gt;
&lt;p&gt;Next, you need to create a code extension for the CustParameters table. Because encrypted fields are shown on forms as an edit field, we need a method to encrypt and decrypt the content of the field, and that’s what we’ll do here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ExtensionOf(tableStr(CustParameters))]
final class CustParameters_aristeinfo_Extension
{
    public edit Name AASEncryptedNameEdit(boolean _set, Name _value)
    {
        return Global::editEncryptedField(this, _value, fieldNum(CustParameters, AASEncryptedField), _set);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a regular edit method, but it’s calling Global’s &lt;em&gt;editEncryptedField&lt;/em&gt; method and there it’s calling Appl’s class &lt;em&gt;EncryptForPurpose&lt;/em&gt; and &lt;em&gt;DecryptForPurpose&lt;/em&gt; kernel methods that do the job.&lt;/p&gt;
&lt;p&gt;Defining an EDT as the return type in the method will help us display the right label in the form, so create your EDTs or use one that fits your purpose. I’m using the Name one because this is just a demo!&lt;/p&gt;
&lt;p&gt;Finally, we’ll add a string field in the form and set the CustParameters table as its Data Source property, and the &lt;em&gt;CustParameters_aristeinfo_Extension.AASEncryptedNameEdit&lt;/em&gt; method as its Data Method property. If your field’s base data type is not a string, you need to add the correct type of new field!&lt;/p&gt;
&lt;p&gt;Synchronize, compile and let’s take a look at the field in the UI:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-17.png&quot; alt=&quot;That doesn&apos;t look like it&apos;s encrypted...&quot; width=&quot;927&quot; height=&quot;410&quot; /&gt;&lt;figcaption&gt;That doesn’t look like it’s encrypted…&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Ooops, I forgot about something! You need to set the form field’s property &lt;em&gt;Password style&lt;/em&gt; to Yes! Otherwise, it’ll only show plain text. Let me change that…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-18.png&quot; alt=&quot;Encrypted field with password style&quot; width=&quot;928&quot; height=&quot;407&quot; /&gt;&lt;figcaption&gt;Encrypted field with password style&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;That’s much more better. You might be wondering… if it was showing plain text before using the password style… was it really encrypted? Yes, it was. If we take a look at the field’s value in SQL Server Management Studio:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-19-1024x438.png&quot; alt=&quot;Encrypted value in SSMS&quot; width=&quot;1024&quot; height=&quot;438&quot; /&gt;&lt;figcaption&gt;Encrypted value in SSMS&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;That’s the encrypted value stored in SQL Server. It was showing plain text because the &lt;em&gt;editEncryptedField&lt;/em&gt; method of the Global class takes care of unencryption. But believe me that in SQL it’s stored as encrypted data! Done!&lt;/p&gt;
&lt;p&gt;And just like that, you created a new encrypted field and added a bit more of security to your customizations. This has been quite easy, and as I always say, if you need to do something that the standard application already does, take a look at it because that’ll be the best way of doing it.&lt;/p&gt;</content:encoded><category>Development</category></item><item><title>Dynamics 365 F&amp;O and firewalls: monitor Azure IP ranges</title><link>https://ariste.info/2023/02/dynamics-365-firewall-monitor-azure-ip/</link><guid isPermaLink="true">https://ariste.info/2023/02/dynamics-365-firewall-monitor-azure-ip/</guid><description>If you’re integrating Dynamics 365 Finance &amp; Operations with 3rd parties, and your organization or the 3rd party one are using a firewall, you might’ve found yourself in the scenario of being asked “which is the production/sandbox IP address?”. Well, we don’t know. We know which IP it has now , but we don’t know</description><pubDate>Thu, 09 Feb 2023 10:52:21 GMT</pubDate><content:encoded>&lt;p&gt;If you’re integrating Dynamics 365 Finance &amp;amp; Operations with 3rd parties, and your organization or the 3rd party one are using a firewall, you might’ve found yourself in the scenario of being asked “which is the production/sandbox IP address?”.&lt;/p&gt;
&lt;p&gt;Well, we don’t know. We know which IP it has &lt;strong&gt;now&lt;/strong&gt;, but we don’t know if it will have the same IP in the future, you will have to monitor this if you plan on opening single IPs. This is something Dag Calafell wrote about on his blog: &lt;a href=&quot;https://calafell.me/static-ip-not-guaranteed-for-dynamics-365-for-finance-and-operations/&quot;&gt;Static IP not guaranteed for Dynamics 365 for Finance and Operations&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/dintre-300x300.png&quot; alt=&quot;I monitor with my eye&quot; width=&quot;300&quot; height=&quot;300&quot; /&gt;&lt;figcaption&gt;I monitor with my eye&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;So, what should I do if I have a firewall and need to allow access to/from Dynamics 365 F&amp;amp;O or any other Azure service? The network team usually doesn’t like the answer: if you can’t allow a FQDN, you should open all the address ranges for the datacenter and service you want to access. And that’s a lot of addresses that make the network team sad.&lt;/p&gt;
&lt;p&gt;In today’s post, I’ll show you a way to keep an eye on the ranges provided by Microsoft, and hopefully make our life easier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: due to &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7029402983624761344?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7029402983624761344%2C7029431447912427520%29&quot;&gt;this LinkedIn comment,&lt;/a&gt; I want to remark that the ranges you can find using this method are for INBOUND communication into Dynamics 365 or whatever service. For outbound communication, check this on Learn: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/deployment/deploymentfaq#for-my-microsoft-managed-environments-i-have-external-components-that-have-dependencies-on-an-explicit-outbound-ip-safe-list-how-can-i-ensure-my-service-is-not-impacted-after-the-move-to-self-service-deployment&quot;&gt;For my Microsoft-managed environments, I have external components that have dependencies on an explicit outbound IP safe list. How can I ensure my service is not impacted after the move to self-service deployment?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Azure IP Ranges: can we monitor them?&lt;/h2&gt;
&lt;p&gt;Microsoft offers a JSON file you can download with the ranges for all its public cloud datacenters and different services. Yes, a file, not an API.&lt;/p&gt;
&lt;p&gt;But wait, don’t complain yet, there IS an API we can use: the &lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/azure?WT.mc_id=BA-MVP-5003976&quot;&gt;Azure REST API&lt;/a&gt;. And specifically the &lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/virtualnetwork/service-tags/list?tabs=HTTP&amp;amp;WT.mc_id=BA-MVP-5003976&quot;&gt;Service Tags section under Virtual Networks&lt;/a&gt;. The results from calling this API or downloading the file are a bit different, the JSON is structured differently, but both could serve our purpose.&lt;/p&gt;
&lt;h2&gt;My proposal: an Azure function&lt;/h2&gt;
&lt;p&gt;We will be querying a REST API, so we could perfectly be using a Power Automate flow to do this. Why am I overdoing things with an Azure function? Because I hate parsing JSON in Power Automate. That’s the main reason, but not the only one. Also because I love &lt;a href=&quot;https://ariste.info/2020/06/azure-functions-dynamics-365/&quot;&gt;Azure functions&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Authentication&lt;/h3&gt;
&lt;p&gt;To authenticate and be able to access the Azure REST API we need to create an Azure Active Directory app registration, we’ve done this a million times, right? No need to repeat it.&lt;/p&gt;
&lt;p&gt;We will need a secret for that app registration too. Keep both. We will create a service principal using it.&lt;/p&gt;
&lt;p&gt;Go to your subscription, to the “Access control (IAM)” section and add a new role assignment:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image.png&quot; alt=&quot;Create a service principal&quot; width=&quot;1192&quot; height=&quot;745&quot; /&gt;&lt;figcaption&gt;Create a service principal&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Select the contributor role and click next:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-1.png&quot; alt=&quot;Contributor role&quot; width=&quot;743&quot; height=&quot;583&quot; /&gt;&lt;figcaption&gt;Contributor role&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Click the select members text and look for the name of the app registration you created before:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-2-1024x725.png&quot; alt=&quot;Add members&quot; width=&quot;1024&quot; height=&quot;725&quot; /&gt;&lt;figcaption&gt;Add members&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Finally, click the “Select” button, and the “Review + assign” one to end. Now we have a service principal with access to a subscription. We will use this to authenticate and access the Azure REST API.&lt;/p&gt;
&lt;h3&gt;The function&lt;/h3&gt;
&lt;p&gt;The function will have an HTTP trigger, and will read and write data in an &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/storage/tables/table-storage-overview?WT.mc_id=BA-MVP-5003976&quot;&gt;Azure Table&lt;/a&gt;. We will do a POST call to the function’s endpoint, and that will trigger the process.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-3.png&quot; alt=&quot;Nice diagram&quot; width=&quot;1846&quot; height=&quot;731&quot; /&gt;&lt;figcaption&gt;Nice diagram&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then the function will download the JSON content from the Azure REST API, do several things, like looking into the table to see if we stored a previous version of the addresses file, compare both and save the latest version into the table. This is the code of the function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[FunctionName(&quot;CheckRanges&quot;)]
        public static async Task&amp;lt;IActionResult&amp;gt; Run(
            [HttpTrigger(AuthorizationLevel.Function, &quot;post&quot;, Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation(&quot;C# HTTP trigger function processed a request.&quot;);

            string ret = string.Empty;

            try
            {
                string body = String.Empty;

                using (StreamReader streamReader = new StreamReader(req.Body))
                {
                    body = await streamReader.ReadToEndAsync();
                }

                if (string.IsNullOrEmpty(body))
                {
                    throw new Exception(&quot;No request body found.&quot;);
                }

                dynamic data = JsonConvert.DeserializeObject(body);
                string serviceTagRegion = data.serviceTagRegion;
                string region = data.region;

                if (string.IsNullOrEmpty(serviceTagRegion) || string.IsNullOrEmpty(region))
                {
                    throw new Exception(&quot;The values in the cannot be empty.&quot;);
                }

                // Get token and call the API
                var token = GetToken().Result;

                var latestServiceTag = GetFile(token, region).Result;

                if (latestServiceTag is null)
                {
                    throw new Exception(&quot;No tag file has been downloaded.&quot;);
                }

                // Download existing file from the blob, if exists, and compare the root changeNumber                
                var existingServiceTagEntity = await ReadTableAsync();

                // If there&apos;s a file in the blob container we retrieve it and compare the changeNumber value. If it&apos;s the same there&apos;s no changes in the file.
                if (existingServiceTagEntity is not null)
                {
                    if (existingServiceTagEntity.ChangeNumber == latestServiceTag.changeNumber)
                    {
                        // Return empty containers in the JSON file
                        AddressChanges diff = new AddressChanges();

                        diff.addedAddresses = Array.Empty&amp;lt;string&amp;gt;();
                        diff.removedAddresses = Array.Empty&amp;lt;string&amp;gt;(); ;

                        ret = JsonConvert.SerializeObject(diff);

                        log.LogInformation(&quot;The downloaded file has the same changenumber as the already existing one. No changes.&quot;);

                        // Return empty JSON containers
                        return new OkObjectResult(ret);
                    }
                }

                // Process the new file
                var serviceTagSelected = latestServiceTag.values.FirstOrDefault(st =&amp;gt; st.name.ToLower() == serviceTagRegion);

                if (serviceTagSelected is not null)
                {
                    ServiceTagAddresses addresses = new ServiceTagAddresses();

                    addresses.rootchangenumber = latestServiceTag.changeNumber;
                    addresses.nodename = serviceTagSelected.name;
                    addresses.nodechangenumber = serviceTagSelected.properties.changeNumber;
                    addresses.addresses = serviceTagSelected.properties.addressPrefixes;

                    // If a file exists in the table get the differences
                    if (existingServiceTagEntity is not null)
                    {
                        string[] existingAddresses = JsonConvert.DeserializeObject&amp;lt;string[]&amp;gt;(existingServiceTagEntity.Addresses);

                        ret = CompareAddresses(existingAddresses, addresses.addresses);
                    }

                    // Finally upload the file with the new addresses
                    var newAddressJson = JsonConvert.SerializeObject(addresses);

                    //await UploadFileAsync(fileName, newAddressJson);
                    await WriteToTableAsync(addresses);
                }
                else
                {
                    AddressChanges diff = new AddressChanges();

                    diff.addedAddresses = Array.Empty&amp;lt;string&amp;gt;();
                    diff.removedAddresses = Array.Empty&amp;lt;string&amp;gt;();

                    ret = JsonConvert.SerializeObject(diff);

                    // Return empty JSON containers
                    return new OkObjectResult(ret);
                }
            }
            catch (Exception ex)
            {
                return new BadRequestObjectResult(ex.Message);
            }

            return new OkObjectResult(ret);
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve added comments to the code to make it clearer, but we’ll go a bit through it. You can find the complete Visual Studio solution in the &lt;a href=&quot;https://github.com/aariste/AzureTagsIPWatcher/&quot;&gt;AzureTagsIPWatcher GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Environment variables&lt;/h3&gt;
&lt;p&gt;You will find this piece of code several times in the code. This is used to retrieve the environment variables:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Environment.GetEnvironmentVariable(&quot;ContainerName&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It means that once you’ve deployed the function, you need to create as many different application settings in the Azure function as different variables you’ll find in the code. The variables are used to save the value of keys, secrets, etc. to create the connections to storage and get the token credentials. These are the ones I have used, and you should complete with your own values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ContainerConn&lt;/strong&gt;: the connection string to your Azure storage account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ContainerName&lt;/strong&gt;: the name of your storage account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StorageKey&lt;/strong&gt;: the key for your storage account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;StorageTable&lt;/strong&gt;: the name of the table.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;appId&lt;/strong&gt;: your app registration id.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;secret&lt;/strong&gt;: the secret in your app registration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tokenUrl&lt;/strong&gt;: this will be the URL used to get the token. You need to change YOUR_TENANT_ID by the id of your tenant: “&lt;code&gt;https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/token&lt;/code&gt;”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;resource&lt;/strong&gt;: “&lt;a href=&quot;https://management.azure.com/%E2%80%9D&quot;&gt;https://management.azure.com/”&lt;/a&gt;, this is the root URL we will authenticate to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;apiUrl&lt;/strong&gt;: “&lt;code&gt;https://management.azure.com/subscriptions/YOUR_SUBSCRIPTION_ID/providers/Microsoft.Network/locations/{0}/serviceTags?api-version=2022-07-01&lt;/code&gt;”, and this is the API endpoint URL. You need to change YOUR_SUBSCRIPTION_ID for the id of the subscription in which you’ve created the service principal.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;HTTP call&lt;/h3&gt;
&lt;p&gt;The function is expecting a POST call with a JSON body in its request, containing the node we want to get the addresses from, and also the region:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;serviceTagRegion&quot;: &quot;azurecloud.westeurope&quot;,
    &quot;region&quot;: &quot;westeurope&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Function response&lt;/h3&gt;
&lt;p&gt;The function will return a JSON file with two containers, one for added addresses if any, and one for removed addresses:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;removedAddresses&quot;:[],
    &quot;addedAddresses&quot;:[]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Using the function&lt;/h2&gt;
&lt;p&gt;Now we have an Azure function with an HTTP trigger that returns a JSON string with two nodes with the changed addresses, or empty nodes if no changes exist.&lt;/p&gt;
&lt;p&gt;How can we trigger this function on a schedule? With Power Automate, of course! We can use a scheduled cloud flow, that runs once a day, or as many times as you want.&lt;/p&gt;
&lt;p&gt;Once we have a trigger, we will call the function. I like to create “Compose” blocks where I “initialize” the value of several “variables”. They’re not variables, but you’re getting what I mean, right?&lt;/p&gt;
&lt;p&gt;In this first compose block I just add the body of the function:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-4.png&quot; alt=&quot;Function body&quot; width=&quot;614&quot; height=&quot;158&quot; /&gt;&lt;figcaption&gt;Function body&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then we add an HTTP action, we set it to the POST method and add the function URL, and in the body the output of the previous compose block:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-5.png&quot; alt=&quot;Calling the Azure function&quot; width=&quot;615&quot; height=&quot;367&quot; /&gt;&lt;figcaption&gt;Calling the Azure function&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The next step will be parsing the response of the function. It will always return the containers, filled in or empty, so you can use this as the payload to generate the schema. And finally add the Body output of the function:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-6.png&quot; alt=&quot;Parsing the function output&quot; width=&quot;614&quot; height=&quot;347&quot; /&gt;&lt;figcaption&gt;Parsing the function output&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then I will create two more “Compose” blocks where I’ll save the output values of parsing the JSON, one for the added addresses and one for the removed ones:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-7.png&quot; alt=&quot;Values from the JSON parsing&quot; width=&quot;609&quot; height=&quot;256&quot; /&gt;&lt;figcaption&gt;Values from the JSON parsing&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And now I add a condition where we’ll check whether there’s anything inside the new or deleted arrays. How can we check this? We will use an expression in the value field to check if the container’s length is 0:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-13.png&quot; alt=&quot;Check if the arrays are empty&quot; width=&quot;689&quot; height=&quot;357&quot; /&gt;&lt;figcaption&gt;Check if the arrays are empty&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And if it’s 0 (“If yes” branch) we’ll do nothing, and if it contains elements (“If no” branch) we’ll send an email:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-10.png&quot; alt=&quot;Outcomes&quot; width=&quot;1301&quot; height=&quot;342&quot; /&gt;&lt;figcaption&gt;Outcomes&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;Let’s test it!&lt;/h2&gt;
&lt;p&gt;To test it, I’ll go into my Azure Table and edit the latest entry it has, because I’ll run the function locally several times. I’ll change the value of the columns ChangeNumber and Addresses.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-11.png&quot; alt=&quot;Changing values&quot; width=&quot;828&quot; height=&quot;195&quot; /&gt;&lt;figcaption&gt;Changing values&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And now run the flow…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-14.png&quot; alt=&quot;Flow run&quot; width=&quot;1336&quot; height=&quot;1017&quot; /&gt;&lt;figcaption&gt;Flow run&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We can see the flow has gone through the “If no” branch in the condition, because the arrays contained changes. And I can see those in the email I’ve received:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/02/image-15.png&quot; alt=&quot;Email from Flow&quot; width=&quot;331&quot; height=&quot;251&quot; /&gt;&lt;figcaption&gt;Email from Flow&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And that’s all! As I said at the beginning, this could also be done solely using a Power automate cloud flow, but I preferred using an Azure function to do the dirty job of comparing values.&lt;/p&gt;
&lt;p&gt;You can find all the code in the &lt;a href=&quot;https://github.com/aariste/AzureTagsIPWatcher&quot;&gt;GitHub project AzureTagsIPWatcher&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Azure</category></item><item><title>2023 Release Wave 1 plans: what’s new?</title><link>https://ariste.info/2023/01/2023-release-wave-1-plans-whats-new/</link><guid isPermaLink="true">https://ariste.info/2023/01/2023-release-wave-1-plans-whats-new/</guid><description>It’s already that time of the year again, the Dynamics 365 and Power Platform 2023 Release Wave 1 plans have just been released! The 2023 release wave 1 for Microsoft Dynamics 365 Finance and Operations is focused on improving the development, administration, and user experiences by removing barriers, tightening integrations, and enhancing cross platform capabilities.</description><pubDate>Wed, 25 Jan 2023 16:35:12 GMT</pubDate><content:encoded>&lt;p&gt;It’s already that time of the year again, the Dynamics 365 and Power Platform 2023 Release Wave 1 plans have just been released!&lt;/p&gt;
&lt;p&gt;The 2023 release wave 1 for Microsoft Dynamics 365 Finance and Operations is focused on improving the development, administration, and user experiences by removing barriers, tightening integrations, and enhancing cross-platform capabilities. This release will bring a range of new features and capabilities that will help improve the performance of the platform and enhance the overall experience for developers, administrators, and end-users.&lt;/p&gt;
&lt;p&gt;The features described here are planned to be delivered from April to September 2023.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/dins-1024x1024.png&quot; alt=&quot;The 2023 release wave 1: I bring fresh features!&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;The 2023 release wave 1: I bring fresh features! by Eva&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2&gt;2023 Release wave 1: Cross-Application Features&lt;/h2&gt;
&lt;p&gt;The features described here will be available for ALL the Dynamics 365 Finance and Operations apps, including Finance, Supply Chain Management, Human Resources,&lt;br /&gt;
Commerce, and Project Operations.&lt;/p&gt;
&lt;h2&gt;Existing features&lt;/h2&gt;
&lt;p&gt;First, I will start with the list of &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/view-updates-client-feature-states-version-10032?WT.mc_id=BA-MVP-5003976&quot;&gt;existing features that will be mandatory or automatically enabled starting on the version 10.0.32&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These features were introduced in the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1?WT.mc_id=BA-MVP-5003976&quot;&gt;2022 release wave 1 plan&lt;/a&gt; and &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave2?WT.mc_id=BA-MVP-5003976&quot;&gt;2022 release wave 2 plan&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Mandatory features with the 10.0.32 release&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/get-started/feature-management/feature-management-overview?WT.mc_id=BA-MVP-5003976#mandatory&quot;&gt;Mandatory&lt;/a&gt; features will be automatically enabled in all your environments when 10.0.32 arrives.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/updates-saved-views-personalization#view-support-for-dialogs?WT.mc_id=BA-MVP-5003976&quot;&gt;Saved views support for dialogs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/updates-saved-views-personalization?WT.mc_id=BA-MVP-5003976#queries-on-views-on-task-pages&quot;&gt;Allow queries to be saved to views on Task Single and Task Double pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/allow-users-select-change-tile-sizes?WT.mc_id=BA-MVP-5003976&quot;&gt;Allow users to select and change tile sizes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/streamlined-tabbing-behavior-full-page-forms?WT.mc_id=BA-MVP-5003976&quot;&gt;Streamline tabbing behavior in full-page forms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/optimize-loading-action-center-notifications?WT.mc_id=BA-MVP-5003976&quot;&gt;Optimize loading of Action center notifications&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Enabled-by-default features with the 10.0.32 release&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/fin-ops/get-started/feature-management/feature-management-overview?WT.mc_id=BA-MVP-5003976#on-by-default-features-optional&quot;&gt;Enabled-by-default&lt;/a&gt; features will be automatically enabled on new environments, but not the ones that are already deployed. If you deploy a new environment on the 10.0.32 version they will be enabled, if you update an existing environment to the 10.0.32 version they won’t.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/updates-saved-views-personalization?WT.mc_id=BA-MVP-5003976#view-support-for-workspaces&quot;&gt;Saved views support for workspaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave1/finance-operations/finance-operations-crossapp-capabilities/enable-system-notification-management?WT.mc_id=BA-MVP-5003976&quot;&gt;Enable system notification management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave2/finance-operations/finance-operations-crossapp-capabilities/extended-grid-aggregation-capabilities?WT.mc_id=BA-MVP-5003976&quot;&gt;Extended grid aggregation capabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave2/finance-operations/finance-operations-crossapp-capabilities/saved-views-performance-enhancement?WT.mc_id=BA-MVP-5003976&quot;&gt;Saved views performance enhancement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;One Dynamics One Platform&lt;/h2&gt;
&lt;p&gt;One of the main features of this release, and it’s something that is becoming a norm for the F&amp;amp;O release notes, is One Dynamics One Platform, which focuses on the &lt;a href=&quot;https://ariste.info/2021/10/dataverse-cross-apps-future-fno/&quot;&gt;convergence plans of Finance and Operations Apps with Microsoft Power Platform&lt;/a&gt;. This will enable customers to take full advantage of Power Platform with each Finance and Operations environment and help improve end-user productivity and enhance the overall user experience.&lt;/p&gt;
&lt;p&gt;Let’s see what’s waiting for us in 2023 in the cross apps section.&lt;/p&gt;
&lt;h3&gt;Improve performance from apps and integrations built on F&amp;amp;O virtual tables&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Link to Learn&lt;/strong&gt;: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/improve-performance-apps-integrations-built-finance-operations-virtual-tables?WT.mc_id=BA-MVP-5003976&quot;&gt;Improve virtual table perfomance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public preview&lt;/strong&gt;: not available&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General availability&lt;/strong&gt;: June 2023&lt;/p&gt;
&lt;p&gt;This feature will offer us an improved latency in API responses between Dataverse and the F&amp;amp;O platform. All integrations that use the F&amp;amp;O &lt;a href=&quot;https://ariste.info/2020/09/dynamics-odata-dataverse-virtual-entities/&quot;&gt;virtual tables&lt;/a&gt; on Dataverse will perform better.&lt;/p&gt;
&lt;h3&gt;Ensure consistency in service requests between Finance and Operations apps and Microsoft Dataverse&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Link to Learn&lt;/strong&gt;: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/ensure-consistency-service-requests-between-finance-operations-apps-dataverse?WT.mc_id=BA-MVP-5003976&quot;&gt;Ensure consistency in service requests&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public preview&lt;/strong&gt;: February 2023&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General availability&lt;/strong&gt;: June 2023&lt;/p&gt;
&lt;p&gt;If you’re a member of the Dynamics 365 Insider Program, you’ve probably heard about &lt;strong&gt;One Transaction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;One Transaction will allow X++ developers to have X++ code and calls to Dataverse inside a single transaction, thanks to the &lt;a href=&quot;https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/iorganizationservice-interface?WT.mc_id=BA-MVP-5003976&quot;&gt;IOrganizationService API&lt;/a&gt;. This will enable commits and rollbacks to be consistent between F&amp;amp;O and Dataverse. And this will also work with transactions originating in Dataverse!&lt;/p&gt;
&lt;h3&gt;Simplify finance and operations apps privileged access to Microsoft Dataverse&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Link to Learn&lt;/strong&gt;: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/simplify-finance-operations-apps-privileged-access-to-microsoft-dataverse?WT.mc_id=BA-MVP-5003976&quot;&gt;Simplify access to Dataverse by Finance and Operations apps&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public preview&lt;/strong&gt;: February 2023&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General availability&lt;/strong&gt;: June 2023&lt;/p&gt;
&lt;p&gt;More IOrganizationService API. In this case to simplify the process of granting access to the IOrganizationService API for Finance and Operations apps. The administrator of the Dataverse environment will be able to give these apps administrative access to send requests to the API, which is connected to the Dataverse environment via the Microsoft Power Platform integration.&lt;/p&gt;
&lt;h3&gt;Enable background jobs in Dataverse of One Batch&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Link to Learn&lt;/strong&gt;: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/enable-background-jobs-dataverse-one-batch?WT.mc_id=BA-MVP-5003976&quot;&gt;Enable background jobs in Dataverse of One Batch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public preview&lt;/strong&gt;: June 2023&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General availability&lt;/strong&gt;: July 2023&lt;/p&gt;
&lt;p&gt;This will be the phase 2 of the feature (&lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365-release-plan/2022wave2/finance-operations/finance-operations-crossapp-capabilities/one-batch--enable-background-jobs-dataverse?WT.mc_id=BA-MVP-5003976&quot;&gt;this was phase 1&lt;/a&gt;) will prepare the platform for enabling Finance and Operations customers to use the Background job capabilities in Dataverse.&lt;/p&gt;
&lt;p&gt;Power automate Flows to execute batch jobs, that’ll be nice to see.&lt;/p&gt;
&lt;h3&gt;Use IP-based cookie binding in finance and operations apps to block cookie replay attacks&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Link to Learn&lt;/strong&gt;: &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/finance-operations/finance-operations-crossapp-capabilities/use-ip-based-cookie-binding-finance-operations-apps-block-cookie-replay-attacks?WT.mc_id=BA-MVP-5003976&quot;&gt;Block cookie replay attacks with IP-based cookie binding&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public preview&lt;/strong&gt;: May 2023&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General availability&lt;/strong&gt;: July 2023&lt;/p&gt;
&lt;p&gt;This is a security feature, maybe it looks like it has nothing to do with the Uno project or One Dynamics One Platform, but it has. It’s always nice to have better security features for the ERP, in this case to prevent cookie replay attacks, &lt;a href=&quot;https://learn.microsoft.com/en-us/power-platform/admin/block-cookie-replay-attack?WT.mc_id=BA-MVP-5003976&quot;&gt;something that’s already available for Dataverse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A cookie replay attack is done by getting an auhtentication cookie or token that has been issued by a server to your browser. Somebody could use that to impersonate you and gain access to the system.&lt;/p&gt;
&lt;p&gt;The feature will help prevent this by offering an option to add the IP address of your computer with that cookie to block the access to F&amp;amp;O apps.&lt;/p&gt;
&lt;p&gt;And that’s all regarding the new cross apps features. There’s also many cool other features in the Finance and SCM spaces, like data archival and purging, invoice automation or the tax rate API. If you want to learn more about all of them, you can go to the &lt;a href=&quot;https://learn.microsoft.com/en-us/dynamics365/release-plan/2023wave1/?WT.mc_id=BA-MVP-5003976&quot;&gt;Learn page of 2023 release wave 1 plan&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Dataverse, dataverse, dataverse, dataverse&lt;/h2&gt;
&lt;p&gt;(Read it with the same rythm as &lt;a href=&quot;https://www.youtube.com/watch?v=KMU0tzLwhbE&quot;&gt;Developers, developers, developers, developers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;As I said earlier, all the new platform features point to the same direction: Dataverse.&lt;/p&gt;
&lt;p&gt;The 2023 release wave 1 plan for Finance and Operations apps includes the One Dynamics One Platform project, which aims to converge the Finance and Operations apps with Microsoft Power Platform and Dataverse.&lt;/p&gt;
&lt;p&gt;This will enable customers to take full advantage of Power Platform with each Finance and Operations environment, helping to improve end-user productivity and enhance the overall user and admin experience.&lt;/p&gt;</content:encoded><category>Community &amp; news</category></item><item><title>Configure Azure Bastion for Dynamics 365 F&amp;O VMs</title><link>https://ariste.info/2023/01/configure-azure-bastion-dynamics-365-vm/</link><guid isPermaLink="true">https://ariste.info/2023/01/configure-azure-bastion-dynamics-365-vm/</guid><description>We’ve been working with the F&amp;O development VMs for a long time, specially Microsoft partners that need to be able to quickly and easily change between different customer environments, and using the VHD is a bit more complicated in that scenario. And of course, we use Remote Desktop Protocol to connect to these VMs. RDP</description><pubDate>Tue, 17 Jan 2023 07:31:33 GMT</pubDate><content:encoded>&lt;p&gt;We’ve been working with the F&amp;amp;O development VMs for a long time, specially Microsoft partners that need to be able to quickly and easily change between different customer environments, and using the VHD is a bit more complicated in that scenario.&lt;/p&gt;
&lt;p&gt;And of course, we use Remote Desktop Protocol to connect to these VMs. RDP is insecure due to its weak encryption, widespread use, and the lack of security features built-in to the protocol. So hackers often target RDP to gain unauthorized access to systems. You can learn more about securing your VMs in &lt;a href=&quot;https://www.microsoft.com/en-us/security/blog/2020/10/07/best-practices-for-defending-azure-virtual-machines/&quot;&gt;Best practices for defending Azure Virtual Machines&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/Gandalf-1024x1024.png&quot; alt=&quot;With Bastion you&apos;re as safe as with Gandalf!&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;With Bastion you’re as safe as with Gandalf!&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Today, we will walk through the steps of configuring Azure Bastion for Dynamics 365 Finance and Operations development VMs.&lt;/p&gt;
&lt;h2&gt;Azure Bastion&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/bastion/bastion-overview?WT.mc_id=BA-MVP-5003976&quot;&gt;Azure Bastion&lt;/a&gt; is a service in Azure that enables you to remotely access your Azure Virtual Machines (VMs) directly from the Azure Portal. This eliminates the need to configure and maintain Remote Desktop (RDP) or Secure Shell (SSH) connections separately.&lt;/p&gt;
&lt;p&gt;It is one solution to fix the RDP vulnerabilities, but if you’re using Microsoft Defender for Cloud, you could also use &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/defender-for-cloud/just-in-time-access-overview?WT.mc_id=BA-MVP-5003976&quot;&gt;JIT access&lt;/a&gt;. As always, deciding between one or another is a matter of your or your customer’s preferences.&lt;/p&gt;
&lt;p&gt;If you’re working at a partner, and you need to configure this in one of your customers, you’ll need access to their Azure subscription, and once it’s configured the people that need to access the VMs will need the following roles in Azure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reader role on the virtual machine.&lt;/li&gt;
&lt;li&gt;Reader role on the NIC with private IP of the virtual machine.&lt;/li&gt;
&lt;li&gt;Reader role on the Azure Bastion resource.&lt;/li&gt;
&lt;li&gt;Reader role on the virtual network of the target virtual machine, if the Bastion deployment is in a peered virtual network, which we’ll see what it is later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Let’s configure it&lt;/h2&gt;
&lt;p&gt;We will create all the resources in the Azure Portal, make sure you can do that before starting.&lt;/p&gt;
&lt;h3&gt;VMs Virtual networks and peering&lt;/h3&gt;
&lt;p&gt;This section is long, but it’s also important because it gives a bit of basic details of virtual networks that must be taken into account when configuring Bastion.&lt;/p&gt;
&lt;p&gt;Azure Bastion provides access via RDP to virtual machines within a virtual network. Of course, there’s something called virtual network peering in Azure that lets us connect two different virtual networks and share the resources in the networks.&lt;/p&gt;
&lt;p&gt;BUT, because there is one big BUT, you can only configure peering between networks with different network addresses. This means that if VNet A has the address space 10.1.0.0/16, and you want to peer it to VNet B, VNet B must have a different address space.&lt;/p&gt;
&lt;p&gt;Let me ask Drake to explain it better:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image.png&quot; alt=&quot;How VNet peering should be&quot; width=&quot;1081&quot; height=&quot;851&quot; /&gt;&lt;figcaption&gt;How VNet peering should be&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And something else: imagine we have VNet A with the address space 10.1.0.0/16, and VNet B with the addressing 10.3.0.0/16, and we configure peering for both. If we wanted to peer a third VNet C with VNet A, it should have a different address space than VNet A and VNet B.&lt;/p&gt;
&lt;p&gt;Or as Drake would say…&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-1.png&quot; alt=&quot;VNet peering with several VNets&quot; width=&quot;1081&quot; height=&quot;858&quot; /&gt;&lt;figcaption&gt;VNet peering with several VNets&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This may seem obvious, but I want to remark this before starting with Bastion because the defaults of a Dynamics 365 F&amp;amp;O development VM from LCS will always create the VM address space as 10.1.0.0/16.&lt;/p&gt;
&lt;p&gt;Fortunately, we can change that in the advanced settings option while deploying a new CHE, and assign the VM VNet to an already existing virtual network.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What would I suggest?&lt;/strong&gt; I would suggest planning things, if you can. This means creating a virtual network in Azure first, and deploying the VMs that need Bastion inside that VNet. If you do this, you can forget about peering.&lt;/p&gt;
&lt;p&gt;If you have your VMs in different virtual networks, there’s a high probability that they have all the same address space. In that case, you can either deploy a bastion for each VM (more expensive) or redeploy the VMs in a single VNet (more time-consuming). It’s up to you.&lt;/p&gt;
&lt;p&gt;If the VNets of the virtual machines are different, we’ll see how to set peering up later.&lt;/p&gt;
&lt;h3&gt;Resources location&lt;/h3&gt;
&lt;p&gt;Another important thing to remember: your NSG, virtual network and Bastion instance must be in the same region. If you’ll be using different virtual networks for Bastion and your VMs, the Bastion VNet can be in a different region than that of your VMs, but it will be the same as the one of the NSG and Bastion.&lt;/p&gt;
&lt;h3&gt;Network security group&lt;/h3&gt;
&lt;p&gt;After this long remark, we can take the first step: create a new Network Security Group (NSG). This NSG will be used in the virtual network of the Bastion we’ll create later.&lt;/p&gt;
&lt;p&gt;This step is mandatory and is described in &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/bastion/bastion-nsg?WT.mc_id=BA-MVP-5003976&quot;&gt;Working with NSG access and Azure Bastion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You need to create the following security rules in the NSG.&lt;/p&gt;
&lt;h4&gt;Inbound&lt;/h4&gt;
&lt;div&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Port&lt;/th&gt;&lt;th&gt;Protocol&lt;/th&gt;&lt;th&gt;Source&lt;/th&gt;&lt;th&gt;Destination&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;443&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;Internet&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;443&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;GatewayManager&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8080, 5701&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;VirtualNetwork&lt;/td&gt;&lt;td&gt;VirtualNetwork&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;443&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;AzureLoadBalancer&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;Outbound&lt;/h4&gt;
&lt;div&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Port&lt;/th&gt;&lt;th&gt;Protocol&lt;/th&gt;&lt;th&gt;Source&lt;/th&gt;&lt;th&gt;Destination&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;22, 3389&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;VirtualNetwork&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;443&lt;/td&gt;&lt;td&gt;TCP&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;AzureCloud&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8080, 5701&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;VirtualNetwork&lt;/td&gt;&lt;td&gt;VirtualNetwork&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;80&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;Any&lt;/td&gt;&lt;td&gt;Internet&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;To get the VirtualNetwork, AzureCloud, Internet, GatewayManager and AzureLoadBalancer values in the source or destination, you need to select “Service tag” in the Source field when creating a new rule.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: even if you’re not using the SSH port (22) you &lt;strong&gt;must&lt;/strong&gt; add it to the outbound rule, otherwise when you apply the NSG to the virtual network you’ll get an error. Believe me, I lost some time until I discovered that…&lt;/p&gt;
&lt;h3&gt;Optional: Create a virtual network&lt;/h3&gt;
&lt;p&gt;If you want, you can create a separate virtual network for Bastion. If you choose to do this, remember to create it with a different address space than the existing virtual network where your VMs are.&lt;/p&gt;
&lt;p&gt;For example, create a new VNet with the 10.10.0.0/16 address space. And, this is important, you must create a subnet named &lt;strong&gt;AzureBastionSubnet&lt;/strong&gt; and with an address range size of at least 26. The minimum subnet size is 26, but it can be something larger, like 25 or 24.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-3.png&quot; alt=&quot;Create virtual network IP Addresses tab with the 10.10.0.0/16 address space and a subnet named AzureBastionSubnet with range 10.10.3.0/26&quot; width=&quot;770&quot; height=&quot;415&quot; /&gt;&lt;/figure&gt;
&lt;h4&gt;Virtual network peering&lt;/h4&gt;
&lt;p&gt;And if you create a different virtual network, for Bastion, we need to create a peering with the one of your VMs. Go to your Bastion VNet, and go to the Peerings section:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-4.png&quot; alt=&quot;Virtual network peerings menu&quot; width=&quot;425&quot; height=&quot;812&quot; /&gt;&lt;figcaption&gt;Virtual network peerings menu&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;In the setup screen, name your links and select the target virtual network:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-5-857x1024.png&quot; alt=&quot;Virtual network peering setup&quot; width=&quot;857&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Virtual network peering setup&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3&gt;Deploy Bastion&lt;/h3&gt;
&lt;p&gt;Now we can deploy the Bastion instance. Firstly, we need to decide with tier we’ll be using. Bastion offers a Basic and a Standard tier, and other than the price they have some differences.&lt;/p&gt;
&lt;p&gt;The Basic tier only allows two instances of Bastion, and you can only connect to the Bastion instance through the browser.&lt;/p&gt;
&lt;p&gt;The Standard tier lets you decide how many instances it has, plus it also allows you to connect to Bastion using your RDP client in your PC, plus other features.&lt;/p&gt;
&lt;p&gt;Depending on how many VMs you need to connect to, and if you want to use your local RDP client you will go with one or another.&lt;/p&gt;
&lt;p&gt;Price-wise, it’s a bit cheaper to deploy 1 standard Bastion instance to access more than 2 VMs, than creating multiple Basic Bastion instances. So if you have 8 VMs, you can create 4 Basic instances or 1 Standard instance, and the Standard one will be a bit less expensive than the Basic ones.&lt;/p&gt;
&lt;p&gt;So go to the portal and create a new instance of Bastion, remember the /26 subnet thing? If the virtual network you choose doesn’t have it, you will get a warning:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-6.png&quot; alt=&quot;Azure Bastion subnet warning&quot; width=&quot;961&quot; height=&quot;488&quot; /&gt;&lt;figcaption&gt;Subnet warning&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You can create it clicking on the “Manage subnet configuration” link, add a new Subnet named &lt;strong&gt;AzureBastionSubnet&lt;/strong&gt;, we already talked about this, and select the NSG we created before here:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-7.png&quot; alt=&quot;Add subnet panel with the name AzureBastionSubnet, address range 10.1.3.0/26 and a bastion network security group&quot; width=&quot;714&quot; height=&quot;1241&quot; /&gt;&lt;/figure&gt;
&lt;p&gt;Go back to the Create a Bastion screen and click on the Review + create button, and Create. It will take like 10 minutes to be ready.&lt;/p&gt;
&lt;h3&gt;Access the VM&lt;/h3&gt;
&lt;p&gt;Now in your VM in Azure, go to Bastion under the Operations section and you’ll see this:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2023/01/image-8.png&quot; alt=&quot;Bastion access&quot; width=&quot;965&quot; height=&quot;593&quot; /&gt;&lt;figcaption&gt;Bastion access&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now you just need to enter the RDP credentials you find in LCS, the username without the “builtin\” part, and click Connect.&lt;/p&gt;
&lt;p&gt;A new browser tab will open, and you’ll see the desktop of your VM. Done!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In conclusion, Azure Bastion is a powerful service that allows you to access your Azure VMs directly from the Azure Portal. By following the steps outlined in this article, you can configure Azure Bastion for your Dynamics 365 F&amp;amp;O VMs and enjoy the convenience and security of this service.&lt;/p&gt;
&lt;p&gt;You will need to give each developer access to the Azure subscription of the dev VMs with the roles we’ve seen before, but it’s a way to avoid using RDP if you have to and secure your workflow.&lt;/p&gt;</content:encoded><category>Azure</category></item><item><title>IaC with Bicep: deploy Azure API Management Architecture</title><link>https://ariste.info/2022/09/bicep-azure-api-management-architecture/</link><guid isPermaLink="true">https://ariste.info/2022/09/bicep-azure-api-management-architecture/</guid><description>In the previous post, we learned about my proposal of an architecture for Azure API Management for integrations. And I closed the post with a pending thing , which was details on how to deploy and configure all the Azure resources the solution uses. And in today’s post we’re going to see how all the</description><pubDate>Tue, 13 Sep 2022 06:14:43 GMT</pubDate><content:encoded>&lt;p&gt;In the previous post, we learned about my proposal of an &lt;a href=&quot;https://ariste.info/2022/09/azure-api-management-architecture/&quot;&gt;architecture for Azure API Management&lt;/a&gt; for integrations. And &lt;strong&gt;I closed the post with a pending thing&lt;/strong&gt;, which was details on &lt;strong&gt;how to deploy and configure all the Azure resources&lt;/strong&gt; the solution uses.&lt;/p&gt;
&lt;p&gt;And in today’s post we’re going to see how all the resources are created, linked together and configured to have a fully working API logging solution, and how we can deploy everything using Bicep.&lt;/p&gt;
&lt;h2&gt;You may also like&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2024/08/apiops-alm-for-azure-api-management/&quot;&gt;APIOps: ALM for Azure API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2022/09/bicep-azure-api-management-architecture/&quot;&gt;IaC with Bicep: deploy Azure API Management Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ariste.info/2022/09/azure-api-management-architecture/&quot;&gt;Azure API Management for your integrations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/Bicep2-1024x1024.png&quot; alt=&quot;Nice Bicep...&quot; width=&quot;1024&quot; height=&quot;1024&quot; /&gt;&lt;figcaption&gt;Nice Bicep…&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And the trick to deploying it is to…&lt;/p&gt;
&lt;h2&gt;Do it manually!&lt;/h2&gt;
&lt;p&gt;Of course! That’s the logic way of doing it! With our keyboards and mouses, clicking buttons and entering names, then looking in Azure for configurations, connection strings, enabling options, etc.&lt;/p&gt;
&lt;p&gt;That’s what I did the first time I set this up, I created all the resources from the Azure portal by hand, changing the configuration of the different elements until I had everything working fine.&lt;/p&gt;
&lt;p&gt;But then I thought that there should be a way of deploying this automatically and if, for example, I had to deploy this in different customers or projects, could reduce the chance of having errors, or just avoid repeating work that can be automated.&lt;/p&gt;
&lt;p&gt;I knew about the ARM templates, and I also knew that I don’t like them a lot. That’s when I remembered I had heard about this new thing…&lt;/p&gt;
&lt;h2&gt;Use Bicep!&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep&amp;amp;WT.mc_id=BA-MVP-5003976&quot;&gt;Bicep is a language&lt;/a&gt; used to deploy Azure resources that we’ll use to create &lt;a href=&quot;https://docs.microsoft.com/en-us/devops/deliver/what-is-infrastructure-as-code?WT.mc_id=DOP-MVP-5003976&quot;&gt;infrastructure-as-code (IaC)&lt;/a&gt; solutions.&lt;/p&gt;
&lt;p&gt;One of the benefits of using IaC is that it helps automate resource deployments, and helps us to make sure that we deploy the same resources each time our code is run. The same code will always be deployed as the same infrastructure.&lt;/p&gt;
&lt;p&gt;To deploy all the resources, you need an Azure subscription. Remember, you can &lt;a href=&quot;https://azure.microsoft.com/es-es/free/search?WT.mc_id=DOP-MVP-5003976&quot;&gt;register and get one free and with $200 of credit for 30 days&lt;/a&gt;, and that several services are always free of charge.&lt;/p&gt;
&lt;p&gt;Bicep uses declarative syntax, which means that we describe what we want the code to do, and how the code does it is a problem of the compiler. I personally find it much easier to read than the JSON from the ARM templates.&lt;/p&gt;
&lt;p&gt;And starting using it is really easy, you just need to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/install?WT.mc_id=BA-MVP-5003976&quot;&gt;install the Bicep tools&lt;/a&gt;, which if you’re using Visual Studio Code is as easy as installing an extension.&lt;/p&gt;
&lt;p&gt;Then imagine you want to deploy a new storage account to your Azure subscription. We’ll write the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource aristeinfoStorageAccount &apos;Microsoft.Storage/storageAccounts@2021-09-01&apos; = {
  name: &apos;aristeinfostorage&apos;
  location: &apos;westeurope&apos;
  kind: &apos;StorageV2&apos;
  sku: {
    name: &apos;Standard_LRS&apos;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use the VS Code extension it’s almost as easy as writing .NET code, hitting the tab key 🙊 Joking… let’s take a look at the different parts of the code.&lt;/p&gt;
&lt;p&gt;All resources are declared using the &lt;em&gt;resource&lt;/em&gt; keyword, then &lt;em&gt;aristeinfoStorageAccount&lt;/em&gt; would be like the variable name for that resource, and you can use it later in your code to access its members, just like an object in any OOP language. And what comes next, the ‘&lt;em&gt;Microsoft.Storage/storageAccounts@2021-09-01&lt;/em&gt;‘ piece, is the type of resource, which the extension helps us look for it, you don’t need to remember how all resources are named.&lt;/p&gt;
&lt;p&gt;Then inside the curly brackets we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;name&lt;/strong&gt;, which will be the name of the resource in Azure.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;location&lt;/strong&gt;, is the region where the resource is deployed to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kind&lt;/strong&gt;, is the version of the storage account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sku&lt;/strong&gt;, the tier of the resource.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, different types of resources have different elements in its setup, and different properties.&lt;/p&gt;
&lt;p&gt;Before deploying the file, make sure you have installed both the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep&quot;&gt;Bicep&lt;/a&gt; and &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account&quot;&gt;Azure Account&lt;/a&gt; extensions for VS Code. And first, connect to your subscription by pressing F1 and looking for “Azure: Sign in”.&lt;/p&gt;
&lt;p&gt;Now to deploy it, you just need to right-click on the VS Code tab and select the “Deploy Bicep file…” option:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-9.png&quot; alt=&quot;Deploying a Bicep file from VS Code&quot; width=&quot;701&quot; height=&quot;258&quot; /&gt;&lt;figcaption&gt;Deploying a Bicep file from VS Code&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Thanks to the Bicep extension you will see a dialog to select the subscription, the resource group (or create one) and after that it will also ask to select a parameter file, select None.&lt;/p&gt;
&lt;p&gt;You will see a message in the output window, and if you go to the Azure portal (or click the link in the output window), navigate to the resource group you’re deploying this to, and select the Deployments option you will see the deployment:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-10.png&quot; alt=&quot;Bicep deployment in Azure portal&quot; width=&quot;889&quot; height=&quot;416&quot; /&gt;&lt;figcaption&gt;Bicep deployment in Azure portal&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And when it’s finished, you can go to overview and see your resource:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-11.png&quot; alt=&quot;Storage account deployed using Bicep&quot; width=&quot;1763&quot; height=&quot;315&quot; /&gt;&lt;figcaption&gt;Storage account deployed using Bicep&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It’s that easy, and if you want to learn more about Bicep, you can check &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/learn-bicep?WT.mc_id=BA-MVP-5003976&quot;&gt;Microsoft Learn learning paths for Bicep&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Deploying the APIM solution&lt;/h2&gt;
&lt;p&gt;I wanted it to be easy for everyone to try this, so I prepared everything and if you go check &lt;a href=&quot;https://github.com/aariste/APIMIntegrationArchitecture&quot;&gt;this GitHub project&lt;/a&gt; I’ve created, you will find the following things:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;X++ folder&lt;/strong&gt; that contains a model with a custom web service that will create a calculator API in your F&amp;amp;O environment. There’s both a model folder and an axpp file you can import in Visual Studio.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;AzureFunction folder&lt;/strong&gt; contains the code to create an Azure Function that will create a calculator API.&lt;/p&gt;
&lt;p&gt;And finally, and what interests us the most, the &lt;strong&gt;Bicep folder&lt;/strong&gt; that contains the needed code to create all the Azure resources, so you can test what I explained in &lt;a href=&quot;https://ariste.info/2022/09/azure-api-management-architecture/&quot;&gt;my previous post&lt;/a&gt; without having to create all the resources manually. And you have two options.&lt;/p&gt;
&lt;h3&gt;Deploy an Azure Function&lt;/h3&gt;
&lt;p&gt;If you choose to try the solution using an &lt;strong&gt;Azure function, you just need to deploy the mainAzureFunction.bicep file&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You don’t need to create and deploy the Azure Function because the Bicep file will download &lt;a href=&quot;https://ariste.info/wp-content/uploads/2022/08/CalculatorFunction.zip&quot;&gt;this ZIP file&lt;/a&gt; which is only the Azure Function packaged to make your life easier, and this faster to try. If you prefer to deploy the function for security reasons, you’ll need to update the azureFunction.bicep file inside the modules folder.&lt;/p&gt;
&lt;p&gt;If you choose this option, the dialogs will ask you for the following parameters: Subscription, Resource Group, location (which is defaulted to that of the resource group), prefix to be used as the name of the resources and, finally, email and organization to deploy the APIM (just your email and tenant but don’t worry no emails will be sent).&lt;/p&gt;
&lt;h3&gt;Deploy X++ code&lt;/h3&gt;
&lt;p&gt;If you choose this option, &lt;strong&gt;you have to download the code from the X++ folder and add it to your environment&lt;/strong&gt;. Once you’ve done that, you can &lt;strong&gt;deploy the mainDynamics.bicep file&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you prefer using an X++ custom web service to try this, you’ll have to provide additional information. In addition to the same parameters as the ones of the Azure Function solution, you’ll need to provide the environment URL (without the final /), the environment URL with the API information (service group and service name, also without the final /), tenant, AAD App Id and secret.&lt;/p&gt;
&lt;p&gt;This way of deploying the code will create a policy in your APIM that will &lt;a href=&quot;https://ariste.info/2022/02/azure-api-management-dynamics-365-fo/&quot;&gt;handle the token authentication&lt;/a&gt;, that’s why it’s asking for the tenant, App Id and secret.&lt;/p&gt;
&lt;h2&gt;The Bicep files&lt;/h2&gt;
&lt;p&gt;In the Bicep folder you will find these files:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-12.png&quot; alt=&quot;Bicep folder&quot; width=&quot;239&quot; height=&quot;291&quot; /&gt;&lt;figcaption&gt;Bicep folder&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The mainAzureFunction.bicep and mainDynamics.bicep files are what needs to be executed/deployed, depending on which way you choose to go.&lt;/p&gt;
&lt;p&gt;Let’s take a look at the mainAzureFunction.bicep file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@description(&apos;The location where the resources will be deployed&apos;)
param location string = resourceGroup().location
@description(&apos;A prefix that will be added to the resources.&apos;)
param prefix string
// Analytics
@description(&apos;Log Analytics workspace name&apos;)
var logAnalyticsWorkspaceName = &apos;${prefix}LogAnalytics&apos;
@description(&apos;Name for the App Insights resource&apos;)
var appInsightsName = &apos;${prefix}AppInsights&apos;
// Azure Function
@description(&apos;The name of the account storage used by the Azure function&apos;)
var storageName = toLower(&apos;${prefix}Storage&apos;)
@description(&apos;Azure function name&apos;)
var functionName = &apos;${prefix}Function&apos;
@description(&apos;App Service plan name&apos;)
var appServicePlanName = &apos;${prefix}AppServicePlan&apos;
// APIM
@description(&apos;Name of the APIM resource&apos;)
var apiManagementInstanceName = &apos;${prefix}apim&apos;
@description(&apos;APIM publisher email&apos;)
@secure()
param publisherEmail string
@description(&apos;APIM publisher name&apos;)
param publisherName string
@description(&apos;Name of the API that will be created in the APIM&apos;)
var apiName = &apos;calculator&apos;
// Analytics resources
module analytics &apos;modules/analytics.bicep&apos; = {
  name: logAnalyticsWorkspaceName
  params: {
    appInsightsName: appInsightsName
    location: location
    logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
  }
}
// Azure Function Resources
module azureFunction &apos;modules/azureFunction.bicep&apos; = {
  name: functionName
  params: {
    appInsightsId: analytics.outputs.appInsightsId
    appServicePlanName: appServicePlanName
    functionName: functionName
    location: location
    storageName: storageName
  }
}
// APIM Resources
module apiManagement &apos;modules/apiManagement.bicep&apos; = {
  name: apiManagementInstanceName
  params: {
    apiManagementInstanceName: apiManagementInstanceName
    apiName: apiName
    endpointUrl: azureFunction.outputs.azureFunctionUrl
    location: location
    publisherEmail: publisherEmail
    publisherName: publisherName
    apiKey: azureFunction.outputs.azureFunctionApi    
    appInsightsId: analytics.outputs.appInsightsId
    appInsightsKey: analytics.outputs.appInsightsKey    
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve put the parameters and variables at the beginning of the file and the resources after that.&lt;/p&gt;
&lt;p&gt;When you use a parameter, that’s declared using the parm keyword, you need to provide that parameter each time the Bicep file is run.&lt;/p&gt;
&lt;p&gt;Variables are declared with the var keyword and… you remember about resources, right? But there are no resources in this file, instead there are…&lt;/p&gt;
&lt;h3&gt;Modules&lt;/h3&gt;
&lt;p&gt;In Bicep, modules are files that are deployed from other files. Think of them as other classes which we can use in the main file. It’s something like this.&lt;/p&gt;
&lt;p&gt;In modules, you usually group resources that need to be deployed together or that can be reused in other files.&lt;/p&gt;
&lt;p&gt;The modules’ folder contains other Bicep files that help deploy all the infrastructure.&lt;/p&gt;
&lt;p&gt;For example, let’s take a look at the azureFunction.bicep file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;param functionName string
param appServicePlanName string
param appInsightsId string
param storageName string
param location string
// Storage resources
resource storageAccount &apos;Microsoft.Storage/storageAccounts@2021-09-01&apos; = {
  name: storageName
  location: location
  kind: &apos;StorageV2&apos;
  sku: {
    name: &apos;Standard_LRS&apos;
  }
}
resource appservice_plan &apos;Microsoft.Web/serverfarms@2022-03-01&apos; = {
  name: appServicePlanName
  location: location
  properties: {}
  sku: {
    name: &apos;Y1&apos;
    tier: &apos;Dynamic&apos;
  }
}
resource azureFunction &apos;Microsoft.Web/sites@2022-03-01&apos; = {
  name: functionName
  location: location
  kind: &apos;functionapp&apos;  
  properties: {
    serverFarmId: appservice_plan.id
    siteConfig: {
      appSettings: [
        {
          name: &apos;AzureWebJobsStorage&apos;
          value: &apos;DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}&apos;
        }
        {
          name: &apos;APPINSIGHTS_INSTRUMENTATIONKEY&apos;
          value: reference(appInsightsId, &apos;2020-02-02&apos;).InstrumentationKey
        }
        {
          name: &apos;FUNCTIONS_EXTENSION_VERSION&apos;
          value: &apos;~4&apos;
        }
        {
          name: &apos;FUNCTIONS_WORKER_RUNTIME&apos;
          value: &apos;dotnet&apos;
        }
        {
          name: &apos;WEBSITE_RUN_FROM_PACKAGE &apos;          
          value: &apos;http://ariste.info/wp-content/uploads/2022/08/CalculatorFunction.zip&apos;
        }        
      ]
    }
  }  
}
output azureFunctionId string = azureFunction.id
output azureFunctionApiVersion string = azureFunction.apiVersion
output azureFunctionUrl string = azureFunction.properties.hostNames[0]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we see parameters too. These parameters will be required when using the module in another file.&lt;/p&gt;
&lt;p&gt;And then we see the resources. You can see I’m deploying a storage account, an app service plan and the function. These go in this module because those are things needed when you deploy a function.&lt;/p&gt;
&lt;p&gt;And at the end we see output variables, that can be accessed from the file that uses the module, like a return in a method.&lt;/p&gt;
&lt;h2&gt;Deploy&lt;/h2&gt;
&lt;p&gt;I’m going to show this using the Azure function, I open the mainAzureFunction.bicep file and right click in the tab and select “Deploy Bicep file…”:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-13.png&quot; alt=&quot;Deploy Bicep file&quot; width=&quot;716&quot; height=&quot;510&quot; /&gt;&lt;figcaption&gt;Deploy Bicep file&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;You will see a list of the subscription the user you’ve authenticated with has access to:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-14.png&quot; alt=&quot;Azure subscriptions&quot; width=&quot;689&quot; height=&quot;206&quot; /&gt;&lt;figcaption&gt;Azure subscriptions&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then you’ll get asked which resource group you want to deploy it to, or if you want to create a new one:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-15.png&quot; alt=&quot;Create new resource group&quot; width=&quot;662&quot; height=&quot;160&quot; /&gt;&lt;figcaption&gt;Create new resource group&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;I’ll create a new resource group. When you do this, you will get an additional dialog asking in which region you want to create it from a list.&lt;/p&gt;
&lt;p&gt;Then you can select a parameter file, but we’re not using it, so select “&lt;em&gt;None&lt;/em&gt;“.&lt;/p&gt;
&lt;p&gt;And now we have to select the region where the resources are going to be deployed. Because in the code we’re using the &lt;em&gt;resourceGroup().location&lt;/em&gt; value, we see this option in the dialog, but we could use a different one.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-16.png&quot; alt=&quot;Resource group selection&quot; width=&quot;649&quot; height=&quot;138&quot; /&gt;&lt;figcaption&gt;Resource group selection&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Next we enter the prefix, keep in mind it has to be a unique name:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-17.png&quot; alt=&quot;Prefix&quot; width=&quot;654&quot; height=&quot;139&quot; /&gt;&lt;figcaption&gt;Prefix&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And finally enter the email and publisher name, your name or domain.&lt;/p&gt;
&lt;p&gt;And, finally, a dialog will ask if we want to create a parameter file, select “No”:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-18.png&quot; alt=&quot;Parameter file from values&quot; width=&quot;648&quot; height=&quot;127&quot; /&gt;&lt;figcaption&gt;Parameter file from values&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;After that we’ll see the message in the output window, and if we want to see the operation status we go to the resource group and “Deployments” option:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-19.png&quot; alt=&quot;Deployments in resource group&quot; width=&quot;913&quot; height=&quot;408&quot; /&gt;&lt;figcaption&gt;Deployments in resource group&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It will take a few minutes to deploy everything, and when all it’s done we can go to the overview and see all the resources there:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-20.png&quot; alt=&quot;APIM architecture resources&quot; width=&quot;881&quot; height=&quot;699&quot; /&gt;&lt;figcaption&gt;APIM architecture resources&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;And just like that, everything is ready to try!&lt;/p&gt;
&lt;h2&gt;One last cool trick&lt;/h2&gt;
&lt;p&gt;You may already have noticed this while testing the bicep solution, but when you right-click on the VS Code tab for your main Bicep file, you can select any one of these two options:&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-21.png&quot; alt=&quot;Bicep Visualizer&quot; width=&quot;552&quot; height=&quot;550&quot; /&gt;&lt;figcaption&gt;Bicep Visualizer&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The Bicep Visualizer will open a new tab and show a diagram of the resources, with their icons, and arrows pointing to the relationships between resources.&lt;/p&gt;
&lt;figure&gt;&lt;img src=&quot;https://ariste.info/wp-content/uploads/2022/09/image-22-1024x991.png&quot; alt=&quot;Bicep diagram&quot; width=&quot;1024&quot; height=&quot;991&quot; /&gt;&lt;figcaption&gt;Bicep diagram&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Maybe it’s not that important, but I really like it 😛&lt;/p&gt;
&lt;p&gt;Let me insist: if you want to learn more about Bicep, go right now to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/learn-bicep?WT.mc_id=BA-MVP-5003976&quot;&gt;Microsoft Learn learning paths for Bicep&lt;/a&gt;, you don’t even need an Azure subscription because the Learn modules deploy a free sandbox account for you to do the exercises.&lt;/p&gt;
&lt;p&gt;And that’s all, I hope this helps you a bit as an introduction to Bicep, and also to deploy the APIM architecture I proposed and try it, and see if it can be helpful for your projects.&lt;/p&gt;</content:encoded><category>Azure</category></item></channel></rss>