<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Mart&#39;s blog</title>
    <link>https://blog.martdegraaf.nl/</link>
    <description>Recent content on Mart&#39;s blog</description>
    <webfeeds:analytics id="G-ZFQGR29JJQ" engine="GoogleAnalytics"/>
    <webfeeds:accentColor>0078D3</webfeeds:accentColor>
    <webfeeds:related layout="card" target="browser"/>
    <webfeeds:icon>https://gravatar.com/userimage/91624600/fb51d6d9172e5043a1e59d59f3f84e62.jpeg?v=1666815123000&amp;size=200</webfeeds:icon>
    <generator>Hugo -- 0.155.3</generator>
    <language>en-us</language>
    <managingEditor>blog@martdegraaf.nl (Mart de Graaf)</managingEditor>
    <webMaster>blog@martdegraaf.nl (Mart de Graaf)</webMaster>
    <copyright>Mart de Graaf</copyright>
    <atom:link href="https://blog.martdegraaf.nl/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Cleanup Friday - A FinOps Story</title>
      <link>https://blog.martdegraaf.nl/posts/cleanup-friday/</link>
      <pubDate>Fri, 13 Feb 2026 13:47:14 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/cleanup-friday/</guid>
      <description><![CDATA[Cleanup Friday - A FinOps Story: How we saved 1006 meatballs by optimizing our non-production environments and what we learned from it.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/cleanup-friday/cover.jpg" type="image/jpeg" />
      <media:content url="https://blog.martdegraaf.nl/posts/cleanup-friday/cover.jpg" medium="image" type="image/jpeg">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/cleanup-friday/cover.jpg" alt="" /><br/><br/><blockquote>
<p>We saved 1006 meatballs!</p>
</blockquote>
<p>On 12 February 2026, my client organized a hackathon themed around FinOps. The goal was to come up with innovative solutions to optimize cloud costs for non-production environments.</p>
<h2 id="big-spenders">Big spenders</h2>
<p>During the hackathon, we discovered that our non-production environments were running 24/7, which was a significant contributor to our cloud costs. We also identified three major Azure resources that were particularly expensive:</p>
<ul>
<li>Azure App Service</li>
<li>Azure SQL Database</li>
<li>Log analytics workspaces</li>
</ul>
<h3 id="azure-app-service">Azure App Service</h3>
<p>Nobody is using the app service on Development at night, so let&rsquo;s save money. We have scripted to downscale the app service plans and turn off app services at night.</p>
<p>To achieve direct results during this hackathon we minified the scope to only the acceptance environment and the second (shadow) region. We only needed this region when we are doing failover testing, wich was already implemented in a pipeline. This meant that we could turn off the app services in this region for the rest of the time, which resulted in significant cost savings, by our calculations we saved 1006 meatballs!</p>
<h3 id="azure-sql-database">Azure SQL Database</h3>
<p>We have not done something for our Azure SQL Databases yet, but we discovered that we could potentially save money by switching from DTU based to vCore based pricing. This would allow us to scale down the databases during non-working hours and scale them up when needed. We will explore this option further in the future.</p>
<h3 id="log-analytics-workspaces">Log analytics workspaces</h3>
<p>We investigated our log analytics workspaces and found that there was already ahead of the hackathon a big ingestion spike was resolved. I would like to reccomend companies to set alerting on the price of their log analytics workspaces, so that they can quickly identify and resolve any unexpected cost spikes.</p>
<p>Also if you are an engineer and looking to save some money on your application insights. I blogged about this topic before:</p>
<ul>
<li><a href="https://blog.martdegraaf.nl/posts/azure-application-insights-in-control-of-costs/">Learn how to verify the biggest costs of your Log Analytics workspace</a>.</li>
<li><a href="https://blog.martdegraaf.nl/posts/duplicate-logging-azure-application-insights/">Duplicate logging in Azure Log Analytics / Application Insights</a>.</li>
</ul>
<h2 id="learnings">Learnings</h2>
<p>I learned three key lessons from this hackathon:</p>
<ol>
<li>Non-production environments can be a significant cost driver: It&rsquo;s important to regularly review and optimize non-production environments to avoid unnecessary costs. If nobody is using the resources, isn&rsquo;t it a waste of money and energy to keep them running?</li>
<li>You think you are doing well, but there is always room for improvement: Even though we had already implemented some cost-saving measures, we still found significant opportunities for further optimization.</li>
<li>Quantifying costs by using a relatable metric (like meatballs) can help to communicate the impact of cost-saving measures in a more engaging way.</li>
</ol>
<h3 id="5-lenses-of-finops">5 lenses of FinOps</h3>
<p>The FinOps Framework also has an assesment tool and it has 5 lenses to look at your FinOps maturity:</p>
<ol>
<li>Knowledge: Do teams know what they are spending in the cloud?</li>
<li>Process: Is there a process in place to manage cloud costs?</li>
<li>Metrics: Are there metrics in place to track cloud costs and identify areas for optimization?</li>
<li>Adoption: Is the mindset of cost optimization adopted across the organization?</li>
<li>Automation: Are there automated processes in place to optimize cloud costs?</li>
</ol>
<p>See <a href="https://www.finops.org/wg/finops-assessment/" target="_blank" rel="noopener">https://www.finops.org/wg/finops-assessment/</a> for the full assesment tool and more details on the 5 lenses. They apply a scoring system to assess the maturity of an organization&rsquo;s FinOps practices across these lenses.</p>
<p>In our presentation at the end of the day we shared our findings using these 5 lenses, which helped to structure our learnings and recommendations for future improvements.</p>
<h2 id="cleanup-friday">Cleanup Friday</h2>
<p>Let&rsquo;s take this habit of cleaning up non-production environments to the next level.</p>
<p>I think it will be perfect to establish a &ldquo;Cleanup Friday&rdquo; routine. Clean your desk, clean your cloud environment. Every Friday, we can dedicate time to review and clean up our non-production resources. This can include:</p>
<ul>
<li>Deleting unused resources</li>
<li>Automating scaling down resources that are not needed at full capacity</li>
<li>Reviewing and optimizing resource configurations for cost savings</li>
</ul>
<p>The day after I think i also saved an extra 214 monthly meatballs by deleting proof of concept resources nobody looked at for months.</p>
<p>Let&rsquo;s make Cleanup Friday a tradition!</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.finops.org/framework/" target="_blank" rel="noopener">https://www.finops.org/framework/</a></li>
</ul>
<blockquote>
<p>The price of the client&rsquo;s meatball in their cafetaria is €2,33. I can recommend the meatballs, however they are hybrid balls, so half meat half vegan. Still delicious though, but honerable mention to the other half of the meatball, the vegan part.</p>
</blockquote>
]]></content:encoded>
      <category>azure</category>
      <category>finops</category>
      <category>cost management</category>
      <category>cleanup</category>
      <category>automation</category>
    </item>
    <item>
      <title>Naming Azure Deployments to avoid overwriting</title>
      <link>https://blog.martdegraaf.nl/posts/naming-azure-deployments/</link>
      <pubDate>Tue, 03 Feb 2026 19:54:28 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/naming-azure-deployments/</guid>
      <description><![CDATA[Best practices for naming Bicep deployments and Bicep modules to avoid overwriting and ensure uniqueness.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/naming-azure-deployments/cover.jpg" type="image/jpeg" />
      <media:content url="https://blog.martdegraaf.nl/posts/naming-azure-deployments/cover.jpg" medium="image" type="image/jpeg">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/naming-azure-deployments/cover.jpg" alt="" /><br/><br/><h2 id="azure-overwrites-deployments">Azure overwrites deployments</h2>
<p>Azure Resource Manager (ARM) is the control plane of Azure that executes all deployments, regardless of whether you use Bicep or ARM templates to describe your desired state. When you deploy with the same name, ARM treats it as an update to the existing deployment. This means that if you use a static name for your deployments, each new deployment will overwrite the previous one. This can lead to confusion and makes it difficult to track changes over time.</p>
<p>You may encounter a limit on the number of deployments you can have in a resource group. This limit is 800 deployments. When you reached this limit before 2020, you had to delete old deployments manually. However, since 2020, Azure automatically cleans up old deployments, so this is less of a concern now.</p>
<blockquote>
<p><a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-history-deletions?tabs=azure-powershell" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/deployment-history-deletions?tabs=azure-powershell</a></p>
</blockquote>
<h3 id="concurrent-deployments-with-the-same-name">Concurrent deployments with the same name</h3>
<p>A common issue occurs when two deployments with the same name execute simultaneously. This often happens unintentionally when multiple modules within a larger deployment receive the same deployment name. The deployment will fail with an error indicating a conflict, though the exact error message can vary. To avoid this, ensure that each module deployment receives a unique name, especially when deploying multiple instances of the same module in parallel.</p>
<p>
<img loading="lazy" src="deployment_failed_conflict.png" alt="Deployment failed, conflict"  />
</p>
<h2 id="constraints">Constraints</h2>
<p>When naming your deployments, keep in mind the following constraints:</p>
<ul>
<li>Deployment names must be unique within a resource group.</li>
<li>Deployment names can contain alphanumeric characters, hyphens, and underscores.</li>
<li>Deployment names cannot exceed 64 characters in length.</li>
</ul>
<p>When using modules you want to ensure the deployment names are also unique within the scope of the module deployment.</p>
<h2 id="naming-strategy">Naming strategy</h2>
<p>To avoid overwriting deployments and to keep track of changes, it&rsquo;s a good practice to use a unique naming strategy for each deployment. Here are some common approaches:</p>
<ol>
<li><strong>Timestamp</strong>: Append a timestamp to the deployment name. This ensures that each deployment has a unique name based on the date and time it was created.
Example: <code>myDeployment_20231119T075428</code></li>
<li><strong>Versioning</strong>: Use a version number in the deployment name. This can be:
<ul>
<li><strong>Build number</strong> from your CI/CD pipeline (e.g., <code>myDeployment_12345</code>)</li>
<li><strong>Semantic version</strong> when using tools like GitVersion (e.g., <code>myDeployment_1.2.3</code>)</li>
<li><strong>Git commit hash</strong> for traceability to source code (e.g., <code>myDeployment_a1b2c3d</code>)</li>
<li><strong>PR number</strong> when deploying from pull requests (e.g., <code>myDeployment_PR-123</code>)</li>
</ul>
</li>
<li><strong>Combination</strong>: Combine both timestamp and versioning for even more clarity.
Example: <code>myDeployment_v1_20231119T075428</code></li>
<li><strong>Unique Identifiers</strong>: Use GUIDs or other unique identifiers to ensure uniqueness.
Example: <code>myDeployment_123e4567-e89b-12d3-a456-426614174000</code></li>
</ol>
<h3 id="naming-starts-in-your-ci-cd-pipeline">Naming starts in your CI CD pipeline</h3>
<p>When deploying from a CI/CD pipeline, you can generate a unique deployment name using the pipeline&rsquo;s built-in variables. For example, in Azure DevOps, you can use the build ID or timestamp to create a unique name.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">AzureResourceManagerTemplateDeployment@3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">inputs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="nt">deploymentName</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;$(Build.BuildId)&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="c"># Other deployment inputs</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>Or using the Azure CLI in yaml:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      az deployment group create
</span></span></span><span class="line"><span class="cl"><span class="sd">        --resource-group myResourceGroup
</span></span></span><span class="line"><span class="cl"><span class="sd">        --template-file main.bicep
</span></span></span><span class="line"><span class="cl"><span class="sd">        --name &#34;$(Date:yyyyMMddTHHmmss)&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>Or using the build number:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">      az deployment group create
</span></span></span><span class="line"><span class="cl"><span class="sd">        --resource-group myResourceGroup
</span></span></span><span class="line"><span class="cl"><span class="sd">        --template-file main.bicep
</span></span></span><span class="line"><span class="cl"><span class="sd">        --name &#34;$(Build.BuildId)&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>In Github Actions, you can use the <code>github.run_id</code> or <code>github.run_number</code> to create a unique deployment name.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy to Azure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          az deployment group create
</span></span></span><span class="line"><span class="cl"><span class="sd">            --resource-group myResourceGroup
</span></span></span><span class="line"><span class="cl"><span class="sd">            --template-file main.bicep
</span></span></span><span class="line"><span class="cl"><span class="sd">            --name &#34;${{ github.run_id }}&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="bicep-sample-for-timestamp-naming">Bicep sample for timestamp naming</h3>
<p>The <code>utcNow()</code> function in Bicep can only be used as a default value for parameters, and must be provided at deployment time. You cannot use it directly in variable assignments or resource names. Here&rsquo;s how you can use it to create unique module deployment names:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="c1">// main.bicep</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">timestamp</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">utcNow</span><span class="p">(</span><span class="s">&#39;yyyyMMddTHHmmss&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">storage</span><span class="w"> </span><span class="s">&#39;modules/storage.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;storage-</span><span class="si">${</span><span class="nv">timestamp</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">storageAccountName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;mystorageacct&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">keyvault</span><span class="w"> </span><span class="s">&#39;modules/keyvault.bicep&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;keyvault-</span><span class="si">${</span><span class="nv">timestamp</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">vaultName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;mykeyvault&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="user-defined-function-for-naming">User defined function for naming</h3>
<p>You can create a user-defined function in Bicep to generate a unique deployment name based on your preferred strategy.</p>
<p>This example function creates a prefixed name and ensures it does not exceed 64 characters:
If the name exceeds 64 characters, the deployment will still fail, but you can adjust the logic as needed.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="p">@</span><span class="nf">export</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">@</span><span class="nf">description</span><span class="p">(</span><span class="s">&#39;Returns a module name with a prefix and ensures the name is at most 64 characters&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">prefixedName</span><span class="p">(</span><span class="nv">suffix</span><span class="w"> </span><span class="nv">string</span><span class="p">)</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nf">length</span><span class="p">(</span><span class="s">&#39;</span><span class="si">${</span><span class="nv">az</span><span class="p">.</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">suffix</span><span class="si">}</span><span class="s">&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nv">64</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="nf">substring</span><span class="p">(</span><span class="s">&#39;</span><span class="si">${</span><span class="nv">az</span><span class="p">.</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">suffix</span><span class="si">}</span><span class="s">&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">0</span><span class="p">,</span><span class="w"> </span><span class="nv">64</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">az</span><span class="p">.</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">suffix</span><span class="si">}</span><span class="s">&#39;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>So to fix this, we want a suffix that ensures the name is unique and fits within the constraints. When the base name exceeds the max length, we truncate it to fit.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="p">@</span><span class="nf">export</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">@</span><span class="nf">description</span><span class="p">(</span><span class="s">&#39;Returns a module name with a prefix and ensures the name is at most 64 characters&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">prefixedName</span><span class="p">(</span><span class="nv">suffix</span><span class="w"> </span><span class="nv">string</span><span class="p">)</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">var</span><span class="w"> </span><span class="nv">baseName</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">az</span><span class="p">.</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">suffix</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">var</span><span class="w"> </span><span class="nv">maxLength</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nv">64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">return</span><span class="w"> </span><span class="nf">length</span><span class="p">(</span><span class="nv">baseName</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nv">maxLength</span><span class="w"> </span><span class="p">?</span><span class="w"> </span><span class="nf">substring</span><span class="p">(</span><span class="nv">baseName</span><span class="p">,</span><span class="w"> </span><span class="nv">0</span><span class="p">,</span><span class="w"> </span><span class="nv">maxLength</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="nv">baseName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>Make sure deployment names are unique. Module names no longer need to be unique, but using unique names can be preferred when you want to easily see which build belongs to which deployment. Using timestamps or build IDs from your CI/CD pipeline can help ensure uniqueness and avoid overwriting deployments.</p>
<p>Let&rsquo;s be happy to know you don&rsquo;t hit the deployment limit anymore 🎉.</p>
]]></content:encoded>
      <category>azure</category>
      <category>bicep</category>
      <category>infrastructure-as-code</category>
      <category>naming</category>
      <category>devops</category>
    </item>
    <item>
      <title>2026 Better Workflows</title>
      <link>https://blog.martdegraaf.nl/posts/2026-workflows/</link>
      <pubDate>Fri, 09 Jan 2026 11:14:19 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/2026-workflows/</guid>
      <description><![CDATA[Optimizing workflows for podcasting and blogging and more in 2026.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/2026-workflows/cover.jpg" type="image/jpeg" />
      <media:content url="https://blog.martdegraaf.nl/posts/2026-workflows/cover.jpg" medium="image" type="image/jpeg">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/2026-workflows/cover.jpg" alt="" /><br/><br/><blockquote>
<p>&ldquo;I can&rsquo;t believe how much you get done, and running your family with children.&rdquo; - a few colleagues</p>
</blockquote>
<p>In this article I want to tell you about the workflows and habits that got me started with podcasting and how to keep blogging regularly. I will also share some of my personal goals for 2026 and new workflows I have or will adopt.</p>
<p>Everytime you do something often, it is good to invest in optimizing the workflow. In some books like Tiny Habits and Atomic Habits they say small changes can lead to great things over time. Also there is a thing called &lsquo;de drempel&rsquo; in Dutch, which means &rsquo;the threshold&rsquo;. The idea is that when you lower the threshold to start something, you are more likely to start doing it. This is why it for me it is important that if I want to blog, it should be as easy as possible to just start.</p>
<p>Also i tend to focus on doing things fast and ask for feedback as soon as I can. This means that my current workflow is writing a post pushing it to a test environment and asking for feedback.</p>
<p>In 2025 I also used my blog to write the information what i also were using for presenting. this way I could reuse content and be certain that the content was available for people after the presentations.</p>
<p>It looks like I have a lot of workflows going on, but in reality I focus on one at the time. Setting my own goals and deadlines helps me to focus on one thing at the time. Setting concrete goals continually lets me learn and feel what I am capable of.</p>
<h2 id="workflows">Workflows</h2>
<p>Let&rsquo;s talk about workflows. I have optimized my workflows for podcasting and blogging. It is just like Agile, start with something small and improve it over time. And focussing on the most important parts first. In this article I will share with you some cool workflows on different thinks I work on.</p>
<h3 id="podcast">Podcast</h3>
<p>To get started with podcasting Peter and I discussed how to start and what tools to use. Peter tended to act a lot more like Eric, hold back and rethink everthing. I was more of a Ryan &ldquo;let&rsquo;s just do it&rdquo; kind of person. I want to share with you how we set up our podcasting workflow.</p>
<blockquote>
<p>Read about Eric and Ryan in my blog post: <a href="https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/">Meet Ryan and Eric</a></p>
</blockquote>
<h4 id="recording">Recording</h4>
<p>We wanted to keep things simple and use tools we already had. So we used what we had available:</p>
<figure class="align-center ">
    <img loading="lazy" src="/posts/2026-workflows/rode_mics.jpeg#center"
         alt="Rode mics used for podcasting" height="300"/> <figcaption>
            <p>Rode mics used for podcasting</p>
        </figcaption>
</figure>

<ul>
<li><strong>Microphones</strong>: Two sets of Rode Wireless GO II</li>
<li><strong>Recording Software</strong>: Rode Connect</li>
</ul>
<figure class="align-center ">
    <img loading="lazy" src="/posts/2026-workflows/Rode_connect.jpeg#center"
         alt="Connecting to Rode Connect" height="300"/> <figcaption>
            <p>Connecting to Rode Connect</p>
        </figcaption>
</figure>

<h4 id="editing">Editing</h4>
<p>For editing, we decided to use Audacity, a free and open-source audio editing software. It offers all the basic features we needed to clean up our recordings, add intros/outros, and adjust audio levels. But to edit a full episode using audacity is still quite a bit of work. That is why we we looked into alternatives.</p>
<h5 id="ai-powered-transcription">AI-Powered Transcription</h5>
<p>To streamline the editing process, we explored AI-powered transcription services. To do this i created a .NET console application that used Whisper.Net to transcribe our recordings. This allowed us to quickly identify sections that needed editing without having to listen to the entire recording.</p>
<blockquote>
<p>See the code on GitHub: <a href="https://github.com/martdegraaf/TranscribePodcast/blob/master/TranscribePodcast/Program.cs" target="_blank" rel="noopener">https://github.com/martdegraaf/TranscribePodcast</a></p>
</blockquote>
<p>The output was good enough but not good enough to use for a workflow within Audacity.</p>
<h5 id="online-ai-powered-services">Online AI powered services</h5>
<p>We also experimented with online AI-powered services that offer podcast editing features. These platforms can automatically remove filler words, background noise, and even suggest improvements to the audio quality. This significantly reduced the time we spent on manual editing.</p>
<p>The tools we used for this were:</p>
<ul>
<li><strong>Auphonic</strong>: For automatic audio post-production.</li>
<li><strong>Trebble.fm</strong>: For AI-driven podcast editing and enhancements.</li>
</ul>
<h4 id="podcast-naming">Podcast naming</h4>
<p>We prioritized recording over naming our podcast. So we decided to come up with a name after recording the first episode.
We decided to name our podast &ldquo;Oogkleppen&rdquo; which translates to &ldquo;Blinders&rdquo; in English. The name reflects our focus on discussing topics from a unique perspective, challenging conventional wisdom, and exploring new ideas.</p>
<h4 id="podcast-intro">Podcast Intro</h4>
<p>To create a catchy intro for our podcast, we searched for royalty-free music online. We searched on <a href="https://pixabay.com/" target="_blank" rel="noopener">https://pixabay.com/</a> and editted music in our spoken intro and field tested it with some friends and colleagues. The feedback was positive, and we felt the intro set the right tone for our podcast.</p>
<h3 id="blogging">Blogging</h3>
<p>After optimizing our podcasting workflow, I decided to optimize my blogging workflow as well. I mainly blog in markdown in Visual Studio Code. To streamline the process of creating new blog posts, I created a custom task in VS Code.</p>
<p>By using the <code>tasks.json</code> file I automated the process of creating a new blog post file using the hugo cli, so I only have to provide the post name and it will create the file and open it in VS Code.</p>
<p>So my new workflow for creating a new blog post is:</p>
<ol>
<li>Use the command palette in VS Code <code>Ctrl+Shift+P</code> and select &ldquo;Run Task&rdquo;</li>
<li>Select &ldquo;Hugo: New code post&rdquo;</li>
<li>Enter the post name (e.g., &ldquo;my-new-post&rdquo;)</li>
<li>Start writing the blog post in the newly created markdown file.</li>
<li>Branch and commit your changes as usual.</li>
<li>Push and create a PR to merge my new blog post.</li>
</ol>
<p>This will setup a test environment in Azure Static Web Apps and allow me to preview the blog post before merging it to main.</p>
<div class="codecontainer" hi="mart"><div class="code-name">tasks.json</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;2.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;tasks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;label&#34;</span><span class="p">:</span> <span class="s2">&#34;Hugo: New code post&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;shell&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;hugo new --kind code-post posts/${input:postName}; code ${workspaceFolder}/src/blog/content/posts/${input:postName}/index.md&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceFolder}/src/blog&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;presentation&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;reveal&#34;</span><span class="p">:</span> <span class="s2">&#34;always&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;panel&#34;</span><span class="p">:</span> <span class="s2">&#34;shared&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;problemMatcher&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;postName&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;promptString&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Post name (e.g., my-new-post)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;default&#34;</span><span class="p">:</span> <span class="s2">&#34;new-post&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>This post was made using this workflow and i can tell you it works great! After creating the post I also wanted to be up to date with hugo and my theme. So I Updated Hugo to the latest version and my theme to the latest version as well, this took me longer than expected but now everything is up to date.</p>
<p>I have many uncommitted open changes in this repository because i tend to expiriment a lot with new ideas. I fixed some issues with the latest hugo version locally but did not commit these changes, it took me <strong>2 hours</strong> to debug and find out what was wrong. The lesson for me is to commit things more quickly and make PR&rsquo;s small. Updating a theme should be a seperate pull request if you would be normally working in a team. Why would I do this differently for my own blog?</p>
<h3 id="blocking-youtube">Blocking YouTube</h3>
<p>We have kids at home. And they love YouTube. But I tell them the so-called &lsquo;Shorts&rsquo; are videos that make them dumb. So to help them focus on better content I have blocked YouTube  on our home network using AdGuard within home assistant. This helped us as parents to have more control over what content our kids can access.</p>
<blockquote>
<p>Basically we as parents have been framing short videos as &lsquo;dumb videos&rsquo; or &lsquo;videos that make you dumb&rsquo;</p>
</blockquote>
<p>Our current workflow was to navigate to AdGuard, go to Filters &gt; Block Services and search youtube in the list and disable it. But, we all know, that blocking youtube completely is not always desired. That is why I want to improve this workflow.</p>
<p>I wanted to have this in our dashboard with a toggle switch. So I searched for solutions and found out that AdGuard has an API that can be used to toggle services. see: <a href="https://community.home-assistant.io/t/toggling-adguard-services-from-ha-dashboard/719007" target="_blank" rel="noopener">https://community.home-assistant.io/t/toggling-adguard-services-from-ha-dashboard/719007</a></p>
<h3 id="consuming-blog-posts-and-podcasts">Consuming Blog posts and podcasts</h3>
<p>For the setup of our podcast we needed to make it available for multiple platforms. Such as Spotify and Apple podcasts. Most other platforms work with RSS Feeds. So when we make the RSS Feed from Spotify available other third party platforms can also use it.</p>
<h4 id="consuming-podcasts">Consuming podcasts</h4>
<p>Earlier I just used Spotify to listen to podcasts. Annoying because when you listen to music via Spotify it is difficult to find and continue the right podcast while in car. Or just always.</p>
<blockquote>
<p>I can recommend listening to the podcasts: Darknet Diaries and How to take over the World. If you are Dutch, take our podcast Oogkleppen! 😉</p>
</blockquote>
<p>My colleague Peter recommended using Antennapod on Android. Jan uses Pocket Casts on iOS.
I installed both apps on Android, i am going to test both, and let you know. A big plus would be if a Android Auto integration is available.</p>
<h4 id="consuming-blog-posts">Consuming blog posts</h4>
<p>I earlier just checked content reguraly but from now on will organise blogs via Readly. RSS feeds mostly are available via /index.xml on most blogs. Some are available via /feed or /feed.rss. With Readly I can organise blogs into categories and read them when I want to.</p>
<p>After typing this i went on and tested my own blog to see if the feed is working. But i saw many improvements. I also tested it in Inoreader and discovered that my blog images are not showing up in the feed. So I have fixed that as well. Also my relative images in blog text were not showing up correctly so i made sure they are now absolute urls.</p>
<h3 id="making-the-right-tactical-switch">Making the right tactical switch</h3>
<p>I am using a Dygma Defy keyboard for my daily work. It is a split keyboard that allows me to type more ergonomically. The keyboard has hot swappable switches. And i used the keyboard a year using linear switches. As of 2026 I have switched my right half to tactile switches. Let&rsquo;s see what half i am most comfortable with.</p>
<p>Checkout this video about the switch choice: <a href="https://www.youtube.com/watch?v=noUj7uUyIk4" target="_blank" rel="noopener">https://www.youtube.com/watch?v=noUj7uUyIk4</a></p>
<figure class="align-center ">
    <img loading="lazy" src="/posts/2026-workflows/defy_switches.jpeg#center"
         alt="Dygma Defy - the right half. Brown are tactile, Yellow are linear." height="300"/> <figcaption>
            <p>Dygma Defy - the right half. Brown are tactile, Yellow are linear.</p>
        </figcaption>
</figure>

<blockquote>
<p>I was using Gateron G Pro 2.0 Yellow (linear) switches. I have just installed Kailh Silent Brown switches on the right half of my keyboard.</p>
</blockquote>
<h2 id="conclusion">Conclusion</h2>
<p>There will be more workflows to talk about, I am curious what workflows you have optimized. Please share them with me on LinkedIn or in the comments below.</p>
]]></content:encoded>
      <category>workflows</category>
      <category>podcast</category>
      <category>blogging</category>
      <category>productivity</category>
    </item>
    <item>
      <title>2026 A New Year, New Goals</title>
      <link>https://blog.martdegraaf.nl/posts/2026-new-year-workflows/</link>
      <pubDate>Wed, 07 Jan 2026 10:49:12 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/2026-new-year-workflows/</guid>
      <description><![CDATA[Kicking off 2026 with new goals and workflows for podcasting and blogging.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/2026-new-year-workflows/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/2026-new-year-workflows/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/2026-new-year-workflows/cover.png" alt="" /><br/><br/><p>I want you to know I started podcasting last year and the first episode went live on January 1st, 2026! 🎉 The podcast is called Oogkleppen and is recorded in Dutch.</p>
<h2 id="wrapping-up-2025">Wrapping up 2025</h2>
<p>I want to share some numbers here to give some insights in my 2025.</p>
<ul>
<li>Blog posts: 10</li>
<li>Blog post as draft: 3</li>
<li>Spoken on stage: 8</li>
<li>Active Microsoft certifications: 5</li>
</ul>
<p>If you think, how can this Mart do this much? Stay tuned for my next blog post in this series about my workflows for blogging, speaking and more.</p>
<h3 id="books">Books</h3>
<p>I read multiple books in 2025. The most impactful book was &ldquo;Verbaal meesterschap&rdquo; by Remco Claassen. It helped me to develop my Web Application Firewall session. I used magic tricks during the talk to engage the audience. I recommend reading this book if you want to improve your speaking skills.</p>
<figure class="align-center ">
    <img loading="lazy" src="/posts/2026-new-year-workflows/speaking_with_fire.jpeg#center"
         alt="Spoke at Move Up with wallet full of fire" height="300"/> <figcaption>
            <p>Spoke at Move Up with wallet full of fire</p>
        </figcaption>
</figure>

<p>The company i work for 4Dotnet celebrated its 20th anniversary in 2025. I have been working there for 5 years now. We went on a trip to Lisbon with the team. Some of the cover images of the blog posts were taken there.</p>
<h2 id="setting-goals-for-2026">Setting goals for 2026</h2>
<p>As we step into 2026, it&rsquo;s a great time to set some personal and professional goals. Here are a few goals I have set for myself this year:</p>
<ul>
<li>Speaking: give 6 talks, develop 1 new talk, speak at 2 new events (never spoken at before).</li>
<li>Podcast: release 6 episodes, have 6 guests, decide if Peter and I want to continue.</li>
<li>Health : Lose some kg, strength training 2x/week.</li>
<li>Blog: Publish 12 blog posts, every last Friday of the month at 16:00.</li>
</ul>
<h3 id="anti-goal-2026-only-fun-or-promised">Anti-goal 2026: Only &ldquo;Fun&rdquo; or &ldquo;Promised&rdquo;</h3>
<p>In 2026, I commit to only engaging in activities that I either genuinely enjoy (≥7/10 energy) or have explicitly promised (clear agreement/expectation). Anything outside of that: don&rsquo;t do it or renegotiate.</p>
<h3 id="review">Review</h3>
<p>I reviewed my goals with a peer and he asked me what my personal goals were for 2026 especially for my family. I tought about this throughly and decided to book a vacation with my family for our wedding anniversary. Thanks Peter for the great question!</p>
<h2 id="all-the-pancakes-in-the-world">All the pancakes in the world</h2>
<p>Let us be better in 2026 by optimizing our workflows and embracing new technologies. Whether it&rsquo;s podcasting or blogging, finding efficient ways to create and share content can lead to greater productivity and enjoyment. I wish you all the pancakes in the world in 2026! 🥞🎉</p>
]]></content:encoded>
      <category>podcast</category>
      <category>goals</category>
      <category>blogging</category>
      <category>personal</category>
    </item>
    <item>
      <title>Meet Ryan and Eric</title>
      <link>https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/</link>
      <pubDate>Fri, 21 Nov 2025 21:41:58 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/</guid>
      <description><![CDATA[Meet Ryan and Eric, two software developers but completely different.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/meet-ryan-and-eric/cover.png" alt="" /><br/><br/><p>For my presentation &ldquo;Visual Studio Code: One Tool to Rule Them All?&rdquo; I created two fictional characters: Ryan and Eric. Both are software developers, but they take very different approaches to their work.</p>
<p>A colleague of mine suggested I write a blog post to introduce them properly. Everyone who heard my story about them immediately recognized these two archetypes. Even presenters who spoke after my session started using Ryan and Eric in their talks. So let&rsquo;s give them the spotlight they deserve, right?</p>
<h2 id="meet-ryan">Meet Ryan</h2>
<p>Have you met Ryan? Ryan is a young developer, fresh out of college. He has a couple of core qualities: he learns fast, is curious and loves new technologies. Ryan probably has more unfinished projects on his laptop than finished ones.</p>
<blockquote>
<p>&#x1f916; The images of Ryan and Eric are AI-generated using Sora, by providing my own image as input and describing the character traits.</p>
</blockquote>
<figure class="align-center ">
    <img loading="lazy" src="/posts/meet-ryan-and-eric/Ryan.png#center"
         alt="Ryan - fast, experimental, shortcut-obsessed" height="300"/> <figcaption>
            <p>Ryan - fast, experimental, shortcut-obsessed</p>
        </figcaption>
</figure>

<p>Ryan is the kind of person who uses hip IDEs like Visual Studio Code, because it is lightweight and fast. Ideally Ryan would like to use his phone to code on the go, while watching his favorite series on Netflix.</p>
<blockquote>
<p>“Move fast and fix things later.” - Ryan</p>
</blockquote>
<p>When you have a Ryan on your team, you will notice that he will run before thinking his actions through. He will push fast to production, only to find out later that something is broken. But hey, at least he tried, right? And if he is just as fast to fix it again, everything is fine.</p>
<h2 id="meet-eric">Meet Eric</h2>
<p>Eric is a seasoned developer with years of experience. He is pragmatic, values stability and prefers to stick to tried-and-true tools. Eric will start Notepad++ to quickly format his XML or JSON or compare files. He has been using and running projects in Visual Studio for years. He jokes that you cannot write any good code in Visual Studio Code, because where is the &ldquo;Play&rdquo; button to start the project? Not to mention how do you debug without it?</p>
<figure class="align-center ">
    <img loading="lazy" src="/posts/meet-ryan-and-eric/Eric.png#center"
         alt="Eric - stable, cautious, prefers proven tools" height="300"/> <figcaption>
            <p>Eric - stable, cautious, prefers proven tools</p>
        </figcaption>
</figure>

<p>When you have an Eric on your team, you will notice that he will take his time to think things through. He will criticize decisions beforehand knowing that changing things later will be costly. Why change when you have worked this way for years?</p>
<blockquote>
<p>“If it ain&rsquo;t broke, don&rsquo;t fix it.” - Eric</p>
</blockquote>
<h2 id="a-conversation-between-ryan-and-eric">A conversation between Ryan and Eric</h2>
<p>If Ryan and Eric were to have a conversation, it might go something like this:</p>
<h3 id="having-multiple-prs-open">Having multiple PR&rsquo;s open</h3>
<blockquote>
<p><strong>Context:</strong> Eric leans back.</p>
</blockquote>
<div class="chat">

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    We’re going to make some rules. Ready?
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    Is “no” an acceptable answer?
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    No.<br>
<strong>Rule 1:</strong> No more than two open PRs at a time.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    What if I have three great ideas?
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    Write them down. Ship two. The third can wait.
  </div>
</div>

</div>

<h3 id="writing-an-adr">Writing an ADR</h3>
<blockquote>
<p><strong>Context:</strong> A new framework is being discussed. Ryan is already prototyping.</p>
</blockquote>
<div class="chat">

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    Before we change the framework, we write an ADR.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    Can&rsquo;t we just try it first and see what happens?
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    We can try it. But we still write an ADR.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    What do you even want in there? &ldquo;Ryan had a feeling&rdquo;?
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    No.<br/>
<strong>ADR Rule 1:</strong> Capture the problem, the options, and why we picked this one.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    That sounds like homework.
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    It&rsquo;s future-you cheating on future-homework. You won&rsquo;t remember why you did this in six months.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    Fine. I&rsquo;ll write the ADR. Short one.
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    Short is okay. Undocumented is not.
  </div>
</div>

</div>

<h3 id="setting-up-sql-server">Setting up SQL Server</h3>
<blockquote>
<p><strong>Context:</strong> Eric is setting up his new development machine and needs SQL Server.</p>
</blockquote>
<div class="chat">

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    I&rsquo;m downloading the SQL Server installer. This should take about an hour to set up properly.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    An hour? Just run <code>docker run -d -p 1433:1433 mcr.microsoft.com/mssql/server</code>
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    I don&rsquo;t trust containers for database work. What if something goes wrong?
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    <em>types command</em> Done. SQL Server is running. Took 30 seconds.
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    Wait, my installer failed. Compatibility issues with my Windows version.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    That&rsquo;s why I use containers. Same environment everywhere.
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    Fine. But what happens when I need to upgrade? Or backup? Or configure security?
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    Docker Compose file. Version control your entire database setup.
  </div>
</div>

<div class="chat-row right">
  <div class="chat-bubble ryan">
    <div class="chat-name">Ryan</div>
    Plus, you can automate the whole thing in your CI/CD pipeline. Database provisioning becomes part of your deployment process.
  </div>
</div>

<div class="chat-row left">
  <div class="chat-bubble eric">
    <div class="chat-name">Eric</div>
    <em>sighs</em> Show me this Docker thing. But I&rsquo;m keeping the installer just in case.
  </div>
</div>

</div>

<h2 id="ryan-and-eric">Ryan and Eric</h2>
<p>These two fictional characters represent two very different behavior patterns in software development teams. Ryan pushes for speed and experimentation; Eric optimizes for stability and predictability. Most of us have both inside us, and the trick is to know when to lean into which.</p>
<p>Let&rsquo;s introduce an example I myself encounter often: My colleague acts like Eric. He locks his computer using <code>Ctrl+Alt+Del</code> and selects &lsquo;Lock&rsquo; from the menu. I think he should use Ryan to optimize his workflow. I, on the other hand, use <code>Windows+L</code> to lock my computer instantly. The same pattern shows up in formatting code, switching screens, or running builds: you can click your way through, or you can invest in shortcuts and automation. Ryan would use <code>Windows+P</code> to extend his screen while Eric would right click on his desktop and select &lsquo;Display settings&rsquo; to do the same.</p>
<p>The tension between Ryan and Eric is useful. Ryan pulls the team toward new tools and faster workflows; Eric stops us from breaking production every week. Healthy teams make space for both.</p>
<h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>Ryan and Eric are two fictional characters that represent two extremely different behavior patterns in software development teams. You can act like Ryan or Eric, but you should use both to balance innovation and stability. I hope Ryan and Eric will return in future blog posts as well. If you want to refer to them, you are welcome to do so. Please link back to this post.</p>
<p>Let&rsquo;s set the mode accordingly:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">mode</span> <span class="o">=</span> <span class="nx">DeadlineIsToday</span> <span class="o">?</span> <span class="nx">Ryan</span> <span class="o">:</span> <span class="nx">Eric</span><span class="p">;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><blockquote>
<p>What about you? Are you more like Ryan or Eric? Or do you have another archetype in mind? Let me know in the comments!</p>
</blockquote>
<h3 id="further-reading">Further reading</h3>
<p>I wrote an Ryan-like article before read it here: <a href="https://blog.martdegraaf.nl/posts/marts-cheatsheet/">Mart&rsquo;s Cheatsheet</a></p>
]]></content:encoded>
      <category>Ryan</category>
      <category>Eric</category>
      <category>keyboard-shortcuts</category>
      <category>visual-studio-code</category>
      <category>developer-archetypes</category>
    </item>
    <item>
      <title>Monitor Your Waf using workbooks</title>
      <link>https://blog.martdegraaf.nl/posts/monitor-your-waf/</link>
      <pubDate>Fri, 31 Oct 2025 11:12:14 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/monitor-your-waf/</guid>
      <description><![CDATA[Small blog on how to monitor your WAF using Azure Workbooks.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/monitor-your-waf/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/monitor-your-waf/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/monitor-your-waf/cover.png" alt="" /><br/><br/><p>You can monitor your WAF using Azure Monitor. In this blog post, I will share my insights on how to set up monitoring for your WAF using workbooks.</p>
<h2 id="set-up-azure-workbook">Set up Azure Workbook</h2>
<p>You can use the Azure WAF Monitor Workbook to monitor your WAF. This workbook provides insights into the WAF logs and helps you identify potential issues.</p>
<p>
  <figure>
    <img loading="lazy" src="workbook.png#center" alt="Workbook for WAF, application gateway sample"  title="Workbook for WAF, application gateway sample"  />
    <figcaption style="text-align: center;">Workbook for WAF, application gateway sample</figcaption>
  </figure>
</p>
<p>Get the workbook here:
<a href="https://github.com/Azure/Azure-Network-Security/tree/master/Azure%20WAF/Workbook%20-%20WAF%20Monitor%20Workbook" target="_blank" rel="noopener">https://github.com/Azure/Azure-Network-Security/tree/master/Azure%20WAF/Workbook%20-%20WAF%20Monitor%20Workbook</a></p>
<p>You can deploy the workbook using the &lsquo;Deploy to Azure&rsquo; button in the GitHub repository.</p>
<p>You can use this as a base for your custom Workbook.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li>Troubleshoot your Application gateway&rsquo;s WAF. (see <a href="https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/">Troubleshoot Application Gateway in Production</a>)</li>
</ul>
]]></content:encoded>
      <category>WAF</category>
      <category>application gateway</category>
      <category>monitoring</category>
      <category>azure</category>
      <category>workbooks</category>
      <category>application gateway</category>
      <category>frontdoor</category>
    </item>
    <item>
      <title>Techorama 2025 notes and personal learnings</title>
      <link>https://blog.martdegraaf.nl/posts/event-notes/techorama/techorama-2025/</link>
      <pubDate>Thu, 30 Oct 2025 22:13:48 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/event-notes/techorama/techorama-2025/</guid>
      <description><![CDATA[My notes and personal learnings from Techorama 2025 in Utrecht.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/event-notes/techorama/techorama-2025/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/event-notes/techorama/techorama-2025/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/event-notes/techorama/techorama-2025/cover.png" alt="" /><br/><br/><h2 id="tuesday">Tuesday</h2>
<p>My Tuesday started with a carpool with my colleague. We drove to Utrecht but came stuck in traffic unfortunately missed the opening keynote. For this day my tactic was to avoid AI talks, and choose on the last second what session to attend.</p>
<h3 id="how-building-a-portable-escape-room-made-me-a-better-developer-by-william-brander"><strong>How building a portable escape room made me a better developer</strong> by William Brander</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;I built a portable IoT escape room, and it changed how I think about development! In this session, we&rsquo;ll uncover unexpected parallels between escape rooms and architecting resilient, scalable systems. Let me share the lessons learned, and how they translate into invaluable guidance for Domain-Driven Design (DDD), modularity, communication, scalability and fault tolerance. Doors will be unlocked at the end - even if the audience can&rsquo;t solve the puzzles.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Monitoring distributed systems can be devided into 3 piramids: Monitoring Area, Conitoring Concern and Interaction Type.
<ul>
<li>Monitoring Area
<ul>
<li>Infrastructure</li>
<li>Application</li>
<li>Capability</li>
</ul>
</li>
<li>Monitoring Area
<ul>
<li>Health</li>
<li>Performance</li>
<li>Capacity</li>
</ul>
</li>
<li>Interaction Type
<ul>
<li>Passive</li>
<li>Reactive</li>
<li>Proactive</li>
</ul>
</li>
</ul>
</li>
<li>If you build systems with joy, it will be reflected in the end product.</li>
<li>William showed us his remote escape room, which was really cool.</li>
</ul>
</li>
</ul>
<h3 id="creating-an-observable-cloud-platform-and-application-landscape-with-azure-by-alex-thissen"><strong>Creating an observable cloud platform and application landscape with Azure</strong> by Alex Thissen</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;In this session you will learn how to build and create a cloud platform for your applications that offers observability and monitoring capabilities. You are going to be introduced to the Azure resources for monitoring and operations and learn how to architect the landscape for multiple applications and business domains. Also, we will have a look at instrumenting applications with OpenTelemetry and the various patterns and practices to collect, filter and combine telemetry signals into an observable backplane. Combined with the Azure resources, the outcome is an environment that is capable to host, monitor and operate observable applications. You will see how this can help you in everyday situations in getting insights into and gaining control over your cloud-native applications running in Azure.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Alex explained perfectly what the difference is between Azure monitor and a log analytics workspace.
<ul>
<li>Azure Monitor is the overall service that provides a unified monitoring solution for your applications and resources in Azure. It collects, analyzes, and acts on telemetry data from various sources to help you understand the performance and health of your applications. <em>It is managed by Azure</em>.</li>
<li>Log Analytics Workspace is a specific component within Azure Monitor that serves as a centralized repository for storing and analyzing log data. It allows you to run queries, create visualizations, and set up alerts based on the collected log data.</li>
</ul>
</li>
<li>Alex tought the eye opener was that you can view your application insights and log analytics workspace in the Azure Data Explorer. I am not really sure why this is a big advantage, but I will explore this further.</li>
<li>Alex explained that the log Analytics workspace should be a shared resource for multiple applications. This could even be in the hub environment of a landing zone.</li>
<li>There is also an Kusto Explorer available at <a href="https://aka.ms/kustoexplorer" target="_blank" rel="noopener">https://aka.ms/kustoexplorer</a> which is a desktop application to query Kusto databases. I have to try this out.</li>
<li>Alex showed a new feature Health Monitoring in Azure. This is a feature to monitor the health of your applications. It is possible to build a Health Model to show the health if dependencies are not healthy.</li>
</ul>
</li>
</ul>
<h3 id="delivering-on-identity-solutions---practical-guidance-and-pitfalls-to-avoid-by-michele-leroux-bustamante"><strong>Delivering on Identity Solutions - practical guidance and pitfalls to avoid</strong> by Michele Leroux Bustamante</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Identity is a critical part to any solution - whether that solution is deployed on premises or to a cloud provider. The challenge most organizations have is lack of deep expertise with identity protocols, authentication techniques and standards, associated use cases and solution design, user management and user self-service lifecycles and the recommended best practices and necessary threat modeling to deliver a solution to production. Even when using a hosted identity provider, you are not excused from understanding how applications integrate with that provider and participate in the holistic solution, and you typically also need to build significant custom work around the user lifecycle which should also follow recommended best practices that aren’t always obvious. In this session, Michele will share recommended best practices for identity solution design and delivery, drawing examples from experience with actual customer solutions with varying requirements. The goal is to educate you on common and practical ways to approach the identity solution design while adhering to recommended best practices. You’ll get an overview of critical protocol flows, the evolving preference for SPA / BFF patterns for application integration, the challenges that arise when you let identity become “too custom” and how this impacts your choice of identity platform framework or vendor.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Every flow is deprecated except the authorization code flow with PKCE. So you should use Backends for Frontends (BFF) architecture.</li>
<li>Use no tokens in the browser. Store them in a secure cookie or use session storage.</li>
<li>Some banks do actually have access token valid for 30 days for some reason. Michele does not bank there :).</li>
<li>When you combine social with enterprise logins you have to be carefull with account linking. You don&rsquo;t want that a user can link his social account to an enterprise account without verification.</li>
<li>She explained Gateway federation, wich i am familiar with. This makes use of Home realm discovery.</li>
<li>Buying some tool like Auth0 is not enough, you still have to understand identity.</li>
</ul>
</li>
</ul>
<h3 id="top-5-techniques-for-building-the-worst-microservice-system-ever-by-william-brander"><strong>Top 5 techniques for building the worst microservice system ever</strong> by William Brander</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Microservices come with promises of scalability, reliability, and autonomy. But if everything is so rosy, how come the only success stories we hear about are at places like Netflix or Uber? I&rsquo;ve spent countless hours working on all kinds of microservice systems to come up with the definitive top 5 tips to ensure your microservices become complete disasters. Join me on a tour of insanity through some of the worst ways to make distributed mistakes.&rdquo;</em>
- Key takeaways:
- I think i identify with Dough the dog in the Pixar movie Up, because i am just as destracted as he is.
- Five techniques to build a bad microservice architecture:
1. Put an HTTP call in front of everything
2. Do a big bang rewrite
3. Don&rsquo;t use off the shelf frameworks
4. Use Nouns instead of Verbs for service boundaries
5. Conflate the logical with deployment boundaries
- He explained that you should avoid these anti patterns to build a successful microservice architecture.</p>
<h3 id="c-11--net-7-by-stefan-pölz"><strong>C# 11 + .NET 7</strong> by Stefan Pölz</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Although we are capable of overloading operators for owned types with traditional C#, we cannot define generic mathematical algorithms that may be consumed by multiple types of the .NET ecosystem. Previewed in .NET 6, November 2022 marked the release of Generic Math in .NET 7. New language features of C# 11 enable both Microsoft&rsquo;s BCL team and the community to ship powerful mathematical operations in data types and generic interfaces. These result in algorithms that supersede repetitive concepts, such as System.Math and System.MathF. Alongside exploring these additions to .NET, we&rsquo;ll assemble our own custom type with calculations that utilizes Generic Math.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Stefan explained all types have interfaces for math operations now. For example <code>IAdditionOperators&lt;TSelf, TOther, TResult&gt;</code>.</li>
<li><a href="https://github.com/Flash0ver" target="_blank" rel="noopener">https://github.com/Flash0ver</a></li>
<li>Cool example is that he could calculate with custom types like Bytes Gigabytes and Terabyte, all in one type.</li>
</ul>
</li>
</ul>
<h3 id="what-by-bart-de-smet"><strong>What&rsquo;s new in C# 13, 14, and beyond?</strong> by Bart de Smet</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Come and catch up on the most recent C# language evolution and a peek into the future. We&rsquo;ll start this talk by looking at various enhancements to existing language features, such as params collections, improvements to the lock statement, null-conditional assignment, the new field keyword, and various restrictions that have been lifted around the use of ref structs. Next, we&rsquo;ll have an in-depth overview of the new extension member support, finally enabling extension properties and indexers. To finish the talk, we&rsquo;ll look at brand new features slated for upcoming versions, such as dictionary expressions, union types, and more.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>the extension members are coming to C# 13. This makes it possible to add methods to existing types without modifying their source code.</li>
<li>Some things are improved to get a Span<T> in a params array.</li>
<li>Null-conditional assignment in C# 14: <code>customer?.Order = GetOrder();</code></li>
</ul>
</li>
</ul>
<p><a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13</a></p>
<p><a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14</a></p>
<h2 id="wednesday">Wednesday</h2>
<p>Wednesday was starting early, we had to vote for the Dutch elections first before going to the conference center. After that I attended the following sessions.</p>
<h3 id="kafka-for-net-developers-by-ian-cooper"><strong>Kafka for .NET Developers</strong> by Ian Cooper</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Kafka is a low-latency streaming solution with a rich ecosystem of tools such as Kafka Connect and Flink, but is less well-known to .NET developers. In this session we will introduce Kafka, explaining the mysteries of records, offsets, SerDes and schema registries, in-sync replicas, partitions and tools like Connect and Flink. There will be code, as we work with examples in .NET of using as a messaging solution in your applications. By the end of this session you should feel comfortable with the concepts required to use Kafka as a .NET developer.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Kafka is async messaging system with topics and partitions.</li>
<li>If your code fails, it can reprocess messages from a specific offset. If it keeps failing, you can move the offset forward to skip bad messages, but according to Ian, you should have gotten a queue instead of Kafka then.</li>
<li>Every partition can be consumed by only one consumer in a consumer group. This has it&rsquo;s own offset.</li>
<li>There is something called Kraft. Zookeeper was something old. Kraft can elect the leader nodes itself.</li>
<li>There is a setting called: <code>EnableAutoOffsetStore</code> which can be set to false to manage offsets yourself. Otherwise the offset is stored after each message is <strong>read</strong>. This can lead to message loss if your processing fails after reading the message but before processing it.</li>
<li>There is something called sticky partitions. This makes sure that messages with the same key always go to the same partition. This is usefull for ordering of messages.</li>
<li>Messages in Kafka are mostly in Avro, JSON or Protobuf format.</li>
</ul>
</li>
</ul>
<h3 id="scaling-azure-container-apps-maximize-performance-and-minimize-costs-by-anthony-chu"><strong>Scaling Azure Container Apps: maximize performance and minimize costs</strong> by Anthony Chu</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Azure Container Apps is a fully managed serverless container platform that allows you to run containerized workloads at scale. By using the correct scaling strategies and pricing plans, you can maximize performance and minimize costs of your Azure Container Apps workloads. In this session, we&rsquo;ll show you how to scale container apps and jobs on Azure Container Apps using its built-in KEDA scalers. Whether you&rsquo;re running a small app or a large-scale microservice application, we&rsquo;ll show you how to build and scale your containerized workloads on Azure Container Apps to maximize performance and minimize costs.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>You can create container apps with dedicated GPU support now.</li>
<li>You can use KEDA scalers to scale your container apps based on anything. For example eventhubs blobs service bus via Managed Identity.</li>
<li>On top of KEDA Azure Container Apps can scale based on HTTP request.</li>
</ul>
</li>
</ul>
<h3 id="debugging-like-a-coach-fixing-team-bugs-before-they-crash-the-game-by-amber-vanderburg"><strong>Debugging Like a Coach: Fixing Team Bugs Before They Crash the Game</strong> by Amber Vanderburg</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Every developer knows how to debug bad code—but how good are you at debugging a bad team dynamic? In sports, coaches analyze real-time data, player behavior, and performance breakdowns to fix issues before they cause the game. Yet in tech, we often let misaligned teams, inefficient workflows, and poor communication go unchecked—until we hit a major release failure or a full-on team meltdown. This session will show you how to apply the coaching mindset to engineering leadership, using game film analysis, real-time feedback loops, and proactive team debugging to prevent small misalignments from becoming full-blown disasters. You’ll learn how to spot early warning signs of dysfunction, use data-driven decision-making to optimize team performance, and run post-mortems that actually lead to change (not just another boring meeting). If you can debug a program, you can debug a team—let’s break down the playbook. Learning Objectives: -Identify the “silent bugs” in tech teams—bottlenecks, misalignment, and ineffective communication—that hurt performance but often go unnoticed. -Use data-driven coaching techniques from pro sports (real-time analytics, film reviews, and performance tracking) to improve engineering team efficiency. -Learn how to run engineering retrospectives like a game tape review—breaking down past plays to improve future execution. -Apply in-the-moment coaching strategies to fix issues before they derail sprints—just like a coach making mid-game adjustments. -Develop a “team debugging” toolkit that ensures every project iteration is smoother, faster, and more efficient than the last. Bonus: We’ll do a live debugging session—not of code, but of common team dysfunctions—and break down exactly how to fix them.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>Books:
<ul>
<li>5 Dysfunctions of a team</li>
<li>Crucial conversations</li>
<li>Only the paranoid survive</li>
</ul>
</li>
<li>There is a difference between a personal need and a practical need. Get rid of the personal need first to make it easier to handle the practical need.</li>
<li>Use sentices like &ldquo;Help me understand&hellip;&rdquo; to get more information.</li>
<li>To make desicions in a team use ELMO (Enough, let&rsquo;s move on). And time box the discussion.</li>
<li>You should &lsquo;map your script&rsquo;. Write down what you want to say and what the other person would say. This helps you to prepare for difficult conversations. Flip the script by changing your perspective.</li>
<li>If you want to change behavior give feedback with this format:
| What behaviour <strong>unwanted</strong> happened | Why |
| What alternative behaviour <strong>wanted</strong> is expected| Why |
| Clarify expectations |</li>
</ul>
</li>
</ul>
<h3 id="avoiding-common-pitfalls-with-asyncawait-by-stephen-cleary"><strong>Avoiding common pitfalls with async/await</strong> by Stephen Cleary</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;Did you know that most async/await mistakes are the result of misconceptions? Join this talk to discover how to detect common async/await mistakes, and how to fix them! We&rsquo;ll be covering What You Need To Know about async/await in C#, including several different correct conceptual models of async/await that you can switch between at will. We will take a look at the common async pitfalls, best practices, and guidelines along with a careful examination of when to ignore the best practices. We&rsquo;ll be including some semi-advanced topics (specifically ValueTasks and Channels) to round out techniques for modern asynchronous development. While this talk is geared towards developers who already use async and await, it would also be appropriate for those who do not (yet). Feel free to bring questions!&rdquo;</em>
- Key takeaways:
- USE ASYNC and AWAIT keywords always. Dont run in to deadlocks.
- Avoid async void methods, except for event handlers in UI code.
- Avoid using Task.Run in ASP.NET code. It does not help you except in UI code.
- <a href="https://stephencleary.com" target="_blank" rel="noopener">https://stephencleary.com</a>
- Task represents a methods execution. It as a completion, results and exceptions.
- Async <strong>wraps</strong> the Task<T>.
- await <strong>unwraps</strong> the Task<T>.
- Don&rsquo;t run async code in constructors</p>
<h3 id="all-time-sportiest-hacks-by-eric-de-maar"><strong>All-Time Sportiest Hacks</strong> by Eric de Maar</h3>
<p><strong>Session abstract:</strong> <em>&ldquo;In the high-stakes world of professional sports; data is the new gold, and hackers know it. From stolen medical records of star athletes to espionage between rival teams, the digital frontier has become as contested as the playing field itself. In this special talk, Eric de Maar, the creator of Hack the Bank, takes you on a thrilling journey through the most shocking sports-related hacks of all time. Discover how attackers infiltrated supposedly secure networks, manipulated sensitive information for competitive advantage and exposed organisations for corruption. You&rsquo;ll gain a front-row seat to tales of cyber sabotage that influenced World Cups and Olympics. Prepare to be challenged and inspired, because in the sport of security, every second counts.&rdquo;</em></p>
<ul>
<li>Key takeaways:
<ul>
<li>FIA got hacked because of broken register form, which allowed role attributes.</li>
<li>You should bring your adapter when you are speaking at conferences.</li>
</ul>
</li>
</ul>
<h3 id="influencing">Influencing</h3>
<p>I wanted to go to the influencing session but it was a &ldquo;Stairs session&rdquo;, as in a full room. Thats why i decided to explore some stands instead.</p>
<h3 id="ending-keynote---robert-doornbos">Ending keynote - Robert Doornbos</h3>
<p>Amazing keynote about racing and many insights in the racing world. I learned about the value of teamwork, preparation, and focus. And also the real value of a formula 1 car, which is $12 to $16 million. I have watched the last couple of seasons and getting the insights from Robert was really cool.</p>
<h2 id="conclusion-and-key-learnings">Conclusion and key learnings</h2>
<p>I have learned a lot during Techorama 2025. I even won a Lego set of the Red Bull signed by Robert Doornbos.</p>
]]></content:encoded>
      <category>techorama</category>
      <category>conference</category>
      <category>notes</category>
    </item>
    <item>
      <title>Troubleshoot Web Application Firewall in Production</title>
      <link>https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/</link>
      <pubDate>Tue, 28 Oct 2025 18:13:48 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/</guid>
      <description><![CDATA[How do you troubleshoot a Web Application Firewall in production? How can you find false positives? In this blog I will share some tips and tricks.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/cover.png" alt="" /><br/><br/><p>You implemented an Application Gateway with Web Application Firewall (WAF) in front of your web applications in Azure. Everything seems to be working fine, but suddenly you notice some issues. Maybe some users report that they cannot access the application, or you see unusual traffic patterns in your logs. In this blog, we will discuss how to troubleshoot common issues with Application Gateway in production.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f6aa; All the things mentioned are closely related to the WAF used for Azure Front Door.
</div>
<h2 id="mr-havinga">Mr Havinga</h2>
<p>Imagine you are Mr. Havinga, a normal Dutch person. You want to buy a new phone, but when you try to access the online store, you get a 403 Forbidden error. You are blocked! The firewall blocked you because the request matched a WAF rule. HAVING is a SQL keyword the WAF triggered a SQL Injection rule. What if this would happen if you do this during more general processes like paying your taxes or applying for a loan? It is extremely important to identify false positives and tune your WAF rules accordingly to prevent blocking legitimate traffic.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1fae3; &ldquo;Mr Havinga is not <em>having</em> a good day.&rdquo;
</div>
<h2 id="403-forbidden">403 Forbidden</h2>
<p>When users report that they receive a 403 Forbidden error when trying to access the application, it is often due to WAF rules blocking legitimate traffic. You should be aware that the WAF is a different layer than your application.</p>
<p>
<img loading="lazy" src="403.gif#center" alt="403 Forbidden"  />
</p>
<h3 id="application-insights-doesnt-know">Application Insights doesn&rsquo;t know</h3>
<p>When a request is blocked by the WAF, it may not be logged in Application Insights. This can make it difficult to troubleshoot issues, as you won&rsquo;t see any telemetry for the blocked requests. To get around this, you can enable diagnostic logging for your Application Gateway. This will log all requests, including those blocked by the WAF, to your Log Analytics workspace. You will need to search on the log analytics workspace and not in the application insights resource to get a full picture of the situation.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   🤓 The Log Analytics workspace is your friend here. it contains all the logs you need to troubleshoot WAF issues.
</div>
<p>You can enable diagnostic logging in the Azure portal by navigating to your Application Gateway, selecting &ldquo;Diagnostics settings,&rdquo; and configuring a new diagnostic setting to send logs to your Log Analytics workspace.</p>
<p>
  <figure>
    <img loading="lazy" src="diagnosticssettings.png#center" alt="Diagnostic settings"  title="Diagnostic settings"  />
    <figcaption style="text-align: center;">Diagnostic settings</figcaption>
  </figure>
</p>
<h3 id="watch-out-for-generalisation">Watch out for generalisation</h3>
<p>In a project there was builtin that when the frontend received a 403 on <em>any</em> request it would redirect to the login page. It was extremely frustrating because the page it happened on was anonymous. After investigation we found out that the WAF was blocking some requests because of false positives. The redirection to the login page made it seem like an authentication issue, while it was actually a WAF issue.</p>
<p>If you have this aswell, you can edit the code to show a specific error message when a 403 is received from the Application. Here is an example of how to do this in bicep for the WAF policy. Let&rsquo;s make it more fun by changing the status code to 418 (I&rsquo;m a teapot) instead of the standard 403.</p>
<div class="codecontainer" hi="mart"><div class="code-name">policysettings.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">applicationGatewayWAFPolicy</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2024-10-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;WafPolicy&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;West Europe&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">managedRules</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">managedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">//trimmed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">policySettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">customBlockResponseStatusCode</span><span class="p">:</span><span class="w"> </span><span class="nv">418</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">customBlockResponseBody</span><span class="p">:</span><span class="w"> </span><span class="nf">base64</span><span class="p">(</span><span class="s">&#39;Oops! Your request was blocked by the WAF. But don\&#39;t worry, I\&#39;m just a teapot!&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">mode</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Prevention&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="investigate-in-your-log-analytics-workspace">Investigate in your log analytics workspace</h2>
<p>First, check the logs in your Log Analytics workspace. Look for entries that indicate blocked requests. You can filter the logs by status code 403 to see which requests were blocked.</p>
<pre><code class="language-kusto" data-line-numbers="" data-ln-start-from="1">AzureDiagnostics
| where Category == &#34;ApplicationGatewayFirewallLog&#34;
| where action_s == &#34;Blocked&#34; or action_s == &#34;Matched&#34;
| order by TimeGenerated</code></pre><p>It is important to know that the WAF logs are logging Matches and Blocked actions together and will Block if the after &lsquo;Inbound Anomaly Score Exceeded (Total Score: 5)&rsquo;.</p>
<p>
  <figure>
    <img loading="lazy" src="found-rule.png#center" alt="Found the ruleId 942230 in the Log analytics workspace"  title="Found the ruleId 942230 in the Log analytics workspace"  />
    <figcaption style="text-align: center;">Found the ruleId 942230 in the Log analytics workspace</figcaption>
  </figure>
</p>
<h2 id="identify-the-blocking-rule">Identify the blocking rule</h2>
<p>Once you have identified the blocked requests, look for the <code>ruleId_s</code> field in the logs. This field indicates which WAF rule blocked the request. You can then look up this rule in the <a href="https://coreruleset.org/" target="_blank" rel="noopener">OWASP ModSecurity Core Rule Set</a> to understand why it was triggered.</p>
<p>To know more about the specific rule that was triggered, you can see the list on <a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/application-gateway-crs-rulegroups-rules?tabs=drs21%2Cowasp32" target="_blank" rel="noopener">Microsoft Docs</a>.</p>
<h2 id="create-an-exclusion-rule">Create an exclusion rule</h2>
<p>If you determine that the blocking rule is causing false positives, you can create an exclusion rule in your WAF policy. This allows you to exclude specific requests from being evaluated by certain rules.</p>
<div class="codecontainer" hi="mart"><div class="code-name">waf.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">applicationGatewayWAFPolicy</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2024-10-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;WafPolicy&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;West Europe&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">Environment</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Dev&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#39;hidden-title&#39;</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Smart Web Application Firewall&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">managedRules</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">managedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleGroupOverrides</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;OWASP&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;3.2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleGroupOverrides</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft_BotManagerRuleSet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;0.1&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">exclusions</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">exclusionManagedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;OWASP&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;3.2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">ruleGroupName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;REQUEST-942-APPLICATION-ATTACK-SQLI&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">rules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                      </span><span class="nv">ruleId</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;942230&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">matchVariable</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RequestArgNames&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">selector</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;email&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">selectorMatchOperator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Contains&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">exclusionManagedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">ruleGroupName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;REQUEST-942-APPLICATION-ATTACK-SQLI&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">rules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                      </span><span class="nv">ruleId</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;942450&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">ruleGroupName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;REQUEST-932-APPLICATION-ATTACK-RCE&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">rules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                      </span><span class="nv">ruleId</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;932150&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;OWASP&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;3.2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">matchVariable</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RequestCookieNames&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">selector</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ai_session&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">selectorMatchOperator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;StartsWith&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">customRules</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">policySettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">mode</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Prevention&#39;</span><span class="w"> </span><span class="c1">// &#39;Detection&#39; or &#39;Prevention&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">state</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">customBlockResponseBody</span><span class="p">:</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>By identifying false positives and creating exclusion rules, you can ensure that legitimate traffic is not blocked while still protecting your applications from threats. This is also the reason why I always recommend starting with a WAF in detection mode first, to monitor and tune the rules before enforcing them via prevention mode.</p>
<h2 id="more-reading">More reading</h2>
<ul>
<li><a href="https://alanta.nl/posts/2021/04/manage-waf-rules-for-appgateway" target="_blank" rel="noopener">https://alanta.nl/posts/2021/04/manage-waf-rules-for-appgateway</a></li>
<li><a href="https://docs.microsoft.com/en-us/azure/web-application-firewall/ag/custom-waf-rules-overview" target="_blank" rel="noopener">https://docs.microsoft.com/en-us/azure/web-application-firewall/ag/custom-waf-rules-overview</a></li>
</ul>
]]></content:encoded>
      <category>WAF</category>
      <category>application gateway</category>
      <category>troubleshooting</category>
      <category>azure</category>
      <category>security</category>
    </item>
    <item>
      <title>Getting Started With Application Gateway</title>
      <link>https://blog.martdegraaf.nl/posts/getting-started-with-application-gateway/</link>
      <pubDate>Tue, 21 Oct 2025 14:16:30 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/getting-started-with-application-gateway/</guid>
      <description><![CDATA[How secure are your web applications? Deploy an Azure Application Gateway with Web Application Firewall (WAF). This is a powerful tool to protect your applications while ensuring seamless performance. Let&rsquo;s dive in and explore how you can get started!]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/getting-started-with-application-gateway/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/getting-started-with-application-gateway/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/getting-started-with-application-gateway/cover.png" alt="" /><br/><br/><p>How secure are your web applications? Deploy an Azure Application Gateway with Web Application Firewall (WAF). This is a powerful tool to protect your applications while ensuring seamless performance. Let&rsquo;s dive in and explore how you can get started!</p>
<h2 id="why-deploy-an-application-gateway">Why deploy an Application Gateway?</h2>
<p>Have you ever heard about the <a href="https://owasp.org/www-project-top-ten/" target="_blank" rel="noopener">OWASP Top 10</a>? It is a list of the most common web application security risks. Could you even name all ten risks? If not, you are not alone. But you want to protect against these risks, right? An Application Gateway with Web Application Firewall (WAF) can help you with that. It is a managed service that provides a web application firewall to protect your web applications from common threats and vulnerabilities.</p>
<h3 id="alternatives">Alternatives</h3>
<p>An alternative is using the Azure FrontDoor service. Or look outside of Azure, but I will not discuss those options in this blog. Frontdoor has some additional features like CDN and global load balancing.</p>
<h2 id="how-to-migrate-to-an-application-gateway">How to migrate to an Application Gateway?</h2>
<p>Let&rsquo;s say you have a web application running on Azure App Service. You want to migrate this application to use an Application Gateway with WAF. Here are the steps you need to take:</p>
<ol>
<li>Create a Key Vault to store the SSL/TLS certificates.</li>
<li>Create a Vnet and subnet for the Application Gateway.</li>
<li>Create a Public IP Address for the Application Gateway.</li>
<li>Create a new Application Gateway with WAF enabled.</li>
<li>Configure the backend pool to point to your Azure App Service.</li>
<li>Create a new routing rule to forward traffic to the backend pool.</li>
<li>Update your DNS settings to point to the Application Gateway.</li>
</ol>
<p>I assume you have a Key Vault and know how to provide it with an SSL/TLS certificate. Let&rsquo;s combine the 2,3, 4, and 5 steps in a Bicep deployment.</p>
<h3 id="deploy-application-gateway-using-bicep">Deploy Application gateway using bicep</h3>
<p>Let&rsquo;s deploy an Application Gateway with a backend pool pointing to an Azure App Service.</p>
<div class="codecontainer" hi="mart"><div class="code-name">main.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span><span class="lnt">132
</span><span class="lnt">133
</span><span class="lnt">134
</span><span class="lnt">135
</span><span class="lnt">136
</span><span class="lnt">137
</span><span class="lnt">138
</span><span class="lnt">139
</span><span class="lnt">140
</span><span class="lnt">141
</span><span class="lnt">142
</span><span class="lnt">143
</span><span class="lnt">144
</span><span class="lnt">145
</span><span class="lnt">146
</span><span class="lnt">147
</span><span class="lnt">148
</span><span class="lnt">149
</span><span class="lnt">150
</span><span class="lnt">151
</span><span class="lnt">152
</span><span class="lnt">153
</span><span class="lnt">154
</span><span class="lnt">155
</span><span class="lnt">156
</span><span class="lnt">157
</span><span class="lnt">158
</span><span class="lnt">159
</span><span class="lnt">160
</span><span class="lnt">161
</span><span class="lnt">162
</span><span class="lnt">163
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">location</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">().</span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">region</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">environment</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">tags</span><span class="w"> </span><span class="nv">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">virtualNetwork</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/virtualNetworks@2023-09-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;vnet-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">addressSpace</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">addressPrefixes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#39;10.0.0.0/16&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">subnets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;snet-agw&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">addressPrefix</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;10.0.1.0/24&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">privateEndpointNetworkPolicies</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Disabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">privateLinkServiceNetworkPolicies</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Disabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w"> </span><span class="c1">// Sample for if your backends are in Azure Container apps</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;snet-containerapp&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">addressPrefix</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;10.0.2.0/23&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">delegations</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.App/environments&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">serviceName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.App/environments&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">keyvault</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/key-vault/vault:0.13.3&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-kv&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;kv-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Other parameters...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">wafPolicy</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/application-gateway-web-application-firewall-policy:0.2.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;applicationGatewayWebApplicationFirewallPolicyDeployment&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">managedRules</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">managedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleGroupOverrides</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;OWASP&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;3.2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft_BotManagerRuleSet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;0.1&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;mycoolwaf&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Non-required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">policySettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">fileUploadLimitInMb</span><span class="p">:</span><span class="w"> </span><span class="nv">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">jsChallengeCookieExpirationInMins</span><span class="p">:</span><span class="w"> </span><span class="nv">60</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">mode</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Detection&#39;</span><span class="w"> </span><span class="c1">// &#39;Detection&#39; or &#39;Prevention&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">state</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">publicIp</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/public-ip-address:0.6.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-pip&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pip-ag-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Other parameters...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nv">gatewayId</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="s">&#39;Microsoft.Network/applicationGateways/probes&#39;</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;ag-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">appGateway</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/application-gateway:0.7.1&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-agw&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ag-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">sku</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;WAF_v2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">gatewayIPConfigurations</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGatewayIpConfig&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">subnet</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nv">virtualNetwork</span><span class="p">.</span><span class="nv">properties</span><span class="p">.</span><span class="nv">subnets</span><span class="p">[</span><span class="nv">0</span><span class="p">].</span><span class="nv">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">frontendIPConfigurations</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwFrontend&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">publicIPAddress</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nv">publicIp</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">resourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">privateAllocationMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Dynamic&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Add IPv6 frontend configuration if needed, see my other blog post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">firewallPolicyResourceId</span><span class="p">:</span><span class="w"> </span><span class="nv">wafPolicy</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">resourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">backendAddressPools</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwBackendPool&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">backendAddresses</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="nv">fqdn</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;your-app-service.azurewebsites.net&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">httpListeners</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwHttpListener&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">frontendIpConfiguration</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">gatewayId</span><span class="si">}</span><span class="s">/frontendIpConfigurations/appGwFrontend&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">frontendPort</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">gatewayId</span><span class="si">}</span><span class="s">/frontendPorts/appGwFrontendPort&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">protocol</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Http&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">requestRoutingRules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwRoutingRule&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">httpListener</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">gatewayId</span><span class="si">}</span><span class="s">/httpListeners/appGwHttpListener&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">backendAddressPool</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">gatewayId</span><span class="si">}</span><span class="s">/backendAddressPools/appGwBackendPool&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">backendHttpSettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">gatewayId</span><span class="si">}</span><span class="s">/backendHttpSettings/appGwBackendHttpSettings&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="configure-dns-for-application-gateway">Configure DNS for Application Gateway</h3>
<p>After deploying the Application Gateway, you need to update your DNS settings to point to the Application Gateway&rsquo;s public IP address. This ensures that all incoming traffic to your web application is routed through the Application Gateway, allowing it to provide the necessary security and performance enhancements.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4d3; See also my other blog post on <a href="https://martdegraaf.github.io/blog/content/posts/application-gateway-ipv6/" target="_blank" rel="noopener">Dual stack support with Application Gateway and IPv6</a> for more details on updating DNS for dual stack support.
</div>
<h2 id="detection-vs-prevention-mode">Detection vs Prevention mode</h2>
<p>When configuring the WAF policy, you have the option to set the mode to either &ldquo;Detection&rdquo; or &ldquo;Prevention&rdquo;. In Detection mode, the WAF will monitor and log potential threats without blocking any traffic. This is useful for testing and tuning the WAF rules before enforcing them. In Prevention mode, the WAF will actively block traffic that matches the defined rules, providing a higher level of security for your web applications.</p>
<p>I recommend starting with Detection mode to understand the traffic patterns and potential threats to your application. Once you are confident that the WAF rules are properly configured, you can switch to Prevention mode for enhanced protection.</p>
<p>See my next blog post on how to find false positives and tune your WAF rules.</p>
<h2 id="next-steps">Next steps</h2>
<p>We now got an Application gateway before your application. Your next steps could be:</p>
<ul>
<li>Configure custom WAF rules to tailor the security settings to your specific application needs.</li>
<li>Set up monitoring and alerting to keep an eye on the WAF logs and performance metrics. ( see <a href="https://blog.martdegraaf.nl/posts/monitor-your-waf/">WAF monitoring</a>)</li>
<li>If you see no false positives you can switch from Detection to Prevention mode. (see <a href="https://blog.martdegraaf.nl/posts/troubleshoot-web-application-firewall-in-production/">Troubleshoot Application Gateway in Production</a>)</li>
<li>Implement ip restrictions for your Application gateway on Staging or acceptance environments. (see <a href="https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/">Implement IP Restrictions</a>)</li>
<li>Learn more about ipv6. (see <a href="https://blog.martdegraaf.nl/posts/application-gateway-ipv6/">Application Gateway with IPv6 support</a>).</li>
</ul>
]]></content:encoded>
      <category>application gateway</category>
      <category>WAF</category>
      <category>azure</category>
      <category>bicep</category>
      <category>getting started</category>
      <category>security</category>
    </item>
    <item>
      <title>Use IPV6 in Azure with the Application Gateway</title>
      <link>https://blog.martdegraaf.nl/posts/application-gateway-ipv6/</link>
      <pubDate>Fri, 17 Oct 2025 10:55:50 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/application-gateway-ipv6/</guid>
      <description><![CDATA[Learn how to configure an Azure Application Gateway to support IPv6 in Azure.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/application-gateway-ipv6/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/application-gateway-ipv6/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/application-gateway-ipv6/cover.png" alt="" /><br/><br/><p>Dutch governments need to be available over IPv6 according to <a href="https://www.forumstandaardisatie.nl/ipv6/" target="_blank" rel="noopener">this mandate</a>. Therefore I had to make sure my Application Gateway with Web Application Firewall (WAF) is also available over IPv6. In this blog I will explain some of the challenges we faced and how we solved them.</p>
<h2 id="system-context">System context</h2>
<p>In the existing system we have multiple apps running on Azure App Services behind an Application Gateway with WAF. The Application Gateway is currently only available over IPv4.</p>
<p>We have a listener and rule in place for each app. Each app is configured with its own FQDN (Fully Qualified Domain Name) and a TLS/SSL certificate is used for secure communication.</p>
<p>The system was geo redundant with two Application Gateways in different regions, each with its own public IP address.</p>
<p>A traffic manager profile is used to distribute traffic between the two Application Gateways based on priority. A common active-passive setup.</p>
<p>
  <figure>
    <img loading="lazy" src="appgateway.drawio.svg#center" alt="Architecture v1 - Application gateway behind an Traffic Manager"  title="Architecture v1 - Application gateway behind an Traffic Manager"  />
    <figcaption style="text-align: center;">Architecture v1 - Application gateway behind an Traffic Manager</figcaption>
  </figure>
</p>
<h2 id="dual-stack">Dual stack</h2>
<p>To make the Application Gateway available over IPv6, we needed to configure dual stack support. This means you have to <strong>redeploy</strong> the Application Gateway. This has a massive impact because of our current strategy with multiple listeners and rules. We had to make sure that all configurations were preserved during the redeployment.</p>
<p>If you need to do this I recommend:</p>
<ol>
<li>Add Ipv6 address prefixes to your virtual network.</li>
<li>Use an AVM module to build your new Application Gateway with dual stack support.</li>
<li>Reconsider if you need zones for your Application Gateway. Those also require a full redeployment.</li>
</ol>
<div class="codecontainer" hi="mart"><div class="code-name">application-gateway-ipv6.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">region</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">environment</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">applicationGatewayNr</span><span class="w"> </span><span class="nv">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">vnet</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/virtual-network:0.7.1&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ag-vnet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">addressPrefixes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="s">&#39;10.0.0.0/16&#39;</span><span class="w"> </span><span class="c1">// IPv4 address prefix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="s">&#39;fd00::/48&#39;</span><span class="w"> </span><span class="c1">// IPv6 address prefix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">subnets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appgw-subnet&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">addressPrefixes</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="s">&#39;10.0.1.0/24&#39;</span><span class="w"> </span><span class="c1">// IPv4 subnet for Application Gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="s">&#39;fd00:0:0:1::/64&#39;</span><span class="w"> </span><span class="c1">// IPv6 subnet for Application Gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">delegation</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/applicationGateways&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">publicIpV4</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/public-ip-address:0.9.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pip-v4-mart&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Non-required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">availabilityZones</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nv">1</span><span class="p">,</span><span class="w"> </span><span class="nv">2</span><span class="p">,</span><span class="w"> </span><span class="nv">3</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">publicIPAddressVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;IPv4&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">publicIPAllocationMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Static&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">publicIpV6</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/public-ip-address:0.9.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pip-v6-mart&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Non-required parameters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">availabilityZones</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nv">1</span><span class="p">,</span><span class="w"> </span><span class="nv">2</span><span class="p">,</span><span class="w"> </span><span class="nv">3</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">publicIPAddressVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;IPv6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">publicIPAllocationMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Static&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nv">agName</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#39;ag-</span><span class="si">${</span><span class="nv">applicationGatewayNr</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">appGateway</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/application-gateway:0.7.1&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-agw&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">agName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">sku</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;WAF_v2&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">availabilityZones</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nv">1</span><span class="p">,</span><span class="w"> </span><span class="nv">2</span><span class="p">,</span><span class="w"> </span><span class="nv">3</span><span class="p">]</span><span class="w"> </span><span class="c1">//TODO: Remove this line if you don&#39;t need zones</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">gatewayIPConfigurations</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;agconfig&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">subnet</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nv">vnet</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">subnetResourceIds</span><span class="p">[</span><span class="nv">0</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">frontendIPConfigurations</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwFrontendIPv4&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">publicIPAddress</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nv">publicIpV4</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">resourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">privateAllocationMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Dynamic&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;appGwFrontendIPv6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">publicIPAddress</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nv">publicIpV6</span><span class="p">.</span><span class="nv">outputs</span><span class="p">.</span><span class="nv">resourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">privateAllocationMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Dynamic&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">listeners</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;mart-listener&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">frontendIPConfiguration</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;Microsoft.Network/applicationGateways/frontendIPConfigurations&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">agName</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;appGwFrontendIPv6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">frontendPort</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="s">&#39;Microsoft.Network/applicationGateways/frontendPorts&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">agName</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;port_443&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">protocol</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Https&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">hostname</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ag.martdegraaf.nl&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">sslCertificate</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="s">&#39;Microsoft.Network/applicationGateways/sslCertificates&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">agName</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;sslCert&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">routingRules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;route-ipv6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Basic&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">priority</span><span class="p">:</span><span class="w"> </span><span class="nv">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">httpListener</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="s">&#39;Microsoft.Network/applicationGateways/httpListeners&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">agName</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;mart-listener&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">backendAddressPool</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="s">&#39;Microsoft.Network/applicationGateways/backendAddressPools&#39;</span><span class="p">,</span><span class="w"> </span><span class="nv">agName</span><span class="p">,</span><span class="w"> </span><span class="s">&#39;mart-backendpool&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">backendHttpSettings</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">id</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceId</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;Microsoft.Network/applicationGateways/backendHttpSettingsCollection&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">agName</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;mart-backendSetting&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   <p>&#x1f916; In the example &lsquo;Azure Verified Modules&rsquo; are used, see the documentation of these components here:</p>
<ul>
<li><a href="https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway" target="_blank" rel="noopener">https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway</a></li>
<li><a href="https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway-web-application-firewall-policy" target="_blank" rel="noopener">https://github.com/Azure/bicep-registry-modules/tree/main/avm/res/network/application-gateway-web-application-firewall-policy</a></li>
</ul>
</div>
<h3 id="public-ip-addresses-connected">public ip addresses connected</h3>
<p>Let&rsquo;s take a look what this means for our architecture. For each Application Gateway we now have two public ip addresses. We just need to configure that the DNS can find this new IPV6 address.</p>
<p>
  <figure>
    <img loading="lazy" src="appgateway-dualstack.drawio.svg#center" alt="Architecture v2 - added IPV6 support"  title="Architecture v2 - Added Public ip addresses for IPV6"  />
    <figcaption style="text-align: center;">Architecture v2 - Added Public ip addresses for IPV6</figcaption>
  </figure>
</p>
<h2 id="traffic-manager">Traffic Manager</h2>
<p>We had a traffic manager profile in place to distribute traffic between the two Application Gateways. The traffic manager profile also needed to be updated to support IPv6.</p>
<p>This was actually really complex because Traffic Manager does not support dual stack endpoints directly. Therefore we had to create separate endpoints for IPv4 and IPv6 in the traffic manager profile.</p>
<p>Thereby the Traffic manager routing method priority only supports one endpoint per target. So we had to create three traffic manager profiles to achieve the desired failover behavior. One with the routing method priority with two external endpoints pointing to the nested traffic manager profiles. Each nested traffic manager profile with routing method &lsquo;MultiValue&rsquo; had one endpoint for IPv4 and one for IPv6.</p>
<h3 id="serve-traffic-over-ipv4-and-ipv6">Serve traffic over IPv4 and IPv6</h3>
<p>Because we want to serve traffic over both IPv4 and IPv6, we had to create a traffic manager profile with the routing method &lsquo;MultiValue&rsquo;. This profile contains two external endpoints, one for the IPv4 address and one for the IPv6 address of the Application Gateway.</p>
<p>
  <figure>
    <img loading="lazy" src="appgateway-dualstack-nested.drawio.svg#center" alt="Architecture v3 - added trafficmanager with MultiValue support"  title="Architecture v3 - Added trafficmanager with MultiValue support"  />
    <figcaption style="text-align: center;">Architecture v3 - Added trafficmanager with MultiValue support</figcaption>
  </figure>
</p>
<p>This can be achieved with the following Bicep code:</p>
<div class="codecontainer" hi="mart"><div class="code-name">traffic-manager-multivalue.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">region</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">environment</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">applicationGatewaySequence</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">publicIpV4</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/publicIPAddresses@2023-11-01&#39;</span><span class="w"> </span><span class="kd">existing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pip-ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">-v4&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">publicIpV6</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/publicIPAddresses@2023-11-01&#39;</span><span class="w"> </span><span class="kd">existing</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;pip-ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">-v6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">agTrafficManager</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/trafficmanagerprofile:0.3.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-tm&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">ttl</span><span class="p">:</span><span class="w"> </span><span class="nv">15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">trafficRoutingMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;MultiValue&#39;</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">maxReturn</span><span class="p">:</span><span class="w"> </span><span class="nv">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">endpoints</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">-v4&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">type</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/trafficManagerProfiles/externalEndpoints&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">target</span><span class="p">:</span><span class="w"> </span><span class="nv">publicIpV4</span><span class="p">.</span><span class="nv">properties</span><span class="p">.</span><span class="nv">ipAddress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointMonitorStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Unmonitored&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">alwaysServe</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">region</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">-v6&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">type</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/trafficManagerProfiles/externalEndpoints&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">target</span><span class="p">:</span><span class="w"> </span><span class="nv">publicIpV6</span><span class="p">.</span><span class="nv">properties</span><span class="p">.</span><span class="nv">ipAddress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointMonitorStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Unmonitored&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">alwaysServe</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="parent-profile-for-failover">Parent profile for failover</h3>
<p>To achieve failover between the two Application Gateways in different regions, we created a parent traffic manager profile with the routing method &lsquo;Priority&rsquo;. This profile contains two external endpoints, each pointing to one of the nested traffic manager profiles.</p>
<p>There are three different endpoint types in Traffic Manager:</p>
<ol>
<li>Azure endpoints</li>
<li>External endpoints</li>
<li>Nested endpoints</li>
</ol>
<h4 id="azure-endpoints">Azure endpoints</h4>
<p>Azure endpoints point directly to Azure resources. However, in our case, we are not directly refering to one Web App or one Public IP address. Therefore, Azure endpoints are not suitable for our scenario here.</p>
<h4 id="nested-endpoints">Nested endpoints</h4>
<p>I initially attempted to use nested endpoints, but they were incompatible with the health probes.</p>
<p>A solution for the healthchecks to work was to create a nested traffic manager profile for each application instead of per Application Gateway. This approach forced us to create three Traffic Manager profiles per application, which seemed like a good solution until we encountered the subscription limit of 200 Traffic Manager profiles.</p>
<h4 id="external-endpoints">External endpoints</h4>
<p>Another solution for the healthchecks to work was to use external endpoints instead of nested endpoints in the parent Traffic Manager profile.
When we switched to using external endpoints that pointed to the FQDNs of the nested Traffic Manager profiles, this had some major advantages.</p>
<ul>
<li>It allowed us to stay within the subscription limits while still achieving the desired failover behavior.</li>
<li>It simplified the overall architecture by reducing the number of Traffic Manager profiles needed.</li>
<li>It ensured that the health probes functioned correctly, providing reliable monitoring of the Application Gateways&rsquo; availability.</li>
</ul>
<div class="codecontainer" hi="mart"><div class="code-name">traffic-manager-priority.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">region</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">environment</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">primaryRegion</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">secondaryRegion</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">applicationGatewaySequence</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">serviceTrafficManagerFailover</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/network/trafficmanagerprofile:0.3.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nf">deployment</span><span class="p">().</span><span class="nv">name</span><span class="si">}</span><span class="s">-tm-failover&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">scope</span><span class="p">:</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-ag-failover-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">ttl</span><span class="p">:</span><span class="w"> </span><span class="nv">15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">trafficRoutingMethod</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Priority&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">monitorConfig</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">protocol</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;HTTPS&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">port</span><span class="p">:</span><span class="w"> </span><span class="nv">443</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">path</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;/healthcheck&#39;</span><span class="w"> </span><span class="c1">//TODO: Adjust based on your health probe requirements</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">intervalInSeconds</span><span class="p">:</span><span class="w"> </span><span class="nv">30</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">timeoutInSeconds</span><span class="p">:</span><span class="w"> </span><span class="nv">10</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">toleratedNumberOfFailures</span><span class="p">:</span><span class="w"> </span><span class="nv">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">customHeaders</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Host&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">value</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;someapi.martdegraaf.nl&#39;</span><span class="w"> </span><span class="c1">//TODO: Adjust based on your health probe requirements</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">endpoints</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-</span><span class="si">${</span><span class="nv">primaryRegion</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">type</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/trafficManagerProfiles/externalEndpoints&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">target</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">primaryRegion</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">.trafficmanager.net&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">priority</span><span class="p">:</span><span class="w"> </span><span class="nv">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-</span><span class="si">${</span><span class="nv">secondaryRegion</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">type</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/trafficManagerProfiles/externalEndpoints&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">target</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;tm-ag-</span><span class="si">${</span><span class="nv">applicationGatewaySequence</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">secondaryRegion</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">environment</span><span class="si">}</span><span class="s">.trafficmanager.net&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">endpointStatus</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Enabled&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">priority</span><span class="p">:</span><span class="w"> </span><span class="nv">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="update-dns-records">Update DNS records</h2>
<p>If you are using a Traffic Manager profile to expose your application gateway you can update your CNAME record to point to the Traffic Manager FQDN. This way both IPv4 and IPv6 traffic will be routed correctly.</p>
<p>If you are routing directly to the Application Gateway you will need to update both the A record (IPv4) and the AAAA record (IPv6) in your DNS settings to point to the respective IP addresses of the Application Gateway.</p>
<p>After updating the DNS records, it may take some time for the changes to propagate. You can use tools like <code>nslookup</code> or online DNS checkers to verify that the records have been updated correctly.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">nslookup yourapp.yourdomain.com</span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>It has consequences to enable IPv6 on your Application Gateway. You have to redeploy the Application Gateway which can be complex depending on your current setup. Also for every listener you will need two listeners (one for IPv4 and one for IPv6). The limit of active listeners is 100 so keep that in mind for your architecture. with two listeners for each application you can only have 50 applications behind one Application Gateway.</p>
<p>Now you have dual stack support. Your Application Gateway is available over both IPv4 and IPv6. Make sure to test your setup thoroughly to ensure that everything is working as expected.</p>
<h2 id="further-reading">Further reading</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/application-gateway/ipv6-application-gateway-portal" target="_blank" rel="noopener">Microsoft Docs: Configure an Application Gateway to use IPv6</a></li>
</ul>
]]></content:encoded>
      <category>application-gateway</category>
      <category>ipv6</category>
      <category>azure</category>
      <category>WAF</category>
    </item>
    <item>
      <title>How to Demo Without Azure Credit</title>
      <link>https://blog.martdegraaf.nl/posts/how-to-demo-without-azure-credit/</link>
      <pubDate>Mon, 15 Sep 2025 15:36:27 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/how-to-demo-without-azure-credit/</guid>
      <description><![CDATA[My last demo was a disaster because I ran out of Azure credit. Here are some tips to avoid that happening to you.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/how-to-demo-without-azure-credit/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/how-to-demo-without-azure-credit/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/how-to-demo-without-azure-credit/cover.png" alt="" /><br/><br/><p>On my presentation for the APE Meetup on the 11th of September, I had a demo prepared to show how easy you could build stuff using bicep and Visual Studio Code. A week before i deployed a Application Gateway with a Web Application Firewall (WAF) in front of a simple web app. I have a monthly budget of 150 euro&rsquo;s to spend on Azure credit. And it was all gone just before the demo. Luckily I had a backup subscription with some credit left, but it was nerve wrecking.</p>
<h2 id="blog-down">Blog down</h2>
<p>After the demo I realized that my blog was down too because the subscription was disabled. I had to move my blog to another subscription and did change the CNAME to the new Static Web App. I just created a new Static Web App in the backup subscription and deployed my blog there. It took 5 minutes to change, but my blog was down for a weekend because you first need to do this.</p>
<p>So here are some tips to avoid this happening to you.</p>
<h2 id="separate-demo-from-production">Separate demo from production</h2>
<p>So I had a production resource, my blog deployed in the same subscription I play around in with my demos. Not really smart, learn from my mistake and always separate production from demo/test/dev environments. Use different subscriptions if possible.</p>
<h2 id="setup-a-budget-and-alerts">Setup a budget and alerts</h2>
<p>In the Azure portal you can setup a budget for your subscription. You can set alerts to be send when you reach 50%, 75% and 90% of your budget. This way you will be warned in time.</p>
<p>To learn more about setting up budgets and alerts in Azure, check out the <a href="https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/tutorial-acm-create-budgets" target="_blank" rel="noopener">official Azure documentation on budgets</a>.</p>
<p>Once you have ran out of credit, you cannot do anything in that subscription anymore, so you cannot create budgets to prevent this from happening right after.</p>
<h2 id="automate-deletion-of-resources">Automate deletion of resources</h2>
<p>If you are doing demos often, you can automate the deletion of resources after a certain time. You can use Azure CLI or PowerShell scripts to delete resources after your demo is done. You can also use Azure Automation or Logic Apps to schedule the deletion of resources. Or just create a pipeline in Azure DevOps or GitHub Actions to do this for you.</p>
]]></content:encoded>
      <category>azure</category>
      <category>speaking</category>
      <category>budget</category>
      <category>demo</category>
    </item>
    <item>
      <title>Log Masking in Application Insights</title>
      <link>https://blog.martdegraaf.nl/posts/log-masking-in-application-insights/</link>
      <pubDate>Thu, 08 May 2025 17:27:38 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/log-masking-in-application-insights/</guid>
      <description><![CDATA[In this post, we will discuss the methods to mask log data in Azure Application Insights.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/log-masking-in-application-insights/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/log-masking-in-application-insights/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/log-masking-in-application-insights/cover.png" alt="" /><br/><br/><p>For building good and reliable software, logging is essential. But not every type of data should be logged. With all the GDRP regulations it&rsquo;s important to take care of your logging.</p>
<p>By default you should not expose data in your logging. But sometimes it can be handy to see your objects in the logs. This post will discuss methods and show you how to mask data in Azure Application Insights.</p>
<h2 id="log-masking-in-net-8">Log masking in .NET 8</h2>
<p>There are serveral ways to do Log masking in .NET since version 6. Now let&rsquo;s look what is available for us for .NET 8 and higher.</p>
<h3 id="logging-source-generation">Logging source generation</h3>
<p>By using Source generation for logging you will be able to mask data in your logs. Let&rsquo;s take a look at how.</p>
<h4 id="step-1-add-microsoftextensionslogging">Step 1: Add Microsoft.Extensions.Logging</h4>
<p>First, you need to add the <code>Microsoft.Extensions.Logging</code> package to your project. You can do this by running the following command in your project directory:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet add package Microsoft.Extensions.Logging</span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="step-2-use-the-logpropertyignore-in-your-models">Step 2: Use the LogPropertyIgnore in Your Models</h4>
<p>Now you can use the <code>[LogPropertyIgnore]</code> in your models to mark properties that should be not be logged.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Extensions.Logging</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MyModel</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">        [LogPropertyIgnore]</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">string</span> <span class="n">SensitiveData</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">string?</span> <span class="n">NullableString</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="step-3-logging-or-sending-data">Step 3: Logging or Sending Data</h4>
<p>When you log or send instances of <code>MyModel</code>, the <code>SensitiveData</code> property will be masked in the JSON output.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MyController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">MyController</span><span class="p">&gt;</span> <span class="n">_logger</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">MyController</span><span class="p">(</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">MyController</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_logger</span> <span class="p">=</span> <span class="n">logger</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">MyAction</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">model</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MyModel</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Name</span> <span class="p">=</span> <span class="s">&#34;John Doe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">SensitiveData</span> <span class="p">=</span> <span class="s">&#34;Secret123&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Log the model (the sensitive data will be masked)</span>
</span></span><span class="line"><span class="cl">            <span class="n">LogMySensitiveModel</span><span class="p">(</span><span class="n">_logger</span><span class="p">,</span> <span class="n">model</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Ok</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">        [LoggerMessage(
</span></span></span><span class="line"><span class="cl"><span class="na">            Level = LogLevel.Information,
</span></span></span><span class="line"><span class="cl"><span class="na">            Message = &#34;Logging Sensitive model&#34;)]</span>
</span></span><span class="line"><span class="cl">        <span class="kd">private</span> <span class="kd">static</span> <span class="kd">partial</span> <span class="k">void</span> <span class="n">LogMySensitiveModel</span><span class="p">(</span><span class="n">ILogger</span> <span class="n">logger</span><span class="p">,</span> <span class="p">[</span><span class="n">LogProperties</span><span class="p">]</span> <span class="n">MyModel</span> <span class="n">model</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>See a codesample on GitHub: <a href="https://github.com/martdegraaf/kql-demo/tree/main/source/Demo.KQL.FunctionsNet9/Demo.KQL.FunctionsNet9/SensitiveLogging" target="_blank" rel="noopener">Log Masking in Application Insights</a>.</p>
<h3 id="log-masking-using-custom-own-made-code">Log masking using Custom own made code</h3>
<p>While researching for log masking in .NET core i stumbled on this older article: <a href="https://dev.to/krusty93/hide-sensitive-data-in-azure-application-insights-logs-of-our-aspnet-core-apis-4ji7" target="_blank" rel="noopener">https://dev.to/krusty93/hide-sensitive-data-in-azure-application-insights-logs-of-our-aspnet-core-apis-4ji7</a>.</p>
<p>This article describes how to mask data in your logs. The article describes how to use a custom <code>JsonConverter</code> to mask sensitive data in your logs. This approach allows you to define which properties should be masked and how they should be masked. You can use this approach to mask sensitive data in your logs while still using the built-in JSON serialization features of .NET.</p>
<p>It feels cheaty to do this. But it is a possible path to mask your logs. Also as you can see in the Performance section of this blog, you should be carefull with this approach.</p>
<h3 id="using-the-redaction-package">Using the Redaction package</h3>
<p>The <code>Redaction</code> package is a simple and effective way to mask sensitive data in your logs. This package allows you to define a list of properties that should be redacted when logging objects. You can install the package using the following command:</p>
<h4 id="step-1-installing-the-package">Step 1 installing the package</h4>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet add package Microsoft.Extensions.Compliance.Redaction</span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="step-2-create-data-classification">Step 2 Create data classification</h4>
<p>Create a data classification that defines the properties that should be redacted. You can do this by creating a class that implements the <code>IDataClassification</code> interface:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Extensions.Compliance.Redaction</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">PrivateDataAttribute</span> <span class="p">:</span> <span class="n">DataClassificationAttribute</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">PrivateDataAttribute</span><span class="p">()</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">DataClassifications</span><span class="p">.</span><span class="n">PrivateDataClassification</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="step-3-use-the-data-classification">Step 3 Use the data classification</h4>
<p>Now you can use the <code>PrivateDataAttribute</code> attribute to mark properties that should be redacted:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MyModel</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [PrivateData]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">SensitiveData</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string?</span> <span class="n">NullableString</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="step-4-configure-the-redaction-options">Step 4 Configure the Redaction Options</h4>
<p>You can add custom Redactors, and apply them to different classifications of data.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MyCustomRedactor</span> <span class="p">:</span> <span class="n">Redactor</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">string</span> <span class="n">Stars</span> <span class="p">=</span> <span class="s">&#34;****&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">override</span> <span class="kt">int</span> <span class="n">GetRedactedLength</span><span class="p">(</span><span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">input</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">Stars</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">override</span> <span class="kt">int</span> <span class="n">Redact</span><span class="p">(</span><span class="n">ReadOnlySpan</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">source</span><span class="p">,</span> <span class="n">Span</span><span class="p">&lt;</span><span class="kt">char</span><span class="p">&gt;</span> <span class="n">destination</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Stars</span><span class="p">.</span><span class="n">CopyTo</span><span class="p">(</span><span class="n">destination</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Stars</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>You can configure the redaction options in your <code>Startup.cs</code> file:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">ConfigureServices</span><span class="p">(</span><span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="p">.</span><span class="n">AddRedaction</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="p">.</span><span class="n">AddDataClassification</span><span class="p">&lt;</span><span class="n">DataClassifications</span><span class="p">.</span><span class="n">PrivateDataClassification</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="p">.</span><span class="n">SetRedactor</span><span class="p">&lt;</span><span class="n">MyCustomRedactor</span><span class="p">&gt;(</span><span class="k">new</span> <span class="n">DataClassificationSet</span><span class="p">(</span><span class="n">DataClassifications</span><span class="p">.</span><span class="n">OtherDataClassification</span><span class="p">));</span> <span class="c1">// Set redactor for specific Data Classification</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="discussion">Discussion</h3>
<p>Personally i am most charmed by the source generation method of logging. The logging is clean and simple enough to use. The redaction package is a more complex way of masking your logs, but has more capabilities to mask and classify different types of data.</p>
<h2 id="log-masking-in-net-framework-48">Log masking in .NET Framework 4.8</h2>
<p>So you are still using .NET Framework 4.8? No worries, you can still mask data in your logs. You are entitled to use build your own TelemetryInitializer. This way you can mask data in your logs.</p>
<h3 id="step-1-create-a-telemetryinitializer">Step 1: Create a TelemetryInitializer</h3>
<p>You need to add properties yourself to the request telemetry. You can do this as seen in the code below. This code does not support lists and such, but suit yourself for that. You can even make it recursive if you would like to log nested objects.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">LogData</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">LogObject</span><span class="p">(</span><span class="n">RequestTelemetry</span> <span class="n">telemetry</span><span class="p">,</span> <span class="kt">object</span> <span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Get the properties of the object</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">properties</span> <span class="p">=</span> <span class="n">obj</span><span class="p">.</span><span class="n">GetType</span><span class="p">().</span><span class="n">GetProperties</span><span class="p">(</span><span class="n">BindingFlags</span><span class="p">.</span><span class="n">Public</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span><span class="p">(</span><span class="kt">var</span> <span class="n">property</span> <span class="k">in</span> <span class="n">properties</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="p">(!</span><span class="n">property</span><span class="p">.</span><span class="n">canWrite</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">attributes</span> <span class="p">=</span> <span class="n">property</span><span class="p">.</span><span class="n">GetCustomAttributes</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">SensitiveDataAttribute</span><span class="p">),</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span><span class="p">(</span><span class="n">attributes</span><span class="p">.</span><span class="n">Length</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">                <span class="n">telemetry</span><span class="p">.</span><span class="n">Properties</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="s">&#34;****&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span><span class="k">else</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// Log the property</span>
</span></span><span class="line"><span class="cl">                <span class="n">telemetry</span><span class="p">.</span><span class="n">Properties</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">property</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">property</span><span class="p">.</span><span class="n">GetValue</span><span class="p">(</span><span class="n">obj</span><span class="p">).</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">telemetry</span><span class="p">.</span><span class="n">Properties</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">properties</span><span class="p">[</span><span class="n">key</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="step-2-use-the-logobject">Step 2: Use the LogObject</h3>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MyController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">MyAction</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">model</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MyModel</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Name</span> <span class="p">=</span> <span class="s">&#34;John Doe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">SensitiveData</span> <span class="p">=</span> <span class="s">&#34;Secret123&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Log the model (the sensitive data will be masked)</span>
</span></span><span class="line"><span class="cl">        <span class="n">LogData</span> <span class="n">logData</span> <span class="p">=</span> <span class="k">new</span> <span class="n">LogData</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">telemetry</span> <span class="p">=</span> <span class="n">HttpContext</span><span class="p">.</span><span class="n">Current</span><span class="p">.</span><span class="n">Items</span><span class="p">[</span><span class="s">&#34;Microsoft.ApplicationInsights.RequestTelemetry&#34;</span><span class="p">]</span> <span class="k">as</span> <span class="n">RequestTelemetry</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">logData</span><span class="p">.</span><span class="n">LogObject</span><span class="p">(</span><span class="n">telemetry</span><span class="p">,</span> <span class="n">model</span><span class="p">);</span> <span class="c1">// logdata will be added to telementry client</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Ok</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>By creating a custom attribute and a JSON converter, you can effectively mask sensitive properties during serialization in your .NET 8 application. This approach allows you to maintain clean and secure logging practices while still using the built-in JSON serialization features of .NET. Always ensure that you are compliant with data protection regulations when handling sensitive data.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://andrewlock.net/behind-logproperties-and-the-new-telemetry-logging-source-generator/" target="_blank" rel="noopener">https://andrewlock.net/behind-logproperties-and-the-new-telemetry-logging-source-generator/</a></li>
<li><a href="https://andrewlock.net/exploring-dotnet-6-part-8-improving-logging-performance-with-source-generators/" target="_blank" rel="noopener">https://andrewlock.net/exploring-dotnet-6-part-8-improving-logging-performance-with-source-generators/</a></li>
<li><a href="https://zimmergren.net/redacting-sensitive-information-application-insights/" target="_blank" rel="noopener">Log Masking</a></li>
<li><a href="https://learn.microsoft.com/en-us/training/modules/dotnet-compliance-cloud-native-applications/" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/training/modules/dotnet-compliance-cloud-native-applications/</a></li>
<li><a href="https://gist.github.com/joperezr/f5f022bcb4d0ce8f077e40e1f77239c8#file-redaction-md" target="_blank" rel="noopener">https://gist.github.com/joperezr/f5f022bcb4d0ce8f077e40e1f77239c8#file-redaction-md</a></li>
<li><a href="https://dev.to/krusty93/hide-sensitive-data-in-azure-application-insights-logs-of-our-aspnet-core-apis-4ji7" target="_blank" rel="noopener">dev.to - Hide sensitive data in Azure Application Insights logs of our ASP.NET (Core) APIs</a></li>
</ul>
]]></content:encoded>
      <category>Application Insights</category>
      <category>Azure</category>
      <category>Logging</category>
    </item>
    <item>
      <title>Alerting in Application Insights</title>
      <link>https://blog.martdegraaf.nl/posts/alerting-in-application-insights/</link>
      <pubDate>Thu, 01 May 2025 17:11:54 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/alerting-in-application-insights/</guid>
      <description><![CDATA[This blog post is about smart alerting and setting up alerting via bicep in Azure Application Insights.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/alerting-in-application-insights/cover.png" type="image/png" />
      <media:content url="https://blog.martdegraaf.nl/posts/alerting-in-application-insights/cover.png" medium="image" type="image/png">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/alerting-in-application-insights/cover.png" alt="" /><br/><br/><p>So you have your logging in place in Application Insights and you want to be alerted when something goes wrong. This post will show you how to set up alerting in Azure Application Insights.</p>
<h2 id="setting-up-smart-alerting-in-azure-application-insights">Setting up Smart Alerting in Azure Application Insights</h2>
<p>Azure Application Insights has a feature called Smart Detection. This feature can automatically detect anomalies in your data. You can set up alerts based on these anomalies.</p>
<p>To set up Smart Detection in Azure Application Insights, follow these steps:</p>
<ol>
<li>Go to the Azure portal.</li>
<li>Navigate to your Application Insights resource.</li>
<li>Click on the <code>Smart Detection</code> tab.</li>
</ol>
<p>You can set up alerts based on various metrics, such as:</p>
<ul>
<li>Server response time</li>
<li>Server requests</li>
<li>Server exceptions</li>
<li>Server failures</li>
</ul>
<p>
  <figure>
    <img loading="lazy" src="smart-detection.png#center" alt="Smart detection"  title="Set up Alerting via Smart Detection"  />
    <figcaption style="text-align: center;">Set up Alerting via Smart Detection</figcaption>
  </figure>
</p>
<h2 id="custom-alerts-via-bicep">Custom alerts via Bicep</h2>
<p>If you want more control over your alerts, you can use Bicep to define your alerts. Bicep is a domain-specific language for deploying Azure resources declaratively.</p>
<p>Here is an example of how you can define an alert rule that checks if there are more than 0 exception checking frequently.</p>
<p>By creating Action groups, you can define what should happen when the alert is triggered. In this example, we are using a webhook to call a function app. But you can also send an email or call a logic app.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">version</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">utcNow</span><span class="p">(</span><span class="s">&#39;yyMMddHHmm&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nv">location</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">().</span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">actionGroup</span><span class="w"> </span><span class="s">&#39;Microsoft.Insights/actionGroups@2023-01-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;FunctionActionGroup&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Global&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">groupShortName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;FuncAG&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">webhookReceivers</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;FunctionWebhook&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// This is obviously a fake URL, replace it with your own</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">serviceUri</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;https://my-cool-fnapp.azurewebsites.net/api/HttpTrigger1?code=1234567890&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">applicationInsights</span><span class="w"> </span><span class="s">&#39;Microsoft.Insights/components@2020-02-02&#39;</span><span class="w"> </span><span class="kd">existing</span><span class="w"> </span><span class="p">=</span><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;bicep-appi-mart&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">alert</span><span class="w"> </span><span class="s">&#39;Microsoft.Insights/scheduledQueryRules@2021-08-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ExceptionAlert&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">description</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Alert for specific exception&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">actions</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">actionGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">actionGroup</span><span class="p">.</span><span class="nv">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">scopes</span><span class="p">:[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">applicationInsights</span><span class="p">.</span><span class="nv">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">criteria</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">allOf</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">query</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;exceptions | where exceptionType == &#34;Demo.KQL.FunctionsNet9.DemoException&#34;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">timeAggregation</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Count&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;GreaterThan&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">threshold</span><span class="p">:</span><span class="w"> </span><span class="nv">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">evaluationFrequency</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;PT5M&#39;</span><span class="w"> </span><span class="c1">// every 5 min</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">windowSize</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;PT5M&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">severity</span><span class="p">:</span><span class="w"> </span><span class="nv">1</span><span class="w"> </span><span class="c1">//0 Critical, 1 Error, 2 Warning, 3 Informational, 4 Verbose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="multiple-alerts-via-bicep">Multiple alerts via Bicep</h3>
<p>With bicep you can make some alerts generic and use parameters to make them more flexible. This way you can reuse the same alert for different applications. By using the for loop you can create multiple alerts at once.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">alerts</span><span class="w"> </span><span class="nv">array</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;ExceptionAlert&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">description</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Alert for specific exception&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">query</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;exceptions | where exceptionType == &#34;Demo.KQL.FunctionsNet9.DemoException&#34;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">threshold</span><span class="p">:</span><span class="w"> </span><span class="nv">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RequestAlert&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">description</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Alert for specific request&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">query</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;requests | where url contains &#34;api/HttpTrigger1&#34;&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">threshold</span><span class="p">:</span><span class="w"> </span><span class="nv">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">QueryAlerts</span><span class="w"> </span><span class="s">&#39;Microsoft.Insights/scheduledQueryRules@2021-08-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="kd">for</span><span class="w"> </span><span class="nv">alert</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nv">alerts</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">alert</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">-</span><span class="si">${</span><span class="nv">version</span><span class="si">}</span><span class="s">&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">description</span><span class="p">:</span><span class="w"> </span><span class="nv">alert</span><span class="p">.</span><span class="nv">description</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">actions</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">actionGroups</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">actionGroup</span><span class="p">.</span><span class="nv">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">scopes</span><span class="p">:[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">applicationInsights</span><span class="p">.</span><span class="nv">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">criteria</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">allOf</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">query</span><span class="p">:</span><span class="w"> </span><span class="nv">alert</span><span class="p">.</span><span class="nv">query</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">timeAggregation</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Count&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;GreaterThan&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">threshold</span><span class="p">:</span><span class="w"> </span><span class="nv">alert</span><span class="p">.</span><span class="nv">threshold</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">evaluationFrequency</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;PT5M&#39;</span><span class="w"> </span><span class="c1">// every 5 min</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">windowSize</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;PT5M&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">severity</span><span class="p">:</span><span class="w"> </span><span class="nv">1</span><span class="w"> </span><span class="c1">//0 Critical, 1 Error, 2 Warning, 3 Informational, 4 Verbose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}]</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion-and-discussion">Conclusion and Discussion</h2>
<p>Setting up alerting in Azure Application Insights is essential to monitor your application&rsquo;s health. By using Smart Detection or defining your alerts via Bicep, you can ensure that you are alerted when something goes wrong.</p>
<p>If you have these alerts set via Bicep, you can also version control them and deploy them via your CI/CD pipeline. This way, you can ensure that your alerts are always in sync with your application. And if seen on Acceptance for example you will fail faster and will iterate sooner.</p>
]]></content:encoded>
      <category>Bicep</category>
      <category>Azure</category>
      <category>Application Insights</category>
      <category>Alerting</category>
    </item>
    <item>
      <title>Creating Azure Pipelines Using PowerShell</title>
      <link>https://blog.martdegraaf.nl/posts/creating-azure-pipelines-using-powershell/</link>
      <pubDate>Fri, 07 Mar 2025 22:22:58 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/creating-azure-pipelines-using-powershell/</guid>
      <description><![CDATA[Have you ever created a new project and had code and pipelines and such ready to go, but still need to manually create the pipelines in Azure DevOps? Annoying, right? The flow is so simple it does not allow you to select folders or name your pipelines. This post will show you how to create Azure Pipelines using PowerShell.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/creating-azure-pipelines-using-powershell/cover.jpg" type="image/jpeg" />
      <media:content url="https://blog.martdegraaf.nl/posts/creating-azure-pipelines-using-powershell/cover.jpg" medium="image" type="image/jpeg">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/creating-azure-pipelines-using-powershell/cover.jpg" alt="" /><br/><br/><p>Have you ever created a new project and had code and pipelines and such ready to go, but still need to manually create the pipelines in Azure DevOps? Annoying, right? The flow is so simple it does not allow you to select folders or name your pipelines. This post will show you how to create Azure Pipelines using PowerShell.</p>
<p>And if thats possible, why not automate more, like giving permissions to the pipelines and creating branchpolices? Let&rsquo;s dive in!</p>
<h1 id="solution">Solution</h1>
<p>To script the code i am using a mix of the Az CLI and the Azure DevOps REST API. The Az CLI is used to get the repo id and getting the authorization token. The REST API is used to create the pipeline, and more.
Enough talk, let&rsquo;s get to the code.</p>
<p>Also make sure the azure-devops extension is installed.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">az</span> <span class="n">extension</span> <span class="n">add</span> <span class="p">-</span><span class="n">-name</span> <span class="nb">azure-devops</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="getting-access">Getting access</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="n">-o</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$accessToken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Could not get access token&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span>  <span class="p">=</span> <span class="s2">&#34;Bearer </span><span class="nv">$accessToken</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content-Type&#34;</span>   <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="getting-the-repo-id">Getting the repo id</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$repoName</span> <span class="p">=</span> <span class="s2">&#34;MyRepo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;MyProject&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$repo</span> <span class="p">=</span> <span class="n">az</span> <span class="n">repos</span> <span class="n">list</span> <span class="p">-</span><span class="n">-project</span> <span class="nv">$projectName</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;[?name==&#39;</span><span class="nv">$repoName</span><span class="s2">&#39;]&#34;</span> <span class="n">-o</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$repo</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Could not find repo&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nv">$repoId</span> <span class="p">=</span> <span class="nv">$repo</span><span class="p">.</span><span class="py">id</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Repo id is </span><span class="nv">$repoId</span><span class="s2">&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="creating-the-pipeline">Creating the pipeline</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># $headers and $repoId are already set from earlier samples</span>
</span></span><span class="line"><span class="cl"><span class="c"># Create a new pipeline</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;MyProject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$repoName</span> <span class="p">=</span> <span class="s2">&#34;MyRepo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$branchName</span> <span class="p">=</span> <span class="s2">&#34;main&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$yamlPath</span> <span class="p">=</span> <span class="s2">&#34;path/to/your/pipeline.yaml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/your-org/</span><span class="nv">$projectName</span><span class="s2">/_apis/pipelines?api-version=7.1-preview.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$body</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">folder</span> <span class="p">=</span> <span class="s2">&#34;\\&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="p">=</span> <span class="s2">&#34;MyPipeline&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">configuration</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">path</span> <span class="p">=</span> <span class="nv">$yamlPath</span>
</span></span><span class="line"><span class="cl">        <span class="n">repository</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="p">=</span> <span class="nv">$repoId</span>
</span></span><span class="line"><span class="cl">            <span class="nb">type </span><span class="p">=</span> <span class="s2">&#34;azureReposGit&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span> <span class="p">=</span> <span class="nv">$repoName</span>
</span></span><span class="line"><span class="cl">            <span class="n">defaultBranch</span> <span class="p">=</span> <span class="nv">$branchName</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$pipelineId</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Body</span> <span class="nv">$body</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Pipeline created with id </span><span class="p">$(</span><span class="nv">$pipelineId</span><span class="p">.</span><span class="n">id</span><span class="p">)</span><span class="s2">&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="giving-permissions">Giving permissions</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># $headers and $repoId and $pipelineId are already set from earlier samples</span>
</span></span><span class="line"><span class="cl"><span class="c"># Give permissions to the pipeline</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;MyProject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$repoName</span> <span class="p">=</span> <span class="s2">&#34;MyRepo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pipelineId</span> <span class="p">=</span> <span class="s2">&#34;MyPipelineId&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/your-org/</span><span class="nv">$projectName</span><span class="s2">/_apis/pipelines/</span><span class="nv">$pipelineId</span><span class="s2">/permissions?api-version=7.1-preview.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$body</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">resource</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">id</span> <span class="p">=</span> <span class="nv">$pipelineId</span>
</span></span><span class="line"><span class="cl">        <span class="nb">type </span><span class="p">=</span> <span class="s2">&#34;pipeline&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">role</span> <span class="p">=</span> <span class="s2">&#34;roleName&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">roleAssignment</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">principal</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">id</span> <span class="p">=</span> <span class="s2">&#34;principalId&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">descriptor</span> <span class="p">=</span> <span class="s2">&#34;principalDescriptor&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">6</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$permission</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Body</span> <span class="nv">$body</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Permission created with id </span><span class="p">$(</span><span class="nv">$permission</span><span class="p">.</span><span class="n">id</span><span class="p">)</span><span class="s2">&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="setting--the-branch-policy">Setting  the branch policy</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="c"># $headers and $repoId and $pipelineId are already set from earlier samples</span>
</span></span><span class="line"><span class="cl"><span class="c"># Give the default branch a policy</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;MyProject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$repoName</span> <span class="p">=</span> <span class="s2">&#34;MyRepo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$branchName</span> <span class="p">=</span> <span class="s2">&#34;main&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/your-org/</span><span class="nv">$projectName</span><span class="s2">/_apis/policy/configurations?api-version=7.1-preview.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$body</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">isEnabled</span> <span class="p">=</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">    <span class="n">isBlocking</span> <span class="p">=</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">    <span class="nb">type </span><span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">id</span> <span class="p">=</span> <span class="s2">&#34;0609b952-1397-4640-95ec-e00a01b2c241&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">buildDefinitionId</span> <span class="p">=</span> <span class="nv">$pipelineId</span>
</span></span><span class="line"><span class="cl">        <span class="n">queueOnSourceUpdate</span> <span class="p">=</span> <span class="vm">$true</span>
</span></span><span class="line"><span class="cl">        <span class="n">displayName</span> <span class="p">=</span> <span class="s2">&#34;THE CODE SHOULD BUILD&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">scope</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">refName</span> <span class="p">=</span> <span class="s2">&#34;refs/heads/</span><span class="nv">$branchName</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">matchKind</span> <span class="p">=</span> <span class="s2">&#34;exact&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">repositoryId</span> <span class="p">=</span> <span class="nv">$repoId</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">6</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$policy</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Body</span> <span class="nv">$body</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Policy created with id </span><span class="p">$(</span><span class="nv">$policy</span><span class="p">.</span><span class="n">id</span><span class="p">)</span><span class="s2">&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h1 id="conclusion-and-discussion">Conclusion and discussion</h1>
<p>Having a script do the work can help you deliver consistent pipelines and policies. It can also help you to automate the creation of pipelines and policies in a new project. This can be useful when you have a lot of projects to manage.</p>
<p>I will continue on this topic in a later blogpost using a different view. But as an addition to these scripts.</p>
<p>Let&rsquo;s automate more together!</p>
]]></content:encoded>
      <category>powershell</category>
      <category>azure devops</category>
      <category>automation</category>
      <category>pipelines</category>
    </item>
    <item>
      <title>Unlocking Angular Potential: Provide Root Services That Listen to Routing Events</title>
      <link>https://blog.martdegraaf.nl/posts/angular-singleton-services-listening-to-routing-events/</link>
      <pubDate>Thu, 12 Dec 2024 22:00:00 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/angular-singleton-services-listening-to-routing-events/</guid>
      <description><![CDATA[Lets create a global singleton that listens to router events in Angular. Is it a good idea? Let&rsquo;s find out.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/angular-singleton-services-listening-to-routing-events/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/angular-singleton-services-listening-to-routing-events/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/angular-singleton-services-listening-to-routing-events/cover.webp" alt="" /><br/><br/><p><strong>In a project with over 50 Angular projects we had quite some duplicate code. The first thing you will think of is putting code in a NPM package. But that can be a challenge when some of the 50 had a different implementation.</strong></p>
<h1 id="system-context">System context</h1>
<p>The applications we make are wizard like applications. We update the title on a route change. This is done by subscribing to the router events.
In the Angular projects we had this code sample in the <code>app.component.ts</code>:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">public</span> <span class="nx">AppComponent</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">public</span> <span class="kr">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">router</span>: <span class="kt">Router</span><span class="p">,</span> <span class="kr">private</span> <span class="nx">titleService</span>: <span class="kt">Title</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">this</span><span class="p">.</span><span class="nx">handleRouterEvent</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">private</span> <span class="nx">handleRouterEvent</span><span class="p">()</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">this</span><span class="p">.</span><span class="nx">router</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">takeUntill</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">destroy$</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">filter</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">:</span> <span class="nx">event</span> <span class="k">is</span> <span class="nx">NavigationEnd</span> <span class="o">=&gt;</span> <span class="nx">event</span> <span class="nx">instanceOf</span> <span class="nx">NavigationEnd</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">).</span><span class="nx">subscribe</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="p">.</span><span class="nx">titleService</span><span class="p">.</span><span class="nx">setTitle</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getTitle</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h1 id="solution">Solution</h1>
<p>The solution was found using the <code>APP_INITIALIZER</code> token. This token is used to run a function before the application is bootstrapped. When using this method we can create a singleton service that does not need to be depended upon in the constructor of the components.</p>
<h2 id="the-root-listener">The root listener</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kd">@Injectable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">providedIn</span><span class="o">:</span> <span class="s1">&#39;root&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">RouteHandlerService</span> <span class="kr">implements</span> <span class="nx">OnDestroy</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">private</span> <span class="nx">destroy$</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Subject</span><span class="p">&lt;</span><span class="nt">void</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="kr">constructor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kr">private</span> <span class="nx">router</span>: <span class="kt">Router</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kd">@Inject</span><span class="p">(</span><span class="s1">&#39;ROUTE_HANDLING_LISTENERS&#39;</span><span class="p">)</span> <span class="kr">private</span> <span class="nx">subServices</span>: <span class="kt">RouteHandlerSubService</span><span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">startListening</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">private</span> <span class="nx">startListening</span><span class="p">()</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">router</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">takeUntill</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">destroy$</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">filter</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">event</span> <span class="k">instanceof</span> <span class="nx">NavigationEnd</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">map</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">event</span> <span class="kr">as</span> <span class="nx">NavigationEnd</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">).</span><span class="nx">subscribe</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">this</span><span class="p">.</span><span class="nx">titleService</span><span class="p">.</span><span class="nx">setTitle</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getTitle</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">public</span> <span class="nx">ngOnDestroy</span><span class="p">()</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">destroy$</span><span class="p">.</span><span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">destroy$</span><span class="p">.</span><span class="nx">complete</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">provideRouteHandler</span><span class="p">(...</span><span class="nx">subServices</span>: <span class="kt">Provider</span><span class="p">[][])</span><span class="o">:</span> <span class="nx">Provider</span><span class="p">[]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span><span class="nx">subServices</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">service</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">service</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">provide</span>: <span class="kt">APP_INITIALIZER</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">useFactory</span><span class="o">:</span> <span class="p">(</span><span class="nx">routeHandlerService</span>: <span class="kt">RouteHandlerService</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">routeHandlerService</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">deps</span><span class="o">:</span> <span class="p">[</span><span class="nx">RouteHandlerService</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="nx">multi</span>: <span class="kt">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="the-sub-services">The sub services</h2>
<p>We have some actions that need to be done on navigation such as logging a page view and setting the title of the page. We can create a sub service for this. This sub service can be injected in the <code>provideRouteHandler</code> method.</p>
<h3 id="titleservice">TitleService</h3>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kd">@Injectable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">providedIn</span><span class="o">:</span> <span class="s1">&#39;root&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">TitleService</span> <span class="kr">implements</span> <span class="nx">RouteHandlerSubService</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">titleService</span>: <span class="kt">Title</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">public</span> <span class="nx">handleRoute</span><span class="p">(</span><span class="nx">event</span>: <span class="kt">NavigationEnd</span><span class="p">)</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">titleService</span><span class="p">.</span><span class="nx">setTitle</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">getTitle</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">withTitleService</span><span class="p">()</span><span class="o">:</span> <span class="nx">Provider</span><span class="p">[]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="nx">TitleService</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">provide</span><span class="o">:</span> <span class="s1">&#39;ROUTE_HANDLING_LISTENERS&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">useExisting</span>: <span class="kt">TitleService</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">multi</span>: <span class="kt">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="pageviewlogger">PageViewLogger</h3>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kd">@Injectable</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">providedIn</span><span class="o">:</span> <span class="s1">&#39;root&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">PageViewLogger</span> <span class="kr">implements</span> <span class="nx">RouteHandlerSubService</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">logger</span>: <span class="kt">LoggingService</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">public</span> <span class="nx">handleRoute</span><span class="p">(</span><span class="nx">event</span>: <span class="kt">NavigationEnd</span><span class="p">)</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">logger</span><span class="p">.</span><span class="nx">logPageView</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">withPageViewLogger</span><span class="p">()</span><span class="o">:</span> <span class="nx">Provider</span><span class="p">[]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="nx">PageViewLogger</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">provide</span><span class="o">:</span> <span class="s1">&#39;ROUTE_HANDLING_LISTENERS&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">useExisting</span>: <span class="kt">PageViewLogger</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">multi</span>: <span class="kt">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="implementing-in-your-application">Implementing in your application</h2>
<p>By creating a provide method, we can configure any behaviour that is needed for that specific project. And by allowing parameters to implement this method we can extend even for specific business logic for that project. I got this idea when we migrated to a newer version of Angular and my eye fell on the <code>provideRoutes</code> method. This method is used to provide routes to the application. My solution is based on this method but implemented generic and simple.</p>
<p>So in your <code>main.ts</code>, you can now register the route handler like this:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="nx">bootstapApplication</span><span class="p">(</span><span class="nx">AppComponent</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">providers</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">        <span class="nx">provideRouteHandler</span><span class="p">(</span><span class="nx">withTitleService</span><span class="p">(),</span> <span class="nx">withPageViewLogger</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>This allows even consumers to hook into those events and write their own subservice for hanlding router events.</p>
<h1 id="conclusion-and-discussion">Conclusion and discussion</h1>
<p>Please don&rsquo;t use this as an golden hammer. Normally you would want to be as explicit as possible when subscribing to route events. In our case we removed complexity and variations in our codebase. Also I am not sure what kind of performance impact this has on applications. We use these subscriptions at root level, so it was a good fit for us.</p>
<p>Also if you are using my code here, please note it only supports <code>NavigationEnd</code> events. If you want to support more events, you should edit the design to support a method to in the service if it handles the event type. You could use the <code>Event</code> type from <code>@Angular/router</code> to do this.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">public</span> <span class="nx">startListening</span><span class="p">()</span><span class="o">:</span> <span class="k">void</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="nx">router</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">takeUntill</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">destroy$</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">).</span><span class="nx">subscribe</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="p">.</span><span class="nx">subServices</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">service</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">service</span><span class="p">.</span><span class="nx">handles</span><span class="p">(</span><span class="nx">event</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">service</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">service</span><span class="p">.</span><span class="nx">handle</span><span class="p">(</span><span class="nx">event</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="benefits">Benefits</h2>
<p>The move of this duplicate code over all projects also resulted in a higher code coverage and less stress to test the same code over and over again, and creating variants of the same code. It feels a lot like the Loki show of Disney+ where there are multiple Lokis in the same universe. We just restored the sacred timeline by implementing and using the package.</p>
<p>We are flexible in the use of the provide method. Let&rsquo;s say only one service does not want to log page views in the same way, we can just remove the <code>withPageViewLogger</code> method from the <code>provideRouteHandler</code> method. A concrete example may be when logging to application insights in 90% of the projects and 10% of the projects want to log to a different service like Google Analytics or New Relic.</p>
<h2 id="drawbacks">Drawbacks</h2>
<p>The drawback is that we have a global singleton that listens to router events. This can be a performance issue when you have a lot of subscriptions. Also the code is not as explicit as it was before. This can be a problem when you are debugging the application. You will have to look at the <code>provideRouteHandler</code> method to see what is happening.</p>
<p>We also made it very easy to add more services to the route handling. It still can be possible to create duplicate own implementations and when updating the package, the application will break. This is a risk we took and we are aware of it.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://angular.io/api/router/Router" target="_blank" rel="noopener">Angular Router</a></li>
<li><a href="https://angular.dev/api/router/provideRouter" target="_blank" rel="noopener">provideRouter</a></li>
<li><a href="https://angular.io/api/core/APP_INITIALIZER" target="_blank" rel="noopener">APP_INITIALIZER</a></li>
</ul>
<h1 id="endnotes">Endnotes</h1>
<p>This design was my idea but I implemented it with a colleague, so he has some of the credit of the code in this post. Jarco, thanks a lot!</p>
<p><strong>This blogpost is the first blog post using my new Dygma Defy keyboard. I did not edit the layers yet just trying to get used to the keyboard.</strong></p>
]]></content:encoded>
      <category>Angular</category>
      <category>typescript</category>
      <category>singleton</category>
      <category>design pattern</category>
      <category>router</category>
      <category>npm</category>
    </item>
    <item>
      <title>Bicep Template Spec Latest</title>
      <link>https://blog.martdegraaf.nl/posts/bicep-template-spec-latest/</link>
      <pubDate>Mon, 02 Dec 2024 23:40:20 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/bicep-template-spec-latest/</guid>
      <description><![CDATA[When using template specs in your organization you should think about how you want to handle the versioning of these template specs. Read it in this blog post.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/bicep-template-spec-latest/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/bicep-template-spec-latest/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/bicep-template-spec-latest/cover.webp" alt="" /><br/><br/><p>Let&rsquo;s say you have chosen to use Template Spec for your Bicep code. And you are using these Template specs as templates for your organization in your applications. Now you have a centralized place for your template specs. But you have also introduced a version dependency. Let&rsquo;s explore some challenges with this approach.</p>
<h1 id="possible-solutions">Possible solutions</h1>
<p>There are some choices that you can make:</p>
<ul>
<li>Always use the latest version of the template spec</li>
<li>Use a specific version of the template spec</li>
</ul>
<h2 id="using-latest-version">Using Latest version</h2>
<div class="codecontainer" hi="mart"><div class="code-name">templatespec-latest.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="c1">//https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules#path-to-a-module</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">smartkeysKeyvault</span><span class="w"> </span><span class="s">&#39;ts/&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;smartkeys&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;smartkeys&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="pros">Pros</h3>
<p>You will always build on the latest version of the template specs, this allows you to freely use the latest features and fixes that are available in the template spec. New builds will automatically break when a breaking change is introduced in the template spec, so you have a quick feedback loop.</p>
<h3 id="cons">Cons</h3>
<p>When a template spec has a breaking change in the latest version, your deployment will break. This can lead to a lot of issues when critical applications are deployed.</p>
<p>A rollback of an earlier ran pipeline will be insufficient because when deploying bicep using templace spec it will then fetch again the latest version and will result in the same result as the latest deployment. In short, you will have no control.</p>
<h2 id="using-a-specific-version">Using a specific version</h2>
<div class="codecontainer" hi="mart"><div class="code-name">templatespec.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="c1">//https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules#path-to-a-module</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">module</span><span class="w"> </span><span class="nv">smartkeysKeyvault</span><span class="w"> </span><span class="s">&#39;br/public:avm/res/key-vault/vault:0.9.0&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;smartkeys&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">params</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;smartkeys&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="pros-1">Pros</h3>
<p>You have full control over what version you are using for deploying your infrastructure. This allows you to test the new version of the template spec before deploying it to production.</p>
<h3 id="cons-1">Cons</h3>
<p>Updating versions for many applications can be a lot of work. When you have for example 100 applications that are using the template spec, you have to update all of them when a newer secure version is released.</p>
<p>This does happen some examples of this are:</p>
<ul>
<li>The application insights key is deprecated and connection string should be used.</li>
<li>The 1.2 TLS was configured and now 1.3 should be the default.</li>
</ul>
<p>These can be requirements by your company and you will have to update to comply to them.</p>
<h1 id="considerations">Considerations</h1>
<h2 id="use-a-specific-version-and-use-a-dependency-updater">Use a specific version and use a dependency updater</h2>
<p>What you could do is use a dependency updater like dependabot. This will automatically create a PR when a new version is available. This way you can review the changes. Just as if a colleague would update the template specs.</p>
<p>At the moment of writing this blog, bicep and template specs are not supported by dependabot. It is however already available for terraform, so i expect that the need for this will be there in the future. <a href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem" target="_blank" rel="noopener">https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem</a></p>
<h2 id="save-the-latest-version-build-time">Save the latest version build-time</h2>
<p>Another option is when you build your application to build your bicep code to plain old ARM. This is a JSON-file that will be static when generated, and allows you to use the latest version at build time. You can rerun your old deployments using this static ARM teplate to get an consistent deployment.</p>
<p>
<img loading="lazy" src="deployment.drawio.svg" alt="Deployment pipeline"  />
</p>
<p>One note this will not prevent unwanted errors in new builds, your application can still fail when a breaking change is introduced in the template spec.</p>
<h1 id="conclusion">Conclusion</h1>
<p>There is not a one size fits all but when you are using template specs in your organization you should think about how you want to handle the versioning of these template specs.</p>
<p>It can be a lifesaver to run latest versions when you have good tests in place in your CI/CD pipelines. But realise that you as a developer are taking a risk using this deployment method. When using a specific version you have more control but you will have to update all your applications when a new version is required.</p>
]]></content:encoded>
      <category>bicep</category>
      <category>azure</category>
      <category>template spec</category>
    </item>
    <item>
      <title>Make Github Copilot a Habit</title>
      <link>https://blog.martdegraaf.nl/posts/make-github-copilot-a-habit/</link>
      <pubDate>Tue, 29 Oct 2024 21:42:43 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/make-github-copilot-a-habit/</guid>
      <description><![CDATA[Now with the capabilities of AI, we code on steroids. Let&rsquo;s make it a habit to use it the right way.]]></description><content:encoded><![CDATA[<p>GitHub Copilot has made his way into your daily life. For many developers habits are made, but please use the feature that suits your case best. Don&rsquo;t use the Chat or completion as golden hammer for every problem.</p>
<p>The goals in this post are to make you aware of the possibilities and to make you think about how you should look at tools such as GitHub Copilot.</p>
<h2 id="github-copilot">GitHub Copilot</h2>
<p>GitHub Copilot is super cool. Lets see those features!</p>
<h3 id="chat">Chat</h3>
<p>The chat feature in VS or VS Code. It&rsquo;s ChatPGT with knowledge of your code. It&rsquo;s handy to get a quick answer to a question. It&rsquo;s not a golden hammer for every problem. What is the difference when u ask it to do something and you are still copy pasting code?</p>
<p>The chat window can be a big part of your screen, be aware of that.</p>
<h3 id="code-completions">Code completions</h3>
<p>Use a comment to get a completion. When entering the code will appear.

<img loading="lazy" src="completion.png" alt="alt text"  />
</p>
<p>You should know by the way that you can also use only the beginning of the suggestion.</p>
<p>Use <code>Ctrl + right arrow</code> to accept one word at the time of the completion.</p>
<h3 id="inline-chat">Inline chat</h3>
<p>For example using <code>Ctrl + i</code> in VS Code. It will give you a completion for the line you are on.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">/tests
</span></span><span class="line"><span class="cl">/fix
</span></span><span class="line"><span class="cl">/explain
</span></span><span class="line"><span class="cl">/docs
</span></span><span class="line"><span class="cl">/help
</span></span><span class="line"><span class="cl">/optimize
</span></span><span class="line"><span class="cl">/generate</span></span></code></pre></td></tr></table>
</div>
</div></div><h4 id="chat-pallete">Chat pallete</h4>
<p>Use <code>Ctrl + Shift + I</code> in VS Code to open the chat in a pallete. This is handy to get a quick chat while keeping focus on your code.</p>
<p>To close the chat pallete, use <code>Esc</code>.</p>
<h3 id="commit-message-generation">Commit message generation</h3>
<p>use the Stars to generate a commit message. It&rsquo;s a good way to save time thinking of a commit message.

<img loading="lazy" src="commit-generation.png" alt="Generate a commit message using GitHub Copilot"  />
</p>
<h3 id="reference-a-filemethod-in-a-prompt">Reference a file/method in a prompt</h3>
<p>Use the <code>#</code> prefix to reference a file in your prompt.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">/fix This method should return a #Address.cs</span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="reference-a-scope">Reference a scope</h3>
<p>Use the <code>@</code> prefix to reference a file in your prompt.</p>
<p>
<img loading="lazy" src="scopes.png" alt="GitHub Copilot scopes"  />
</p>
<h3 id="prompting">Prompting</h3>
<p>Your prompt is is key for good results. You should be clear in what you want to achieve.</p>
<p>If your first prompt is not giving you the results you want, try to be more specific.</p>
<h2 id="code-worthy">Code worthy</h2>
<p>Let me give you a statement:</p>
<blockquote>
<p>Code is less valuable if it&rsquo;s generated by a tool.</p>
</blockquote>
<p>That is a bold statement. I think it&rsquo;s true. The engineers should be aware of the problem and the solution. The tool can help you with the implementation.</p>
<h3 id="engineers-are-puppeteers">Engineers are puppeteers</h3>
<p>The software engineers of the next years are going to tell the story using their puppets. The management will see output of the tooling and think they can do without the engineers. The technology is not there yet, but i think the engineers without the use of AI tooling will be pruned, just like variants in Loki, the Marvel series.</p>
<h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>I Like to have focus and not be distracted by the chat window. I think you should use the feature that suits your case best. For generating tests for example i think its best to do it inline, and accept the completion afterwards instead of copy pasting parts of the code.</p>
<p>I also think you should be reading release notes of GitHub Copilot. It&rsquo;s a tool that is constantly evolving. It&rsquo;s good to know what the changes are.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://learn.microsoft.com/en-us/visualstudio/ide/copilot-chat-context?view=vs-2022" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/visualstudio/ide/copilot-chat-context?view=vs-2022</a></li>
</ul>
]]></content:encoded>
      <category>habit</category>
      <category>github copilot</category>
      <category>ai</category>
      <category>tools</category>
    </item>
    <item>
      <title>Marts Cheatsheet</title>
      <link>https://blog.martdegraaf.nl/posts/marts-cheatsheet/</link>
      <pubDate>Mon, 28 Oct 2024 16:42:32 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/marts-cheatsheet/</guid>
      <description><![CDATA[Get the most out of tooling using Mart&rsquo;s cheatsheet.]]></description><content:encoded><![CDATA[<h2 id="visual-studio-code">Visual Studio Code</h2>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Ctrl + Shift + P</code> or <code>F1</code></td>
          <td>Open command palette</td>
      </tr>
      <tr>
          <td><code>Ctrl + i</code></td>
          <td>Open GitHub Copilot inline</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + i</code></td>
          <td>Open GitHub Copilot</td>
      </tr>
      <tr>
          <td><code>F2</code></td>
          <td>Rename variables and methods</td>
      </tr>
      <tr>
          <td><code>Alt + Shift + F</code></td>
          <td>Format document</td>
      </tr>
      <tr>
          <td><code>Ctrl + /</code></td>
          <td>Comment out code</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + O</code></td>
          <td>Remove unused usings</td>
      </tr>
  </tbody>
</table>
<h3 id="navigation">Navigation</h3>
<p>| <code>Ctrl + B</code> | Open / Close sidebar |
| <code>Ctrl + 1</code> | Focus on first editor group |
| <code>Ctrl + 2</code> | Focus on second editor group |
| <code>Ctrl + Shift + E</code> | Open editor |
| <code>Ctrl + Shift + G</code> | Open source control |</p>
<p>If you want a one-page cheatsheet, check <a href="https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf" target="_blank" rel="noopener">https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf</a>.</p>
<h2 id="visual-studio">Visual Studio</h2>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Ctrl + Q</code></td>
          <td>Visual Studio search</td>
      </tr>
      <tr>
          <td><code>Ctrl + -</code> <br> <code>Ctrl + Shift -</code></td>
          <td>Navigating Backward <br> Navigating Forward</td>
      </tr>
      <tr>
          <td><code>Alt + /</code></td>
          <td>Open GitHub Copilot</td>
      </tr>
      <tr>
          <td><code>Ctrl + R, Ctrl + R</code></td>
          <td>Rename variables and methods</td>
      </tr>
      <tr>
          <td><code>Ctrl + K, Ctrl + D</code></td>
          <td>Format document</td>
      </tr>
      <tr>
          <td><code>Ctrl + K, Ctrl + C</code> <br> <code>Ctrl + K, Ctrl + U</code></td>
          <td>Comment out code <br> Uncomment code</td>
      </tr>
      <tr>
          <td><code>Ctrl + R, Ctrl + G</code></td>
          <td>Remove and sort usings</td>
      </tr>
  </tbody>
</table>
<p>If you want all from Microsoft, check their cheatsheet for Visual Studio at <a href="https://visualstudio.microsoft.com/keyboard-shortcuts.pdf" target="_blank" rel="noopener">https://visualstudio.microsoft.com/keyboard-shortcuts.pdf</a>.</p>
<h2 id="git-fork">Git Fork</h2>
<p>I am frequently using Git Fork. Here are some commands I think help me be more productive.</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Ctrl + P</code></td>
          <td>Show Quick launch window</td>
      </tr>
      <tr>
          <td><code>Ctrl + Enter</code></td>
          <td>Commit</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + Enter</code></td>
          <td>Commit and push</td>
      </tr>
      <tr>
          <td><code>Ctrl + Tab / Shift Tab</code></td>
          <td>Next tab / previous tab</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + B</code></td>
          <td>New branch</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + T</code></td>
          <td>New tag</td>
      </tr>
      <tr>
          <td><code>Ctrl + Shift + H</code></td>
          <td>Create stash</td>
      </tr>
  </tbody>
</table>
<h2 id="windows">Windows</h2>
<p>I am frequently using Git Fork. Here are some commands I think help me be more productive.</p>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Windows + L</code></td>
          <td>Windows lunch, lock your PC when you &rsquo;re away.</td>
      </tr>
      <tr>
          <td><code>Windows + Shift + S</code></td>
          <td>Screenshot via snipping tool</td>
      </tr>
      <tr>
          <td><code>Windows + Shift + R</code></td>
          <td>Video snip via snipping tool</td>
      </tr>
      <tr>
          <td><code>Windows + Plus (+)</code></td>
          <td>Zoom in using magnifier</td>
      </tr>
  </tbody>
</table>
<p>See all the shortcuts at <a href="https://github.com/fork-dev/TrackerWin/issues/333" target="_blank" rel="noopener">https://github.com/fork-dev/TrackerWin/issues/333</a>.</p>
<h2 id="git">Git</h2>
<p>Fix an casing issue in your files.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git mv --force address.cs Address.cs
</span></span><span class="line"><span class="cl">git mv --force &lt;old_file&gt; &lt;new_file&gt;</span></span></code></pre></td></tr></table>
</div>
</div></div><p>Creating an annotated tag using the <code>-a</code> flag.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git tag -a v1.4 -m <span class="s2">&#34;my version 1.4&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="npm">NPM</h2>
<p>Update multiple packages at once using the scope.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx update-by-scope <span class="s2">&#34;@angular&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div>]]></content:encoded>
      <category>Visual Studio</category>
      <category>Visual Studio Code</category>
      <category>Git Fork</category>
      <category>cheatsheet</category>
    </item>
    <item>
      <title>Apply Ip Restrictions Application Firewall</title>
      <link>https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/</link>
      <pubDate>Tue, 21 May 2024 18:09:39 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/</guid>
      <description><![CDATA[In this blog post, I&rsquo;ll show you how to restrict access by IP addresses and domain names to your backend services using a Web Application Firewall (WAF) policy. We&rsquo;ll create a Bicep file, where I&rsquo;ll introduce the first custom type that I have deployed to production!]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/apply-ip-restrictions-application-firewall/cover.webp" alt="" /><br/><br/><p>Imagine you have an application gateway deployed in Azure. Behind this gateway, multiple app services handle requests for different domains. Now, you&rsquo;re adding a new application that needs to be ready for the GO-live moment. We want to deploy an application to production, but we want to restrict access until GO-live. We also want a select number of IP addresses to have access to the new application. Only the company or the development team should have access to the new application at this point.</p>
<p>In this blog post, I&rsquo;ll show you how to restrict access to your backend services using a Web Application Firewall (WAF) policy. We&rsquo;ll create a Bicep file, where I&rsquo;ll introduce the first custom type that I have deployed to production!</p>
<p>
  <figure>
    <img loading="lazy" src="appgateway.drawio.svg#center" alt="Architecture of an application gateway in front of multiple app services"  title="Example Azure infrastructure"  />
    <figcaption style="text-align: center;">Example Azure infrastructure</figcaption>
  </figure>
</p>
<h2 id="restricting-access">Restricting access</h2>
<p>When you want to release new code to production but still want it not exposed to the outside world, you can restrict access to the backend services by IP. This can be done by adding custom rules to your web application firewall policy (also known as a WAF-policy).</p>
<h3 id="adding-custom-rules-in-the-portal">Adding custom rules in the portal</h3>
<p>In the Azure portal, you can add custom rules to your WAF-policy. This can be done by navigating to the WAF-policy and selecting the <code>Custom rules</code> blade. Here you can add a new rule and select the action you want to take when the rule is matched. The portal was easy to uncover all possibilities, every field is a dropdown. This gave me insights into the capabilities of the WAF-policy custom rules. In the portal is visible that multiple conditions in a custom rule are logically using the <code>AND</code> condition. Let&rsquo;s put these insights into repeatable and releasable code!</p>
<p>
  <figure>
    <img loading="lazy" src="application-gateway-portal.png#center" alt="Showing custom policies of a WAF policy in the Azure Portal"  title="Custom policies of a WAF policy in the Azure Portal"  />
    <figcaption style="text-align: center;">Custom policies of a WAF policy in the Azure Portal</figcaption>
  </figure>
</p>
<h3 id="defining-a-custom-type">Defining a custom type</h3>
<p>Let&rsquo;s use custom types to give ourselves a better intellisense when configuring the parameters.</p>
<div class="codecontainer" hi="mart"><div class="code-name">WAFPolicyExclusions.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="p">@</span><span class="nf">export</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nv">restrictedDomain</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">allowedIPs</span><span class="p">:</span><span class="w"> </span><span class="nv">array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">url</span><span class="p">:</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="parameters">Parameters</h3>
<p>To use the custom type we need to define a parameter using the <code>restrictedDomain</code> type. To always allow a set of IP addresses we will add a <code>defaultAllowedIPs</code> array parameter. Those will be unioned with the ones given for a specific domain.</p>
<div class="codecontainer" hi="mart"><div class="code-name">WAFPolicyExclusions.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">defaultAllowedIPs</span><span class="w"> </span><span class="nv">array</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#39;127.0.0.1&#39;</span><span class="w"> </span><span class="c1">// Your company IP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">restrictedDomains</span><span class="w"> </span><span class="nv">restrictedDomain</span><span class="p">[]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[]</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="custom-rules">Custom rules</h3>
<p>Within the WAF-policy resource, we can provide custom rules. To do so we use the a for loop. The for loop loops over all <code>restrictedDomains</code> to add a custom rule for each domain.</p>
<p>The default action for this rule will be block, we want to block access to a domain <em>except</em> for the allowed IP addresses.</p>
<h4 id="condition-1---does-the-domain-match">Condition 1 - Does the domain match</h4>
<p>For the first condition, we will add a rule that matches the <code>Host</code> in the <code>RequestHeaders</code>, this has to match the given domain. I chose for <code>BeginsWith</code> because I would not want people to make mistakes and possibly take down all domains.</p>
<h4 id="condition-2---is-the-ip-address-in-the-whitelist">Condition 2 - Is the IP address in the whitelist?</h4>
<p>For the second condition, we will add a rule that matches the <code>RemoteAddr</code>, this has to <strong>NOT</strong> match the given IP addresses.
please note the <code>negationConditon: true</code> to make sure the rule is matched when the IP address is <strong>not</strong> in the list.</p>
<div class="codecontainer" hi="mart"><div class="code-name">WAFPolicyExclusions.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="w">    </span><span class="nv">customRules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kd">for</span><span class="w"> </span><span class="p">(</span><span class="nv">item</span><span class="p">,</span><span class="w"> </span><span class="nv">index</span><span class="p">)</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nv">restrictedDomains</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">item</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">ipBlock&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">priority</span><span class="p">:</span><span class="w"> </span><span class="nv">index</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">ruleType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;MatchRule&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">action</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Block&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">matchConditions</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchVariables</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">variableName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RequestHeaders&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">selector</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Host&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchValues</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">item</span><span class="p">.</span><span class="nv">url</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;BeginsWith&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">transforms</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;Lowercase&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">negationConditon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchVariables</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">variableName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RemoteAddr&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchValues</span><span class="p">:</span><span class="w"> </span><span class="nf">union</span><span class="p">(</span><span class="nv">defaultAllowedIPs</span><span class="p">,</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">allowedIPs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;IPMatch&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">negationConditon</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="lessons-learned">Lessons learned</h2>
<p>The process of building this custom rule was done in only two days. I learned two things that I want to share with you.</p>
<ol>
<li>The name of the rule has to be unique.</li>
<li>The priority must be unique and can only be a value between 1 and 100.</li>
</ol>
<p>This also means you only can have 100 custom rules in a WAF policy. This is a limitation you have to take into account when designing your solution.</p>
<h2 id="full-solution">Full Solution</h2>
<p>Here&rsquo;s a full bicep file with the custom type, parameters, and custom rules.</p>
<div class="codecontainer" hi="mart"><div class="code-name">WAFPolicyExclusions.bicep</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="p">@</span><span class="nf">export</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nv">restrictedDomain</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">allowedIPs</span><span class="p">:</span><span class="w"> </span><span class="nv">array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">url</span><span class="p">:</span><span class="w"> </span><span class="nv">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">location</span><span class="w"> </span><span class="nv">string</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">().</span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">tags</span><span class="w"> </span><span class="nv">object</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nf">resourceGroup</span><span class="p">().</span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">defaultAllowedIPs</span><span class="w"> </span><span class="nv">array</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#39;127.0.0.1&#39;</span><span class="w"> </span><span class="c1">// Your company IP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">restrictedDomains</span><span class="w"> </span><span class="nv">restrictedDomain</span><span class="p">[]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">resource</span><span class="w"> </span><span class="nv">WebApplicationFirewallPolicies</span><span class="w"> </span><span class="s">&#39;Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies@2023-11-01&#39;</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;WafPolicy&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">location</span><span class="p">:</span><span class="w"> </span><span class="nv">location</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">tags</span><span class="p">:</span><span class="w"> </span><span class="nv">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nv">properties</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">customRules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="kd">for</span><span class="w"> </span><span class="p">(</span><span class="nv">item</span><span class="p">,</span><span class="w"> </span><span class="nv">index</span><span class="p">)</span><span class="w"> </span><span class="kd">in</span><span class="w"> </span><span class="nv">restrictedDomains</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;</span><span class="si">${</span><span class="nv">item</span><span class="p">.</span><span class="nv">name</span><span class="si">}</span><span class="s">ipBlock&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">priority</span><span class="p">:</span><span class="w"> </span><span class="nv">index</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">ruleType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;MatchRule&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">action</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Block&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nv">matchConditions</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchVariables</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">variableName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RequestHeaders&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">selector</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Host&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchValues</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">item</span><span class="p">.</span><span class="nv">url</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;BeginsWith&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">transforms</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="s">&#39;Lowercase&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">negationConditon</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchVariables</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nv">variableName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;RemoteAddr&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">matchValues</span><span class="p">:</span><span class="w"> </span><span class="nf">union</span><span class="p">(</span><span class="nv">defaultAllowedIPs</span><span class="p">,</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">allowedIPs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">operator</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;IPMatch&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nv">negationConditon</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">managedRules</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nv">managedRuleSets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetType</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;OWASP&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleSetVersion</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;3.0&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nv">ruleGroupOverrides</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">ruleGroupName</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;REQUEST-920-PROTOCOL-ENFORCEMENT&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nv">rules</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">ruleId</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;920350&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nv">action</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;Block&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>And of course an example bicep parameters file.</p>
<div class="codecontainer" hi="mart"><div class="code-name">WAFPolicyExclusions.bicepparam</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bicep" data-lang="bicep"><span class="line"><span class="cl"><span class="kd">using</span><span class="w"> </span><span class="s">&#39;./WAFPolicyExclusions.bicep&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">defaultAllowedIPs</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="s">&#39;127.0.0.1&#39;</span><span class="w"> </span><span class="c1">// Your company IP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">param</span><span class="w"> </span><span class="nv">restrictedDomains</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;MartService&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">url</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;mart.contoso.com&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">allowedIPs</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">name</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;SMartService&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">url</span><span class="p">:</span><span class="w"> </span><span class="s">&#39;smart.contoso.com&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nv">allowedIPs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="s">&#39;0.0.0.0&#39;</span><span class="w"> </span><span class="c1">// Someone who needs access</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="deploying">Deploying</h3>
<p>The only thing to do now is to deploy the bicep files. When you want to enable access to a certain domain you can remove the domain from the parameters file. In this specific case, the application gateway was used for multiple domains and many web applications. Therefore it was easier to restrict access to a specific domain on the WAF-policy. This can be different for your specific use case.</p>
<h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>The application gateway can be used to restrict access to your backend services. This can be done by adding custom rules to your WAF-policy. Keep in mind that this has some limits.</p>
<h3 id="references">References</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/custom-waf-rules-overview" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/web-application-firewall/ag/custom-waf-rules-overview</a></li>
<li><a href="https://alanta.nl/posts/2021/04/manage-waf-rules-for-appgateway" target="_blank" rel="noopener">https://alanta.nl/posts/2021/04/manage-waf-rules-for-appgateway</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/user-defined-data-types" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/user-defined-data-types</a></li>
</ul>
]]></content:encoded>
      <category>Azure</category>
      <category>bicep</category>
      <category>WAF</category>
      <category>Application Gateway</category>
      <category>Security</category>
    </item>
    <item>
      <title>Productivity Guilt</title>
      <link>https://blog.martdegraaf.nl/posts/productivity-guilt/</link>
      <pubDate>Fri, 26 Apr 2024 13:13:10 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/productivity-guilt/</guid>
      <description><![CDATA[Have you ever felt guilty for not being productive? I have. Let&rsquo;s talk about it.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/productivity-guilt/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/productivity-guilt/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/productivity-guilt/cover.webp" alt="" /><br/><br/><p><strong>Have you ever felt guilty for not being productive? I have. Let&rsquo;s talk about it.</strong></p>
<h2 id="the-office--work">The office == work?</h2>
<p>With more than 10 years of work experience, I have worked in the office, hybrid, and remote.
I want to talk about productivity guilt. When I was in the office, I never felt guilty when I was not productive. When I checked in and out of the office those were my working hours. I was there, I was working. There was no guilt, even when drinking coffee with colleagues or smashing a ping pong ball in the office.</p>
<p>Now with hybrid and remote work, I feel more guilty. I have a family with four children and that takes time, and that does feel like I burn time that was supposed to be for working. As a result, I work later in the evening to compensate for my guilty feelings.</p>
<h2 id="the-guilt">The guilt</h2>
<p>If you do not experience this feeling let me explain what this does for me. This feeling makes me work more hours, and work more in the evening. I feel like I have to make up for the time I was not productive. For example, after a Thursday full of work, the thought comes up to work more to pre-compensate for the Friday after. But on Friday I work the full amount of hours. In the end, I am working more hours than I should.</p>
<h2 id="imposter-syndrome">Imposter syndrome</h2>
<p>I think this productivity guilt is a form of imposter syndrome. I think this is related because the feeling is also about feeling not good enough. We as programmers often talk about the &lsquo;Imposter syndrome&rsquo;. The feeling that other people know more than you, the imposter syndrome can prevent you from public speaking about your expertise.</p>
<h2 id="talking-helps">Talking helps</h2>
<p>I have talked about this with colleagues and they scoped the problem differently. Some quotes came along: &ldquo;Work never ends&rdquo;, and  It does not mean the inner voice will stop talking. I think it is important to talk about this and share your feelings with others. It helps to get instructions to stop, or to get compliments on your results.</p>
<h2 id="discipline">Discipline</h2>
<p>To get control of your productivity guilt, my approach will be to work on habits. Because i think habits will help you to regulate working hours and feel productive. Work on habits regarding your work. Habits will help you to regulate working hours and feel productive. Some tips I have gathered are:</p>
<ul>
<li>Start and end your workday at the same time.</li>
<li>Make sure you have a dedicated workspace.</li>
<li>Shut down your computer at the end of the workday.</li>
</ul>
<p>In the book &lsquo;Tiny Habits&rsquo; by BJ Fogg, he talks about the MAUI habit. This habit is saying when your feet touch the floor in the morning: &ldquo;It&rsquo;s going to be a great day.&rdquo;. This helps you to start your day with a positive mindset.</p>
<p>I suggest another habit to enclose your working day. I will try and make this my habit:</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   After I shut down my computer, I will say to myself: &ldquo;I did a great job today.&rdquo;
</div>
<h2 id="final-thoughts">Final thoughts</h2>
<p>I am still struggling with this feeling. If this is what people mean by Work-Life balance, I have to learn daily to balance this correctly.</p>
<p>If you experience this too, I invite you to share your thoughts with me. I am curious how you cope with this feeling.</p>
<h3 id="further-reading">Further reading</h3>
<p>While fetching feedback from friends and colleagues, Jan mentioned he fixed his imposter syndrome by using the &lsquo;Circle of Influence&rsquo; by Stephen Covey. He wrote a very simple tool to make you think about a concern. You can find the tool at <a href="https://janterschure.github.io/CircleOfInfluenceTool/" target="_blank" rel="noopener">CircleOfInfluenceTool</a>. The circle of influence is from the book &lsquo;The 7 Habits of Highly Effective People&rsquo; by Stephen Covey. It is a great book to read.</p>
]]></content:encoded>
      <category>imposter</category>
      <category>self-improvement</category>
      <category>productivity</category>
      <category>guilt</category>
      <category>work</category>
    </item>
    <item>
      <title>Angular Relative Navigation</title>
      <link>https://blog.martdegraaf.nl/posts/angular-relative-navigation/</link>
      <pubDate>Thu, 25 Apr 2024 07:47:30 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/angular-relative-navigation/</guid>
      <description><![CDATA[Learn how to navigate to a route relative to the current route in Angular. This is useful when you have a wizard-like flow in your application.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/angular-relative-navigation/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/angular-relative-navigation/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/angular-relative-navigation/cover.webp" alt="" /><br/><br/><p>For a project, we have routes that live in states. We wanted to navigate to a route relative to the current route. for each child route, we will be able to navigate to the previous and next route.</p>
<h2 id="system-context">System context</h2>
<p>Let&rsquo;s say we have a <code>app-routing.module.ts</code> with the following routes:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">routes</span>: <span class="kt">Routes</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;parent&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">component</span>: <span class="kt">ParentComponent</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">children</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;child1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">component</span>: <span class="kt">Child1Component</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">stepName</span><span class="o">:</span> <span class="s1">&#39;child1&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;child2&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">component</span>: <span class="kt">Child2Component</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">stepName</span><span class="o">:</span> <span class="s1">&#39;child2&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">path</span><span class="o">:</span> <span class="s1">&#39;child3&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">component</span>: <span class="kt">Child3Component</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">stepName</span><span class="o">:</span> <span class="s1">&#39;child3&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="routernavigate">Router.navigate</h2>
<p>We do not want to edit the url property, because it can lead to complexity. You would just say Angular does have the router to do so. The current route is a chain of activated routes when you are navigating to url like <code>/parent/child1/child2/child3</code>.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">//Inside Some component
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">constructor</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="kr">private</span> <span class="nx">router</span>: <span class="kt">Router</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kr">private</span> <span class="nx">activatedRoute</span>: <span class="kt">ActivatedRoute</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">navigate() {</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">lastChildRoute</span> <span class="o">=</span> <span class="nx">getLastActivatedRouteInChain</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">activatedRoute</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">this</span><span class="p">.</span><span class="nx">router</span><span class="p">.</span><span class="nx">navigate</span><span class="p">([</span><span class="s1">&#39;../child2&#39;</span><span class="p">],</span> <span class="p">{</span> <span class="nx">relativeTo</span>: <span class="kt">lastChildRoute</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>To get the activated route for for example <code>child3</code> when the url is <code>/parent/child1/child2/child3</code> we need to get the last child route in the chain. This <code>ActivatedRoute</code> object is passed to the <code>relativeTo</code> property of the <code>router.navigate</code> method.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">getLastActivatedRouteInChain</span> <span class="o">=</span> <span class="p">(</span><span class="nx">activatedRoute</span>: <span class="kt">ActivatedRoute</span><span class="p">)</span><span class="o">:</span> <span class="nx">ActivatedRoute</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">lastRoute</span> <span class="o">=</span> <span class="nx">activatedRoute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="nx">lastRoute</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">lastRoute</span> <span class="o">=</span> <span class="nx">lastRoute</span><span class="p">.</span><span class="nx">firstChild</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">lastRoute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>This way we can navigate to a route relative to the current route. This is useful when you have a wizard-like flow in your application.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="https://angular.io/guide/router" target="_blank" rel="noopener">Angular Router</a></li>
</ul>
]]></content:encoded>
      <category>angular</category>
      <category>navigation</category>
      <category>routing</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Migrating Classic Azure DevOps Releases to YAML Pipelines</title>
      <link>https://blog.martdegraaf.nl/posts/consulting/migrating-classic-to-yaml-releases/</link>
      <pubDate>Wed, 17 Apr 2024 23:45:36 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/consulting/migrating-classic-to-yaml-releases/</guid>
      <description><![CDATA[Migrate your classic releases to YAML pipelines in Azure DevOps with our comprehensive guide. Streamline your deployment process today!]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/consulting/migrating-classic-to-yaml-releases/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/consulting/migrating-classic-to-yaml-releases/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/consulting/migrating-classic-to-yaml-releases/cover.webp" alt="" /><br/><br/><p>In a previous blog post, we tackled the issue of pushing files and creating pull requests using Powershell. In this blog post, we are going to tackle the issue of migrating classic releases to YAML pipelines in Azure DevOps. We are going to use a script to fetch current classic releases and make yaml files out of it.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f916; Check out my previous blog to get more context: <a href="https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/" target="_blank" rel="noopener">Foreach repositories push and create a pull request</a>.
</div>
<h2 id="one-script-to-rule-them-all">One script to rule them all</h2>
<p>This thing is not a one-size-fits-all solution. This script fetches all variables and puts them in variable files for you and puts a yaml file that extends an existing template. The template is let out of scope. The script is a starting point for you to migrate your classic releases to YAML pipelines and might give you ideas and inspiration.</p>
<h3 id="why-automate">Why automate</h3>
<p>
  <figure>
    <img loading="lazy" src="should_we_automate.jpg#center" alt="Should we automate?"  title="Automation has benefits over manual work when exceeding a number of repositories."  />
    <figcaption style="text-align: center;">Automation has benefits over manual work when exceeding a number of repositories.</figcaption>
  </figure>
</p>
<p>As visualized in the chart above we can easily calculate the time saved by automating the process. When the time of the automation is smaller than the number of repositories times the time to do it manually, we should automate. In this case, we have 60 repositories and the time to do it manually is 1 hour per repository. I set the time to automate to 32 hours. This is the time I spent on the script. The time to do it manually would be 60 hours. So we saved 28 hours by automating the process. And we can reuse the script for future migrations. Or now you can too &#x1f609;.</p>
<h2 id="export-release-and-archive-it">Export release and archive it</h2>
<p>In this script, a release is fetched from Azure DevOps using the Azure DevOps REST API. The release is then exported to a yaml file and moved to an archive folder. We can use the output of this script as input for our script to push files and create a pull request.</p>
<div class="codecontainer" hi="mart"><div class="code-name">ReleaseExportAndMoveToArchive.ps1</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="c"># Description: This script moves a release definition to a target folder in Azure DevOps</span>
</span></span><span class="line"><span class="cl"><span class="c"># it depends on you being signed in in the Azure cli, that can be done by `az login`</span>
</span></span><span class="line"><span class="cl"><span class="c"># Usage: ReleaseExportAndMoveToArchive.ps1 -releaseDefinitionName &#34;MartService Release&#34; -serviceName &#34;MartService&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">param</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="nb">Parameter</span><span class="p">(</span><span class="na">Mandatory</span><span class="p">=</span><span class="vm">$true</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="no">string</span><span class="p">]</span><span class="nv">$releaseDefinitionName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="nb">Parameter</span><span class="p">(</span><span class="na">Mandatory</span><span class="p">=</span><span class="vm">$true</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="no">string</span><span class="p">]</span><span class="nv">$serviceName</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Prompt the user to login and get the access token</span>
</span></span><span class="line"><span class="cl"><span class="c"># see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli</span>
</span></span><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="p">-</span><span class="n">-output</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$accessToken</span> <span class="o">-eq</span> <span class="vm">$null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nv">$orgUrl</span> <span class="p">=</span> <span class="s2">&#34;https://vsrm.dev.azure.com/MartOrg&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$project</span> <span class="p">=</span> <span class="s2">&#34;MartProject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$targetFolderId</span> <span class="p">=</span> <span class="s2">&#34;/Archive&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span> <span class="p">=</span> <span class="p">(</span><span class="s2">&#34;Bearer {0}&#34;</span> <span class="o">-f</span> <span class="nv">$accessToken</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span>        <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get the release definitions matching the given name</span>
</span></span><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$orgUrl</span><span class="s2">/</span><span class="nv">$project</span><span class="s2">/_apis/release/definitions?api-version=7.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$definitionsResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Found </span><span class="p">$(</span><span class="nv">$definitionsResponse</span><span class="p">.</span><span class="n">count</span><span class="p">)</span><span class="s2"> release definitions&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$definition</span> <span class="p">=</span> <span class="nv">$definitionsResponse</span><span class="p">.</span><span class="py">value</span> <span class="p">|</span> <span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="py">name</span> <span class="o">-eq</span> <span class="nv">$releaseDefinitionName</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Move the release definition to the target folder</span>
</span></span><span class="line"><span class="cl"><span class="nv">$definition</span><span class="p">.</span><span class="py">id</span> <span class="p">=</span> <span class="nv">$definition</span><span class="p">.</span><span class="py">id</span> <span class="o">-replace</span> <span class="s2">&#34;:&#34;</span><span class="p">,</span> <span class="s2">&#34;%3A&#34;</span>  <span class="c"># Escape the colon in the definition ID</span>
</span></span><span class="line"><span class="cl"><span class="nv">$uri</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="p">$(</span><span class="nv">$definition</span><span class="p">.</span><span class="n">url</span><span class="p">)</span><span class="s2">?api-version=7.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;</span><span class="nv">$uri</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c"># get the current release definition</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pipeline</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pipeline</span><span class="p">.</span><span class="py">path</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$targetFolderId</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$json</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span><span class="nv">$pipeline</span><span class="p">)</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">99</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># create serviceName folder</span>
</span></span><span class="line"><span class="cl"><span class="nb">New-Item</span> <span class="n">-ItemType</span> <span class="n">Directory</span> <span class="n">-Force</span> <span class="n">-Path</span> <span class="s2">&#34;tmp\</span><span class="p">$(</span><span class="nv">$serviceName</span><span class="p">.</span><span class="py">ToLower</span><span class="p">())</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">Function</span><span class="w"> </span><span class="nb">ConvertTo-Yml</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">param</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="nb">Parameter</span><span class="p">(</span><span class="na">Mandatory</span><span class="p">=</span><span class="vm">$true</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="no">object</span><span class="p">]</span><span class="nv">$object</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="nb">Parameter</span><span class="p">(</span><span class="na">Mandatory</span><span class="p">=</span><span class="vm">$true</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="no">string</span><span class="p">]</span><span class="nv">$Path</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$yml</span> <span class="p">=</span> <span class="s1">&#39;variables:&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$variable</span> <span class="k">in</span> <span class="nv">$object</span><span class="p">.</span><span class="py">PsObject</span><span class="p">.</span><span class="n">Properties</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$yml</span> <span class="p">+=</span> <span class="s2">&#34;</span><span class="se">`n</span><span class="s2">  </span><span class="p">$(</span><span class="nv">$variable</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span><span class="s2">: </span><span class="p">$(</span><span class="nv">$variable</span><span class="p">.</span><span class="py">Value</span><span class="p">.</span><span class="n">Value</span><span class="p">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="nv">$yml</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$yml</span> <span class="p">|</span> <span class="nb">Out-File</span> <span class="n">-FilePath</span> <span class="nv">$Path</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">ConvertTo-Yml</span> <span class="nv">$pipeline</span><span class="p">.</span><span class="py">variables</span> <span class="s2">&#34;tmp\</span><span class="p">$(</span><span class="nv">$serviceName</span><span class="p">.</span><span class="py">ToLower</span><span class="p">())</span><span class="s2">\variables.yml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$environment</span> <span class="k">in</span> <span class="nv">$pipeline</span><span class="p">.</span><span class="n">environments</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c">#transform $environment.name to o, t, a or p</span>
</span></span><span class="line"><span class="cl">    <span class="c">#Possible inputs: Development, Test, Acceptance, Production</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$environmentName</span> <span class="p">=</span> <span class="nv">$environment</span><span class="p">.</span><span class="py">name</span><span class="p">.</span><span class="py">ToLower</span><span class="p">().</span><span class="py">Substring</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="mf">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$environmentSuffix</span> <span class="p">=</span> <span class="k">switch</span> <span class="p">(</span><span class="nv">$environmentName</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;d&#34;</span> <span class="p">{</span> <span class="s2">&#34;o&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;t&#34;</span> <span class="p">{</span> <span class="s2">&#34;t&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;a&#34;</span> <span class="p">{</span> <span class="s2">&#34;a&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;p&#34;</span> <span class="p">{</span> <span class="s2">&#34;p&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="nb">ConvertTo-Yml</span> <span class="nv">$environment</span><span class="p">.</span><span class="py">variables</span> <span class="s2">&#34;tmp\</span><span class="p">$(</span><span class="nv">$serviceName</span><span class="p">.</span><span class="py">ToLower</span><span class="p">())</span><span class="s2">\variables-</span><span class="p">$(</span><span class="nv">$environmentSuffix</span><span class="p">)</span><span class="s2">.yml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$yml</span> <span class="p">=</span> <span class="s2">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">trigger:
</span></span></span><span class="line"><span class="cl"><span class="s2">  branches:
</span></span></span><span class="line"><span class="cl"><span class="s2">    include:
</span></span></span><span class="line"><span class="cl"><span class="s2">    - main
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">pool: &#39;default&#39;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">resources:
</span></span></span><span class="line"><span class="cl"><span class="s2">  repositories:
</span></span></span><span class="line"><span class="cl"><span class="s2">  - repository: Pipelines
</span></span></span><span class="line"><span class="cl"><span class="s2">    type: git
</span></span></span><span class="line"><span class="cl"><span class="s2">    name: Pipelines
</span></span></span><span class="line"><span class="cl"><span class="s2">    ref: refs/heads/main
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">name: </span><span class="se">`$</span><span class="s2">(Build.DefinitionName)_</span><span class="se">`$</span><span class="s2">(SourceBranchName)_</span><span class="se">`$</span><span class="s2">(date:yyyyMd).</span><span class="se">`$</span><span class="s2">(Rev:r)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">variables:
</span></span></span><span class="line"><span class="cl"><span class="s2">  - template: variables.yml
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">extends:
</span></span></span><span class="line"><span class="cl"><span class="s2">  template: Yml/service.pipeline.yml@Pipelines
</span></span></span><span class="line"><span class="cl"><span class="s2">  parameters:&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$yml</span> <span class="p">|</span> <span class="nb">Out-File</span> <span class="n">-FilePath</span> <span class="s2">&#34;tmp\</span><span class="p">$(</span><span class="nv">$serviceName</span><span class="p">.</span><span class="py">ToLower</span><span class="p">())</span><span class="s2">\</span><span class="p">$(</span><span class="nv">$serviceName</span><span class="p">.</span><span class="py">ToLower</span><span class="p">())</span><span class="s2">-pipeline.yml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># update the release definition with the new path</span>
</span></span><span class="line"><span class="cl"><span class="nv">$response</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$uri</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Put</span> <span class="n">-Body</span> <span class="nv">$json</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Release definition &#39;</span><span class="nv">$releaseDefinitionName</span><span class="s2">&#39; moved to folder &#39;</span><span class="nv">$targetFolderId</span><span class="s2">&#39;&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>We have now migrated our classic releases to YAML pipelines. We can now use the output of this script to push files and create a pull request. This way we can automate the process of updating all our repositories with the same type of change. We have saved time and can now focus on <del>browsing/chilling/netflixing</del> other tasks.</p>
]]></content:encoded>
      <category>azure-devops</category>
      <category>powershell</category>
      <category>yaml</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Foreach repository push files and create a pull request</title>
      <link>https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/</link>
      <pubDate>Wed, 17 Apr 2024 22:45:36 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/</guid>
      <description><![CDATA[Automate repetitive tasks using code, even when it is in code.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/consulting/foreach-repo-push-and-pr/cover.webp" alt="" /><br/><br/><p>For a client, we manage over 60 repositories with frontends and an API behind them. To realize some features, we need to update all repositories with the same change. That seems like a lot of work doesn&rsquo;t it?</p>
<h2 id="clone-60-projects">Clone 60 projects?</h2>
<p>I talked in previous posts about automating these kinds of changes. But for this case, we don&rsquo;t want to clone all 60 repositories. We could go for a temp folder and clone them one by one and then clean them up. Another solution could be creating a yaml pipeline for it, that clones on the agent machine and loops over all repositories, but I wanted to keep the blame on the person of the initial change.</p>
<p>For this case I used GitHub copilot to help me brainstorm for solutions, another solution I did not follow was using a <code>$gitClient</code> object in Powershell.</p>
<h2 id="push-files">Push files</h2>
<p>In the underlying script, I can create a branch from the main branch and push the contents of the readme file. This is a simple example, but it could be any file. The script can push the file to the repository and create a pull request.</p>
<div class="codecontainer" hi="mart"><div class="code-name">PushReadmeAzureDevOps.ps1</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="c"># Prompt the user to login and get the access token</span>
</span></span><span class="line"><span class="cl"><span class="c"># see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli</span>
</span></span><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="p">-</span><span class="n">-output</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$accessToken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nv">$orgUrl</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/MARTORG&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$project</span> <span class="p">=</span> <span class="s2">&#34;MARTPROJECT&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$repositoryId</span> <span class="p">=</span> <span class="s2">&#34;YOURREPO&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$readmeFilePath</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span> <span class="p">=</span> <span class="p">(</span><span class="s2">&#34;Bearer {0}&#34;</span> <span class="o">-f</span> <span class="nv">$accessToken</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span>        <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Content-Type&#34;</span> <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Convert the README file to base64</span>
</span></span><span class="line"><span class="cl"><span class="nv">$readmeContent</span> <span class="p">=</span> <span class="nb">Get-Content</span> <span class="n">-Path</span> <span class="nv">$readmeFilePath</span> <span class="n">-Raw</span>
</span></span><span class="line"><span class="cl"><span class="nv">$readmeBase64</span> <span class="p">=</span> <span class="p">[</span><span class="no">System.Convert</span><span class="p">]::</span><span class="n">ToBase64String</span><span class="p">([</span><span class="no">System.Text.Encoding</span><span class="p">]::</span><span class="n">UTF8</span><span class="p">.</span><span class="py">GetBytes</span><span class="p">(</span><span class="nv">$readmeContent</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;README file content: </span><span class="nv">$readmeContent</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># get current commit object id</span>
</span></span><span class="line"><span class="cl"><span class="nv">$commitUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$orgUrl</span><span class="s2">/</span><span class="nv">$project</span><span class="s2">/_apis/git/repositories/</span><span class="nv">$repositoryId</span><span class="s2">/refs/heads/main?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$commitResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$commitUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span>
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="nv">$commitResponse</span><span class="p">.</span><span class="py">Value</span>
</span></span><span class="line"><span class="cl"><span class="nv">$commitObjectId</span> <span class="p">=</span> <span class="nv">$commitResponse</span><span class="p">.</span><span class="py">Value</span><span class="p">.</span><span class="py">objectId</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Current commit object id: </span><span class="nv">$commitObjectId</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Create the request body</span>
</span></span><span class="line"><span class="cl"><span class="nv">$changes</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">changeType</span> <span class="p">=</span> <span class="s2">&#34;edit&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="n">item</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">path</span> <span class="p">=</span> <span class="s2">&#34;README.md&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="n">newContent</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">content</span> <span class="p">=</span> <span class="nv">$readmeBase64</span>
</span></span><span class="line"><span class="cl">                        <span class="n">contentType</span> <span class="p">=</span> <span class="s2">&#34;base64encoded&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$requestBody</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">refUpdates</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span> <span class="p">=</span> <span class="s2">&#34;refs/heads/add-readme&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">oldObjectId</span> <span class="p">=</span> <span class="nv">$commitObjectId</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">commits</span> <span class="p">=</span> <span class="vm">@</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">comment</span> <span class="p">=</span> <span class="s2">&#34;Adding README file&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">changes</span> <span class="p">=</span> <span class="nv">$changes</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">6</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Set the API endpoint</span>
</span></span><span class="line"><span class="cl"><span class="nv">$apiUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$orgUrl</span><span class="s2">/</span><span class="nv">$project</span><span class="s2">/_apis/git/repositories/</span><span class="nv">$repositoryId</span><span class="s2">/pushes?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Send the API request</span>
</span></span><span class="line"><span class="cl"><span class="nv">$response</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$apiUrl</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Body</span> <span class="nv">$requestBody</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Check the response</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$response</span><span class="p">.</span><span class="n">pushId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;README file pushed successfully.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Failed to push README file. Error: </span><span class="p">$(</span><span class="nv">$response</span><span class="p">.</span><span class="n">message</span><span class="p">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># create a pull request for myfirstbranch</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pullRequestUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$orgUrl</span><span class="s2">/</span><span class="nv">$project</span><span class="s2">/_apis/git/repositories/</span><span class="nv">$repositoryId</span><span class="s2">/pullrequests?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pullRequestBody</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sourceRefName</span> <span class="p">=</span> <span class="s2">&#34;refs/heads/add-readme&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">targetRefName</span> <span class="p">=</span> <span class="s2">&#34;refs/heads/main&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="p">=</span> <span class="s2">&#34;Add README file&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">description</span> <span class="p">=</span> <span class="s2">&#34;Adding README file&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">6</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$pullResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$pullRequestUrl</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Body</span> <span class="nv">$pullRequestBody</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$pullResponse</span><span class="p">.</span><span class="n">pullRequestId</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Pull request created successfully.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Failed to create pull request. Error: </span><span class="p">$(</span><span class="nv">$pullResponse</span><span class="p">.</span><span class="n">message</span><span class="p">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>I am going to use this script to update files across all of our repositories. For this client, the code review is important, so the automation would end in a set pull request. In the next blog post, I am going to create a script that will create files to be pushed for every repository.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting started with Azure OpenAI</title>
      <link>https://blog.martdegraaf.nl/posts/getting-started-with-azure-openai/</link>
      <pubDate>Thu, 26 Oct 2023 07:00:00 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/getting-started-with-azure-openai/</guid>
      <description><![CDATA[AI is happening. How can you make sure your organization benefits from AI?]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/getting-started-with-azure-openai/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/getting-started-with-azure-openai/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/getting-started-with-azure-openai/cover.webp" alt="" /><br/><br/><h2 id="introduction">Introduction</h2>
<p>Artificial intelligence is hip and happening. Everyone can use ChatGPT right now easily. But there are some worries about where your chats will go. Is it safe? Is the model trained by my conversations?</p>
<p>As a consultant, it is important to know the possibilities and the tradeoffs of using AI.
In this article, we discuss the OpenAI services in Azure. The Azure OpenAI services promise that the data stays in your environment in the cloud. This is a minimal requirement for many companies. We will show how to use the playground, the API, and the Azure Cognitive Search integration.</p>
<h2 id="creating-your-openai-resource">Creating your OpenAI resource</h2>
<p>Before you can start using Azure OpenAI you will need to be accepted by Microsoft. You need to apply for the &lsquo;Azure OpenAI Service&rsquo; preview. You can do this by following the procedure to create this resource, a link to the application form is provided.</p>
<ol>
<li>Create a new resource in Azure.</li>
<li>Search for OpenAI</li>
<li>Select the OpenAI resource</li>
<li>Create the resource</li>
</ol>
<p>
  <figure>
    <img loading="lazy" src="marketplace-openai.png#center" alt="Azure Marketplace - OpenAI"  title="Azure Marketplace - OpenAI"  />
    <figcaption style="text-align: center;">Azure Marketplace - OpenAI</figcaption>
  </figure>
</p>
<h2 id="using-openai-studio">Using OpenAI studio</h2>
<p>After creating the resource you can go to the OpenAI studio.</p>
<p>
  <figure>
    <img loading="lazy" src="openai_studio.png#center" alt="OpenAI Studio"  title="OpenAI Studio"  />
    <figcaption style="text-align: center;">OpenAI Studio</figcaption>
  </figure>
</p>
<p>This is a web-based tool the &lsquo;OpenAI Playground&rsquo; that allows you to test the models and see the results. You can also create your models and train them.</p>
<h3 id="create-a-new-model">Create a new Model</h3>
<p>You can create a new model by clicking the &lsquo;Create Model&rsquo; button. You can select the model you want to use. The default is the GPT-3 turbo model. I did my testing with the GPT-35 turbo Model. From the model, you need to make a deployment. This will take a few minutes. After the deployment is done you can start using the model. The DaVinci model can be a better fit for autocompletion and text generation. Dall-E is only available in East US.</p>
<h3 id="chat-playground">Chat Playground</h3>
<p>In the chat playground, there are some cool features:</p>
<ol>
<li>Add data sources</li>
<li>Add system prompts</li>
<li>Clear the chat, and view the code</li>
<li>Parameters into the model.</li>
<li>Import and export the setup.</li>
</ol>
<p>The Chat Playground doesn&rsquo;t have a dark mode, so beware of your eyes if you do this at nighttimes.</p>
<p>
  <figure>
    <img loading="lazy" src="chat_playground.png#center" alt="OpenAI Studio - Chat Playground"  title="OpenAI Studio - Chat Playground"  />
    <figcaption style="text-align: center;">OpenAI Studio - Chat Playground</figcaption>
  </figure>
</p>
<h3 id="defining-system-prompts">Defining System Prompts</h3>
<p>The default will get you somewhere but you can also use the system prompts to get better results. The system prompts are a way to give the model some context. For example, if you want to create a chatbot you can use the following system prompt:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">You are an AI assistant named Mart that helps people find information. The assistant is helpful, creative, clever, and very friendly.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Provide the full schedule when the user asks for a certain date.
</span></span><span class="line"><span class="cl">End every message with &#34;Mart knows everything&#34;. Add a mermaid diagram written in markdown to clarify your statement.</span></span></code></pre></td></tr></table>
</div>
</div></div><p>This kind of prompting will result in some hilarious results. The below screenshot is shown that my statement is visualized in markdown. GPT drew me a mermaid :).</p>
<p>
  <figure>
    <img loading="lazy" src="sample-chat.png#center" alt="Chat Playground"  title="Sample Chat"  />
    <figcaption style="text-align: center;">Sample Chat</figcaption>
  </figure>
</p>
<p>After correcting the prompt to <code>Add a mermaid diagram written in markdown to clarify your answer on the question.</code>. I got the response:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Azure is a cloud computing platform that offers a wide range of resources to help users build, deploy, and manage applications and services. Some of the most common Azure resources include:
</span></span><span class="line"><span class="cl">Virtual Machines - Virtual Machines (VMs) are the basic building blocks of Azure. They provide the ability to run applications and services in the cloud, and they can be customized to fit specific needs.
</span></span><span class="line"><span class="cl">Storage Accounts - Storage Accounts are used to store data in the cloud. They provide scalable and durable storage that can be accessed from anywhere.
</span></span><span class="line"><span class="cl">App Services - App Services are used to host web applications and services. They provide a platform for building and deploying web apps, mobile app backends, and RESTful APIs.
</span></span><span class="line"><span class="cl">SQL Databases - SQL Databases are used to store relational data in the cloud. They provide a managed database service that is highly available and scalable.
</span></span><span class="line"><span class="cl">Virtual Networks - Virtual Networks are used to connect resources in the cloud. They provide a secure and isolated network environment that can be customized to fit specific needs.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">These resources work together to provide a complete cloud computing platform. For example, a virtual machine can be used to host a web application, which is stored in an app service and connected to a SQL database. Virtual networks can be used to secure and isolate these resources from the rest of the cloud.
</span></span><span class="line"><span class="cl">Here&#39;s a mermaid diagram to help clarify:
</span></span><span class="line"><span class="cl">graph LR  
</span></span><span class="line"><span class="cl">A[Virtual Machines] --&gt; B[Storage Accounts]  
</span></span><span class="line"><span class="cl">A --&gt; C[App Services]  
</span></span><span class="line"><span class="cl">C --&gt; D[SQL Databases]  
</span></span><span class="line"><span class="cl">A --&gt; E[Virtual Networks]  
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">Mart knows everything.</span></span></code></pre></td></tr></table>
</div>
</div></div><p>When putting it in a mermaid renderer I got the following result:</p>
<pre class="mermaid">graph LR  
A[Virtual Machines] --> B[Storage Accounts]  
A --> C[App Services]  
C --> D[SQL Databases]  
A --> E[Virtual Networks]  
</pre>

<p>This was more what I expected. Only the mermaid diagram connections don&rsquo;t make sense. This is a limitation, it can&rsquo;t model the connections well enough. You should check the output and test when integrating these features. ChatGPT is a statistical model, so it will give you a result that is the best fit based on its data. That does not imply everything is correct. You should always be critical at the output.</p>
<h2 id="properties">Properties</h2>
<p>In the parameters tab, you can play with some settings. You will need to know about those to get the best results.</p>
<h3 id="max-response">Max response</h3>
<p>The <code>Max response</code> setting sets a limit on tokens that can be used per response. I found better results with complex prompts with the Max response just above 2000.</p>
<h3 id="temperature">Temperature</h3>
<p>The temperature is a setting that controls the randomness of the model. The higher the temperature the more random the results will be. For more theoretical information see: <a href="https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/" target="_blank" rel="noopener">https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/</a>.</p>
<p>The <code>Top P</code> setting works like the temperature setting, 0 is low probability and 1 is high probability. The higher the value, the more diverse probabilities will be allowed.</p>
<h2 id="documenting-code-to-using-openai">Documenting code to using OpenAI</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">create a mermaid diagram for the flow that is done in this c# code   
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">public static voidFibonacci_Iterative(int len)    
</span></span><span class="line"><span class="cl">{    
</span></span><span class="line"><span class="cl">    int a = 0, b = 1, c = 0;    
</span></span><span class="line"><span class="cl">    Console.Write(&#34;{0} {1}&#34;, a,b);    
</span></span><span class="line"><span class="cl">    for (int i = 2; i &lt; len; i++)    
</span></span><span class="line"><span class="cl">    {    
</span></span><span class="line"><span class="cl">        c= a + b;    
</span></span><span class="line"><span class="cl">        Console.Write(&#34; {0}&#34;, c);    
</span></span><span class="line"><span class="cl">        a= b;    
</span></span><span class="line"><span class="cl">        b= c;    
</span></span><span class="line"><span class="cl">    }    
</span></span><span class="line"><span class="cl">}  </span></span></code></pre></td></tr></table>
</div>
</div></div><p>This prompt resulted in the code being explained in a good flowchart. This is a great way to document your code.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">To create a mermaid diagram for the flow in the given C# code, you can follow these steps:
</span></span><span class="line"><span class="cl">Start by creating a sequenceDiagram block in your mermaid code.
</span></span><span class="line"><span class="cl">Add a participant for the Fibonacci_Iterative method.
</span></span><span class="line"><span class="cl">Add a participant for the Console class.
</span></span><span class="line"><span class="cl">Add a note to the Fibonacci_Iterative participant to indicate that it takes an integer parameter len.
</span></span><span class="line"><span class="cl">Add a note to the Console participant to indicate that it writes to the console.
</span></span><span class="line"><span class="cl">Add a loop block to represent the for loop in the code.
</span></span><span class="line"><span class="cl">Inside the loop block, add a note to indicate that c is the sum of a and b.
</span></span><span class="line"><span class="cl">Inside the loop block, add a note to indicate that a is assigned the value of b.
</span></span><span class="line"><span class="cl">Inside the loop block, add a note to indicate that b is assigned the value of c.
</span></span><span class="line"><span class="cl">Connect the Fibonacci_Iterative participant to the Console participant with an arrow to indicate that it writes to the console.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Here&#39;s an example of what the mermaid code could look like:</span></span></code></pre></td></tr></table>
</div>
</div></div><pre class="mermaid">sequenceDiagram  
    participant Fibonacci_Iterative  
    participant Console  
  
    Note over Fibonacci_Iterative: Takes integer parameter len  
    Note over Console: Writes to console  
  
    loop for i=2 to len  
        Note over Fibonacci_Iterative: c = a + b  
        Note over Fibonacci_Iterative: a = b  
        Note over Fibonacci_Iterative: b = c  
    end  
  
    Fibonacci_Iterative->>Console: Writes to console  
 
</pre>

<h2 id="adding-your-data-using-cognitive-search">Adding your data using Cognitive Search</h2>
<p>You can also add your data to the model. This can be done by using the Cognitive Search feature. You can upload your data and use it to train the model. You can also use the data to create completions.</p>
<p>In the playground click &lsquo;add your data&rsquo; and click &lsquo;+ add Data source&rsquo;.</p>
<p>
  <figure>
    <img loading="lazy" src="chat_playground_add_data_source.png#center" alt="Chat Playground - add data source"  title="Chat playground - Add Data Source"  />
    <figcaption style="text-align: center;">Chat playground - Add Data Source</figcaption>
  </figure>
</p>
<h3 id="structure">Structure</h3>
<p>Whatever you choose in the data source dropdown you will always end up with a Blob storage containing some blob files with a Cognitive Search resource. Make sure when creating the cognitive search resource you select the same region as the OpenAI resource and it has to be at least basic.</p>
<pre class="mermaid">flowchart LR
OpenAI --> |uses| CognitiveSearch
CognitiveSearch -->| indexes| BlobStorage
</pre>

<h3 id="steps-to-add-data">Steps to add data</h3>
<ol>
<li>Create a Blob storage account and put files in a container. I recommend using the Azure Storage Explorer. Upload some PDF files you want to be able to search for. Or use HTML pages from your public website as feed for your data source.</li>
<li>Create a Cognitive Search resource. This has to be at least Basic, performance will be better on higher sku, but costs will be higher too.</li>
<li>Create a data source in the Playground.
<ul>
<li>Select &lsquo;Azure Blob Storage&rsquo;.</li>
<li>Select the Blob storage resource you created.</li>
<li>Select the container you created or want to use.</li>
<li>Select the Cognitive Search resource you created.</li>
<li>Enter an index name</li>
<li>Check the box that you know about the pricing. And continue.</li>
<li>Click Save and Close on the Review and Finish page.</li>
</ul>
</li>
<li>Wait for the Indexer to finish. You can watch the progress in the Cognitive Search resource. Navigate to the Indexers and Indexes tab. Indexers run to create the index.</li>
</ol>
<p>
  <figure>
    <img loading="lazy" src="cognitive_search_indexers.png#center" alt="Cognitive Search - Indexers"  title="Cognitive Search - Indexers"  />
    <figcaption style="text-align: center;">Cognitive Search - Indexers</figcaption>
  </figure>
</p>
<h3 id="chatting-to-your-documents">Chatting to your documents</h3>
<p>Now that the indexer is ready and the index is loaded, let&rsquo;s chat to documents. The blob storage is fed with a slide deck of my Git presentation as PDF to the blob storage. Let&rsquo;s ask GPT about Git.</p>
<p>
  <figure>
    <img loading="lazy" src="playground_chat_with_data_source_git.png#center" alt="Chatting with presentations"  title="Chatting with presentations"  />
    <figcaption style="text-align: center;">Chatting with presentations</figcaption>
  </figure>
</p>
<p>Please note that in the screenshot you can choose to limit responses specific to your data content. Sources will be shown in the response. This is a great way to get insights into your data.</p>
<h3 id="cost-management">Cost management</h3>
<p>The basic sku on Cognitive search is a pay-per-month model, which means you should delete the resource after you are done testing with it. The OpenAI resource is pay-per-use as is the storage account.</p>
<h2 id="deploy-it-to-an-app-service">Deploy it to an App Service</h2>
<p>So you have now seen some power or some stupidity of the OpenAI model. You can also deploy it to an App Service. This can be on a free sku, but make sure when using this that your Chat application will be open on {your-app-name}.azurewebsites.net. You will need to configure VNET integration if you are dealing with company information.</p>
<h3 id="resource-group-overview">Resource group overview</h3>
<p>As wrap up for all the resources we created during our testing. We can see the following resources in our resource group. I know you will be all testing if MartGPT is still online.</p>
<p>
  <figure>
    <img loading="lazy" src="resource_group_overview.png#center" alt="Resource group overview"  title="Resource group overview"  />
    <figcaption style="text-align: center;">Resource group overview</figcaption>
  </figure>
</p>
<h2 id="using-openai-api-services-through-c-sharp">Using OpenAI API services through C sharp</h2>
<p>The OpenAI API is a REST API that allows you to use the models in your applications. You can use the API to create completions and chat. Some real power is when we can use the OpenAI services through our code, where we can create our business scope.</p>
<h3 id="sample-case">Sample case</h3>
<p>Make sure you have <code>Include prerelease</code> enabled when searching for the NuGet package <code>Azure.AI.OpenAI</code>.</p>
<div class="codecontainer" hi="mart"><div class="code-name">Program.cs</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// Note: The Azure OpenAI client library for .NET is in preview.</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Install the .NET library via NuGet: dotnet add package Azure.AI.OpenAI --version 1.0.0-beta.5 </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Azure.AI.OpenAI</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">dieren</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Penguins&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Giraffes&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s">&#34;Smurfen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// more</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">OpenAIClient</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenAIClient</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">Uri</span><span class="p">(</span><span class="s">&#34;https://martgpt-openai.openai.azure.com/&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">AzureKeyCredential</span><span class="p">(</span><span class="s">&#34;HERE COMES YOUR API KEY&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ### If streaming is not selected</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">dier</span> <span class="k">in</span> <span class="n">dieren</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Response</span><span class="p">&lt;</span><span class="n">ChatCompletions</span><span class="p">&gt;</span> <span class="n">responseWithoutStream</span> <span class="p">=</span> <span class="k">await</span> <span class="n">client</span><span class="p">.</span><span class="n">GetChatCompletionsAsync</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;MartGPT&#34;</span><span class="p">,</span> <span class="c1">//&lt;= This is the deployment name</span>
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">ChatCompletionsOptions</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Messages</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">new</span> <span class="n">ChatMessage</span><span class="p">(</span><span class="n">ChatRole</span><span class="p">.</span><span class="n">User</span><span class="p">,</span> <span class="n">dier</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="k">new</span> <span class="n">ChatMessage</span><span class="p">(</span><span class="n">ChatRole</span><span class="p">.</span><span class="n">User</span><span class="p">,</span> <span class="s">@&#34;Geef antwoord in JSON met categorie property van het type string met als waarde een van deze categorien:
</span></span></span><span class="line"><span class="cl"><span class="s">Landdieren
</span></span></span><span class="line"><span class="cl"><span class="s">Zeedieren
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">In welke categorie valt deze diersoort en geef een omschrijving met maximaal 5 woorden en geef ook de naam van het dier terug.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="n">Temperature</span> <span class="p">=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">MaxTokens</span> <span class="p">=</span> <span class="m">800</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">NucleusSamplingFactor</span> <span class="p">=</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">FrequencyPenalty</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">PresencePenalty</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="n">ChatCompletions</span> <span class="n">completions</span> <span class="p">=</span> <span class="n">responseWithoutStream</span><span class="p">.</span><span class="n">Value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">choice</span> <span class="k">in</span> <span class="n">completions</span><span class="p">.</span><span class="n">Choices</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">choice</span><span class="p">.</span><span class="n">Message</span><span class="p">.</span><span class="n">Content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>The output I got from this. I laughed a lot about the response I hope you can do too. What is shown is that even <code>Smurfen</code> is placed in the <code>Landdieren</code> category. Even when you don&rsquo;t edit the parameters or temperature it will give you a different response every time. You have to be very precise if you want to standardize the output.</p>
<div class="codecontainer" hi="mart"><div class="code-name">ProgramOutput.json</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;categorie&#34;</span><span class="p">:</span> <span class="s2">&#34;Zeedieren&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;omschrijving&#34;</span><span class="p">:</span> <span class="s2">&#34;Vogels die niet vliegen&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;naam&#34;</span><span class="p">:</span> <span class="s2">&#34;Pinguïns&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;naam&#34;</span><span class="p">:</span> <span class="s2">&#34;Giraffe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;categorie&#34;</span><span class="p">:</span> <span class="s2">&#34;Landdieren&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;omschrijving&#34;</span><span class="p">:</span> <span class="s2">&#34;Hoge nek, gevlekte vacht&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;categorie&#34;</span><span class="p">:</span> <span class="s2">&#34;Landdieren&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;omschrijving&#34;</span><span class="p">:</span> <span class="s2">&#34;Blauwe wezentjes in het bos&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;naam&#34;</span><span class="p">:</span> <span class="s2">&#34;Smurfen&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>Think of the possibilities by lowering the temperature and making materialized categories of some huge text inputs, for example in an Azure Function reacting to business events.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I just wanted to try out the OpenAI API and see the possibilities it has. Soon corporates will be ready to use AI for their use cases and as a consultant, you should be able to advise them on the possibilities. I learned that the GPT model is statistically choosing output, and the response needs to be verified. That&rsquo;s why Microsoft calls its products `Copilot``. It helps you but isn&rsquo;t perfect.</p>
<!-- Quoteblock here?-->
<p>May the AI be with you.</p>
<h2 id="further-reading">Further reading</h2>
<ul>
<li>
<p><a href="https://learn.microsoft.com/en-us/legal/cognitive-services/openai/overview" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/legal/cognitive-services/openai/overview</a></p>
</li>
<li>
<p><a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/completions#completion" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/completions#completion</a></p>
</li>
<li>
<p><a href="https://blog.iusmentis.com/2023/03/21/van-wie-is-mijn-werk-als-ik-chatgpt-mijn-werk-laat-doen/" target="_blank" rel="noopener">https://blog.iusmentis.com/2023/03/21/van-wie-is-mijn-werk-als-ik-chatgpt-mijn-werk-laat-doen/</a></p>
</li>
<li>
<p><a href="https://blog.iusmentis.com/2023/06/08/mag-een-iso27001-gecertificeerde-organisatie-chatgpt-gebruiken/" target="_blank" rel="noopener">https://blog.iusmentis.com/2023/06/08/mag-een-iso27001-gecertificeerde-organisatie-chatgpt-gebruiken/</a></p>
</li>
<li>
<p><a href="https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/" target="_blank" rel="noopener">https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/</a></p>
</li>
</ul>
<!--Link to Documentation about Playground adding data.-->
<!--Link to Documentation about Cognitive Search.-->
<!--Link to Documentation about System Prompts.-->
<!-- Date and privacy explained Azure vs GPT-->
<!-- Date and privacy explained-->
]]></content:encoded>
      <category>Azure</category>
      <category>AI</category>
      <category>GPT</category>
      <category>OpenAI</category>
      <category>Cognitive Search</category>
    </item>
    <item>
      <title>Learn how to verify the biggest costs of your Log Analytics workspace</title>
      <link>https://blog.martdegraaf.nl/posts/azure-application-insights-in-control-of-costs/</link>
      <pubDate>Mon, 04 Sep 2023 07:00:00 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/azure-application-insights-in-control-of-costs/</guid>
      <description><![CDATA[Many companies have a huge money bill for application logging. In this blog post, I will show you how to get in control of your Application Insights costs.]]></description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Cloud costs can be real money wasters, if you log something you will never need, who is responsible for that? While looking at your costs in Azure, you could see Application Insights as a big cost driver. In this blog post, I will show you how to get in control of your Application Insights costs.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   <p><strong>Prior knowledge</strong></p>
<p>In an earlier post, I showed how to fix duplicate logging and explained how Log Analytics sits on top of Application Insights.</p>
<p>Read the blog post on <a href="/posts/duplicate-logging-azure-application-insights">How to fix duplicate logging in Application Insights</a>.</p>
<p>All KQL queries in this post are based on the Log Analytics workspace.</p>

</div>
<h2 id="identify-the-biggest-cost-tables">Identify the biggest cost tables</h2>
<p>Log Analytics consists of tables, each of those with a specific target to log something. For example, <code>AppTraces</code> for traces, but also tables like <code>SynapseIntegrationTriggerRuns</code> for logging Synapse triggers.</p>
<p>The first step is to identify the biggest cost tables. You can do this by running the following query in your &lsquo;Log Analytics Workspace&rsquo; resource. The payment model is per Gb, so we want to identify the largest tables.</p>
<p>
  <figure>
    <img loading="lazy" src="log-analytics-logs.png#center" alt="Log Analytics Workspace - Logs - Kusto Query Language"  title="Log Analytics Workspace - Logs - Kusto Query Language"  />
    <figcaption style="text-align: center;">Log Analytics Workspace - Logs - Kusto Query Language</figcaption>
  </figure>
</p>
<p>With knowledge about the biggest cost tables, you can start optimizing your logging. In the next sections, I will show you example queries to give insights into logging costs.</p>
<div class="codecontainer" hi="mart"><div class="code-name">QueryByTable.kusto</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">union</span><span class="w"> </span><span class="n">withsource</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">summarize</span><span class="w"> </span><span class="k">Size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sum</span><span class="p">(</span><span class="n">_BilledSize</span><span class="p">)</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="k">table</span><span class="p">,</span><span class="w"> </span><span class="n">_IsBillable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">sort</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="k">Size</span><span class="w"> </span><span class="k">desc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">Size2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">format_bytes</span><span class="p">(</span><span class="k">Size</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="k">Size</span><span class="w"> </span><span class="k">desc</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="azure-diagnostic-logs">Azure Diagnostic Logs</h2>
<p>On many Azure resources, you can configure Log Analytics Workspace as an upstream source. But did you know that this can lead to many logs you have to pay for? A colleague of mine used this query to identify and reduce 90% of their costs. By disabling the Azure Diagnostic Logs for Power BI, they saved a lot of money. By running this query you will gain insights into the <strong>amount</strong> of logs ingested per resource.</p>
<div class="codecontainer" hi="mart"><div class="code-name">QueryTableByResourceId.kusto</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">AzureDiagnostics</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">TimeGenerated</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">ago</span><span class="p">(</span><span class="mi">32</span><span class="n">d</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">summarize</span><span class="w"> </span><span class="k">sum</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">  </span><span class="k">by</span><span class="w"> </span><span class="n">bin</span><span class="p">(</span><span class="n">TimeGenerated</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="n">d</span><span class="p">),</span><span class="w"> </span><span class="n">_ResourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">render</span><span class="w"> </span><span class="n">columnchart</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="application-traces">Application traces</h2>
<p>Traces are good for hunting bugs. But when a system is running, do you need all Debug logs? Do you even think every log is important?</p>
<p>In this query below I will sort unique logging metrics by Resource and Costs. The most expensive logs are on top. The magic number <code>2,52</code> was the price per Gb ingested for Log Analytics. When you insert more than 100Gb which is a lot, you can get discounted pricing. Make sure when you query you think of your scope and environments that also log this trace.</p>
<p>Make sure you configure your log levels correctly. In <code>appsettings.json</code> of <code>host.json</code>.</p>
<div class="codecontainer" hi="mart"><div class="code-name">AppTracesByCosts.kusto</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">AppTraces</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">MessageSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">strlen</span><span class="p">(</span><span class="n">Message</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">MessageSize</span><span class="w"> </span><span class="k">desc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">summarize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">Count</span><span class="o">=</span><span class="k">count</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">BilledTotalSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sum</span><span class="p">(</span><span class="n">_BilledSize</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">MessageTotalSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sum</span><span class="p">(</span><span class="n">MessageSize</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">by</span><span class="w"> </span><span class="n">AppRoleName</span><span class="p">,</span><span class="w"> </span><span class="n">OperationName</span><span class="p">,</span><span class="w"> </span><span class="n">MessageSize</span><span class="p">,</span><span class="w"> </span><span class="n">Message</span><span class="p">,</span><span class="w"> </span><span class="n">SeverityLevel</span><span class="p">,</span><span class="w"> </span><span class="n">_ResourceId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">GbSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BilledTotalSize</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">1024</span><span class="w"> </span><span class="o">/</span><span class="mi">1024</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">1024</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">EuroCost</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GbSize</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="mi">52</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">ResourceName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tostring</span><span class="p">(</span><span class="n">split</span><span class="p">(</span><span class="n">_ResourceId</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/&#34;</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">project</span><span class="w"> </span><span class="n">ResourceName</span><span class="p">,</span><span class="w"> </span><span class="n">EuroCost</span><span class="p">,</span><span class="w"> </span><span class="k">Count</span><span class="p">,</span><span class="w"> </span><span class="n">SeverityLevel</span><span class="p">,</span><span class="w"> </span><span class="n">Message</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">EuroCost</span><span class="w"> </span><span class="k">desc</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="application-dependencies">Application dependencies</h2>
<p>Dependencies are really important. But when writing too much dependency logging it can lead to unwanted costs. This query will give you insights into the dependencies that have a great economic footprint in your Log Analytics Workspace. The <code>EuroCost</code> is determined by the sum of <code>_BilledSize</code> size of all dependencies given in Gb, multiplied by <code>2,52</code>.</p>
<p>The <code>DataTotalSize</code> field indicates the data size, this can contain for example the Database query when that is enabled in your logging. If this value is big and the count of this dependency is high this might be a hotspot to act on.</p>
<div class="codecontainer" hi="mart"><div class="code-name">AppDependenciesByCosts.kusto</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">AppDependencies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">DataSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">strlen</span><span class="p">(</span><span class="k">Data</span><span class="p">)</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">DataSize</span><span class="w"> </span><span class="k">desc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">summarize</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">Count</span><span class="o">=</span><span class="k">count</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">BilledTotalSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sum</span><span class="p">(</span><span class="n">_BilledSize</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">DataTotalSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">sum</span><span class="p">(</span><span class="n">DataSize</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">by</span><span class="w"> </span><span class="n">AppRoleName</span><span class="p">,</span><span class="w"> </span><span class="n">OperationName</span><span class="p">,</span><span class="w"> </span><span class="n">DataSize</span><span class="p">,</span><span class="w"> </span><span class="k">Data</span><span class="p">,</span><span class="w"> </span><span class="n">_ResourceId</span><span class="p">,</span><span class="w"> </span><span class="k">Type</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">GbSize</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BilledTotalSize</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">1024</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">1024</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">1024</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">EuroCost</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">GbSize</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">52</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">extend</span><span class="w"> </span><span class="n">ResourceName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tostring</span><span class="p">(</span><span class="n">split</span><span class="p">(</span><span class="n">_ResourceId</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/&#34;</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="n">project</span><span class="w"> </span><span class="n">ResourceName</span><span class="p">,</span><span class="w"> </span><span class="n">OperationName</span><span class="p">,</span><span class="w"> </span><span class="n">EuroCost</span><span class="p">,</span><span class="w"> </span><span class="k">Count</span><span class="p">,</span><span class="w"> </span><span class="k">Data</span><span class="p">,</span><span class="w"> </span><span class="k">Type</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">|</span><span class="w"> </span><span class="k">order</span><span class="w"> </span><span class="k">by</span><span class="w"> </span><span class="n">EuroCost</span><span class="w"> </span><span class="k">desc</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="health-checks">Health checks</h2>
<p>A special mention is for health checks, do you need the full trace and dependency tree for every health check call? Make sure to exclude those unwanted requests and dependencies. You might only consider keeping health check logging when the health check fails and only the health check result.</p>
<h2 id="dashboard">Dashboard</h2>
<p>By putting the data in a dashboard you will provide your team with an easy way to access these metrics. In my screenshot below there are two of the most important queries, the application traces and the tables.</p>
<p>Make sure to set your dashboard time to a good time scope.</p>
<p>
  <figure>
    <img loading="lazy" src="tracing-dashboard.png#center" alt="Tracing costs dashboard"  title="Tracing costs dashboard"  />
    <figcaption style="text-align: center;">Tracing costs dashboard</figcaption>
  </figure>
</p>
<h2 id="conclusion">Conclusion</h2>
<p>When turning on diagnostics make sure it helps the business. Revisit diagnostic settings and make sure you are in control of your costs. Also make sure that when in development, you are critical about the diagnostic settings. When turned on, it won&rsquo;t be turned off soon, because now you&rsquo;re the expert!</p>
<h2 id="further-reading">Further reading</h2>
<p>For making a custom Processor in Code to make conditional logging make sure to visit my mate&rsquo;s blog <a href="https://www.visoftsolutions.nl/index.php/2023/09/10/reducing-azure-application-insights-logging-costs/" target="_blank" rel="noopener">Thomas Vieveen</a>.</p>
<ul>
<li><a href="https://azure.microsoft.com/nl-nl/pricing/details/monitor/" target="_blank" rel="noopener">https://azure.microsoft.com/nl-nl/pricing/details/monitor/</a></li>
</ul>
]]></content:encoded>
      <category>Azure</category>
      <category>Application Insights</category>
      <category>Monitoring</category>
      <category>KQL</category>
    </item>
    <item>
      <title>Golden Tips To Leave Companies Without A Nasty Smell</title>
      <link>https://blog.martdegraaf.nl/posts/consulting/job-done/</link>
      <pubDate>Thu, 10 Aug 2023 15:00:00 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/consulting/job-done/</guid>
      <description><![CDATA[Leave no technical debt, clean up after yourself when leaving a company. The first impression is just as important as the last impression.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/consulting/job-done/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/consulting/job-done/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/consulting/job-done/cover.webp" alt="" /><br/><br/><p>As a consultant when a job ends you need to transition from the previous to the next. How do you efficiently clean up all open ends? The open branches that you leave behind are sort of dead code. Nobody is going to take care of it. Make sure you don&rsquo;t generate more work for ex-colleagues following these tips.</p>
<p>Using the scripts below we can create a small to-do list, which you should do before leaving a company.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   💬 &ldquo;The first impression is just as important as the last impression. Make sure you leave a good impression.&rdquo; - Mart de Graaf
</div>
<h2 id="1-no-open-work-items">1. No open work items</h2>
<p>Even if you would work with Trello, it would be nice to hand over open items to coworkers. When working in Azure DevOps, you can use the following Powershell script to get all open work items assigned to you.</p>
<p>In the PowerShell script below we can easily get all open work items. This is not only handy when leaving companies but also when you want to get an overview of all open work items. You can use this script to get knowledge of all open work items or to hand over the work to a colleague. You could also do this in Azure DevOps, but when you already have a PAT-token, you can check this as well with this easy script.</p>
<div class="codecontainer" hi="mart"><div class="code-name">OpenWorkItems.ps1</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="c"># Set variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">$organizationUrl</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/MART&#34;</span> <span class="c"># Replace Mart with organization name</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;ProjectName&#34;</span> <span class="c"># Replace ProjectName with project name</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get the access token from current az login session</span>
</span></span><span class="line"><span class="cl"><span class="c"># see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli</span>
</span></span><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="p">-</span><span class="n">-output</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$accessToken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c"># Set headers</span>
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span> <span class="p">=</span> <span class="p">(</span><span class="s2">&#34;Bearer {0}&#34;</span> <span class="o">-f</span> <span class="nv">$accessToken</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span>        <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get a list of workitems for given username</span>
</span></span><span class="line"><span class="cl"><span class="nv">$workItemsUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$organizationUrl</span><span class="s2">/</span><span class="nv">$projectName</span><span class="s2">/_apis/wit/wiql?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$wiql</span> <span class="p">=</span> <span class="sh">@&#34;
</span></span></span><span class="line"><span class="cl"><span class="sh">SELECT [System.Id], [System.Title], [System.State], [System.AssignedTo], [System.Tags], [System.WorkItemType]
</span></span></span><span class="line"><span class="cl"><span class="sh">FROM workitems
</span></span></span><span class="line"><span class="cl"><span class="sh">WHERE [System.TeamProject] = @project
</span></span></span><span class="line"><span class="cl"><span class="sh">AND [System.WorkItemType] = &#39;Task&#39;
</span></span></span><span class="line"><span class="cl"><span class="sh">AND [System.State] &lt;&gt; &#39;Closed&#39;
</span></span></span><span class="line"><span class="cl"><span class="sh">AND [System.State] &lt;&gt; &#39;Removed&#39;
</span></span></span><span class="line"><span class="cl"><span class="sh">AND [System.State] &lt;&gt;  &#39;Done&#39;
</span></span></span><span class="line"><span class="cl"><span class="sh">AND [System.AssignedTo] = @me
</span></span></span><span class="line"><span class="cl"><span class="sh">ORDER BY [System.ChangedDate] desc
</span></span></span><span class="line"><span class="cl"><span class="sh">&#34;@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$body</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span> <span class="n">query</span> <span class="p">=</span> <span class="nv">$wiql</span> <span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$workItemsResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$workItemsUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Body</span> <span class="nv">$body</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># use workitemsbatch api to get all SELECT values</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$workItemsUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$organizationUrl</span><span class="s2">/</span><span class="nv">$projectName</span><span class="s2">/_apis/wit/workitemsbatch?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># get the ids from $workItemsResponse.workItems in a list max 200</span>
</span></span><span class="line"><span class="cl"><span class="nv">$ids</span> <span class="p">=</span> <span class="nv">$workItemsResponse</span><span class="p">.</span><span class="py">workItems</span><span class="p">.</span><span class="py">id</span> <span class="p">|</span> <span class="nb">Select-Object</span> <span class="n">-First</span> <span class="mf">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># body is the list of ids in the workitemsresponse workitems.id, and the fields to select in a fields array</span>
</span></span><span class="line"><span class="cl"><span class="nv">$body</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span> <span class="n">ids</span> <span class="p">=</span> <span class="nv">$ids</span><span class="p">;</span> <span class="n">fields</span> <span class="p">=</span> <span class="s2">&#34;System.Id&#34;</span><span class="p">,</span> <span class="s2">&#34;System.Title&#34;</span><span class="p">,</span> <span class="s2">&#34;System.State&#34;</span><span class="p">,</span> <span class="s2">&#34;System.AssignedTo&#34;</span><span class="p">,</span> <span class="s2">&#34;System.Tags&#34;</span><span class="p">,</span> <span class="s2">&#34;System.WorkItemType&#34;</span> <span class="p">}</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$workItemsResponse2</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$workItemsUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Post</span> <span class="n">-Body</span> <span class="nv">$body</span> <span class="n">-ContentType</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Workitems found &#39;</span><span class="p">$(</span><span class="nv">$workItemsResponse</span><span class="p">.</span><span class="py">workItems</span><span class="p">.</span><span class="n">count</span><span class="p">)</span><span class="s2">&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># write the response to a JSON file</span>
</span></span><span class="line"><span class="cl"><span class="nv">$workItemsResponse2</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="n">-Depth</span> <span class="mf">100</span> <span class="p">|</span> <span class="nb">Out-File</span> <span class="n">-FilePath</span> <span class="s2">&#34;workitems.json&#34;</span> <span class="n">-Force</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="output">Output</h3>
<p>It will be output in a JSON file, for now. I think that will be easy to read if you are leaving. It also limits to 200 work items. If you have more than 200 work items, you probably have a problem.</p>
<h2 id="2-no-open-branches-broccoli-or-pull-requests">2. No open branches &#x1f966; or pull requests</h2>
<p>When working with Git, you can use the following Powershell script to get all open branches. You can use this script to get knowledge of all open branches or to create a pull request for each branch. The pull request can be used to hand over the work to a colleague.</p>
<div class="codecontainer" hi="mart"><div class="code-name">OpenBranches.ps1</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span><span class="lnt">93
</span><span class="lnt">94
</span><span class="lnt">95
</span><span class="lnt">96
</span><span class="lnt">97
</span><span class="lnt">98
</span><span class="lnt">99
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="c"># Set variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">$organizationUrl</span> <span class="p">=</span> <span class="s2">&#34;https://dev.azure.com/MART&#34;</span> <span class="c"># Replace Mart with organization name</span>
</span></span><span class="line"><span class="cl"><span class="nv">$projectName</span> <span class="p">=</span> <span class="s2">&#34;ProjectName&#34;</span> <span class="c"># Replace ProjectName with project name</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$dayTolerance</span> <span class="p">=</span> <span class="mf">14</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get the access token from current az login session</span>
</span></span><span class="line"><span class="cl"><span class="c"># see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli</span>
</span></span><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="p">-</span><span class="n">-output</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$accessToken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c"># Set headers</span>
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span> <span class="p">=</span> <span class="p">(</span><span class="s2">&#34;Bearer {0}&#34;</span> <span class="o">-f</span> <span class="nv">$accessToken</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span>        <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get a list of repositories in the project</span>
</span></span><span class="line"><span class="cl"><span class="nv">$reposUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$organizationUrl</span><span class="s2">/</span><span class="nv">$projectName</span><span class="s2">/_apis/git/repositories?api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$reposResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$reposUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">Write-Host</span> <span class="s2">&#34;Repos found &#39;</span><span class="nv">$reposResponse</span><span class="s2">.count&#39;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">#create dictionary openBranchesPerUser</span>
</span></span><span class="line"><span class="cl"><span class="nv">$openBranchesPerUser</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$repo</span> <span class="k">in</span> <span class="nv">$reposResponse</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$repoName</span> <span class="p">=</span> <span class="nv">$repo</span><span class="p">.</span><span class="py">name</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nv">$repo</span><span class="p">.</span><span class="n">isDisabled</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">Write-Host</span> <span class="s2">&#34;Skipping disabled repo: &#39;</span><span class="nv">$repoName</span><span class="s2">&#39;&#34;</span> <span class="n">-foregroundcolor</span> <span class="n">Gray</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;Checking &#39;</span><span class="nv">$repoName</span><span class="s2">&#39;:&#34;</span> <span class="n">-foregroundcolor</span> <span class="n">yellow</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">    <span class="nv">$branchesUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$organizationUrl</span><span class="s2">/</span><span class="nv">$projectName</span><span class="s2">/_apis/git/repositories/</span><span class="nv">$repoName</span><span class="s2">/refs?filter=heads&amp;api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$branchesResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$branchesUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$branch</span> <span class="k">in</span> <span class="nv">$branchesResponse</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$branchName</span> <span class="p">=</span> <span class="nv">$branch</span><span class="p">.</span><span class="py">name</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$BranchNameTrimmed</span> <span class="p">=</span> <span class="nv">$branchName</span><span class="p">.</span><span class="py">replace</span><span class="p">(</span><span class="s1">&#39;refs/heads/&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$BranchNameTrimmed</span> <span class="o">-eq</span> <span class="s1">&#39;master&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$BranchNameTrimmed</span> <span class="o">-eq</span> <span class="s1">&#39;main&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$encodedBranchName</span> <span class="p">=</span> <span class="p">[</span><span class="no">System.Uri</span><span class="p">]::</span><span class="n">EscapeDataString</span><span class="p">(</span><span class="nv">$branchName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$pushesUrl</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="nv">$organizationUrl</span><span class="s2">/</span><span class="nv">$projectName</span><span class="s2">/_apis/git/repositories/</span><span class="nv">$repoName</span><span class="s2">/pushes?searchCriteria.includeRefUpdates&amp;searchCriteria.refName=</span><span class="nv">$encodedBranchName</span><span class="s2">&amp;api-version=6.0&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c">## Write-Host &#34;$branchName - $pushesUrl&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$pushesResponse</span> <span class="p">=</span> <span class="nb">Invoke-RestMethod</span> <span class="n">-Uri</span> <span class="nv">$pushesUrl</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Method</span> <span class="n">Get</span>
</span></span><span class="line"><span class="cl">        <span class="c"># get first push in the list</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$push</span> <span class="p">=</span> <span class="nv">$pushesResponse</span><span class="p">.</span><span class="n">value</span><span class="p">[</span><span class="mf">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$firstPush</span> <span class="p">=</span> <span class="nv">$pushesResponse</span><span class="p">.</span><span class="n">value</span><span class="p">[</span><span class="mf">-1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c">#Convert $lastPush.date to DateTime object</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$lastPushDate</span> <span class="p">=</span> <span class="p">[</span><span class="no">DateTime</span><span class="p">]::</span><span class="n">Parse</span><span class="p">(</span><span class="nv">$firstPush</span><span class="p">.</span><span class="n">date</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c"># if the last push date is older than today minus the dayTolerance, skip the branch</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$lastPushDate</span> <span class="o">-gt</span> <span class="p">(</span><span class="nb">Get-Date</span><span class="p">).</span><span class="py">AddDays</span><span class="p">(-</span><span class="nv">$dayTolerance</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">Write-Host</span> <span class="s2">&#34;Skipping &#39;</span><span class="nv">$repoName</span><span class="s2">&#39; - &#39;</span><span class="nv">$branchName</span><span class="s2">&#39; - last push date &#39;</span><span class="p">$(</span><span class="nv">$lastPush</span><span class="p">.</span><span class="n">date</span><span class="p">)</span><span class="s2">&#39;  compare date &#39;</span><span class="p">$((</span><span class="nb">Get-Date</span><span class="p">).</span><span class="py">AddDays</span><span class="p">(-</span><span class="nv">$dayTolerance</span><span class="p">))</span><span class="s2">&#39; &#39;</span><span class="p">$(</span><span class="nv">$push</span><span class="p">.</span><span class="py">pushedBy</span><span class="p">.</span><span class="n">uniqueName</span><span class="p">)</span><span class="s2">&#39;&#34;</span>  <span class="n">-foregroundcolor</span> <span class="n">Red</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">Write-Host</span> <span class="s2">&#34;Checking &#39;</span><span class="nv">$repoName</span><span class="s2">&#39; - &#39;</span><span class="nv">$branchName</span><span class="s2">&#39; - last push date &#39;</span><span class="p">$(</span><span class="nv">$lastPush</span><span class="p">.</span><span class="n">date</span><span class="p">)</span><span class="s2">&#39; compare date &#39;</span><span class="p">$((</span><span class="nb">Get-Date</span><span class="p">).</span><span class="py">AddDays</span><span class="p">(-</span><span class="nv">$dayTolerance</span><span class="p">))</span><span class="s2">&#39; &#39;</span><span class="p">$(</span><span class="nv">$push</span><span class="p">.</span><span class="py">pushedBy</span><span class="p">.</span><span class="n">uniqueName</span><span class="p">)</span><span class="s2">&#39; &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nv">$pushedBy</span> <span class="p">=</span> <span class="nv">$firstPush</span><span class="p">.</span><span class="py">pushedBy</span><span class="p">.</span><span class="py">uniqueName</span>
</span></span><span class="line"><span class="cl">        <span class="c"># Add to openBranchesPerUser dictionary with the user name as key and a object as value including branch name, repositoy and respository url</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$openBranchesPerUser</span><span class="p">.</span><span class="py">ContainsKey</span><span class="p">(</span><span class="nv">$pushedBy</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$openBranchesPerUser</span><span class="p">[</span><span class="nv">$pushedBy</span><span class="p">]</span> <span class="p">+=</span> <span class="p">[</span><span class="no">PSCustomObject</span><span class="p">]</span><span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">Repository</span>    <span class="p">=</span> <span class="nv">$repoName</span>
</span></span><span class="line"><span class="cl">                <span class="n">RepositoryUrl</span> <span class="p">=</span> <span class="nv">$repo</span><span class="p">.</span><span class="py">webUrl</span> <span class="p">+</span> <span class="s2">&#34;/branches?_a=all&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="n">Branch</span>        <span class="p">=</span> <span class="nv">$branchName</span>
</span></span><span class="line"><span class="cl">                <span class="n">firstPushDate</span> <span class="p">=</span> <span class="nv">$firstPush</span><span class="p">.</span><span class="py">date</span>
</span></span><span class="line"><span class="cl">                <span class="n">lastPushDate</span>  <span class="p">=</span> <span class="nv">$push</span><span class="p">.</span><span class="py">date</span>
</span></span><span class="line"><span class="cl">                <span class="n">lastPusher</span>    <span class="p">=</span> <span class="nv">$push</span><span class="p">.</span><span class="py">pushedBy</span><span class="p">.</span><span class="py">uniqueName</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$openBranchesPerUser</span><span class="p">.</span><span class="py">Add</span><span class="p">(</span><span class="nv">$pushedBy</span><span class="p">,</span> <span class="vm">@</span><span class="p">([</span><span class="no">PSCustomObject</span><span class="p">]</span><span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">Repository</span>    <span class="p">=</span> <span class="nv">$repoName</span>
</span></span><span class="line"><span class="cl">                        <span class="n">RepositoryUrl</span> <span class="p">=</span> <span class="nv">$repo</span><span class="p">.</span><span class="py">webUrl</span> <span class="p">+</span> <span class="s2">&#34;/branches?_a=all&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="n">Branch</span>        <span class="p">=</span> <span class="nv">$branchName</span>
</span></span><span class="line"><span class="cl">                        <span class="n">firstPushDate</span> <span class="p">=</span> <span class="nv">$firstPush</span><span class="p">.</span><span class="py">date</span>
</span></span><span class="line"><span class="cl">                        <span class="n">lastPushDate</span>  <span class="p">=</span> <span class="nv">$push</span><span class="p">.</span><span class="py">date</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}))</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Write openBranchesPer user as a table, exclude RepositoryUrl</span>
</span></span><span class="line"><span class="cl"><span class="nv">$openBranchesPerUser</span><span class="p">.</span><span class="py">GetEnumerator</span><span class="p">()</span> <span class="p">|</span> <span class="nb">ForEach-Object</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="s2">&#34;User: </span><span class="p">$(</span><span class="nv">$_</span><span class="p">.</span><span class="n">Key</span><span class="p">)</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$_</span><span class="p">.</span><span class="py">Value</span> <span class="p">|</span> <span class="nb">Format-Table</span> <span class="n">-Property</span> <span class="n">Repository</span><span class="p">,</span> <span class="n">Branch</span><span class="p">,</span> <span class="n">firstPushDate</span><span class="p">,</span> <span class="n">lastPushDate</span><span class="p">,</span> <span class="n">lastPusher</span> <span class="n">-AutoSize</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Write openBranchesPerUser to a JSON file</span>
</span></span><span class="line"><span class="cl"><span class="nv">$openBranchesPerUser</span> <span class="p">|</span> <span class="nb">ConvertTo-Json</span> <span class="p">|</span> <span class="nb">Out-File</span> <span class="n">-FilePath</span> <span class="s2">&#34;openBranchesPerUser.json&#34;</span> <span class="n">-Encoding</span> <span class="n">ascii</span>
</span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="output-1">Output</h3>
<p>The output will be visible in the console and a JSON file.</p>
<h2 id="3-get-feedback">3. Get feedback</h2>
<p>Ask for feedback from your colleagues and manager. This feedback can be used to improve yourself in the future. It can also be used to improve the company you worked for. If you don&rsquo;t ask for feedback, you will never know what you could have done better.</p>
<p>When asking for feedback keep in mind it&rsquo;s to improve yourself, not to get a compliment. You can ask for feedback in the following way:</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   💬 &ldquo;What could I have done better while working together?&rdquo;
</div>
<h2 id="4-say-goodbye">4. Say goodbye</h2>
<p>Take the time to say goodbye to your colleagues and express your gratitude for the time you spent working together. You never know when you might cross paths with them again in the future.</p>
<p>Make sure you connect on social media with people you want to connect on the long term.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li>:check_box_with_check: Hand over open work items, or unassign them</li>
<li>:check_box_with_check: Delete open branches</li>
<li>:check_box_with_check: Say Goodbye to your team and colleagues</li>
<li>:check_box_with_check: Check for your ip whitelists in Azure DevOps</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>When leaving a company, you want to leave no technical debt behind and clean up after yourself. You can use the scripts in this article to help you with that.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li>A more complex system to generate emails created by Sven: <a href="https://github.com/sven73a/Powershell-Utils-Azure" target="_blank" rel="noopener">https://github.com/sven73a/Powershell-Utils-Azure</a></li>
</ul>
<!-- 
### Wishlist
- Branch has PR open?
- Work Item has PR open?
- can we make DORA metrics using Powershell AZ DO API?
-->]]></content:encoded>
      <category>Git</category>
      <category>DevOps</category>
      <category>Azure DevOps</category>
      <category>Powershell</category>
    </item>
    <item>
      <title>Efficiently use Outlook Rules to Delete Azure DevOps Email Clutter</title>
      <link>https://blog.martdegraaf.nl/posts/consulting/filter-devops-mentions-in-outlook/</link>
      <pubDate>Thu, 01 Jun 2023 18:14:56 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/consulting/filter-devops-mentions-in-outlook/</guid>
      <description><![CDATA[Are you also annoyed by the number of emails generated by Azure DevOps? Learn how I fixed email overdose using an Outlook rule.]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/consulting/filter-devops-mentions-in-outlook/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/consulting/filter-devops-mentions-in-outlook/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/consulting/filter-devops-mentions-in-outlook/cover.webp" alt="" /><br/><br/><p>We want to spend time on the most important things right? Are you also annoyed by the number of emails generated by Azure DevOps? See how I fixed the email overdose using an Outlook rule.</p>
<h2 id="e-mail-headers">E-mail headers</h2>
<p>To remove all emails would be nice, but we won&rsquo;t see any mentions. To know how to recognize a mention email I opened a mention email.
I found the headers by clicking the three dots in the Outlook web version. And then Open the &lsquo;View&rsquo; dropdown and select &lsquo;View message details&rsquo;.</p>
<p>
  <figure>
    <img loading="lazy" src="outlook-web-message-details.png#center" alt="Outlook web View message details"  title="Outlook web - View &gt; View message details"  />
    <figcaption style="text-align: center;">Outlook web - View &gt; View message details</figcaption>
  </figure>
</p>
<p>We see that some headers indicate that the message trigger contained &lsquo;Mention&rsquo;. This is essential information for our next steps. Here below the headers I found in the email message.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">X-VSS-Scope: organisation/project/Repository
</span></span><span class="line"><span class="cl">X-VSS-Event-Type: ms.vss-mentions.identity-mention-event
</span></span><span class="line"><span class="cl">X-VSS-Subscription-ID: ms.vss-mentions.identity-mention-subscription
</span></span><span class="line"><span class="cl">X-VSS-Event-Initiator: Mart de Graaf
</span></span><span class="line"><span class="cl">X-VSS-Event-Initiator-Ascii: Mart de Graaf
</span></span><span class="line"><span class="cl">X-VSS-Event-Trigger: Mention</span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="outlook-rules">Outlook Rules</h2>
<p>I am not praising the Outlook client, I want to talk about the Rules feature of Outlook. The rules are found in the classic Outlook by pressing &lsquo;File&rsquo; and then clicking the big &lsquo;Manage Rules &amp; Alerts&rsquo; button.
Let&rsquo;s create a rule to delete and mark all those emails as read.</p>
<p>The first step is to define conditions. We know Azure DevOps emails from <code>azuredevops@microsoft.com</code>. For my case, I wanted to scope emails to a certain organization, which can be done by filtering specific words in the message header.

  <figure>
    <img loading="lazy" src="rules-step-1.png#center" alt="Rule conditions"  title="Rule conditions"  />
    <figcaption style="text-align: center;">Rule conditions</figcaption>
  </figure>
</p>
<hr>
<p>The emails should be deleted, but also be marked as read. Nothing is more annoying than a number after your delete folder, right?

  <figure>
    <img loading="lazy" src="rules-step-actions.png#center" alt="Rule actions"  title="Rule actions"  />
    <figcaption style="text-align: center;">Rule actions</figcaption>
  </figure>
</p>
<hr>
<p>We still would like to receive the Mention emails. So we need to make an exception for them. As said earlier this can be done by filtering the message header for specific words.

  <figure>
    <img loading="lazy" src="rules-step-exceptions.png#center" alt="Rule exceptions"  title="Rule exceptions"  />
    <figcaption style="text-align: center;">Rule exceptions</figcaption>
  </figure>
</p>
<hr>
<h2 id="full-outlook-rule">Full Outlook Rule</h2>
<p>The only thing you have to edit in my example is the organisationName.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">from azuredevops@microsoft.com
</span></span><span class="line"><span class="cl"> and with VSS-Scope: organisationName
</span></span><span class="line"><span class="cl">delete it
</span></span><span class="line"><span class="cl"> and mark it as read
</span></span><span class="line"><span class="cl">except if the message header contains &#39;X-VSS-Event-Trigger: Mention&#39; or &#39;X-VSS-Subscription-ID: ms.vss-mentions.identity-mention-subscription&#39;</span></span></code></pre></td></tr></table>
</div>
</div></div><hr>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   :megaphone: Have any suggestions, I would love to hear them!
</div>
<h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>Help yourself keep control of your inbox. I think you should work from the board and business wishes, and not from your inbox.</p>
]]></content:encoded>
      <category>Outlook</category>
      <category>Azure DevOps</category>
    </item>
    <item>
      <title>Efficiently Git Clone All Repositories from Azure DevOps using PowerShell: A Step-by-Step Guide</title>
      <link>https://blog.martdegraaf.nl/posts/consulting/git-clone-all-repos-azure-devops/</link>
      <pubDate>Mon, 15 May 2023 18:14:56 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/consulting/git-clone-all-repos-azure-devops/</guid>
      <description><![CDATA[Learn how to efficiently clone all Git repos in Azure DevOps with our comprehensive consulting guide. Streamline your development workflow today!]]></description>
      <enclosure url="https://blog.martdegraaf.nl/posts/consulting/git-clone-all-repos-azure-devops/cover.webp" type="image/webp" />
      <media:content url="https://blog.martdegraaf.nl/posts/consulting/git-clone-all-repos-azure-devops/cover.webp" medium="image" type="image/webp">
        <media:title></media:title>
      </media:content><content:encoded><![CDATA[<img src="https://blog.martdegraaf.nl/posts/consulting/git-clone-all-repos-azure-devops/cover.webp" alt="" /><br/><br/><p>As a consultant, starting a new project with a client can be a daunting task. One way to make the transition smoother is by cloning all the repositories on your first day. This allows you to have quick access to all the necessary files and resources, enabling you to perform your job efficiently and effectively. In this blog post, we will explore the benefits of cloning repositories, a script for doing so, and some common pitfalls to avoid.</p>
<p><a href="#configuration">Skip to the code sample</a></p>
<h2 id="organizing-your-git-repos">Organizing your Git repos</h2>
<p>When working for multiple clients or even just having private projects next to your client projects it can come in handy to organize your git repositories. For some Frontend repositories, the path with node_modules was too long and that forced me to place my folders on the Disk level. A path for a project for me would look like <code>C:\Git\{ClientName}\{RepositoryName}</code>.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">C:\Git
</span></span><span class="line"><span class="cl"> ┣ Client1
</span></span><span class="line"><span class="cl"> ┃ ┣ Client1.Repository1
</span></span><span class="line"><span class="cl"> ┃ ┣ Client1.Repository2
</span></span><span class="line"><span class="cl"> ┃ ┗ Client1.Repository3
</span></span><span class="line"><span class="cl"> ┣ Client2
</span></span><span class="line"><span class="cl"> ┃ ┣ Client2.Repository1
</span></span><span class="line"><span class="cl"> ┃ ┗ Client2.Repository2
</span></span><span class="line"><span class="cl"> ┗ private
</span></span><span class="line"><span class="cl"> ┃ ┣ Blog
</span></span><span class="line"><span class="cl"> ┃ ┗ Demo</span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="using-workspaces-in-git-fork">Using workspaces in Git Fork</h3>
<p>Fork is a tool that will help you focus on the right workload. Using the structure as discussed with Fork, you can focus on the right repositories. Cloned new repositories but not seen by Fork? Reload the whole folder using right-click and &lsquo;Rescan repositories&rsquo;. Get Git Fork from <a href="https://git-fork.com/" target="_blank" rel="noopener">git-fork.com</a>.</p>
<p>
  <figure>
    <img loading="lazy" src="fork_repository_manager.png#center" alt="Fork Repository Manager"  title="Fork Repository Manager"  />
    <figcaption style="text-align: center;">Fork Repository Manager</figcaption>
  </figure>
</p>
<p>Use Fork Workspaces to focus on the current environment. It will also help you work on private projects outside of work hours on the same workstation. You can also create workspaces for different domains or teams if you are for example the lead or architect in a project.</p>
<p>
  <figure>
    <img loading="lazy" src="fork_workspaces.png#center" alt="Fork Workspaces"  title="Fork workspaces"  />
    <figcaption style="text-align: center;">Fork workspaces</figcaption>
  </figure>
</p>
<h3 id="configure-your-git-username">Configure your git username</h3>
<p>Depending on the network infra, you will need to configure your commit username to the email of your client.
Some instances block all git pushes from committers with a different domain.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">git config [--global] user.email &#34;username@corperate.com&#34;
</span></span></span></code></pre></td></tr></table>
</div>
</div></div><p>In the script to clone all repositories, you can also enable the script to set the committer email for every repository.</p>
<h2 id="clone-all-repositories">Clone all repositories</h2>
<p>To clone all repositories in Azure DevOps we can use the REST API to find all existing repositories. The code example consists of a Powershell script and a configuration file with settings and Authorization.</p>
<h3 id="configuration">Configuration</h3>
<p>Make sure to create a file named: <code>CloneAllRepos.config</code> with the contents written below. Make sure every parameter is configured as your workspace.</p>
<div class="codecontainer" hi="mart"><div class="code-name">CloneAllRepos.config</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cfg" data-lang="cfg"><span class="line"><span class="cl"><span class="k">[General]</span>
</span></span><span class="line"><span class="cl"><span class="na">Url</span><span class="o">=</span><span class="s">https://dev.azure.com/MART/project</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[LocalGitConfig]</span>
</span></span><span class="line"><span class="cl"><span class="na">GitPath</span><span class="o">=</span><span class="s">C:\Git\</span>
</span></span><span class="line"><span class="cl"><span class="na">OrgName</span><span class="o">=</span><span class="s">MART</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[GitOptions]</span>
</span></span><span class="line"><span class="cl"><span class="na">PruneLocalBranches</span><span class="o">=</span><span class="s">false # Optional defaults to false</span>
</span></span><span class="line"><span class="cl"><span class="na">GitEmail</span><span class="o">=</span><span class="s">username@corperate.com</span></span></span></code></pre></td></tr></table>
</div>
</div></div><div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   <p>&#x1f4a1; <del>Don&rsquo;t know where to find a Personal Access Token in Azure DevOps? Read: <a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate" target="_blank" rel="noopener">Microsoft&rsquo;s docs on personal access tokens</a>.</del></p>
<p>On 2024 april 17, I updated the script to get an access token using the current session of the az cli. see <a href="https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli" target="_blank" rel="noopener">Azure DevOps API Authentication</a>.</p>

</div>
<h3 id="cloneallreposps1">CloneAllRepos.ps1</h3>
<p>When I first encountered the idea to clone all repos idea it was on a corporate wiki. After some backtracing, I found the source: <a href="https://blog.rsuter.com/script-to-clone-all-git-repositories-from-your-vsts-collection/" target="_blank" rel="noopener">Script to clone all Git repositories from your Azure DevOps collection</a>.</p>
<p>The PowerShell script below does a <code>git pull</code> for existing repositories and performs a <code>git clone</code> on untracked repositories.</p>
<p>I edited the script to fit my needs with some extra parameters.</p>
<ol>
<li>It puts the repos in the given directory in settings.</li>
<li>It prunes local branches when <code>PruneLocalBranches</code> is set to true.</li>
<li>It sets the git username email to the configured <code>GitUsername</code> under GitOptions, it&rsquo;s ignored when empty.</li>
</ol>
<div class="codecontainer" hi="mart"><div class="code-name">CloneAllRepos.ps1</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="c"># Read configuration file</span>
</span></span><span class="line"><span class="cl"><span class="nb">Get-Content</span> <span class="s2">&#34;CloneAllRepos.config&#34;</span> <span class="p">|</span> <span class="nb">foreach-object</span> <span class="n">-begin</span> <span class="p">{</span><span class="nv">$h</span><span class="p">=</span><span class="vm">@</span><span class="p">{}}</span> <span class="n">-process</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">    <span class="nv">$k</span> <span class="p">=</span> <span class="p">[</span><span class="no">regex</span><span class="p">]::</span><span class="n">split</span><span class="p">(</span><span class="nv">$_</span><span class="p">,</span><span class="s1">&#39;=&#39;</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">((</span><span class="nv">$k</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="py">CompareTo</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">-ne</span> <span class="mf">0</span><span class="p">)</span> <span class="o">-and</span> <span class="p">(</span><span class="nv">$k</span><span class="p">[</span><span class="mf">0</span><span class="p">].</span><span class="py">StartsWith</span><span class="p">(</span><span class="s2">&#34;[&#34;</span><span class="p">)</span> <span class="o">-ne</span> <span class="vm">$True</span><span class="p">))</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">        <span class="nv">$h</span><span class="p">.</span><span class="py">Add</span><span class="p">(</span><span class="nv">$k</span><span class="p">[</span><span class="mf">0</span><span class="p">],</span> <span class="nv">$k</span><span class="p">[</span><span class="mf">1</span><span class="p">])</span> 
</span></span><span class="line"><span class="cl">    <span class="p">}</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c">#AzDO config</span>
</span></span><span class="line"><span class="cl"><span class="nv">$url</span> <span class="p">=</span> <span class="nv">$h</span><span class="p">.</span><span class="py">Get_Item</span><span class="p">(</span><span class="s2">&#34;Url&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c"># LocalGitConfig</span>
</span></span><span class="line"><span class="cl"><span class="nv">$gitPath</span> <span class="p">=</span> <span class="nv">$h</span><span class="p">.</span><span class="py">Get_Item</span><span class="p">(</span><span class="s2">&#34;GitPath&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$orgName</span> <span class="p">=</span> <span class="nv">$h</span><span class="p">.</span><span class="py">Get_Item</span><span class="p">(</span><span class="s2">&#34;OrgName&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$pruneLocalBranches</span> <span class="p">=</span> <span class="nv">$h</span><span class="p">.</span><span class="py">Get_Item</span><span class="p">(</span><span class="s2">&#34;PruneLocalBranches&#34;</span><span class="p">)</span> <span class="o">-eq</span> <span class="s2">&#34;true&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">$gitEmail</span> <span class="p">=</span> <span class="nv">$h</span><span class="p">.</span><span class="py">Get_Item</span><span class="p">(</span><span class="s2">&#34;GitEmail&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Get the access token from current az login session</span>
</span></span><span class="line"><span class="cl"><span class="c"># see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&amp;view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli</span>
</span></span><span class="line"><span class="cl"><span class="nv">$accessToken</span> <span class="p">=</span> <span class="n">az</span> <span class="n">account</span> <span class="nb">get-access</span><span class="n">-token</span> <span class="p">-</span><span class="n">-resource</span> <span class="n">499b84ac</span><span class="p">-</span><span class="mf">1321</span><span class="p">-</span><span class="mf">427f</span><span class="n">-aa17</span><span class="p">-</span><span class="n">267ca6975798</span> <span class="p">-</span><span class="n">-query</span> <span class="s2">&#34;accessToken&#34;</span> <span class="p">-</span><span class="n">-output</span> <span class="n">tsv</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="vm">$null</span> <span class="o">-eq</span> <span class="nv">$accessToken</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span> <span class="mf">1</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">$headers</span> <span class="p">=</span> <span class="vm">@</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Authorization&#34;</span> <span class="p">=</span> <span class="p">(</span><span class="s2">&#34;Bearer {0}&#34;</span> <span class="o">-f</span> <span class="nv">$accessToken</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Accept&#34;</span> <span class="p">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Retrieve list of all repositories</span>
</span></span><span class="line"><span class="cl"><span class="nv">$resp</span> <span class="p">=</span> <span class="nb">Invoke-WebRequest</span> <span class="n">-Headers</span> <span class="nv">$headers</span> <span class="n">-Uri</span> <span class="p">(</span><span class="s2">&#34;{0}/_apis/git/repositories?api-version=1.0&#34;</span> <span class="o">-f</span> <span class="nv">$url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$json</span> <span class="p">=</span> <span class="nb">convertFrom-JSON</span> <span class="nv">$resp</span><span class="p">.</span><span class="py">Content</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Clone or pull all repositories</span>
</span></span><span class="line"><span class="cl"><span class="nv">$initpath</span> <span class="p">=</span>  <span class="p">(</span><span class="s2">&#34;{0}{1}&#34;</span> <span class="o">-f</span>  <span class="nv">$gitPath</span><span class="p">,</span><span class="nv">$orgName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="nv">$entry</span> <span class="k">in</span> <span class="nv">$json</span><span class="p">.</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> 
</span></span><span class="line"><span class="cl">    <span class="nb">set-location</span> <span class="nv">$initpath</span>
</span></span><span class="line"><span class="cl">    <span class="nv">$name</span> <span class="p">=</span> <span class="nv">$entry</span><span class="p">.</span><span class="py">name</span> 
</span></span><span class="line"><span class="cl">    <span class="nb">Write-Host</span> <span class="nv">$name</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">(</span><span class="nv">$entry</span><span class="p">.</span><span class="n">isDisabled</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">        <span class="nb">Write-Host</span> <span class="s2">&#34;Skipping disabled repo: &#39;</span><span class="nv">$name</span><span class="s2">&#39;&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">$url</span> <span class="p">=</span> <span class="nv">$entry</span><span class="p">.</span><span class="py">remoteUrl</span> <span class="c">#-replace &#34;://&#34;, (&#34;://{0}@&#34; -f $gitcred)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">(!(</span><span class="nb">Test-Path</span> <span class="n">-Path</span> <span class="nv">$name</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">git</span> <span class="n">clone</span> <span class="nv">$url</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">Write-Host</span> <span class="s2">&#34;Directory &#39;</span><span class="nv">$name</span><span class="s2">&#39; exists lets pull&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">set-location</span> <span class="nv">$name</span>
</span></span><span class="line"><span class="cl">        <span class="n">git</span> <span class="n">pull</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$defaultBranch</span> <span class="p">=</span> <span class="n">git</span> <span class="nb">symbolic-ref</span> <span class="p">-</span><span class="n">-short</span> <span class="n">HEAD</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="p">(</span><span class="nv">$pruneLocalBranches</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">            <span class="nb">Write-Host</span> <span class="s2">&#34;Pruning local branches </span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Yellow</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$branches</span> <span class="p">=</span> <span class="n">git</span> <span class="n">branch</span> <span class="n">-vv</span> <span class="p">|</span> <span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span> <span class="o">-notmatch</span> <span class="s2">&#34;::&#34;</span> <span class="p">}</span> <span class="p">|</span> <span class="nb">ForEach-Object</span> <span class="p">{</span> <span class="p">(</span><span class="nv">$_</span> <span class="n">-split</span> <span class="s1">&#39;\s+&#39;</span><span class="p">)[</span><span class="mf">1</span><span class="p">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">foreach</span> <span class="p">(</span><span class="nv">$branch</span> <span class="k">in</span> <span class="nv">$branches</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="nv">$branch</span> <span class="o">-eq</span> <span class="nv">$defaultBranch</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">Write-Host</span> <span class="s2">&#34;Skipping default branch &#39;</span><span class="nv">$branch</span><span class="s2">&#39;.&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="k">continue</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">((</span><span class="n">git</span> <span class="n">branch</span> <span class="n">-vv</span> <span class="p">|</span> <span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span> <span class="o">-match</span> <span class="s2">&#34;</span><span class="nv">$branch</span><span class="s2">\s+\[origin\/&#34;</span> <span class="p">}))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">Write-Host</span> <span class="s2">&#34;Skipping branch &#39;</span><span class="nv">$branch</span><span class="s2">&#39; as it has a remote reference.&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">git</span> <span class="n">branch</span> <span class="n">-D</span> <span class="nv">$branch</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">Write-Host</span> <span class="s2">&#34;Deleted local branch &#39;</span><span class="nv">$branch</span><span class="s2">&#39;.&#34;</span> <span class="n">-ForegroundColor</span> <span class="n">Green</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="p">(</span><span class="nv">$gitEmail</span><span class="p">){</span>
</span></span><span class="line"><span class="cl">            <span class="n">git</span> <span class="n">config</span> <span class="n">user</span><span class="p">.</span><span class="py">email</span> <span class="s2">&#34;</span><span class="nv">$gitEmail</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f916; If you have some additional ideas, let ChatGPT help you. Supply ChatGPt with the context: <code>Rewrite this PowerShell script to also &lt;insert new Feature&gt;. Here is the current version of the PowerShell script: &lt;insert PowerShell script&gt;.</code>. Let me know if you thought of a clever solution.
</div>
<h3 id="run-it">Run it</h3>
<p>Run the script it using a PowerShell prompt for example using for example Windows Terminal.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="p">./</span><span class="n">CloneAllRepos</span><span class="p">.</span><span class="n">ps1</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="using-scripting-for-common-tasks">Using scripting for common tasks</h2>
<p>In the world of microservices, we choose to duplicate some of the plumbing. When you want to change multiple repos knowledge on scripting can be helpful. In this series, I explored how to automate git tasks with PowerShell.</p>
<p>Some examples are:</p>
<ul>
<li>Updating multiple NuGet packages.</li>
<li>Enforcing certain <code>Nuget.</code> <code>config</code> configurations.</li>
<li>Renaming business terminology on multiple branches.</li>
</ul>
<h3 id="automating">Automating</h3>
<p>With this structure, you could automate actions over multiple repositories. In the code below I wrote an example of automating script for changing the Nuget.config file in every repository. If your packages have the same layout changes can be done easier and faster. Also, please check out <a href="https://blog.martdegraaf.nl/posts/replacing-your-projects-and-namespaces-using-bire/">my article using binary replace</a>.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-PowerShell" data-lang="PowerShell"><span class="line"><span class="cl"><span class="n">git</span> <span class="n">checkout</span> <span class="n">main</span>
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">pull</span>
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">checkout</span> <span class="n">-b</span> <span class="n">fix</span><span class="p">/</span><span class="n">nugetconfig</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># DO THE NECESSARY CHANGE in nuget.config.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="nb">mv </span><span class="o">-f</span> <span class="n">NuGet</span><span class="p">.</span><span class="py">config</span> <span class="n">nuget</span><span class="p">.</span><span class="py">config</span>
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">add</span> <span class="p">*</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">commit</span> <span class="n">-m</span> <span class="s2">&#34;Only use private Nuget upstream&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">push</span> <span class="p">-</span><span class="n">-set-upstream</span> <span class="n">origin</span> <span class="n">fix</span><span class="p">/</span><span class="n">nugetconfig</span>
</span></span><span class="line"><span class="cl"><span class="n">git</span> <span class="n">checkout</span> <span class="n">main</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion-and-discussion">Conclusion and discussion</h2>
<p>Make your workflow faster with scripting and your knowledge of the Git CLI. When you have to do repetitive tasks such as updating a single package on multiple (microservice-like) repositories, try to automate it. It may for the first occurrence not be profitable, but after three times, you will be faster than doing it manually. It can also help you clean up your workspace and be more tidy.</p>
]]></content:encoded>
      <category>Git</category>
      <category>Powershell</category>
      <category>Azure DevOps</category>
      <category>Consulting</category>
      <category>Automation</category>
    </item>
    <item>
      <title>Replacing Your Projects and Namespaces Using Bire</title>
      <link>https://blog.martdegraaf.nl/posts/replacing-your-projects-and-namespaces-using-bire/</link>
      <pubDate>Thu, 26 Jan 2023 22:45:56 +0100</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/replacing-your-projects-and-namespaces-using-bire/</guid>
      <description><![CDATA[We all sometimes want to rename projects and or namespaces but VS makes it hard, why can&rsquo;t we just take one command to replace all? We can with bire.]]></description><content:encoded><![CDATA[<p>So a few years ago I worked with Jaap Lamfers. Jaap showed me a tool to easily replace .csproject names and namespaces. This tool is named Bire.</p>
<h2 id="why-you-should-care">Why you should care</h2>
<p>Renaming files is hard. When renaming Projects in Visual Studio the Folder does not change and keeps its original name. The project path is declared in the Solution file (.sln). Not a problem when working in Visual Studio, but a minor annoyance when reviewing a pull request.</p>
<p>Also when renaming projects via Visual Studio, not all namespaces will change as well. And when renaming manually you need to change the references as well in the contents of the .csproject file.</p>
<h2 id="bire">Bire</h2>
<p>Jaap encountered a problem where with microservices he needed to build and the namespaces could be changed afterward. The company used abbreviations for services, with bire he just could build and change the name afterward with a single command.</p>
<p>The tool of Jaap is available at <a href="https://github.com/jlamfers/bire" target="_blank" rel="noopener">Github jlamfers/bire</a>.</p>
<p>I thought his tool was brilliant, but it missed the crucial documentation to make it understandable for the whole company.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f37a; Bire is pronounced as the Dutch word &lsquo;Bier&rsquo;. Bier is Dutch for beer. BiRe is an abbreviation for &lsquo;binary replace&rsquo;.
</div>
<h3 id="using-bire">Using Bire</h3>
<p><code>bire -from C:\git\source -to C:\git\target -replace this=that Something=Anything</code></p>
<p>This is an example I wrote in the Readme file. But I want to give you a more concrete example let&rsquo;s imagine this folder structure. The project is in <code>C:\git\MyCoolProject\source</code>.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">source
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.DataAccess
</span></span><span class="line"><span class="cl"> ┃ ┣ ....
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.DataModels
</span></span><span class="line"><span class="cl"> ┃ ┣ ....
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.Business
</span></span><span class="line"><span class="cl"> ┃ ┣ ....
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.WebApi
</span></span><span class="line"><span class="cl"> ┃ ┣ ....
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.Functions
</span></span><span class="line"><span class="cl"> ┃ ┣ ....
</span></span><span class="line"><span class="cl"> ┣ MyCoolProject.sln</span></span></code></pre></td></tr></table>
</div>
</div></div><p>Let&rsquo;s say we don&rsquo;t like <code>MyCoolProject</code> anymore and we want to rename it to <code>MCP</code>.</p>
<p>Then we can use Bire. Download the bire.exe from my <a href="https://github.com/martdegraaf/bire/releases/tag/1.0.0" target="_blank" rel="noopener">GitHub release 1</a>.</p>
<p>Use the following command to get MyCoolProject abbreviated to MCP.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bire -from C:\git\MyCoolProject\source -replace MyCoolProject=MCP </span></span></code></pre></td></tr></table>
</div>
</div></div><p>We&rsquo;re not copying the project we don&rsquo;t need the <code>-to</code> param. You should have your repo in git to do this change safely right?</p>
<h2 id="ignoring-the-right-files-and-file-types">Ignoring the right files and file types</h2>
<p>What if we would have certain files that would refer to MyCoolProject and we want them to stay that way instead of renaming <code>MyCoolProject</code> to <code>MCP</code>. For example a <code>MyCoolProjectClient.cs</code> class.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">bire -from C:\git\MyCoolProject\source -replace MyCoolProject=MCP 
</span></span><span class="line"><span class="cl">   -ignore <span class="s2">&#34;MyCoolProjectClient.cs|(.*(\.|\/|\\)(exe|dll|obj|bin|pdb|zip|\.git|\.vs|cache|packages))$&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>Bire helped me a lot at some companies. Let tools help you and get grip on your time back. Let it be another tool in your toolbox. Let me know if you have used bire and liked it.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Beat your excuses and start blogging</title>
      <link>https://blog.martdegraaf.nl/posts/blogging-agile/start-blogging-today/</link>
      <pubDate>Mon, 21 Nov 2022 17:35:37 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/blogging-agile/start-blogging-today/</guid>
      <description><![CDATA[How to start with your blog. The main focus should be content, starting and moving forward.]]></description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>Just start your creating your first page if you want to blog. Come back if you need motivation, then go back to writing/typing.</p>
<h2 id="blog-in-an-hour">Blog in an hour</h2>
<p>My coworker said to me; &ldquo;In the past time there was someone who did the things I dreamed of in just 4 days.&rdquo;. The person he referred to was me. He praised me for my dedication and courage to go fast and agile. I went fast to production with my blog and iterated new articles and new features. My coworker planned things out like the theme. When he was not fully satisfied with his theme he chose another theme but still had no active blog post. This blog is written to help others to get started. So beat your excuses and start today.</p>
<h2 id="focus-on-your-first-post">Focus on your first post</h2>
<p>To break the chicken-egg problem, the focus for starting your blog should be creating the first post. If there is no content why should you make a blog? If there is no blog why should you make content? Break through the cycle and start creating your first article.</p>
<h2 id="eat-that-frog">Eat that frog</h2>
<p>&lsquo;Eat that frog&rsquo; states that you should prioritize your tasks, and do the thing with the most impact first. You should be acting like there is no tomorrow and start working on your blog today, except when you don&rsquo;t want a blog. If you do not want a blog then why are you reading this article, are you procrastinating other tasks?</p>
<p>See your first article or blog post as the first checkpoint of your blogging world. From there you can check off multiple other checkpoints.</p>
<p>To read more about why you should do this now and not later, read my <a href="https://blog.martdegraaf.nl/reviews/books/eat-that-frog/">Book review on Eat that frog</a>.</p>
<h2 id="one-time-setup">One-time setup</h2>
<p>I recommend using a blog builder that uses git versioning like Hugo. Hugo can be easily deployed. This blog runs on Azure Static Webapps and GitHub Pages. Other alternatives like Wix or WordPress will be more painful to maintain.</p>
<p>Just because this blog iterated to Azure Static Webapps, didn&rsquo;t stop me from producing the actual blog content. The blog content is the most valuable. It&rsquo;s the core reason for your blog to exist. People are coming to read what you have written not for your cool styling.</p>
<p>To make it agile I have made these acceptance criteria for your blog.</p>
<ol>
<li>Your blog has to be accessible through the browser.</li>
<li>Your blog has at least one article.</li>
</ol>
<p>That means that these things are <strong>OUT OF SCOPE</strong> for your <strong>first</strong> production release:</p>
<ol>
<li>A custom URL</li>
<li>A funky-looking theme</li>
<li>A fitting Favicon</li>
<li>An about page.</li>
<li>Comment section</li>
<li>And so much more that gets in your way for your first blog post to be online.</li>
</ol>
<h2 id="iterating-your-blog">Iterating your blog</h2>
<p>Iterating your blog should be about:</p>
<ol>
<li>Finetuning your blogging workflow.</li>
<li>Styling your blog.</li>
<li>Refactoring your content.</li>
<li>Learning about the blogging framework.</li>
<li>Be fearless, when using Hugo and git you have version control to go back to. Just don&rsquo;t commit and push any secrets.</li>
</ol>
<h2 id="prioritize-your-blog-todo-list">Prioritize your blog TODO list</h2>
<p>Make a list of items you want to blog about. When you get new ideas put them on the list and prioritize them. Start with your first item and focus on it until you finish that blog post!</p>
<p>Some questions to help you prioritize:</p>
<ol>
<li>What blog item/subject has the most impact on your career? For example, if you write about a subject you work on daily, you will learn and become better. Another example is: you want to help companies using a certain technology. Writing about a subject helps your branding and the teacher learns the most.</li>
<li>What blog item/subject is hardest to solve?</li>
<li>What blog item/subject is not yet available easily when you searched for your problem?</li>
<li>What blog item/subject gives you the most joy?</li>
<li>What story do you tell about what you do for a living?</li>
<li>What do colleagues admire about you?</li>
</ol>
<h2 id="use-a-mindmap-to-organize-your-ideas">Use a mindmap to organize your ideas</h2>
<p>The mermaid JavaScript plugin builds some cool diagrams. Get started with your mindmap for blogging subjects right now with the <a href="https://mermaid.live/edit#pako:eNpdUk1PAjEQ_SuTniDBeN8YE1BvEojgxexl6M7uNm5naj9IVsJ_t7AsKj3N--jLy7QHpaUiVShruLLoSgbwInEyuRDT6YkCWHnTGA4DAHgVbqA1IYrvR64ojBae1Ag13u1EPqejshaXOvQmYDTCIwuw8Caa0IIbdHCh16100vSAKbbiYSvcwyJ94-XWGwVCr9sxY8VAdU06mj1TCA87f_-IXEFNGJOn8Mc3T1FsLqBBe7op8h5-rcN5Onv2BJF0y-Yr3Ro20WOkJse5DpkNN__1uW-SJY6Ql-iu6lakuwatieFU1qEjP5JL8hZNpWbKXqZCHU5iqWJLlkpV5LGiGlMXS1XyMVuTq3KZl8rk91BF9IlmKm9QNj3rEQ-eZ4ONRzuSDvlDJMMau5AxnSOWw584f43jDxofsC0" target="_blank" rel="noopener">Mermaid Live Editor</a>. Below is an example mindmap of my blogging subjects.</p>
<pre class="mermaid">mindmap
  root)Blog mindmap(
    T[Testing]
      Playwright
      Builder pattern
      UI scraping
    LaC((Life as<br>consultant))
      C[Clone and go]
        Clone script
        Git folder structure
      Personal development planinng
      Client conventions
      Clear all traces
    A[Azure]
      Application insights
    BA[Blogging Agile]
      Getting started
      Template
</pre>



<h2 id="conclusion">Conclusion</h2>
<p>When starting your blog, focus on your content first. That&rsquo;s the thing that will be truly yours. Keep an eye out for more content about blogging agile.</p>
<!-- 
Put Reference when Arno is live
## References

- Please check out: [@ArnoPeters' blog](https://arnopeters.nl/) -->
]]></content:encoded>
      <category>blogging</category>
      <category>inspirational</category>
      <category>mermaid</category>
    </item>
    <item>
      <title>Eat that frog! - Book review by a developer</title>
      <link>https://blog.martdegraaf.nl/reviews/books/eat-that-frog/</link>
      <pubDate>Mon, 21 Nov 2022 00:24:00 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/reviews/books/eat-that-frog/</guid>
      <description><![CDATA[<p>This year I read and listened to &lsquo;Eat that frog!&rsquo; by Brian Tracy. I tried to apply these principles to my daily life.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4a1; This article will be updated when I find new takeaways.
</div>
<h2 id="summary">Summary</h2>
<p>Eat that frog is a practical book on how to &ldquo;get more of the important things done today&rdquo;. It focuses on doing key tasks first and procrastinating the unimportant tasks.</p>]]></description><content:encoded><![CDATA[<p>This year I read and listened to &lsquo;Eat that frog!&rsquo; by Brian Tracy. I tried to apply these principles to my daily life.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4a1; This article will be updated when I find new takeaways.
</div>
<h2 id="summary">Summary</h2>
<p>Eat that frog is a practical book on how to &ldquo;get more of the important things done today&rdquo;. It focuses on doing key tasks first and procrastinating the unimportant tasks.</p>
<p>The author gives 21 principles for getting more things done faster:</p>
<ol>
<li>Set the table (spend some time writing out your goals for year, month, and week). Prioritize those and concentrate on the most important ones.</li>
<li>Plan every day in advance (on paper).</li>
<li>Apply the 80/20 rule to everything: 20% of your work will constitute 80% of your achievements. Concentrate on that 20%.</li>
<li>Consider the consequences of what you choose to work on. Focus on these above all else.</li>
<li>Practice creative procrastination. You&rsquo;re not going to be able to get everything done so put off the less important tasks.</li>
<li>Use the ABCDE method. Organize your tasks by value (a,b,c&hellip;) and priority (1,2,3&hellip;).</li>
<li>Focus on key result areas.</li>
<li>Obey the law of three. Identify the three things you do in work that account for 90% of your contribution.</li>
<li>Prepare thoroughly before you begin. The 6Ps: Proper Prior Planning Prevents Poor Performance.</li>
<li>Take it one barrel at a time. That way you can make it through the desert.</li>
<li>Upgrade your key skills!</li>
<li>Identify your key constraints.</li>
<li>Put pressure on yourself.</li>
<li>Motivate yourself into action (be your own cheerleader).</li>
<li>technology is a terrible master. Take back time your time from tech addictions. Learn to turn off devices and notifications and keep them off.</li>
<li>Technology is a wonderful servant. Use technological tools to confront yourself with what is most important.</li>
<li>Focus your attention</li>
<li>Slice and dice your tasks. Break your tasks into smaller, more manageable chunks.</li>
<li>Create large blocks of time to get your work done.</li>
<li>Develop a sense of urgency.</li>
<li>Single-handle every task.</li>
</ol>
<h2 id="comparing-agile-and-the-lecture-in-eat-that-frog">Comparing Agile and the lecture in Eat that frog</h2>
<p>I see many aspects that are similar in working Agile as what is stated in &lsquo;Eat that frog&rsquo;. For example:</p>
<ul>
<li>(6) Use the ABCDE Method Continually (The Product owner should keep the backlog up to date)</li>
<li>(7) Focus on key result areas (The work in the sprint)</li>
<li>(9) Prepare thoroughly before you begin (refinements)</li>
<li>(18) Slice and Dice the Tasks (The division of Epic Feature and Product backlog, and task level)</li>
<li>(20) Develop a sense of urgency (The sprint is time-boxed)</li>
</ul>
<h2 id="lessons-learned-as-a-developer">Lessons learned as a developer</h2>
<p>Think about your daily, weekly, monthly, and long-term goals.
Make sure you check your daily goals with your long-term goals.
I did write down my long-term goals on paper and am trying to work with daily TODO lists.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   <p>&#x1f4ac; &ldquo;Find out what successful people do, and do the same things until you get results.&rdquo;</p>
<p><strong>Quote from the book</strong></p>

</div>
<h3 id="example-long-term-goals">Example long-term goals</h3>
<ol>
<li>I am certified in security in 2023.</li>
<li>I am speaking on stage as a public speaker in 2023.</li>
<li>I am 90kg or less in November 2023.</li>
</ol>
<p>Important is to write your goals in the present tense, positive voice, and in the first person singular. This way your mind will accept it as a clear goal.</p>
<p>For each of these goals, create a list of work you have to do to achieve your goal. When you break down your goal into tasks it will be easier to do.</p>
<p>I did this for example for my speaking goals. The Gantt chart below was my plan for 2022. The main goal was to be prepared for DevNetNoord, a Dutch tech event.
When I determined my goal I placed tasks that could help me prepare for this event, such as training or smaller sessions. To be transparent to my client I included the sprints, that way I can easily communicate when I am busy.</p>
<pre class="mermaid">gantt
    title Marts Speaking path
    dateFormat  YYYY-MM-DD
    section Private
    Holidays :v1, 2022-08-08, 3w
    section Client sprints
    Sprint 14 :s14, 2022-07-11, 2w
    Sprint 15 :s15, after s14, 2w
    Sprint 16 :s16, after s15, 2w
    Sprint 17 :s17, after s16, 2w
    Sprint 18 :s18, after s17, 2w
    Sprint 19 :s19, after s18, 2w
    Sprint 20 :s20, after s19, 2w
    
    section Other consultancy days
    Consultancy client 1 : 2022-07-27, 2d
    Consultancy client 2 : 2022-08-02, 1d
    
    section Speaking Training
    Speaking in public (training) 1         :a1, 2022-09-16, 1d
    Speaking in public (training) 2         :a1, 2022-09-23, 1d
    section Events
    Pizza session 4dotnet Application insights : milestone, m1, 2022-06-02, 2min
    Pizza session 4dotnet Advanced Git : milestone, m1, 2022-09-02, 2min
    DevNetNoord : milestone, m1, 2022-09-29, 2min
    DotNetFriday : milestone, m1, 2022-11-04, 2min
</pre>

<h2 id="multiple-todo-lists">Multiple TODO lists</h2>
<p>Imagine you are in a team using a kanban board, but besides that, you have an on-paper TODO list. That does seem like you are creating a black market of work on paper next to your board of work, right? Some work may be investigating issues to refine for example an upcoming feature. How do you keep your board work and on-paper lists in sync?</p>
<h3 id="use-procrastination-as-your-friend">Use procrastination as your friend</h3>
<p>Some things you rate as unimportant can be procrastinated on. Try to procrastinate the unimportant tasks in your daily work to get the important work and tasks done.</p>
<h2 id="recommendations">Recommendations</h2>
<p>I recommend reading or listening to this book and practicing some of the given habits. One that will highly depend on the individual is: &lsquo;Upgrade your key skills&rsquo;. I think you should be making goals to keep learning next to your daily work. Sometimes even before doing any daily work. The learning will increase your efficiency especially when you focus on a key skill.</p>
<h3 id="read-eat-that-frog">Read Eat that frog</h3>
<p>Buy the book via <a href="https://amzn.to/3OAhfFY" target="_blank" rel="noopener">Amazon</a> or listen via Spotify:</p>
<iframe style="border-radius:12px" src="https://open.spotify.com/embed/episode/5JygKvMTPllafFaeYrkNUs?utm_source=generator&theme=0" width="100%" height="152" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
]]></content:encoded>
    </item>
    <item>
      <title>Duplicate Logging Azure Application Insights</title>
      <link>https://blog.martdegraaf.nl/posts/duplicate-logging-azure-application-insights/</link>
      <pubDate>Fri, 04 Nov 2022 12:35:37 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/duplicate-logging-azure-application-insights/</guid>
      <description><![CDATA[Save troubles and money fixing duplicate logging in your Azure Application Insights, and Log Analytics Workspace.]]></description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>The Application Insights was configured to send the data twice to the Log Analytics workspace. One of them was done by ARM, the other was enforced by an Azure Policy.
We turned off the Azure Policy and removed all upstream diagnostics settings on all Application Insights resources. This saved 100 euros monthly.</p>
<p><strong>On a recent project, we encountered duplicate logging in Azure Application insights.</strong></p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4a1; In the Classic Azure world, Log workspaces were not a thing yet. So when creating new application insights resources you should not encounter this problem.
</div>
<h2 id="problem-introduction-scope-and-context">Problem introduction scope, and context</h2>
<p>As seen in the screenshot we suffered in the acceptance environment with duplicate exceptions, information, and dependencies. In the development environment, on the left screen, we did not experience this issue.

<img loading="lazy" src="/images/duplicate-logging.png" alt="Duplicate logging"  />

<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f60e; Sensitive information in the screenshots is blurred for obvious reasons.
</div></p>
<h3 id="exclude-the-software-error">Exclude the software error</h3>
<p>To exclude the possibility of a software error, we executed these actions:</p>
<ol>
<li>Debugging the application and looking at the outgoing application insights tab.</li>
<li>The Azure web app / Azure function is misconfigured.</li>
<li>We tested web apps with a single instance. If a single instance generates duplicate logging, it&rsquo;s surely not the instance count.</li>
</ol>
<h3 id="the-cause">The cause</h3>
<p>The Log Analytics Workspace was configured in diagnostic settings as well it was in the properties of the Application Insights. See the screenshots of the Azure portal.

<img loading="lazy" src="/images/diagnostic-settings.png" alt="Diagnostic settings"  />

The screenshot below shows how the application insights resource is configured upstream.

<img loading="lazy" src="/images/properties-ai-workspace.png" alt="Properties Application Insights workspace setting"  />
</p>
<p>So when we concluded the configuration was duplicate, we asked ourselves:</p>
<ol>
<li>Why was it duplicate configured, couldn&rsquo;t that have been spotted in an existing ARM template?</li>
<li>The answer consists of multiple factors and solutions. The two main components were an ARM template and an Azure Policy.</li>
</ol>
<h4 id="1-the-correct-way---arm--workspace-property">1. The correct way - ARM &gt; Workspace property</h4>
<p>The correct implementation of the properties was created by an ARM template.
The infrastructure as code makes sure we&rsquo;re configuring the upstream Log Analytics Workspace.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;microsoft.insights/components&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;kind&#34;</span><span class="p">:</span> <span class="s2">&#34;other&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ai-[YOUR-APPLICATION-INSIGHTS-NAME]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;apiVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;2020-02-02-preview&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;location&#34;</span><span class="p">:</span> <span class="s2">&#34;West Europe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;properties&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;Application_Type&#34;</span><span class="p">:</span> <span class="s2">&#34;web&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;ApplicationId&#34;</span><span class="p">:</span> <span class="s2">&#34;ai-[YOUR-APPLICATION-INSIGHTS-NAME]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;WorkspaceResourceId&#34;</span><span class="p">:</span> <span class="s2">&#34;law-[YOUR-LOG-ANALYTICS-WORKSPACE-NAME]&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4a1; The naming of Azure resources is done using the <a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations" target="_blank" rel="noopener">Azure abbreviations guide</a>.
</div>
<h4 id="2-azure-policy-was-enforced-on-diagnostic-settings">2. Azure Policy was enforced on &lsquo;Diagnostic settings&rsquo;</h4>
<p>There also was an Azure policy checking that there was a diagnostic setting for sending data to the Log Analytics Workspace.
Whenever the IT operations checked and enforced the Azure Policy, we would have duplicated the upstream to our Log Analytics Workspace.</p>
<p>We turned off this Azure Policy and removed all upstream diagnostics settings on all Application Insights resources.</p>
<h2 id="conclusion">Conclusion</h2>
<h3 id="difference-application-insights-and-log-analytics-workspace">Difference Application Insights and Log Analytics workspace</h3>
<pre class="mermaid">graph TD
AI1[Application Insights instance 1]
AI2[Application Insights instance 2]
LA1[Log Analytics Workspace]

LA1 <--- AI1
LA1 <--- AI2
</pre>

<p>Application Insights gives &lsquo;insights&rsquo; into application logging, exceptions, and such. You can use the Kusto query language to fetch data intelligently from Application Insights. The Log Analytics workspace is a set of tables. For the client in this article, the data of the Application insights was forwarded to the Log Analytics workspace. The advantage of the Log Analytics workspace is to query over multiple Application insights as well as data about other resources in Azure, such as API management, application gateways, service busses, or firewalls.</p>
<p>In the screenshot below is seen that when you create a new Application Insights resource the Log Analytics Workspace is configured automatically.

<img loading="lazy" src="/images/create-ai-workspace-based.png" alt="Create Application Insights workspace based"  />
</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x2139;&#xfe0f; The Log Analytics workspace is part of the <a href="https://learn.microsoft.com/en-gb/azure/azure-monitor/overview" target="_blank" rel="noopener">Azure Monitor</a> component in Azure.
</div>
<h3 id="cost-analysis-moneybag">Cost analysis &#x1f4b0;</h3>
<p>This change saved the client over €1000 monthly in Azure Log Analytic costs. The euros won&rsquo;t tell you how much of the total it was. After fixing all duplicate configurations, the total costs of the Application Insights resources were reduced by roughly 40-50%. If you also are experiencing this problem, I hope this article helps. Good logging makes all developers happy.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Whenever you see duplicate logging in your application insights make sure the configuration is correct. Also, make sure that you&rsquo;re not forcing a policy on the diagnostic settings when you configure it in the properties. Only one upstream to the Log Analytic workspace is required &#x1f609;.</p>
<h3 id="references">References</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/answers/questions/883344/application-insights-duplicate-telemetry.html" target="_blank" rel="noopener">Microsoft Learn - Application Insights Duplicate Telemetry</a></li>
<li><a href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/convert-classic-resource#apptraces" target="_blank" rel="noopener">Converting table ApplicationInsights LogAnalytics</a></li>
<li><a href="https://learn.microsoft.com/en-gb/azure/azure-monitor/overview" target="_blank" rel="noopener">Azure Monitor</a></li>
</ul>
]]></content:encoded>
      <category>application insights</category>
      <category>loganalytics workspace</category>
      <category>Azure</category>
      <category>logging</category>
      <category>monitoring</category>
      <category>problemsolving</category>
    </item>
    <item>
      <title>Add project properties for consuming apps to a NuGet package</title>
      <link>https://blog.martdegraaf.nl/posts/add-properties-for-consuming-apps-to-a-nuget-package/</link>
      <pubDate>Wed, 19 Oct 2022 00:00:00 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/add-properties-for-consuming-apps-to-a-nuget-package/</guid>
      <description><![CDATA[This article explains how to add consuming project properties to a NuGet package. Those project properties will be used in the consuming apps.]]></description><content:encoded><![CDATA[<h1 id="introduction">Introduction</h1>
<p>For a recent project, I wanted to add a property to the consuming applications from within my NuGet package. This prevents  making a pull request for every consuming application with a .csproj change.</p>
<div style="
border: #0078D3 1px solid;
padding: 15px;
background: var(--entry);
border-left-width: 3px;">
   &#x1f4d3; Please note in this example the NuGet package has the ID: <code>MyProject.ExampleNuGet</code>, so replace that value for your nuget package. The consuming application is <code>MyProject.ConsumingWebApi</code>.
</div>
<h2 id="nuget-file-structure">NuGet file structure</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">MyProject.ExampleNuGet  (Repository level)
</span></span><span class="line"><span class="cl"> ┣ MyProject.ExampleNuGet
</span></span><span class="line"><span class="cl"> ┃ ┣ Extensions
</span></span><span class="line"><span class="cl"> ┃ ┃ ┗ MySpecialThing.cs
</span></span><span class="line"><span class="cl"> ┃ ┣ MyProject.ExampleNuGet.csproj
</span></span><span class="line"><span class="cl"> ┃ ┗ MyProject.ExampleNuGet.props
</span></span><span class="line"><span class="cl"> ┣ MyProject.ExampleNuGet.sln
</span></span><span class="line"><span class="cl"> ┗ nuget.config</span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="myprojectexamplenugetprops">MyProject.ExampleNuGet.props</h2>
<p>The NuGet package has a .props-file to enforce some properties to the consumers.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;Project&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Enable output XML Docs for Swagger. --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;GenerateDocumentationFile&gt;</span>true<span class="nt">&lt;/GenerateDocumentationFile&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Disable missing XML comment warnings. --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;NoWarn&gt;</span>$(NoWarn);1591<span class="nt">&lt;/NoWarn&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Project&gt;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="myprojectexamplenugetcsproj">MyProject.ExampleNuGet.csproj</h2>
<p>Important is to set the build action of the <code>MyProject.ExampleNuGet.props</code> file to package it to the build directory. See the example below:</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;Project</span> <span class="na">Sdk=</span><span class="s">&#34;Microsoft.NET.Sdk&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;TargetFramework&gt;</span>net6.0<span class="nt">&lt;/TargetFramework&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;None</span> <span class="na">Update=</span><span class="s">&#34;MyProject.ExampleNuGet.props&#34;</span> <span class="na">Pack=</span><span class="s">&#34;true&#34;</span> <span class="na">PackagePath=</span><span class="s">&#34;build&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/None&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/Project&gt;</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>When installing this package on for example <code>MyProject.ConsumingWebApi</code> a file is generated in de build folder <code>MyProject.ConsumingWebApi.csproj.nuget.g.targets</code>.
This ensures the setting is on when building <code>MyProject.ConsumingWebApi</code>.</p>
<h2 id="references">References</h2>
<p>I used the following resources to fix my problem.</p>
<ul>
<li><a href="https://stackoverflow.com/questions/67263924/create-nuget-containing-shared-project-properties-automatic-references" target="_blank" rel="noopener">https://stackoverflow.com/questions/67263924/create-nuget-containing-shared-project-properties-automatic-references</a></li>
<li><a href="https://learn.microsoft.com/en-us/nuget/concepts/msbuild-props-and-targets#packagereference-projects" target="_blank" rel="noopener">https://learn.microsoft.com/en-us/nuget/concepts/msbuild-props-and-targets#packagereference-projects</a></li>
</ul>
]]></content:encoded>
      <category>NuGet</category>
      <category>.NET</category>
      <category>csproj</category>
    </item>
    <item>
      <title>How to verify that ILogger logged an error?</title>
      <link>https://blog.martdegraaf.nl/posts/verifylogged-with-xunit/</link>
      <pubDate>Fri, 29 Jul 2022 00:00:00 +0200</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/posts/verifylogged-with-xunit/</guid>
      <description><![CDATA[Explains how to test LogError with Xunit.]]></description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>For a recent project, I wanted to create a test that verified that <code>LogError</code> was called.
Consider for example this piece of code below. The catch operation was added to swallow the exception of the delete action. We want to test this behavior but still would like to know if the <code>LogError</code> is being called.</p>
<h2 id="the-system-under-test">The system under test</h2>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Delete</span><span class="p">(</span><span class="kt">long</span> <span class="n">sequenceNumber</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">_logger</span><span class="p">.</span><span class="n">LogInformation</span><span class="p">(</span><span class="s">&#34;Deleting `{sequenceNumber}`.&#34;</span><span class="p">,</span> <span class="n">sequenceNumber</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">_client</span><span class="p">.</span><span class="n">Delete</span><span class="p">(..);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_logger</span><span class="p">.</span><span class="n">LogInformation</span><span class="p">(</span><span class="s">&#34;Delete completed `{sequenceNumber}`.&#34;</span><span class="p">,</span> <span class="n">sequenceNumber</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">catch</span> <span class="p">(</span><span class="n">InvalidOperationException</span> <span class="n">ex</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">when</span> <span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">$&#34;The scheduled message with SequenceNumber = {sequenceNumber} is already being cancelled.&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_logger</span><span class="p">.</span><span class="n">LogError</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span> <span class="s">&#34;Already cancelled {sequenceNumber}.&#34;</span><span class="p">,</span> <span class="n">sequenceNumber</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="verify-that-logerror-is-called">Verify that <code>LogError</code> is called</h2>
<p>Have you ever tried to verify your <code>LogError</code> using xUnit? It does not seem to work out of the box as other FakeItEasy.
I tried this code for example, but it just would not work. The mock that throws the exception has been left out to keep the code sample small.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="c1">//Arrange</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">logger</span> <span class="p">=</span> <span class="n">A</span><span class="p">.</span><span class="n">Fake</span><span class="p">&lt;</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">SystemUnderTest</span><span class="p">&gt;&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">sut</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SystemUnderTest</span><span class="p">(</span><span class="n">logger</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//Act</span>
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="n">sut</span><span class="p">.</span><span class="n">Delete</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//Assert</span>
</span></span><span class="line"><span class="cl"><span class="n">A</span><span class="p">.</span><span class="n">CallTo</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="n">logger</span><span class="p">.</span><span class="n">LogError</span><span class="p">(</span><span class="n">A</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;.</span><span class="n">Ignored</span><span class="p">,</span> <span class="n">A</span><span class="p">&lt;</span><span class="kt">object</span><span class="p">[]&gt;.</span><span class="n">Ignored</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">MustHaveHappenedOnceExactly</span><span class="p">();</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="the-loggerextensions-class">The LoggerExtensions class</h2>
<p>The solution was right at hand because my coworker had already figured it out. Thanks, Marnix. Chekout his blog: <a href="https://alanta.nl/" target="_blank" rel="noopener">Marnix&rsquo; blog</a>. Use the extension class as described below.</p>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="c1">//Arrange</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">logger</span> <span class="p">=</span> <span class="n">A</span><span class="p">.</span><span class="n">Fake</span><span class="p">&lt;</span><span class="n">ILogger</span><span class="p">&lt;</span><span class="n">SystemUnderTest</span><span class="p">&gt;&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">sut</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SystemUnderTest</span><span class="p">(</span><span class="n">logger</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//Act</span>
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="n">sut</span><span class="p">.</span><span class="n">Delete</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//Assert</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">VerifyLogged</span><span class="p">(</span><span class="n">LogLevel</span><span class="p">.</span><span class="n">Information</span><span class="p">,</span> <span class="s">&#34;Deleting 1&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="n">VerifyLogged</span><span class="p">(</span><span class="n">LogLevel</span><span class="p">.</span><span class="n">Error</span><span class="p">,</span> <span class="s">&#34;Already cancelled 1&#34;</span><span class="p">);</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h3 id="loggerextensionscs">LoggerExtensions.cs</h3>
<div class="codecontainer" hi="mart"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">FakeItEasy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.Extensions.Logging</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Linq</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Xunit.Sdk</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">SomeCoolNamespace</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">LoggerExtensions</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">VerifyLogged</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">LogLevel</span> <span class="n">level</span><span class="p">,</span> <span class="kt">string</span> <span class="n">logMessage</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="p">(</span><span class="n">found</span><span class="p">,</span> <span class="n">actualLevel</span><span class="p">,</span> <span class="n">actualMessage</span><span class="p">)</span> <span class="p">=</span> <span class="n">logger</span><span class="p">.</span><span class="n">VerifyLog</span><span class="p">(</span><span class="n">logMessage</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">found</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">XunitException</span><span class="p">(</span><span class="s">$&#34;No log message found containing &#39;{logMessage}&#39; at any loglevel&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">actualLevel</span> <span class="p">!=</span> <span class="n">level</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">AssertActualExpectedException</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="s">$&#34;[{level}] {logMessage}&#34;</span><span class="p">,</span> <span class="s">$&#34;[{actualLevel}] {actualMessage}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s">$&#34;Unexpected log level for log message&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">VerifyNotLogged</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">LogLevel</span> <span class="n">level</span><span class="p">,</span> <span class="kt">string</span> <span class="n">logMessage</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="p">(</span><span class="n">found</span><span class="p">,</span> <span class="n">actualLevel</span><span class="p">,</span> <span class="n">actualMessage</span><span class="p">)</span> <span class="p">=</span> <span class="n">logger</span><span class="p">.</span><span class="n">VerifyLog</span><span class="p">(</span><span class="n">logMessage</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">found</span> <span class="p">&amp;&amp;</span> <span class="n">actualLevel</span> <span class="p">==</span> <span class="n">level</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">XunitException</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="err">@</span><span class="s">$&#34;Log message found containing &#39;{logMessage}&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">                    at level {level}{Environment.NewLine}Message: {actualMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">VerifyNotLoggedAtLevel</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">LogLevel</span> <span class="n">level</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">call</span> <span class="p">=</span> <span class="n">Fake</span><span class="p">.</span><span class="n">GetCalls</span><span class="p">(</span><span class="n">logger</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">FirstOrDefault</span><span class="p">(</span><span class="n">call</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">LogLevel</span><span class="p">?)</span><span class="n">call</span><span class="p">.</span><span class="n">Arguments</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">==</span> <span class="n">level</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">call</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">XunitException</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="err">@</span><span class="s">$&#34;Log message found at level {level}{Environment.NewLine}
</span></span></span><span class="line"><span class="cl"><span class="s">                    Message: {call.Arguments[2]}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">VerifyLoggedAtLevel</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">LogLevel</span> <span class="n">level</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">found</span> <span class="p">=</span> <span class="n">Fake</span><span class="p">.</span><span class="n">GetCalls</span><span class="p">(</span><span class="n">logger</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">Any</span><span class="p">(</span><span class="n">call</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="n">LogLevel</span><span class="p">?)</span><span class="n">call</span><span class="p">.</span><span class="n">Arguments</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">==</span> <span class="n">level</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">found</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">XunitException</span><span class="p">(</span><span class="s">$&#34;No log message found at level {level}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="p">(</span><span class="kt">bool</span> <span class="n">found</span><span class="p">,</span> <span class="n">LogLevel</span><span class="p">?</span> <span class="n">level</span><span class="p">,</span> <span class="kt">string?</span> <span class="n">message</span><span class="p">)</span> <span class="n">VerifyLog</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="k">this</span> <span class="n">ILogger</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">logger</span><span class="p">,</span> <span class="kt">string</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">call</span> <span class="p">=</span> <span class="n">Fake</span><span class="p">.</span><span class="n">GetCalls</span><span class="p">(</span><span class="n">logger</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">FirstOrDefault</span><span class="p">(</span><span class="n">call</span> <span class="p">=&gt;</span> <span class="n">call</span><span class="p">.</span><span class="n">Arguments</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">ToString</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">call</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">,</span> <span class="p">(</span><span class="n">LogLevel</span><span class="p">?)</span><span class="n">call</span><span class="p">?.</span><span class="n">Arguments</span><span class="p">[</span><span class="m">0</span><span class="p">],</span> <span class="n">call</span><span class="p">?.</span><span class="n">Arguments</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div></div><h2 id="conclusion">Conclusion</h2>
<p>Using this class you will be able to test your logging with Xunit and FakeItEasy.</p>
]]></content:encoded>
      <category>Azure</category>
      <category>Application Insights</category>
      <category>Monitoring</category>
      <category>Xunit</category>
      <category>FakeItEasy</category>
    </item>
    <item>
      <title>4 mijl oefenen in Assen</title>
      <link>https://blog.martdegraaf.nl/nl/4mijl-oefenen-in-assen/</link>
      <pubDate>Thu, 09 Jun 2022 11:30:03 +0000</pubDate><author>blog@martdegraaf.nl (Mart de Graaf)</author>
      <guid>https://blog.martdegraaf.nl/nl/4mijl-oefenen-in-assen/</guid>
      <description><![CDATA[<h2 id="4-mijl">4 MIJL</h2>
<p>De organisatie van de vier mijl in Assen heeft extra routes uitgelegd in Assen, te zien op <a href="https://www.4mijlvanassen.nl/faq/" target="_blank" rel="noopener">FAQ - 4 Mijl van Assen</a>. Omdat sommige routes lastig te vinden zijn heb ik dit artikel gemaakt. In de volgende wijken zijn extra oefenroutes aangebracht: Assen-Oost, Baggelhuizen, Kloosterveen, Lariks &amp; Pittelo.</p>
<h2 id="startpunten">Startpunten</h2>
<p>Ik heb de startpunten verzameld in een Google maps lijst <a href="https://goo.gl/maps/bBnfnvrUnU2qR2PQA" target="_blank" rel="noopener">Google maps Lijst van startpunten</a>. Zie hieronder de routes.</p>
<h3 id="route-in-kloosterveen">Route in Kloosterveen</h3>
<p>De route in kloosterveen begint midden in de Kloosterveste en gaat daarna over het grindpad bij FC Assen. Vervolgens kun je genieten van de route om de schapenheuvel en zie je de nieuwe speeltuin van Kloosterveen. De rest van de route gaat door delen van de woonwijk heen waarbij er genoeg water is te zien. Let op dat in de Ribeshage pijlen op de weg staan waar ook autos geparkeerd staan. Je moet hier voor de speeltuin langs naar rechts.</p>]]></description><content:encoded><![CDATA[<h2 id="4-mijl">4 MIJL</h2>
<p>De organisatie van de vier mijl in Assen heeft extra routes uitgelegd in Assen, te zien op <a href="https://www.4mijlvanassen.nl/faq/" target="_blank" rel="noopener">FAQ - 4 Mijl van Assen</a>. Omdat sommige routes lastig te vinden zijn heb ik dit artikel gemaakt. In de volgende wijken zijn extra oefenroutes aangebracht: Assen-Oost, Baggelhuizen, Kloosterveen, Lariks &amp; Pittelo.</p>
<h2 id="startpunten">Startpunten</h2>
<p>Ik heb de startpunten verzameld in een Google maps lijst <a href="https://goo.gl/maps/bBnfnvrUnU2qR2PQA" target="_blank" rel="noopener">Google maps Lijst van startpunten</a>. Zie hieronder de routes.</p>
<h3 id="route-in-kloosterveen">Route in Kloosterveen</h3>
<p>De route in kloosterveen begint midden in de Kloosterveste en gaat daarna over het grindpad bij FC Assen. Vervolgens kun je genieten van de route om de schapenheuvel en zie je de nieuwe speeltuin van Kloosterveen. De rest van de route gaat door delen van de woonwijk heen waarbij er genoeg water is te zien. Let op dat in de Ribeshage pijlen op de weg staan waar ook autos geparkeerd staan. Je moet hier voor de speeltuin langs naar rechts.</p>
<div class='strava-embed-placeholder' data-embed-type='activity' data-embed-id="7198868379"></div>
<script src='https://strava-embeds.com/embed.js'></script>
<h3 id="route-assen-oost">Route Assen Oost</h3>
<p>De route van Assen Oost begint aan de rand van de wijk aan het einde van de Vredeveldse weg.</p>
<div class='strava-embed-placeholder' data-embed-type='activity' data-embed-id="7583273408"></div>
<script src='https://strava-embeds.com/embed.js'></script>
<h3 id="route-lariks">Route Lariks</h3>
<p>De route in Lariks begint bij de Delft 73, 1 straat van de Vaart N.Z vandaan. Let op bij de straat Stroom, daar is een punt waar pijlen samen komen, als je ze goed volgt en op het voetbad blijft, heb je er geen last van.</p>
<div class='strava-embed-placeholder' data-embed-type='activity' data-embed-id="7616571956"></div>
<script src='https://strava-embeds.com/embed.js'></script>
<h3 id="route-pittelo">Route Pittelo</h3>
<p>De route in Pittelo start bij de voormalige Kinderopvang Pinokkio, die nu Kids First heet. Zoals in de route hieronder te zien hebben we een keer de verkeerde afslag genomen in de pittelose bossen. De driesprong miste een pijl of bordje, waar je dus verwacht wordt om links te gaan.</p>
<div class='strava-embed-placeholder' data-embed-type='activity' data-embed-id="7627316919"></div>
<script src='https://strava-embeds.com/embed.js'></script>
]]></content:encoded>
      <category>hardlopen</category>
      <category>strava</category>
    </item>
  </channel>
</rss>
