<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Marcos Garcia</title>
    <description>The latest articles on Forem by Marcos Garcia (@yamgarcia).</description>
    <link>https://forem.com/yamgarcia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F446376%2Fc9c58f63-aacd-47ee-b191-95d10161a5a8.jpeg</url>
      <title>Forem: Marcos Garcia</title>
      <link>https://forem.com/yamgarcia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/yamgarcia"/>
    <language>en</language>
    <item>
      <title>How to build Azure Load Tests and Automate It via Pipelines</title>
      <dc:creator>Marcos Garcia</dc:creator>
      <pubDate>Wed, 25 Jun 2025 05:56:43 +0000</pubDate>
      <link>https://forem.com/yamgarcia/how-to-build-azure-load-tests-and-automate-it-via-pipelines-1i51</link>
      <guid>https://forem.com/yamgarcia/how-to-build-azure-load-tests-and-automate-it-via-pipelines-1i51</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Load testing is essential when building scalable applications in the cloud. Azure Functions should scale up naturally, but how can you know if they handle spikes in demand? In this blog, we'll explore how to use Azure Load Testing to stress-test your Azure Functions and automate the process using CI/CD pipelines like Azure DevOps.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you'll know how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prepare an Azure Function for load testing&lt;/li&gt;
&lt;li&gt;Create a request to the Azure Function in Postman&lt;/li&gt;
&lt;li&gt;Create and run a load test in Azure&lt;/li&gt;
&lt;li&gt;Automate the load test in the pipeline&lt;/li&gt;
&lt;li&gt;Set performance thresholds to fail builds&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use Case / Problem Statement
&lt;/h2&gt;

&lt;p&gt;Imagine you're building an API using Azure Functions that will be called thousands of times per minute. How do you ensure your app can handle that load without failing or slowing down? Without proper load testing, performance regressions may go unnoticed until it's too late.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Azure Subscription&lt;/li&gt;
&lt;li&gt;Azure Load Testing resource&lt;/li&gt;
&lt;li&gt;An Azure Function App with at least one HTTP-triggered function&lt;/li&gt;
&lt;li&gt;Azure DevOps or GitHub repository with pipeline access&lt;/li&gt;
&lt;li&gt;Apps: Code Editor of choice, Postman, JMeter&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Prepare The Azure Function for Testing
&lt;/h2&gt;

&lt;p&gt;Make sure the Azure Function is deployed and has a public endpoint. Secure it using Function Keys or authentication as needed.&lt;br&gt;
We will use Postman first to make sure there's proper access to the function and your IP is whitelisted.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Download Postman&lt;/strong&gt;. Open your terminal/shell and run &lt;code&gt;winget install Postman.Postman&lt;/code&gt; if you are on windows or if on Fedora/HREL, use the following commands: &lt;code&gt;sudo dnf install snapd&lt;/code&gt;,&lt;code&gt;sudo ln -s /var/lib/snapd/snap /snap&lt;/code&gt;,&lt;code&gt;sudo snap install postman&lt;/code&gt;. Check &lt;a href="https://snapcraft.io/install/postman/fedora" rel="noopener noreferrer"&gt;snapcraft.io&lt;/a&gt; for more details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get the token ready&lt;/strong&gt;. An app registration will be needed for this step. I went through it in more detail in this &lt;a href="https://dev.to/yamgarcia/moving-from-sharepoint-access-onprem-to-cloud-with-graph-api-1iap"&gt;dev.to post&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Make sure to provide the correct values in the request body. Replace the Tenant id, Client id, and Client secret according to your App Registration, but scope must reflect the Azure Function name (see in the screenshot below).&lt;/p&gt;

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

&lt;p&gt;Optional: add a post response script to Postman to avoid token copy and paste:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AzureToken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Build a request to your Azure Function&lt;/strong&gt;. &lt;/p&gt;

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

&lt;p&gt;Make sure to add the Bearer token manually to the headers or in the Authorization tab, and to use the correct URL, which should be the "Default domain" from the Function overview page, and the chosen route, in my case it's defined in the Function code:&lt;/p&gt;

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


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;If this request succeeds, &lt;strong&gt;proceed to the Load Test creation&lt;/strong&gt;, otherwise, I suggest troubleshooting it first. I'd take a close look at the permissions granted to the App Registration, managed identities, RBAC, Authentication Settings, and network.&lt;/p&gt;

&lt;p&gt;If the Function is configured with "Restricted Access" in the authentication settings, &lt;a href="https://whatismyipaddress.com/" rel="noopener noreferrer"&gt;find your public IP address&lt;/a&gt; and add to the white-listed IPs at Networking &amp;gt; Public network access "Enabled with access restrictions".&lt;/p&gt;

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


&lt;/li&gt;

&lt;/ol&gt;




&lt;h2&gt;
  
  
  Step 2: Create a Load Test in Azure Load Testing
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create a Load Testing resource in Azure&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Install JMeter&lt;/strong&gt;. If using windows install Apache JMeter using &lt;code&gt;winget install DEVCOM.JMeter&lt;/code&gt; and on Fedora/Linux or another HREL distro, &lt;code&gt;dnf install jmeter&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To open the application, &lt;strong&gt;simply run &lt;code&gt;jmeter&lt;/code&gt; in your shell/terminal of choice&lt;/strong&gt;. This command may vary depending on the distro/OS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;I created a template to get started&lt;/strong&gt;. The file can be overwhelming, but contains as many features I find useful for the next step.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;jmeterTestPlan&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.2"&lt;/span&gt; &lt;span class="na"&gt;properties=&lt;/span&gt;&lt;span class="s"&gt;"5.0"&lt;/span&gt; &lt;span class="na"&gt;jmeter=&lt;/span&gt;&lt;span class="s"&gt;"5.6.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;hashTree&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TestPlan&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"TestPlanGui"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"TestPlan"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Function‑API Load Test Template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"TestPlan.serialize_threadgroups"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"TestPlan.tearDown_on_shutdown"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"TestPlan.user_defined_variables"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Arguments"&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"ArgumentsPanel"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"Arguments"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;collectionProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Arguments.arguments"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"function_host"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;function_host&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;FUNCTION_HOST&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;your‑function.azurewebsites.net&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tenant_id"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;tenant_id&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;AAD_TENANT_ID&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: __GetSecret(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;aadTenantId&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;))}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"client_id"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;client_id&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;AAD_CLIENT_ID&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: __GetSecret(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;aadClientId&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;))}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"client_secret"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;client_secret&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;AAD_CLIENT_SECRET&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: __GetSecret(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;aadClientSecret&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;))}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"aad_scope"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;aad_scope&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;AAD_SCOPE&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;api://YOUR‑APP‑ID/.default&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"NumberOfThreads"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;NumberOfThreads&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;THREADS&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;50&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"RampUpInSeconds"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;RampUpInSeconds&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;RAMP_UP&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;30&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"DurationInSeconds"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;DurationInSeconds&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;DURATION&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;60&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"userid_fetch_path"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Argument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;userid_fetch_path&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${__groovy(System.getenv(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;PATH_FETCH&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;) ?: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;api/org/userid/fetch&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/collectionProp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/TestPlan&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;hashTree&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ThreadGroup&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroupGui"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"AAD Token Thread Group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;intProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.num_threads"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/intProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;intProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.ramp_time"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/intProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.same_user_on_next_iteration"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.on_sample_error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;continue&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.main_controller"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"LoopController"&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"LoopControlPanel"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"LoopController"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Loop Controller"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LoopController.loops"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LoopController.continue_forever"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ThreadGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;hashTree&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;JSR223Sampler&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"TestBeanGUI"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"JSR223Sampler"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Acquire AAD Token"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"scriptLanguage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;groovy&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"script"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
import org.apache.http.client.methods.HttpPost
import org.apache.http.impl.client.HttpClients
import org.apache.http.entity.StringEntity
import org.apache.http.util.EntityUtils
import groovy.json.JsonSlurper

def client = HttpClients.createDefault()

def tokenUrl = &lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;https://login.microsoftonline.com/${vars.get(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;tenant_id&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}/oauth2/v2.0/token&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;

def post = new HttpPost(tokenUrl)
post.setHeader(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;Content-Type&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;, &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;application/x-www-form-urlencoded&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)

def body = [
    grant_type: &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;client_credentials&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;,
    client_id : vars.get(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;client_id&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;),
    client_secret: vars.get(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;client_secret&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;),
    scope: vars.get(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;aad_scope&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)
].collect { k, v -&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; &lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;${k}=${URLEncoder.encode(v, &lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;UTF-8&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)}&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt; }.join(&lt;span class="ni"&gt;&amp;amp;apos;&amp;amp;amp;&amp;amp;apos;&lt;/span&gt;)

post.setEntity(new StringEntity(body))

def response = client.execute(post)

def json = new JsonSlurper().parseText(EntityUtils.toString(response.entity))

props.put(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;access_token&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;, json.access_token)
log.info(&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;AAD access token fetched.&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;)
&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cacheKey"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"parameters"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"filename"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/JSR223Sampler&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;hashTree/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/hashTree&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ThreadGroup&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroupGui"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"API Main Thread Group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.num_threads"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${NumberOfThreads}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.ramp_time"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${RampUpInSeconds}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.duration"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${DurationInSeconds}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.same_user_on_next_iteration"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.scheduler"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.on_sample_error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;continue&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"ThreadGroup.main_controller"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"LoopController"&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"LoopControlPanel"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"LoopController"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Loop Controller"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;intProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LoopController.loops"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-1&lt;span class="nt"&gt;&amp;lt;/intProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"LoopController.continue_forever"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ThreadGroup&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;hashTree&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;HTTPSamplerProxy&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"HttpTestSampleGui"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSamplerProxy"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Fetch Record Id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.domain"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${function_host}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.protocol"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;https&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.path"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;${user_fetch_path}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.method"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;POST&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.use_keepalive"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPSampler.postBodyRaw"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPsampler.Arguments"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Arguments"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;collectionProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Arguments.arguments"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"HTTPArgument"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;boolProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HTTPArgument.always_encode"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/boolProp&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;firstName&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;:&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;John&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;,&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;lastName&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;:&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;Doe&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;,&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;email&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;:&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;sample@test.com&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;,&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;dateOfBirth&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;:&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;1980‑09‑01T00:00:00Z&lt;span class="ni"&gt;&amp;amp;quot;&lt;/span&gt;}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Argument.metadata"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;=&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/collectionProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/HTTPSamplerProxy&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;hashTree&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;HeaderManager&lt;/span&gt; &lt;span class="na"&gt;guiclass=&lt;/span&gt;&lt;span class="s"&gt;"HeaderPanel"&lt;/span&gt; &lt;span class="na"&gt;testclass=&lt;/span&gt;&lt;span class="s"&gt;"HeaderManager"&lt;/span&gt; &lt;span class="na"&gt;testname=&lt;/span&gt;&lt;span class="s"&gt;"Bearer Header #1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;collectionProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"HeaderManager.headers"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;elementProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Auth"&lt;/span&gt; &lt;span class="na"&gt;elementType=&lt;/span&gt;&lt;span class="s"&gt;"Header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Header.name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Authorization&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;stringProp&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Header.value"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Bearer ${__P(access_token)}&lt;span class="nt"&gt;&amp;lt;/stringProp&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/elementProp&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/collectionProp&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/HeaderManager&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;hashTree/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/hashTree&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/hashTree&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/hashTree&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/hashTree&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/jmeterTestPlan&amp;gt;&lt;/span&gt;

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

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open the .jmx file in JMeter&lt;/strong&gt;, there should be test structure like this:&lt;/p&gt;

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

&lt;p&gt;Note that all the variables are in the topmost section, the Test Plan definition. To testing locally, replace the values for the token to look just like Postman, and hit "Start".&lt;/p&gt;

&lt;p&gt;Quick explanation for the variables section:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;${__groovy(...)}&lt;/code&gt;: return the value evaluated by the Groovy code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${__GetSecret(...)}&lt;/code&gt;: return the secret value defined in the config file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${__P(access_token)}&lt;/code&gt;: return the value of a JMeter property&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;props.put("access_token", json.access_token)&lt;/code&gt;: set value to a JMeter property&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;System.getenv('FUNCTION_HOST')&lt;/code&gt;: Fetches the value of the environment variable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?:&lt;/code&gt;: Ternary/Elvis Operator&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Before proceeding to the pipeline you should &lt;strong&gt;upload it to the Azure Load Test resource&lt;/strong&gt; and configure it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open the resource&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Tests &amp;gt; Create &amp;gt; Create test:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwabz2dswcwyce05qy35n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwabz2dswcwyce05qy35n.png" alt="Create test" width="671" height="383"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once created open the Test plan tab and upload the file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To authenticate the Load test, go back to the Overview page, then access Settings &amp;gt; Identity, and choose the desired method (User assigned recommended). Back in the Test plan there should be the selected option at the bottom.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Parameters tab set the environment variables and secrets:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Make sure the Key Vault is referenced at the bottom of the page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The rest of it should be already configured, but for good measure it's recommended to reference the Managed identity once again in the Monitoring tab and to add a vnet configuration if the Function is on a private setting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "Apply", access the test and hit "Run". Configure the variables however you'd like and describe it in the Test run description. I use this pattern: "NumberOfThreads 250 RampUpInSeconds 15 DurationInSeconds 600 EngineInstances 1" just to quickly identify each run. Please note that Debug mode runs on a different setting. Try to find a number that start showing a small percentage of errors and identify the performance thresholds of your Azure Function for benchmarking:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Download the result files from the Ellipsis (⋯) in the test results page for further troubleshooting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Automate It in The Pipeline
&lt;/h2&gt;

&lt;p&gt;Use Azure DevOps (ADO) or GitHub Actions to trigger the load test on pull requests or deployments. I'm going to be using ADO for this but any should work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two .yml files are needed to automate it&lt;/strong&gt;. A &lt;strong&gt;Load Test config&lt;/strong&gt; and a &lt;strong&gt;pipeline yml file&lt;/strong&gt;, they reference each other and should be added to the repository, just like the jmx file. If you are not used to pipelines I recommend going through the basics and ADO Pipeline or Github Actions documentation, as you might want to change these files according to your company's needs.&lt;/p&gt;

&lt;p&gt;For the configuration file, it can be downloaded from the results of the previous test run. Access the Test runs and click the Ellipsis (⋯), then "download input file".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The config file should be ready to be used, but I'm adding one here in case as an example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.1&lt;/span&gt;
&lt;span class="na"&gt;testId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;processor-loadtest&lt;/span&gt;
&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Processor Load Test&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Load test for the Processor Azure Function&lt;/span&gt;
&lt;span class="na"&gt;testType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JMX&lt;/span&gt;
&lt;span class="na"&gt;testPlan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Processor-LoadTest.jmx&lt;/span&gt;
&lt;span class="na"&gt;engineInstances&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="na"&gt;subnetId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/subscriptions/id/resourceGroups/dev-network-rg/providers/Microsoft.Network/virtualNetworks/vnet-functions/subnets/snet-azurevirtualmachines&lt;/span&gt;
&lt;span class="na"&gt;publicIPDisabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;splitAllCSVs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;
&lt;span class="na"&gt;failureCriteria&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clientMetrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;avg(response_time_ms) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;60000&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;percentage(error) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;autoStop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;errorPercentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;timeWindow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
  &lt;span class="na"&gt;maximumVirtualUsersPerEngine&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NumberOfThreads&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RampUpInSeconds&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DurationInSeconds&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tokenAuthClientSecret&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://dev-kv.vault.azure.net/secrets/tokenAuthClientSecret/id&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tokenAuthClientId&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://dev-kv.vault.azure.net/secrets/tokenAuthClientId/id&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tokenAuthTenantId&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://dev-kv.vault.azure.net/secrets/tokenAuthTenantId/id&lt;/span&gt;
&lt;span class="na"&gt;referenceIdentities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KeyVault&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UserAssigned&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/subscriptions/id/resourceGroups/dev-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/fndev_processor&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Metrics&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SystemAssigned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The pipeline file can also be created with the help of ADO/Github, but &lt;strong&gt;I'm adding it as another example of what it should look like&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;   &lt;span class="c1"&gt;# Disable automatic CI trigger&lt;/span&gt;
&lt;span class="na"&gt;pr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;        &lt;span class="c1"&gt;# Disable PR trigger&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ManualLoadTestPipeline&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadTesting&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Start&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Azure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RunAndPublishLoadTest&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Publish&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Results'&lt;/span&gt;
        &lt;span class="na"&gt;timeoutInMinutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AzureLoadTest@1&lt;/span&gt;
            &lt;span class="na"&gt;timeoutInMinutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test'&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;azureSubscription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(serviceConnection)&lt;/span&gt;
              &lt;span class="na"&gt;loadTestConfigFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Processor/LoadTests/Dev-Processor-LoadTestConfig.yml'&lt;/span&gt;
              &lt;span class="na"&gt;loadTestResource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dev-load-testing'&lt;/span&gt;
              &lt;span class="na"&gt;resourceGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dev-rg'&lt;/span&gt;
            &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;succeeded()&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Processor/LoadTests&lt;/span&gt;
            &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;loadTestResults&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Publish&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Results'&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;testResultsFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JUnit'&lt;/span&gt;
              &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Processor/LoadTests/results/*.xml'&lt;/span&gt;
              &lt;span class="na"&gt;searchFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(System.DefaultWorkingDirectory)'&lt;/span&gt;
              &lt;span class="na"&gt;mergeTestResults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add a Service Connection and all necessary condiguration to connect ADO and Azure. Azure DevOps itself doesn’t natively support pipeline auth via managed identity for Azure Load Testing, but that could change in the future.&lt;/p&gt;

&lt;p&gt;After reviewing the .yml files and replacing the placeholders with the correct values based on your folder structure and Azure configuration, install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=AzloadTest.AzloadTesting&amp;amp;targetId=a0a2b1c4-9133-49d8-a200-383935898264&amp;amp;utm_source=vstsproduct&amp;amp;utm_medium=ExtHubManageList" rel="noopener noreferrer"&gt;Azure Load Test extension&lt;/a&gt; in ADO. After that, it should be found in here: &lt;code&gt;https://dev.azure.com/&amp;lt;your-org&amp;gt;/_settings/extensions&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;At this point, the pipeline is ready to run. After that, the results will be available for inspection in Azure. Look for a new test in the Load Test resource with the same name as the "displayName" from the config file. If you configured a results folder in the repo, they should also be published after each run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final considerations&lt;/strong&gt;: With this pipeline working and the test running as expected, consider adding it to your development lifecycle. This could run in your pipelines after successful builds on dev, after all the unit tests pass, before deploying from dev to QA or UAT, etc. There are many possible applications for load tests, including SQL queries test directly to a database with JDBC, Azure Kubernetes Service, and Microservices in general, and for the small effort to set it up there's a big value in return.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices &amp;amp; Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't load test production unless necessary&lt;/li&gt;
&lt;li&gt;Use deployment slots or staging environments&lt;/li&gt;
&lt;li&gt;Account for cold starts in consumption plans&lt;/li&gt;
&lt;li&gt;Monitor backend services if the Function triggers other processes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Common issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subnet permissions errors (e.g., &lt;code&gt;join/action&lt;/code&gt; errors)&lt;/li&gt;
&lt;li&gt;Rate limiting (HTTP 429)&lt;/li&gt;
&lt;li&gt;Authentication failures (HTTP 403)&lt;/li&gt;
&lt;li&gt;Configuration issue (usually HTTP 500)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure correct role assignments and firewall rules&lt;/li&gt;
&lt;li&gt;Add retry policies&lt;/li&gt;
&lt;li&gt;Use Application Insights for deeper diagnostics&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Azure Load tests ensures that serverless applications scale up gracefully. Integrating it into a CI/CD pipeline identifies regression early and gives the team confidence during production deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/load-testing/" rel="noopener noreferrer"&gt;Azure Load Testing Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/performance-tips" rel="noopener noreferrer"&gt;Azure Functions Performance Tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/load-testing" rel="noopener noreferrer"&gt;GitHub Action for Azure Load Testing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>testing</category>
      <category>webdev</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>Moving Sharepoint access from Dynamics 365 OnPrem to Cloud with Graph API</title>
      <dc:creator>Marcos Garcia</dc:creator>
      <pubDate>Sat, 18 Jan 2025 23:45:20 +0000</pubDate>
      <link>https://forem.com/yamgarcia/moving-from-sharepoint-access-onprem-to-cloud-with-graph-api-1iap</link>
      <guid>https://forem.com/yamgarcia/moving-from-sharepoint-access-onprem-to-cloud-with-graph-api-1iap</guid>
      <description>&lt;p&gt;I recently stumbled on a bug I completely underestimated, and lived to tell the story. It involved several unexpected issues, like version incompatibility, broken builds, technical debt, and all the classic problems legacy code brings. &lt;br&gt;
I gained valuable insights along the way. Not only about the code but also dealing with clients and tight deadlines.&lt;br&gt;
Let me take you through this journey tracking down this bug, and the lessons I learned.&lt;/p&gt;
&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;My problem started with the bug title, "Online Termination of employee not working". I completely misjudged the situation from the start. There were other bugs I had to fix for this client with very similar titles. Most issues were fixed by tracking down what is trying to access a null value or over-complicated logic that was falling on edge cases.&lt;/p&gt;

&lt;p&gt;For the record, this client has been migrating their Dynamics 365 from OnPrem to the Cloud environment for 3 years, lost most of their staff in the process and was planning to go live just 1 week after that bug was assigned to me. I guess no one ever bothered to QA this, or it wasn't a core feature (but it was). &lt;/p&gt;

&lt;p&gt;Diving into the code, I realized they were using Sharepoint to store termination letters and many other documents. I noticed that the code would fail at the ClientContext, part of the Microsoft.SharePoint.Client package.&lt;br&gt;
&lt;em&gt;Simple fix, I thought. Find out why it fails and close the bug.&lt;/em&gt; Regrettably, Microsoft decided to &lt;a href="https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/complete-basic-operations-using-sharepoint-client-library-code" rel="noopener noreferrer"&gt;deprecate parts of the Sharepoint Client API&lt;/a&gt;.&lt;br&gt;
Here's a quick peek at the OnPrem code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private SharePointDocumentLocation CreateSharepointFolderForApplication(IOrganizationService service, org_application application, Guid applicationId)
{
    SharePointDocumentLocation spDocLoc = new SharePointDocumentLocation
    {
        Name = "Documents on org 1",
        ParentSiteOrLocation = new EntityReference(SharePointDocumentLocation.EntityLogicalName, Guid.Parse("00000000-0000-0000-0000-000000000000")),
        RelativeUrl = application.org_name + "_" + applicationId.ToString(),
        RegardingObjectId = new EntityReference(org_application.EntityLogicalName, applicationId)
    };
    Guid _spDocLocId = service.Create(spDocLoc);

    SecureString securePassword = new SecureString();
    char[] arrPassword = SharePointPWD.ToCharArray();
    foreach (char c in arrPassword)
    {
        securePassword.AppendChar(c);
    }
    ClientContext context = new ClientContext("https://sharepoint.org.com/org/")
    {
        Credentials = new NetworkCredential("s_mywebdoc", securePassword)
    };
    Web web = context.Web;
    context.Load(web);
    CreteFolder(context, "Application", application.org_name + "_" + applicationId.ToString());
    xLog.AddLog("Done creating sharepoint folder", 1);

    return spDocLoc;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point I was starting to realize it would not be that simple, but I was still in denial.&lt;/p&gt;

&lt;p&gt;Since ClientContext wasn't an option anymore, or anything from Microsoft.SharePoint.Client for that matter, I had to find another package that would replace it.&lt;/p&gt;

&lt;p&gt;I quickly found out about &lt;a href="https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&amp;amp;tabs=csharp" rel="noopener noreferrer"&gt;Microsoft Graph client &lt;/a&gt;and started to like it. Again, I thought, &lt;em&gt;"simple fix". Replace the deprecated client with the new and close the bug.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I could not fathom my naivety. One does not simply introduce new packages to legacy code without paying the price.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To venture into legacy code is to tread lightly, for even the smallest change may awaken the wrath of unforeseen version incompatibility. -Tolkien J.R.R&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since the birth of .NET Core 1.0 in 2016, Microsoft has continuously emphasized its compatibility but has not figured out a way to allow Dynamics 365 plugins to be written using any version other than 4.6.2 (or 4.8.1 for the daring).&lt;/p&gt;

&lt;p&gt;Long story short, there were conflicts with some packages, like CrmSdk.CoreAssemblies and CrmSdk.Workflow. And when everything seemed to work, either the build would not complete without errors or the dll could not be deployed to Dataverse. Trying to debug it was a huge waste of time. The use of ILMerge probably caused some of the issues, but the outcome would be the same. And of course I wasn't going to introduce &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/build-and-package#dependent-assemblies" rel="noopener noreferrer"&gt;Package Assemblies &lt;/a&gt;to this legacy code base one week away from go live (as if I didn't make a similar mistake before).&lt;/p&gt;

&lt;p&gt;Scraping the whole idea of adding another package to the project, I did a quick &lt;code&gt;git reset --hard HEAD&lt;/code&gt; and decided to go with the good ol' System.Net.Http.HttpClient. And there's no other way to start other than getting that sweet auth token and start playing with the API on Postman.&lt;/p&gt;

&lt;p&gt;After going through the trouble of setting up an App Registration in Azure for Sharepoint, and start hitting the endpoint with unexpected responses, I noticed the deprecated feature also apply in this case.&lt;br&gt;
Fortunately, I could just add the Graph API permissions and change my request. I'll add all the relevant requests down below:&lt;/p&gt;
&lt;h2&gt;
  
  
  The How To Guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set up an App Registration&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgee9fsswti8gqorj8vl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgee9fsswti8gqorj8vl.png" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;br&gt;
You probably don't need to add all of those. For a more step by step guide I recommend following this &lt;a href="https://www.c-sharpcorner.com/article/setting-up-sharepoint-app-only-principal-with-app-registration/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get your Auth Token&lt;/strong&gt;. You should be ready to start making your requests now:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;After getting all your variables in place, I recommend setting another variable for the token and adding this script. Use {{token}} for simplicity, but you may need a more specific name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Parse the JSON response body
const jsonData = pm.response.json();

if (jsonData.access_token) {
    pm.globals.set("token", jsonData.access_token);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create a request&lt;/strong&gt;. Once you got a token, create a new request and add it to your header value, while the key will be Authorization:&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Notice the URL is different from the one we used to get the token. Use &lt;code&gt;https://graph.microsoft.com/v1.0/&lt;/code&gt; as base URL.&lt;br&gt;
We are also using parameters to search for the sitename. You can replace that with your site of choice, or get all of them. Later, we will see how this is relevant for the code we are writing.&lt;/p&gt;

&lt;p&gt;I don't have a script for you, but you might want to copy the id of the chosen SharePoint site and also add to a variable. I decided to use {{QA-SiteId}}.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get the Drive id&lt;/strong&gt;. By the way, I don't understand how these names make sense since Microsoft uses these terms interchangeably sometimes (site, drive, list, folder). Also I'm not a Sharepoint developer, so take my view of the names with a grain of salt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I recommend is getting all your Drive IDs first:&lt;br&gt;
&lt;code&gt;{{BaseUrl}}sites/{{QA-SiteId}}/drives/&lt;/code&gt;&lt;/p&gt;

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

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

&lt;p&gt;Then copy the id from line 5 from the response and change/duplicate your request to search for the id every time. You might want to create another variable:&lt;/p&gt;

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

&lt;p&gt;Having the specific ID also helps with request URL. You can make a bit shorter if you have the drive ID. It would look like this: &lt;code&gt;{{BaseUrl}}drives/{{ApplicationsDriveId}}&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get folder id&lt;/strong&gt;. This is necessary for this implementation, but may not be for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6ng58g2rj72clfsp457.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6ng58g2rj72clfsp457.png" alt=" " width="600" height="279"&gt;&lt;/a&gt;&lt;br&gt;
We need the parent folder for that drive.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Copy documents into another folder:
Documentation for this can be found &lt;a href="https://learn.microsoft.com/en-us/graph/api/driveitem-move?view=graph-rest-1.0&amp;amp;tabs=http" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3umzi3wvx429r5voc69.png" alt=" " width="672" height="338"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What the code should look like
&lt;/h2&gt;

&lt;p&gt;For this clients' architecture, the folder is created beforehand, then the contents are copied. So we start with CreateSharepointFolderForApplication and move to the rest of the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create SharePoint folder:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private SharePointDocumentLocation CreateSharepointFolderForApplication(
            IOrganizationService service,
            Guid applicationId,
            string applicationNameId)
        {
            CrmServiceContext svcContext = new CrmServiceContext(service);

            string parentSite = svcContext.SharePointDocumentLocationSet
                .Where(site =&amp;gt; site.RelativeUrl == "application")
                .Select(site =&amp;gt; site.Id)
                .FirstOrDefault().ToString();

            SharePointDocumentLocation spDocLoc = new SharePointDocumentLocation
            {
                Name = "Documents",
                ParentSiteOrLocation = new EntityReference(SharePointDocumentLocation.EntityLogicalName, Guid.Parse(parentSite)),
                RelativeUrl = applicationNameId + "_" + applicationId.ToString(),
                RegardingObjectId = new EntityReference(application.EntityLogicalName, applicationId)
            };
            service.Create(spDocLoc);

            return spDocLoc;
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Get you access token:
&lt;code&gt;string accessToken = MyWebBusinessLogic.GetSharePointAccessToken(service).GetAwaiter().GetResult();&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static async Task&amp;lt;string&amp;gt; GetSharePointAccessToken(IOrganizationService service)
{
    var tenantId = GetEnvironmentVariable(service, "org_sharePointAppRegTenantId");
    var clientId = GetEnvironmentVariable(service, "org_sharePointAppRegClientId");
    var clientSecret = GetEnvironmentVariable(service, "org_sharePointAppRegClientSecret");
    string authority = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";

    using (HttpClient client = new HttpClient())
    {
        var requestData = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair&amp;lt;string, string&amp;gt;("grant_type", "client_credentials"),
            new KeyValuePair&amp;lt;string, string&amp;gt;("client_id", clientId),
            new KeyValuePair&amp;lt;string, string&amp;gt;("client_secret", clientSecret),
            new KeyValuePair&amp;lt;string, string&amp;gt;("scope", "https://graph.microsoft.com/.default")
        });
        HttpResponseMessage response = await client.PostAsync(authority, requestData);

        if (!response.IsSuccessStatusCode)
        {
            throw new HttpRequestException($"Error fetching token: {response.ReasonPhrase}");
        }

        string responseContent = await response.Content.ReadAsStringAsync();
        dynamic tokenData = JsonConvert.DeserializeObject(responseContent);
        return tokenData.access_token;
    }

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

&lt;/div&gt;


&lt;p&gt;The environment variable can be fetched this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static string GetEnvironmentVariable(IOrganizationService service, string schemaName)
        {
            // Query the EnvironmentVariableDefinition entity by Schema Name
            QueryExpression query = new QueryExpression("environmentvariabledefinition");
            query.ColumnSet = new ColumnSet("environmentvariabledefinitionid");
            query.Criteria.AddCondition("schemaname", ConditionOperator.Equal, schemaName);

            var envVarDef = service.RetrieveMultiple(query).Entities.FirstOrDefault();

            if (envVarDef == null)
            {
                throw new InvalidOperationException($"Environment Variable '{schemaName}' not found.");
            }

            var envVarDefId = envVarDef.Id;

            // Query the EnvironmentVariableValue entity for the value
            QueryExpression valueQuery = new QueryExpression("environmentvariablevalue");
            valueQuery.ColumnSet = new ColumnSet("value");
            valueQuery.Criteria.AddCondition("environmentvariabledefinitionid", ConditionOperator.Equal, envVarDefId);

            var envVarValue = service.RetrieveMultiple(valueQuery).Entities.FirstOrDefault();

            return envVarValue?.GetAttributeValue&amp;lt;string&amp;gt;("value") ?? string.Empty;
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Get the folder ids
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static async Task&amp;lt;string&amp;gt; GetFolderIdAsync(string accessToken, string driveId, string folderName)
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        var url = $"https://graph.microsoft.com/v1.0/drives/{driveId}/root:/{folderName}";
        var response = await client.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            var jsonResponse = await response.Content.ReadAsStringAsync();

            using (JsonDocument document = JsonDocument.Parse(jsonResponse))
            {
                if (document.RootElement.TryGetProperty("id", out JsonElement idElement))
                {
                    return idElement.GetString(); //Return the ID
                }
                throw new InvalidOperationException("ID not found in the response.");
            }
        }
        else
        {
            throw new HttpRequestException($"Request failed with status code: {response.StatusCode} - {response.ReasonPhrase}");
        }
    }
}

public static async Task&amp;lt;string&amp;gt; GetParentFolderIdAsync(string accessToken, string driveId)
{
    using (var client = new HttpClient())
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        var folderUrl = $"https://graph.microsoft.com/v1.0/drives/{driveId}/root/children?$top=1";
        var response = await client.GetAsync(folderUrl);

        if (response.IsSuccessStatusCode)
        {
            var jsonResponse = await response.Content.ReadAsStringAsync();

            using (JsonDocument document = JsonDocument.Parse(jsonResponse))
            {
                if (document.RootElement.TryGetProperty("value", out JsonElement valueArray) &amp;amp;&amp;amp; valueArray.GetArrayLength() &amp;gt; 0)
                {
                    if (valueArray[0].TryGetProperty("parentReference", out JsonElement parentReference) &amp;amp;&amp;amp;
                        parentReference.TryGetProperty("id", out JsonElement parentId))
                    {
                        return parentId.GetString(); // Return the parent folder ID
                    }
                }

                throw new InvalidOperationException("Parent folder ID not found in the response.");
            }
        }
        else
        {
            throw new HttpRequestException($"Request failed with status code: {response.StatusCode} - {response.ReasonPhrase}");
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Copy the folder content
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async Task&amp;lt;HttpResponseMessage&amp;gt; CopyFolderAsync(ITracingService tracingService, string accessToken, string srcFolderDriveId, string srcFolderId, string desFolderId, string desFolderName, string applicationDriveQAId)
{
    tracingService.Trace($"Source Folder ID: {srcFolderDriveId}, Destination Folder ID: {desFolderId}, Destination Folder Name: {desFolderName}");

    try
    {
        using (HttpClient client = new HttpClient())
        {
            tracingService.Trace("Setting Authorization header with access token.");
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            tracingService.Trace($"Preparing to copy source folder (ID: {srcFolderDriveId}) to destination folder (ID: {desFolderId}).");
            //E.g. srcFolderDriveId = b!3ek... , srcFolderName
            string copyUrl = $"https://graph.microsoft.com/v1.0/drives/{srcFolderDriveId}/items/{srcFolderId}/copy";

            var copyPayload = new JObject
            {
                { "parentReference", new JObject {
                    { "driveId", applicationDriveQAId }, 
                    { "id", desFolderId } } 
                },
                { "name", desFolderName }
            };

            var copyContent = new StringContent(copyPayload.ToString(), Encoding.UTF8, "application/json");

            tracingService.Trace($"Sending POST request to copy folder. URL: {copyUrl}");
            HttpResponseMessage copyResponse = await client.PostAsync(copyUrl, copyContent);

            if (!copyResponse.IsSuccessStatusCode)
            {
                string copyErrorMsg = $"Error copying folder: {copyResponse.StatusCode} - {copyResponse.ReasonPhrase}";
                tracingService.Trace(copyErrorMsg);
                throw new Exception(copyErrorMsg);
            }

            return copyResponse;
        }
    }
    catch (Exception ex)
    {
        tracingService.Trace($"An error occurred in CopyFolderAsync: {ex.Message}");
        throw;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now we are ready to put everything together:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HttpResponseMessage copyResponse = null;
try
{
    var myWebSubmissionDriveId = MyWebBusinessLogic.GetEnvironmentVariable(service, "org_myWebSubmissionDriveId");
    var applicationDriveId = MyWebBusinessLogic.GetEnvironmentVariable(service, "org_applicationDriveId");

    string srcFolderName = results.org_name;
    string desFolderName = response.GetAttributeValue&amp;lt;string&amp;gt;("RelativeUrl");

    string srcFolderId = MyWebBusinessLogic.GetFolderIdAsync(
        accessToken,
        myWebSubmissionDriveId,
        srcFolderName).GetAwaiter().GetResult();

    string desFolderId = MyWebBusinessLogic.GetParentFolderIdAsync(
        accessToken,
        applicationDriveId).GetAwaiter().GetResult();

    copyResponse = myWebLogic.CopyFolderAsync(tracingService,
        accessToken,
        myWebSubmissionDriveId,
        srcFolderId,
        desFolderId,
        desFolderName,
        applicationDriveId).GetAwaiter().GetResult();

}
catch (Exception spEx)
{
    xLog.AddLog("ProcessSubmission Sharepoint Exception: " + spEx.Message, 3);
}
finally
{
    tracingService.Trace($"Copy folder reponse: {JsonConvert.SerializeObject(copyResponse)}");

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final remarks
&lt;/h2&gt;

&lt;p&gt;This code shows the implementation I came up with for my client's needs. Moving away from On-Prem to Cloud in Dynamics 365 can be a lengthy process, but tackling issues like hardcoded IDs, even for non-sensitive data, and tightly coupling is important. Going forward, I guarantee my client will have a much better pattern for the next developers to base their code on.&lt;/p&gt;

&lt;p&gt;There's still room for improvement, like Dependency Injection for the &lt;a href="https://www.youtube.com/watch?v=Z6Y2adsMnAA" rel="noopener noreferrer"&gt;HTTP Client&lt;/a&gt; and better error handling. But overall, I'm proud of the result. The goal was achieved with the minimum required effort while respecting the client's budget and also providing an elegant and easy-to-maintain solution. &lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>webdev</category>
      <category>programming</category>
      <category>microsoftgraph</category>
    </item>
  </channel>
</rss>
