<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Frits Becker</title>
    <description>The latest articles on DEV Community by Frits Becker (@frits-becker).</description>
    <link>https://dev.to/frits-becker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2963764%2Ffcaba76f-9756-45a6-85ca-0b3b16a30a30.jpg</url>
      <title>DEV Community: Frits Becker</title>
      <link>https://dev.to/frits-becker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frits-becker"/>
    <language>en</language>
    <item>
      <title>Auto update version</title>
      <dc:creator>Frits Becker</dc:creator>
      <pubDate>Mon, 19 May 2025 05:46:23 +0000</pubDate>
      <link>https://dev.to/frits-becker/auto-update-version-218i</link>
      <guid>https://dev.to/frits-becker/auto-update-version-218i</guid>
      <description>&lt;p&gt;As developers, we do not want to waste time on releases, and one of the challenges with an automatic release is updating the version number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventional Commits
&lt;/h2&gt;

&lt;p&gt;At multiple companies I have introduced the &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/" rel="noopener noreferrer"&gt;conventional commits&lt;/a&gt; website. This allows us to easily add the type of change in our commit title.&lt;/p&gt;

&lt;p&gt;If every developer uses these conventions, you can automate versioning in the pipeline, and that is what I did about two years ago. I will show you how.&lt;/p&gt;

&lt;p&gt;First of all, I have to admit that I am not a PowerShell guru, so feel free to improve the scripts if this post helped you.&lt;/p&gt;

&lt;p&gt;Now lets start with all the steps we need to take and what better way then using an example pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch number logic
&lt;/h2&gt;

&lt;p&gt;We set the default name of the pipeline to $(date:yyyyMMdd)$(rev:rr), so we can use this as the patch number. This gives us a couple of advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need for +1 or reset to 0 logic&lt;/li&gt;
&lt;li&gt;We always know when a version is released&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now there are some restrictions to this, at least if you create a NuGet package:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version parts in NuGet are integers, so the max version is 2147483647&lt;/li&gt;
&lt;li&gt;Limit is 99 releases a day&lt;/li&gt;
&lt;li&gt;This logic will fail from the year 2148; this will be the new Y2K bug!
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 2147483647&lt;/span&gt;
&lt;span class="c1"&gt;# yyyyMMddrr&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(date:yyyyMMdd)$(rev:rr)&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;system_accesstoken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(System.AccessToken)&lt;/span&gt;
  &lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name'&lt;/span&gt;
  &lt;span class="na"&gt;isMaster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$[eq(variables['Build.SourceBranch'], 'refs/heads/master')]&lt;/span&gt;

&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reuse scripts
&lt;/h2&gt;

&lt;p&gt;After writing and testing the scripts (details later in the blog) we put the scripts in another repository, so they are reusable&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repoName&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TeamProjectName/repoName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nobody is perfect
&lt;/h2&gt;

&lt;p&gt;Sometimes a (new) developer forgets to add the convention. So we added two extra parameters to set the type of release when we run a pipeline manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildConfiguration&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Release'&lt;/span&gt;
&lt;span class="s"&gt;version&lt;/span&gt;
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;releaseNewVersion&lt;/span&gt;
    &lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="s"&gt;- name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;newVersionType&lt;/span&gt;
    &lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;patch'&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;patch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;minor&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;major&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how it looks when manually starting a run&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5rl4bww0zer7ex1t2ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5rl4bww0zer7ex1t2ps.png" alt="manually running a pipeline" width="694" height="590"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  verion tag in git
&lt;/h2&gt;

&lt;p&gt;Our scripts will write the new version as a tag to Git. This means the pipeline needs permission to push to the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checkout&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scripts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;access&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pushing"&lt;/span&gt;
    &lt;span class="na"&gt;persistCredentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;fetchDepth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  flow of scripts
&lt;/h2&gt;

&lt;p&gt;Here you can see all the scripts we built and how we call them in the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/set-latest-version-in-variables.yml@repoName&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/update-version-variables.yml@repoName&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;overruleNewVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.ReleaseNewVersion }}&lt;/span&gt;
      &lt;span class="na"&gt;newVersionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.newVersionType }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/create-git-tag.yml@repoName&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/update-version-in-project-file.yml@repoName&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;buildConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.buildConfiguration }}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/default-build.yml@repoName&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;buildConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.buildConfiguration }}&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scriptsFolderName/push-new-version.yml@repoName&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;buildConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ parameters.buildConfiguration }}&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;powershell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;$newVersion = "$($(Major)).$($(Minor)).$($(Patch))"&lt;/span&gt;
      &lt;span class="s"&gt;Write-Host "##vso[build.updatebuildnumber]$newVersion"&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rename&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pipeline&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version'&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables.isRelease, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting the Latest Version
&lt;/h2&gt;

&lt;p&gt;To set the latest version, we get the latest tag. When no tag is available, we use version 0.0.0.&lt;/p&gt;

&lt;p&gt;When you implement this script but want to start with a higher version, just create that tag if it is not already available.&lt;/p&gt;

&lt;p&gt;After setting the full version, we split it up into major, minor, and patch variables that we can update later in the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#set-latest-version-in-variables.yml&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;powershell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;$latestVersion = git describe --tags --abbrev=0 2&amp;gt;$null&lt;/span&gt;
    &lt;span class="s"&gt;if ($latestVersion) {&lt;/span&gt;
      &lt;span class="s"&gt;echo "Current version: $($latestVersion)"&lt;/span&gt;
    &lt;span class="s"&gt;} else {&lt;/span&gt;
      &lt;span class="s"&gt;$latestVersion = "0.0.0"&lt;/span&gt;
      &lt;span class="s"&gt;echo "No tag was found. Using version 0.0.0 as a base."&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;

    &lt;span class="s"&gt;$versionArray = $latestVersion -split "\."&lt;/span&gt;

    &lt;span class="s"&gt;$major = [int]$versionArray[0]&lt;/span&gt;
    &lt;span class="s"&gt;$minor = [int]$versionArray[1]&lt;/span&gt;
    &lt;span class="s"&gt;$patch = [int]$versionArray[2]&lt;/span&gt;

    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Major]$major"&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Minor]$minor"&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Patch]$patch"&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;latest&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;variables'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables.isMaster, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Incrementing the Version Number
&lt;/h2&gt;

&lt;p&gt;The normal flow will be that the run was triggered automatically and the convention was used, which means that the commit title starts with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fix = patch&lt;/li&gt;
&lt;li&gt;feat = minor&lt;/li&gt;
&lt;li&gt;anyType! = major&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We do this with regex and also get other parts of the commit message. Who knows, maybe we can use them in the future. Some examples of how the regex will return the results.&lt;/p&gt;

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

&lt;p&gt;After this, we just check the type of change and update the corresponding version type, where the patch will always be updated.&lt;/p&gt;

&lt;p&gt;We added some extra checks for the manual run part that overrules the default logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#update-version-variables.yml&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;overruleNewVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;newVersionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;powershell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;$latestCommitTitle = git log -n 1 --pretty=format:"%s"&lt;/span&gt;

    &lt;span class="s"&gt;#Normalize title&lt;/span&gt;
    &lt;span class="s"&gt;$pattern = "^merged pr \d+:\s*(.*)"&lt;/span&gt;
    &lt;span class="s"&gt;$normalizedTitle = $latestCommitTitle.ToLower()&lt;/span&gt;
    &lt;span class="s"&gt;if($latestCommitTitle.ToLower() -match $pattern) {&lt;/span&gt;
      &lt;span class="s"&gt;$normalizedTitle = $matches[1]&lt;/span&gt;
    &lt;span class="s"&gt;}      &lt;/span&gt;

    &lt;span class="s"&gt;$pattern = "(?&amp;lt;type&amp;gt;\w+)(?&amp;lt;scope&amp;gt;(?:\([^()\r\n]*\)|\()?(?&amp;lt;breaking&amp;gt;!)?)(?&amp;lt;subject&amp;gt;:.*)?"&lt;/span&gt;
    &lt;span class="s"&gt;$normalizedTitle -match $pattern&lt;/span&gt;
    &lt;span class="s"&gt;echo "type: $($matches["type"])"&lt;/span&gt;
    &lt;span class="s"&gt;echo "scope: $($matches["scope"])"&lt;/span&gt;
    &lt;span class="s"&gt;echo "breaking: $($matches["breaking"])"&lt;/span&gt;
    &lt;span class="s"&gt;echo "subject: $($matches["subject"])"&lt;/span&gt;

    &lt;span class="s"&gt;$major = [int]$(Major)&lt;/span&gt;
    &lt;span class="s"&gt;$minor = [int]$(Minor)&lt;/span&gt;
    &lt;span class="s"&gt;$patch = $(Patch)&lt;/span&gt;

    &lt;span class="s"&gt;$changed = $false&lt;/span&gt;
    &lt;span class="s"&gt;if('${{ parameters.overruleNewVersion }}' -eq "true") {&lt;/span&gt;
      &lt;span class="s"&gt;echo '${{ parameters.newVersionType }}'&lt;/span&gt;
      &lt;span class="s"&gt;$changed = $true&lt;/span&gt;

      &lt;span class="s"&gt;if('${{ parameters.newVersionType }}' -eq "major") {&lt;/span&gt;
        &lt;span class="s"&gt;echo "breaking change" &lt;/span&gt;
        &lt;span class="s"&gt;$major += 1&lt;/span&gt;
        &lt;span class="s"&gt;$minor = 0&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;elseif('${{ parameters.newVersionType }}' -eq "minor"){&lt;/span&gt;
        &lt;span class="s"&gt;echo "minor change"&lt;/span&gt;
        &lt;span class="s"&gt;$minor += 1&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;else{&lt;/span&gt;
        &lt;span class="s"&gt;echo "patch change"&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;else {    &lt;/span&gt;
      &lt;span class="s"&gt;if($matches["breaking"] -ne $null) {&lt;/span&gt;
        &lt;span class="s"&gt;echo "breaking change" &lt;/span&gt;
        &lt;span class="s"&gt;$major += 1&lt;/span&gt;
        &lt;span class="s"&gt;$minor = 0&lt;/span&gt;
        &lt;span class="s"&gt;$changed = $true&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;elseif($matches["type"] -eq "feat"){&lt;/span&gt;
        &lt;span class="s"&gt;echo "minor change"&lt;/span&gt;
        &lt;span class="s"&gt;$minor += 1&lt;/span&gt;
        &lt;span class="s"&gt;$changed = $true&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;elseif($matches["type"] -eq "fix"){&lt;/span&gt;
        &lt;span class="s"&gt;echo "patch change"&lt;/span&gt;
        &lt;span class="s"&gt;$changed = $true&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;

    &lt;span class="s"&gt;echo "Changed: $($changed)"&lt;/span&gt;
    &lt;span class="s"&gt;if($changed) {&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Major]$major"&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Minor]$minor"&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=Patch]$(Build.BuildNumber)"&lt;/span&gt;
    &lt;span class="s"&gt;Write-Host "##vso[task.setvariable variable=IsRelease]true"&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;change&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables.isMaster, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Git Tag
&lt;/h2&gt;

&lt;p&gt;When there was a change that needed a release, the isRelease variable will be set to true in the last script, and when it is, we just create a new Git tag.&lt;/p&gt;

&lt;p&gt;This will only work if you set the Git permission in the pipeline with &lt;code&gt;persistCredentials: true&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#create-git-tag.yml&lt;/span&gt;
&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;powershell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;git tag "$($(Major)).$($(Minor)).$($(Patch))"&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tag'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables.IsRelease, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optional: Updating project file
&lt;/h2&gt;

&lt;p&gt;We develop libraries/NuGet packages in .NET, so the last step for us will be an update to the .csproj file. This will ensure that when we build, the version of the NuGet package will be correct.&lt;/p&gt;

&lt;p&gt;As I have written earlier, NuGet uses an integer per version type. But if we look deeper, we find something interesting. The type is different per tag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Packageversion: int32&lt;/li&gt;
&lt;li&gt;version: int16 = 32.767
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#update-version-in-project-file.yml&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;projectName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;powershell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;$newVersionTag = git describe --tags --abbrev=0&lt;/span&gt;
    &lt;span class="s"&gt;echo "New version tag: $($newVersionTag)"&lt;/span&gt;

    &lt;span class="s"&gt;$projectName = '$(projectName)'&lt;/span&gt;
    &lt;span class="s"&gt;$pattern = '^(.*?(&amp;lt;PackageVersion&amp;gt;|&amp;lt;Version&amp;gt;).*?)([0-9].[0-9]{1,2}.)(([0-9]{1,2})(-rc([0-9]{2}))?)(.*?)$'&lt;/span&gt;
    &lt;span class="s"&gt;$path = if (Test-Path -Path $projectName) {'{0}\*' -f $projectName } else { '*' }&lt;/span&gt;
    &lt;span class="s"&gt;$ProjectFileName = '{0}.csproj' -f $projectName&lt;/span&gt;
    &lt;span class="s"&gt;$ProjectFiles = Get-ChildItem -Path $path -Include $ProjectFileName -Recurse&lt;/span&gt;

    &lt;span class="s"&gt;foreach ($file in $ProjectFiles)&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;echo "file: $($file)"&lt;/span&gt;
        &lt;span class="s"&gt;echo "file path $($file.PSPath)"&lt;/span&gt;
        &lt;span class="s"&gt;(Get-Content $file.PSPath) | ForEach-Object{&lt;/span&gt;
            &lt;span class="s"&gt;if($_ -match $pattern){&lt;/span&gt;
                &lt;span class="s"&gt;'{0}{1}{2}' -f $matches[1],$newVersionTag,$matches[8]&lt;/span&gt;
            &lt;span class="s"&gt;} else {&lt;/span&gt;
                &lt;span class="s"&gt;# Output line as is&lt;/span&gt;
                &lt;span class="s"&gt;$_&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
        &lt;span class="s"&gt;} | Set-Content $file.PSPath&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;file'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables.IsRelease, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this helps you guys with automating your versioning process and streamlining your releases. Feel free to reach out if you have any questions or need further assistance. Happy coding! 😊&lt;/p&gt;

</description>
      <category>devops</category>
      <category>release</category>
      <category>cicd</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Streaming Azure DevOps auditlogs to azure monitor</title>
      <dc:creator>Frits Becker</dc:creator>
      <pubDate>Sat, 19 Apr 2025 12:06:45 +0000</pubDate>
      <link>https://dev.to/frits-becker/streaming-azure-devops-auditlogs-to-azure-monitor-2dbj</link>
      <guid>https://dev.to/frits-becker/streaming-azure-devops-auditlogs-to-azure-monitor-2dbj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a platform team, one of the platforms we manage is Azure DevOps. The users and teams using the platform keep growing, and with this growth, we need to implement more tools to maintain control and security without requiring additional time.&lt;/p&gt;

&lt;p&gt;One of the tasks is monitoring the audit logs. Unfortunately, the user interface within Azure DevOps is not user-friendly. The only filter available is a time range, but when you want to use the logs, you need to filter by team project, organization, or even the user. Another important tool that is missing are alerts. &lt;/p&gt;

&lt;h2&gt;
  
  
  Improving monitoring
&lt;/h2&gt;

&lt;p&gt;To help us improve the monitoring of these logs, we set up a stream to a log analytics workspace in Azure. However, even though we used the microsoft learn page "&lt;a href="https://learn.microsoft.com/en-us/azure/devops/organizations/audit/auditing-streaming?view=azure-devops#set-up-an-azure-monitor-log-stream" rel="noopener noreferrer"&gt;Create audit streaming&lt;/a&gt;", this did not work out of the box, and we received the error message "Forbidden".&lt;/p&gt;

&lt;p&gt;This meant that the log analytics resource returned an HTTP 403, even though we set it up as publicly available. After some research, we found out that the feature "disableLocalAuth" was set to true, which block connections with agent key's.&lt;/p&gt;

&lt;p&gt;After setting this to false, configuring the stream worked! An example of Bicep code to deploy the log analytics workspace is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module logWorkspaceAdo 'br/public:avm/res/operational-insights/workspace:0.11.0' = {
  params: {
    name: 'log-${resourceName}-001'
    location: location
    publicNetworkAccessForIngestion: 'Enabled'
    publicNetworkAccessForQuery: 'Enabled'
    features: {disableLocalAuth: false}
    tags: tags
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, we can easily implement workbooks, alerts, or create a report in Power BI.&lt;/p&gt;

&lt;p&gt;I hope this helps you set up the stream if you had the same problem as me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving security
&lt;/h2&gt;

&lt;p&gt;Even though this work, I still have some questions around security. The issues that I have are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public network access&lt;/li&gt;
&lt;li&gt;Using a key to connect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all other connections, we go through our own private network and where possible we try to do the authentication with federated credentials end managed identities. And if we really need the allow public network, we use serviceTags for the microsoft service, but I did not see this possibility in the Log Analytics Workspace.&lt;/p&gt;

&lt;p&gt;If you know how to improve security for this setup, please let me know.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>monitoring</category>
      <category>bicep</category>
    </item>
    <item>
      <title>Azure functions throw errors when diagnostic settings are enabled for the storage account.</title>
      <dc:creator>Frits Becker</dc:creator>
      <pubDate>Sat, 19 Apr 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/frits-becker/azure-functions-throw-errors-when-diagnostic-settings-are-enabled-for-the-storage-account-13ba</link>
      <guid>https://dev.to/frits-becker/azure-functions-throw-errors-when-diagnostic-settings-are-enabled-for-the-storage-account-13ba</guid>
      <description>&lt;h2&gt;
  
  
  Issue is fixed!!!
&lt;/h2&gt;

&lt;p&gt;The issue from this blog is fixed with &lt;a href="https://github.com/Azure/azure-functions-host/releases/tag/v4.839.500" rel="noopener noreferrer"&gt;v4.839.500&lt;/a&gt;, &lt;a href="https://github.com/Azure/azure-functions-host/releases/tag/v4.639.500" rel="noopener noreferrer"&gt;v4.639.500&lt;/a&gt; and &lt;a href="https://github.com/Azure/azure-functions-host/releases/tag/v4.1039.500" rel="noopener noreferrer"&gt;v4.1039.500&lt;/a&gt;. Check your logs for the host SDK and when it is updated, we should be able to remove whatever workaround is chosen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, we added a mandatory diagnostic setting to the blob service in our storage account module. This sends audit logs to a central log analytics workspace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  scope: blobService
  name: 'auditlogs'
  properties: {
    logs: [
      {
        enabled: true
        categoryGroup: 'audit'
      }
    ]
    workspaceId: auditWorkspace.id
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Issues Arise
&lt;/h2&gt;

&lt;p&gt;When updating the storage account module in the infra repository of a DevOps team, errors began to appear in their application insights. Errors like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error occurred when attempting to purge previous diagnostic event versions.&lt;/li&gt;
&lt;li&gt;Unable to get table reference or create table. Aborting write operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of them had Category "Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.DiagnosticEventTableStorageRepository"&lt;/p&gt;

&lt;h2&gt;
  
  
  Quest for answers
&lt;/h2&gt;

&lt;p&gt;We had no idea why this happened, so we began our quest and quickly found multiple issues on the "Azure Functions Host" GitHub repository.&lt;/p&gt;

&lt;p&gt;Luckily this bug was fixed in october last year with the PR '&lt;a href="https://github.com/Azure/azure-functions-host/pull/10548" rel="noopener noreferrer"&gt;Added support for identity-based connections to Diagnostic Events&lt;/a&gt;' and released on December 19th in version &lt;a href="https://github.com/Azure/azure-functions-host/releases/tag/v4.837.0" rel="noopener noreferrer"&gt;4.837.0&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We used a managed identity to assign the correct roles to the functions on the storage account. Upon checking the error logs, we discovered that we were using SDK version 4.1038.300.25164. But the problem was that downgrading is not possible. we tried to change FUNCTIONS_EXTENSION_VERSION, but it does not work as expected. So we had to find out why this error is back and how to fix this.&lt;/p&gt;

&lt;p&gt;We found out that MSFT updated to version 4.1038.300.25164 on 4/16/2025. Before that, the logs show version 4.1037.1.23605, where there were no problems. For now, we have to wait for a fix, but we can find 2 issues in the Azure Function Host GitHub with some temporary changes to avoid/ignore the errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/azure-functions-host/issues/10995" rel="noopener noreferrer"&gt;https://github.com/Azure/azure-functions-host/issues/10995&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/azure-functions-dotnet-worker/issues/3030" rel="noopener noreferrer"&gt;https://github.com/Azure/azure-functions-dotnet-worker/issues/3030&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Temporary solutions
&lt;/h2&gt;

&lt;p&gt;Here are some options that are given in the issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the time being, you can add a log filter for Microsoft.Azure.WebJobs.Script.WebHost.Diagnostics.DiagnosticEventTableStorageRepository to host.json and filter out logs.&lt;/li&gt;
&lt;li&gt;You can also disable this feature via an app setting: AzureWebJobsFeatureFlags=DisableDiagnosticEventLogging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PR we are waiting for to be released right now is &lt;a href="https://github.com/Azure/azure-functions-host/pull/10996" rel="noopener noreferrer"&gt;PR 10996&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>bicep</category>
      <category>csharp</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Managed identities</title>
      <dc:creator>Frits Becker</dc:creator>
      <pubDate>Fri, 21 Mar 2025 15:55:32 +0000</pubDate>
      <link>https://dev.to/frits-becker/manged-identities-1m57</link>
      <guid>https://dev.to/frits-becker/manged-identities-1m57</guid>
      <description>&lt;p&gt;In my work, I still see developers struggling with authentication and authorization, that’s why I want to create some posts to explain how simple this can be in the azure cloud, within the same tenant.&lt;/p&gt;

&lt;p&gt;We can use managed identities and they have multiple advantages. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no secrets&lt;/li&gt;
&lt;li&gt;automatic lifecycle management with a system managed identity&lt;/li&gt;
&lt;li&gt;almost no knowledge needed of Entra ID&lt;/li&gt;
&lt;li&gt;easy to understand RBAC roles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post will show, how you can use managed identities to connect to a azure service called key vault from a web app. One of the most basic things most developers will have to deal with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq26rg2spx74q8k4teaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuq26rg2spx74q8k4teaz.png" alt="simple diagram that shows how the web app will communicate with key vault" width="538" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before showing the code, I want to give a shout out to the &lt;a href="https://github.com/Azure/bicep-registry-modules" rel="noopener noreferrer"&gt;public bicep repository&lt;/a&gt;, this repo is perfect for learning bicep and quickly implementing IaC.&lt;/p&gt;

&lt;p&gt;When creating the infra, we need 3 modules:&lt;br&gt;
&lt;strong&gt;Optional: Create a user managed identity that will get the role assignments&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module managedIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.4.0' = {
  name: 'name'
  params: {
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create the key vault&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;a href="https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/key-vault/vault/README.md#parameter-roleassignments" rel="noopener noreferrer"&gt;https://github.com/Azure/bicep-registry-modules/blob/main/avm/res/key-vault/vault/README.md#parameter-roleassignments&lt;/a&gt; for more roles
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module vault 'br/public:avm/res/key-vault/vault:0.12.1' = {
  name: 'name'
  params: {
    roleAssignments: [
      { // Give managed identity a role to read the secrets
        principalId: managedIdentity.outputs.principalId
        principalType: 'ServicePrincipal'
        roleDefinitionIdOrName: 'Key Vault Secrets User'
      }
    ]
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a webapp&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module App 'br/public:avm/res/web/site:0.15.1' = {
  name: 'name'
  params: {
    kind: 'app'
    managedIdentities: { //assigning a managed identity to the web app to access the key vault secrets
      systemAssigned: false //set to true and remove line below if you only want this
      userAssignedResourceIds: [managedIdentity.outputs.resourceId]
    }
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;And a simple implementation within the .net web app&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme&lt;/a&gt; for more information about the authentication types and flows.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
using Azure.Identity;

var credentials = new ChainedTokenCredential(!builder.Environment.IsDevelopment() 
    ? new ManagedIdentityCredential(userManagedIdentityClientId) 
    : new VisualStudioCredential(), new AzureCliCredential(), new InteractiveBrowserCredential());

builder.Configuration.AddAzureKeyVault(new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"), credentials);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, you only need to deploy the infra and your web app and everything should work in the cloud!&lt;/p&gt;

&lt;p&gt;For testing locally, you need to add an extra role assignment to the keyvault, for your personal identity or a group that you are in.&lt;/p&gt;

&lt;p&gt;The bicep for keyvault would look something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module vault 'br/public:avm/res/key-vault/vault:0.12.1' = {
  name: 'name'
  params: {
    roleAssignments: [
      { // Give managed identity a role to read the secrets
        principalId: managedIdentity.outputs.principalId
        principalType: 'ServicePrincipal'
        roleDefinitionIdOrName: 'Key Vault Secrets User'
      }
      { // Give dev group a role to read the secrets for local testing
        principalId: 'GUID'
        principalType: 'Group'
        roleDefinitionIdOrName: 'Key Vault Secrets User'
      }
    ]
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this helps my fellow developers to easily use azure resources in your applications in a safe way.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>identity</category>
      <category>developers</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
