<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-AU" xmlns:media="http://search.yahoo.com/mrss/">
  <id>https://david.gardiner.net.au/tags/Azure%20DevOps.xml</id>
  <title type="html">David Gardiner - Azure DevOps</title>
  <updated>2026-05-12T00:30:32.898Z</updated>
  <subtitle>Blog posts tagged with &apos;Azure DevOps&apos; - A blog of software development, .NET and other interesting things</subtitle>
  <rights>Copyright 2026 David Gardiner</rights>
  <icon>https://www.gravatar.com/avatar/37edf2567185071646d62ba28b868fab?s=64</icon>
  <logo>https://www.gravatar.com/avatar/37edf2567185071646d62ba28b868fab?s=256</logo>
  <generator uri="https://github.com/flcdrg/astrojs-atom" version="3">astrojs-atom</generator>
  <author>
    <name>David Gardiner</name>
  </author>
  <link href="https://david.gardiner.net.au/tags/Azure%20DevOps.xml" rel="self" type="application/atom+xml"/>
  <link href="https://david.gardiner.net.au/tags/Azure%20DevOps" rel="alternate" type="text/html" hreflang="en-AU"/>
  <category term="Azure DevOps"/>
  <category term="Software Development"/>
  <entry>
    <id>https://david.gardiner.net.au/2024/12/sonarcloud</id>
    <updated>2024-12-15T22:00:00.000+10:30</updated>
    <title>.NET Code Coverage in Azure DevOps and SonarCloud</title>
    <link href="https://david.gardiner.net.au/2024/12/sonarcloud" rel="alternate" type="text/html" title=".NET Code Coverage in Azure DevOps and SonarCloud"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <published>2024-12-15T22:00:00.000+10:30</published>
    <summary type="html">Using SonarCloud to display code coverage with a .NET application managed with Azure DevOps</summary>
    <content type="html">&lt;p&gt;Sonar offer some really useful products for analysing the quality of your application&apos;s source code. There&apos;s a great mix of free and paid products, including SonarQube Cloud (formerly known as SonarCloud), SonarQube Server (for on-prem), and &lt;a href=&quot;https://docs.sonarsource.com/sonarqube-for-visual-studio&quot;&gt;SonarQube for IDE&lt;/a&gt; (formerly SonarLint) static code analysers for IntelliJ, Visual Studio, VS Code and Eclipse.&lt;/p&gt;
&lt;p&gt;I was looking to integrate an Azure DevOps project containing a .NET application with SonarQube Cloud, and in particular include code coverage data both for Azure Pipelines (so you can view the coverage in the pipeline run), but also in SonarQube Cloud.&lt;/p&gt;
&lt;p&gt;This process is quite similar if you&apos;re using the self-hosted SonarQube Server product, though note that there are different Azure Pipeline tasks provided by a different extension for SonarQube Server.&lt;/p&gt;
&lt;p&gt;A sample project can be found at &lt;a href=&quot;https://dev.azure.com/gardiner/SonarCloudDemo&quot;&gt;https://dev.azure.com/gardiner/SonarCloudDemo&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You have a SonarQube Cloud account.&lt;/li&gt;
&lt;li&gt;You&apos;ve configured it to be integrated with your Azure DevOps organisation.&lt;/li&gt;
&lt;li&gt;You&apos;ve installed the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud&quot;&gt;SonarQube Cloud extension&lt;/a&gt; (or &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube&quot;&gt;SonarQube Server extension&lt;/a&gt; if you&apos;re using SonarQube Server)&lt;/li&gt;
&lt;li&gt;You&apos;ve created a service connection in the Azure DevOps project pointing to SonarQube Cloud.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;ve created a .NET solution which contains a simple ASP.NET web application and an xUnit test project.&lt;/p&gt;
&lt;p&gt;By default, when you add a new xUnit test project, it includes a reference to the &lt;a href=&quot;https://www.nuget.org/packages/coverlet.collector&quot;&gt;coverlet.collector&lt;/a&gt; NuGet package. This implements a &apos;Data Collector&apos; for the VSTest platform. Normally you&apos;d run this via:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet test --collect:&quot;XPlat Code Coverage&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You would then end up with a &lt;code&gt;TestResults&lt;/code&gt; subdirectory which contains a &lt;code&gt;coverage.cobertura.xml&lt;/code&gt; file. But the problem here is that the xml file is one level deeper - VSTest creates GUID-named subdirectory under TestResults. So you will need to go searching for the file, there&apos;s no way to ensure it gets created in a known location.&lt;/p&gt;
&lt;p&gt;It turns out that&apos;s a problem for Sonar, as the SonarCloudPrepare task needs to be told where the code coverage file is located, and unfortunately that property doesn&apos;t support wildcards!&lt;/p&gt;
&lt;p&gt;We can solve that problem by removing the reference to &lt;code&gt;coverlet.collector&lt;/code&gt;, and instead adding a package reference to &lt;code&gt;coverlet.msbuild&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet remove package coverlet.collector
dotnet add package coverlet.msbuild
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To collect code coverage information with this package, you run it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet test /p:CollectCoverage=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But more importantly, it supports additional parameters so we can now fix the location of output files. The &lt;code&gt;CoverletOutput&lt;/code&gt; property lets us define the directory (relative to the test project) where output files will be written.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet test /p:CollectCoverage=true /p:CoverletOutput=&apos;./results/coverage&apos; /p:CoverletOutputFormat=cobertura
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that I&apos;ve not just set &lt;code&gt;CoverletOutput&lt;/code&gt; to the directory (&lt;code&gt;results&lt;/code&gt;), but also the first part of the coverage filename (&lt;code&gt;coverage&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;In the pipeline task, you can let SonarQube know where the file is by setting &lt;code&gt;sonar.cs.opencover.reportsPaths&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  - task: SonarCloudPrepare@3
    inputs:
      SonarQube: &quot;SonarCloud&quot;
      organization: &quot;gardiner&quot;
      scannerMode: &quot;dotnet&quot;
      projectKey: &quot;Gardiner_SonarCloudDemo&quot;
      projectName: &quot;SonarCloudDemo&quot;
      extraProperties: |
        sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/Tests/results/coverage.opencover.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SonarQube and Azure Pipelines coverage&lt;/h2&gt;
&lt;p&gt;So now we&apos;ve solved the problem of where the coverage file will be saved. Can we also deliver the coverage data to both SonarQube &lt;em&gt;and&lt;/em&gt; Azure Pipelines?&lt;/p&gt;
&lt;p&gt;Let&apos;s review what we need to make that happen.&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md&quot;&gt;the docs for coverlet.msbuild&lt;/a&gt;, it supports generating the following formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;json (default)&lt;/li&gt;
&lt;li&gt;lcov&lt;/li&gt;
&lt;li&gt;opencover&lt;/li&gt;
&lt;li&gt;cobertura&lt;/li&gt;
&lt;li&gt;teamcity*&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(The TeamCity format just generates special service messages in the standard output that TeamCity will recognise, it doesn&apos;t create a file)&lt;/p&gt;
&lt;p&gt;According to &lt;a href=&quot;https://docs.sonarsource.com/sonarqube-cloud/enriching/test-coverage/dotnet-test-coverage&quot;&gt;the docs for SonarCloud&lt;/a&gt;, it supports the following formats for .NET code coverage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio Code Coverage&lt;/li&gt;
&lt;li&gt;dotnet-coverage Code Coverage&lt;/li&gt;
&lt;li&gt;dotCover&lt;/li&gt;
&lt;li&gt;OpenCover&lt;/li&gt;
&lt;li&gt;Coverlet (OpenCover format)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.sonarsource.com/sonarqube-cloud/enriching/test-coverage/generic-test-data&quot;&gt;Generic test data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The docs for the Azure Pipelines &lt;a href=&quot;https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v2?view=azure-pipelines&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;PublishCodeCoverageResults@2 task&lt;/a&gt; don&apos;t actually mention which formats are supported (hopefully this will be fixed soon). But in the &lt;a href=&quot;https://devblogs.microsoft.com/devops/new-pccr-task/&quot;&gt;blog post that announced the availability of the v2 task&lt;/a&gt; the following formats were mentioned (including ones from the v1 task):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cobertura&lt;/li&gt;
&lt;li&gt;JaCoCo&lt;/li&gt;
&lt;li&gt;.coverage&lt;/li&gt;
&lt;li&gt;.covx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So unfortunately there isn&apos;t a single format that all three components understand. Instead we will have to ask &lt;code&gt;coverlet.msbuild&lt;/code&gt; to generate two output files - &lt;strong&gt;OpenCover&lt;/strong&gt; for SonarQube, and &lt;strong&gt;Cobertura&lt;/strong&gt; for Azure Pipelines.&lt;/p&gt;
&lt;p&gt;We want to generate two outputs, but there is a &lt;a href=&quot;https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#note-for-linux-users&quot;&gt;known problem with trying to pass in parameters to dotnet test on Linux&lt;/a&gt;. The workaround is to set properties in the csproj file instead.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;PropertyGroup&amp;gt;
  &amp;lt;CoverletOutputFormat&amp;gt;opencover,cobertura&amp;lt;/CoverletOutputFormat&amp;gt;
&amp;lt;/PropertyGroup&amp;gt;          
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our Azure Pipeline should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;steps:
  - checkout: self
    fetchDepth: 0
    
  - task: SonarCloudPrepare@3
    inputs:
      SonarQube: &quot;SonarCloud&quot;
      organization: &quot;gardiner&quot;
      scannerMode: &quot;dotnet&quot;
      projectKey: &quot;Gardiner_SonarCloudDemo&quot;
      projectName: &quot;SonarCloudDemo&quot;
      extraProperties: |
        # Additional properties that will be passed to the scanner, put one key=value per line

        # Disable Multi-Language analysis
        sonar.scanner.scanAll=false

        # Configure location of the OpenCover report
        sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/Tests/results/coverage.opencover.xml

  - task: DotNetCoreCLI@2
    inputs:
      command: build

  - task: DotNetCoreCLI@2
    inputs:
      command: test
      projects: &quot;Tests/Tests.csproj&quot;
      arguments: &quot;/p:CollectCoverage=true /p:CoverletOutput=results/coverage&quot;

  - task: SonarCloudAnalyze@3
    inputs:
      jdkversion: &quot;JAVA_HOME_17_X64&quot;

  - task: SonarCloudPublish@3
    inputs:
      pollingTimeoutSec: &quot;300&quot;

  - task: PublishCodeCoverageResults@2
    inputs:
      summaryFileLocation: &quot;$(Build.SourcesDirectory)/Tests/results/coverage.cobertura.xml&quot;
      failIfCoverageEmpty: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to point out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We&apos;re doing a full Git clone (not shallow) so that SonarQube can do a proper analysis. This avoids you seeing warnings like this:&lt;ul&gt;
&lt;li&gt;[INFO]  SonarQube Cloud: Analysis succeeded with warning: Could not find ref &apos;main&apos; in refs/heads, refs/remotes/upstream or refs/remotes/origin. You may see unexpected issues and changes. Please make sure to fetch this ref before pull request analysis.&lt;/li&gt;
&lt;li&gt;[INFO]  SonarQube Cloud: Analysis succeeded with warning: Shallow clone detected during the analysis. Some files will miss SCM information. This will affect features like auto-assignment of issues. Please configure your build to disable shallow clone.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;sonar.scanner.scanAll=false&lt;/code&gt; to avoid this warning:&lt;ul&gt;
&lt;li&gt;[INFO]  SonarQube Cloud: Analysis succeeded with warning: Multi-Language analysis is enabled. If this was not intended and you have issues such as hitting your LOC limit or analyzing unwanted files, please set &quot;/d:sonar.scanner.scanAll=false&quot; in the begin step.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And now we can view our code coverage in SonarQube:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/coverage-sonarqube1.B66ordRi_Z18g5QJ.webp&quot; alt=&quot;SonarCloud project overview showing code coverage history&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/coverage-sonarqube2.B62u2ZY4_ai3Xi.webp&quot; alt=&quot;SonarCloud pull request file code coverage summary&quot; /&gt;
And in Azure Pipelines!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/coverage-azure-pipelines.3J7EQ4Iq_Z16OkpR.webp&quot; alt=&quot;Azure Pipeline run showing code coverage tab&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Check out the example project at &lt;a href=&quot;https://dev.azure.com/gardiner/_git/SonarCloudDemo&quot;&gt;https://dev.azure.com/gardiner/_git/SonarCloudDemo&lt;/a&gt;, and you can view the SonarQube analysis at &lt;a href=&quot;https://sonarcloud.io/project/overview?id=Gardiner_SonarCloudDemo&quot;&gt;https://sonarcloud.io/project/overview?id=Gardiner_SonarCloudDemo&lt;/a&gt;&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2024/05/docker-run-mount</id>
    <updated>2024-05-10T17:30:00.000+09:30</updated>
    <title>Docker run from an Azure Pipeline Container Job with a volume mount</title>
    <link href="https://david.gardiner.net.au/2024/05/docker-run-mount" rel="alternate" type="text/html" title="Docker run from an Azure Pipeline Container Job with a volume mount"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <published>2024-05-10T17:30:00.000+09:30</published>
    <summary type="html">How to run Docker with volume mounts inside an Azure Pipelines container job and avoid common pitfalls.</summary>
    <content type="html">&lt;p&gt;This caught me out today. I was trying to run a Docker container directly from a script task, where the pipeline job was already running in a container (as a &lt;a href=&quot;https://learn.microsoft.com/azure/devops/pipelines/process/container-phases?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Container Job&lt;/a&gt;), similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  - job: MyJob
    container:
      image: my-container-job-image:latest

    steps:
      - script: |
          docker run --mount type=bind,source=&quot;$(pwd)&quot;,target=/home/src --rm -w /home/src my-container:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bit that was failing was the &lt;code&gt;--mount&lt;/code&gt;, with the following error message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker: Error response from daemon: invalid mount config for type &quot;bind&quot;: bind source path does not exist: /__w/3/s.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Eventually, I realised the problem - By default, when a job is running as a Container Job, all the tasks are also running in the context of that container. So &lt;code&gt;$(pwd)&lt;/code&gt; was resolving to &lt;code&gt;/__w/3/s&lt;/code&gt;. That happens to be the default directory, and also where your source code is mapped to (via a volume mount that you can see by viewing the output of the &quot;Initialize containers&quot; step).&lt;/p&gt;
&lt;p&gt;But when you invoke &lt;code&gt;docker run&lt;/code&gt;, Docker doesn&apos;t try and run the new container inside the existing Container Job container, rather it will run alongside it! So any paths you pass to Docker need to be relative to the host machine, not relative to inside the container job.&lt;/p&gt;
&lt;p&gt;In my case, the solution was to add a &lt;code&gt;target: host&lt;/code&gt; property to the script task, so that the entire script is now run in the context of the host, rather than the container. eg.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      - script: |
          docker run --mount type=bind,source=&quot;$(pwd)&quot;,target=/home/src --rm -w /home/src my-container:latest
        target: host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when the pipeline runs, &lt;code&gt;$(pwd)&lt;/code&gt; will resolve to &lt;code&gt;/agent/_work/3/s&lt;/code&gt; (which is the actual directory on the host machine), and the mount will work correctly!&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2023/06/propertycollection</id>
    <updated>2023-06-20T22:30:00.000+09:30</updated>
    <title>Azure DevOps API PropertiesCollections</title>
    <link href="https://david.gardiner.net.au/2023/06/propertycollection" rel="alternate" type="text/html" title="Azure DevOps API PropertiesCollections"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2023-06-20T22:30:00.000+09:30</published>
    <summary type="html">Clarifies how Azure DevOps API PropertiesCollection values are represented and how to work with them in real responses.</summary>
    <content type="html">&lt;p&gt;I was looking at some of the Azure DevOps API documentation and noticed that some of the endpoints mention a &lt;code&gt;properties&lt;/code&gt; object of type &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-7.0&amp;amp;WT.mc_id=DOP-MVP-5001655#propertiescollection&quot;&gt;&lt;code&gt;PropertiesCollection&lt;/code&gt;&lt;/a&gt;. Unfortunately, the details for that data structure are not particularly helpful, and I couldn&apos;t figure out how to use it. Some pages include examples, but none that I could find included an expanded &lt;code&gt;properties&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;To figure out how to use it, I created a simple .NET console application. I added references to the following NuGet packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft.TeamFoundationServer.Client&lt;/li&gt;
&lt;li&gt;Microsoft.VisualStudio.Services.InteractiveClient&lt;/li&gt;
&lt;li&gt;Microsoft.VisualStudio.Services.Release.Client&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Clients;
using Microsoft.VisualStudio.Services.WebApi;

const string collectionUri = &quot;https://dev.azure.com/organisation&quot;;
const string projectName = &quot;MyProject&quot;;
const string pat = &quot;YOUR-PAT-HERE&quot;;
const int releaseId = 20;

var creds = new VssBasicCredential(string.Empty, pat);

// Connect to Azure DevOps Services
var connection = new VssConnection(new Uri(collectionUri), creds);

using var client = connection.GetClient&amp;lt;ReleaseHttpClient&amp;gt;();

// Get data about a specific release
var release = await client.GetReleaseAsync(projectName, releaseId);

release.Properties.Add(&quot;Thing&quot;, &quot;hey&quot;);

// Send the updated release back to Azure DevOps Services
var result = await client.UpdateReleaseAsync(release!, projectName, releaseId);

Console.WriteLine();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allowed me to create a property key and value, that I could then examine by querying the item (in this case a &apos;classic&apos; release), by calling the GET endpoint. eg.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases/{releaseId}?propertyFilters=Thing&amp;amp;api-version=7.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you need to specify the &lt;code&gt;propertyFilters&lt;/code&gt; parameter. Otherwise the `properties`` object will not be included in the response.&lt;/p&gt;
&lt;p&gt;And in doing that, we can see the JSON data structure!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &quot;properties&quot;: {
        &quot;Thing&quot;: {
            &quot;$type&quot;: &quot;System.String&quot;,
            &quot;$value&quot;: &quot;hey&quot;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, to add a property, you need to add a new key/value pair to the &lt;code&gt;properties&lt;/code&gt; object, where the key is the name of the property, and the value is an object with two properties: &lt;code&gt;$type&lt;/code&gt; and &lt;code&gt;$value&lt;/code&gt;. The &lt;code&gt;$type&lt;/code&gt; property is the type of the value, and the &lt;code&gt;$value&lt;/code&gt; property is the value itself.&lt;/p&gt;
&lt;p&gt;The documentation clarifies the types supported:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Values of type Byte[], Int32, Double, DateType and String preserve their type, other primitives are retuned as a String. Byte[] expected as base64 encoded string.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(I think &apos;DateType&apos; is a typo, and should be &apos;DateTime&apos;)&lt;/p&gt;
&lt;p&gt;Now that we know the shape of the data, I can jump back to PowerShell and use that to add a new property:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$uri = &quot;https://vsrm.dev.azure.com/$($organisation)/$($project)/_apis/release/releases/$($releaseId)?api-version=7.0&amp;amp;propertyFilters=Extra&quot;

$result = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers

if (-not ($result.properties.Extra)) {
    $result.properties | Add-Member -MemberType NoteProperty -Name &quot;Extra&quot; -Value @{
        &quot;`$type&quot; = &quot;System.String&quot;
        &quot;`$value&quot; = &quot;haaaa&quot;
    }
}
$body = $result | ConvertTo-Json -Depth 20

&quot;Updating via PUT&quot;

Invoke-RestMethod -Uri $uri -Method Put -Headers $headers -Body $body -ContentType &quot;application/json&quot;
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2023/02/list-azure-pipelines-and-yaml</id>
    <updated>2023-02-17T12:00:00.000+10:30</updated>
    <title>Get a list of Azure Pipelines and YAML files</title>
    <link href="https://david.gardiner.net.au/2023/02/list-azure-pipelines-and-yaml" rel="alternate" type="text/html" title="Get a list of Azure Pipelines and YAML files"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2023-02-17T12:00:00.000+10:30</published>
    <summary type="html">Shows how to query Azure DevOps to list pipelines and the YAML files they reference, so project documentation stays accurate.</summary>
    <content type="html">&lt;p&gt;I wanted to document the pipelines in a particular Azure DevOps project. Rather than manually write up the name of each pipeline and the corresponding YAML file, I figured there must be a way to query that data.&lt;/p&gt;
&lt;p&gt;I&apos;ve done &lt;a href=&quot;/2021/06/azure-devops-list-pipelines&quot;&gt;something similar in the past using the Azure DevOps REST API&lt;/a&gt;, but this time I&apos;m using the &lt;a href=&quot;https://learn.microsoft.com/cli/azure/what-is-azure-cli?view=azure-cli-latest&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Azure CLI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Make sure you have the &lt;a href=&quot;https://learn.microsoft.com/cli/azure/service-page/azure%20devops?view=azure-cli-latest&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;&lt;code&gt;devops&lt;/code&gt;&lt;/a&gt; extension installed (&lt;code&gt;az extension add --name azure-devops&lt;/code&gt; if you don&apos;t have it already). The commands provided by this extension use the same REST API under the hood that we used directly last time.&lt;/p&gt;
&lt;p&gt;I can get a list of pipelines for the current project with &lt;a href=&quot;https://learn.microsoft.com/cli/azure/pipelines?view=azure-cli-latest&amp;amp;WT.mc_id=DOP-MVP-5001655#az-pipelines-list&quot;&gt;&lt;code&gt;az pipelines list&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This command returns a list of objects corresponding to the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/build/definitions/list?view=azure-devops-rest-7.1&amp;amp;WT.mc_id=DOP-MVP-5001655#builddefinitionreference&quot;&gt;BuildDefinitionReference&lt;/a&gt; data structure. While it has the pipeline name, I noticed that doesn&apos;t include any information about the YAML file. To get that you need query an individual pipeline using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;az pipelines show --name PipelineName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces a &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/build/definitions/get?view=azure-devops-rest-7.1&amp;amp;WT.mc_id=DOP-MVP-5001655#builddefinition&quot;&gt;BuildDefinition&lt;/a&gt; object, which happens to include a &lt;code&gt;process&lt;/code&gt; property. While it isn&apos;t documented in the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/build/definitions/get?view=azure-devops-rest-7.1&amp;amp;WT.mc_id=DOP-MVP-5001655#buildprocess&quot;&gt;BuildProcess&lt;/a&gt; data structure, if you look at the actual data you&apos;ll see not only the &lt;code&gt;type&lt;/code&gt; property but a &lt;code&gt;yamlFilename&lt;/code&gt; property, which is just what we want.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;process&quot;: {
    &quot;type&quot;: 2,
    &quot;yamlFilename&quot;: &quot;release.yaml&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting it all together, and taking advantage of the &lt;a href=&quot;https://learn.microsoft.com/cli/azure/query-azure-cli?WT.mc_id=DOP-MVP-5001655&quot;&gt;JMESPath query&lt;/a&gt; to limit which fields we get back, I can produce a comma-separated list of pipeline names and their corresponding YAML files with the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(az pipelines list --query &quot;[].name&quot; --query-order NameAsc -o tsv) | % { (az pipelines show --name $_ --query &quot;[name, process.yamlFilename]&quot; | ConvertFrom-Json) -join &quot;,&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So for this project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/azure-pipelines-all.wXh0uce2_1q3KMw.webp&quot; alt=&quot;Screenshot of Azure Pipelines in a project&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You get this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Alternate,alternate.yml
ReleaseTest,release.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will be more useful in a project with many more pipelines. If the project has multiple repositories you could also include the repository name as well.&lt;/p&gt;
&lt;p&gt;e.g.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(az pipelines list --query &quot;[].name&quot; --query-order NameAsc -o tsv) | % { (az pipelines show --name $_ --query &quot;[name, process.yamlFilename, repository.name]&quot; | ConvertFrom-Json) -join &quot;,&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Such a project would produce something similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Custom Git,azure-pipelines.yml,Repro
Repro,azure-pipelines.yml,Repro
task-test,azure-pipelines.yml,task-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can include extra columns of data as needed.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/azure-pipelines-logo.B45UakAg.png" width="80" height="80"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2022/02/passed-az400</id>
    <updated>2022-02-23T19:30:00.000+10:30</updated>
    <title>Passed AZ-400</title>
    <link href="https://david.gardiner.net.au/2022/02/passed-az400" rel="alternate" type="text/html" title="Passed AZ-400"/>
    <category term="Azure"/>
    <category term="Azure DevOps"/>
    <category term="DevOps"/>
    <category term="GitHub"/>
    <category term="Training and Certification"/>
    <published>2022-02-23T19:30:00.000+10:30</published>
    <summary type="html">I&apos;m pleased to report that today I passed Microsoft exam AZ-400: Designing and Implementing Microsoft DevOps Solutions, which combined with AZ-201 that I took last year, now qualifies me for the Microsoft Certified.</summary>
    <content type="html">&lt;p&gt;I&apos;m pleased to report that today I passed Microsoft exam &lt;a href=&quot;https://learn.microsoft.com/credentials/certifications/exams/az-400/?WT.mc_id=DOP-MVP-5001655&quot;&gt;AZ-400: Designing and Implementing Microsoft DevOps Solutions&lt;/a&gt;, which combined with &lt;a href=&quot;/2021/07/passed-az-204&quot;&gt;AZ-201 that I took last year&lt;/a&gt;, now qualifies me for the &lt;a href=&quot;https://learn.microsoft.com/credentials/certifications/devops-engineer/?WT.mc_id=DOP-MVP-5001655&quot;&gt;Microsoft Certified: DevOps Engineer Expert certification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.credly.com/badges/6f929582-8328-48d1-b65b-0dcd99fb7cd8/public_url&quot;&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io_2upomc.webp&quot; alt=&quot;Microsoft Certified: DevOps Engineer Expert badge&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.credly.com/badges/6f929582-8328-48d1-b65b-0dcd99fb7cd8/public_url&quot;&gt;View my verified achievement from Microsoft&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The exam is quite broad in the content it covers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Develop an instrumentation strategy (5-10%)&lt;/li&gt;
&lt;li&gt;Develop a Site Reliability Engineering (SRE) strategy (5-10%)&lt;/li&gt;
&lt;li&gt;Develop a security and compliance plan (10-15%)&lt;/li&gt;
&lt;li&gt;Manage source control (10-15%)&lt;/li&gt;
&lt;li&gt;Facilitate communication and collaboration (10-15%)&lt;/li&gt;
&lt;li&gt;Define and implement continuous integration (20-25%)&lt;/li&gt;
&lt;li&gt;Define and implement a continuous delivery and release management strategy (10-15%)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some areas I&apos;d been working with for quite a few years, but others were new to me. To help prepare I used a couple of resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/learn/certifications/exams/az-400?WT.mc_id=DOP-MVP-5001655#two-ways-to-prepare&quot;&gt;Microsoft Learn content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.archive.org/web/20230522220903/https://www.pluralsight.com/paths/designing-and-implementing-microsoft-devops-solutions-az-400&quot;&gt;Pluralsight certification prep path&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nice to get that one dusted.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io.png" width="300" height="300"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/microsoft-certified-devops-engineer-expert.DB_fO9io.png" width="300" height="300"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2021/06/azure-devops-list-release-definitions-and-builds</id>
    <updated>2021-06-17T20:00:00.000+09:30</updated>
    <title>Azure DevOps PowerShell Scripts - List all release definitions and dependant builds</title>
    <link href="https://david.gardiner.net.au/2021/06/azure-devops-list-release-definitions-and-builds" rel="alternate" type="text/html" title="Azure DevOps PowerShell Scripts - List all release definitions and dependant builds"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2021-06-17T20:00:00.000+09:30</published>
    <summary type="html">It&apos;s easy to open a specific release definition in the Azure DevOps web UI to find out what builds it references, but you can&apos;t do the opposite - open a build to see which release definitions rely on it.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/devopsiconpipelines96.Oa2ZrJjs.svg&quot; alt=&quot;Azure Pipelines logo&quot; /&gt;
It&apos;s easy to open a specific release definition in the Azure DevOps web UI to find out what builds it references, but you can&apos;t do the opposite - open a build to see which release definitions rely on it.&lt;/p&gt;
&lt;p&gt;Last time we got a &lt;a href=&quot;/2021/06/azure-devops-list-release-definitions&quot;&gt;list of Azure Pipelines Release Definitions&lt;/a&gt;. Let&apos;s go the next step and match up the builds that are being consumed by each release definition. With this list, we can now filter down to a build and find which releases use it.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://learn.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Personal access tokens&lt;/a&gt; for instructions on how to create the personal access token.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;param (
    [string] $organisation,
    [string] $personalAccessToken
)

$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(&quot;:$($personalAccessToken)&quot;))
$headers = @{Authorization=(&quot;Basic {0}&quot; -f $base64AuthInfo)}

$result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/_apis/projects?api-version=6.0&quot; -Method Get -Headers $headers

$projectNames = $result.value.name

$projectNames | ForEach-Object {
    $project = $_

    $result = Invoke-RestMethod -Uri &quot;https://vsrm.dev.azure.com/$organisation/$project/_apis/release/definitions?api-version=6.1-preview.4&amp;amp;`$expand=artifacts&quot; -Method Get -Headers $headers

    $result.value | Select-Object name, 
        @{ Name = &quot;Url&quot;; Expression = { $_._links.web.href }},
        @{ Name=&quot;Artifacts&quot;; Expression= { $_.artifacts.alias }}, 
        @{ Name=&quot;artifactSourceDefinitionUrl&quot;; Expression= { $_.artifacts.definitionReference.artifactSourceDefinitionUrl.id }}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This script also makes use of the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-6.0&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Definitions - List&lt;/a&gt; REST API. This time we&apos;re requesting the &lt;code&gt;artifacts&lt;/code&gt; property to be included in the results. It is this property that contains information about builds and their artifacts that are being consumed by the release definition.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/devops-pipelines-logo.B5e7SH2Y.png" width="291" height="295"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/devops-pipelines-logo.B5e7SH2Y.png" width="291" height="295"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2021/06/azure-devops-list-release-definitions</id>
    <updated>2021-06-11T09:00:00.000+09:30</updated>
    <title>Azure DevOps PowerShell Scripts - List all release definitions</title>
    <link href="https://david.gardiner.net.au/2021/06/azure-devops-list-release-definitions" rel="alternate" type="text/html" title="Azure DevOps PowerShell Scripts - List all release definitions"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2021-06-11T09:00:00.000+09:30</published>
    <summary type="html">Note that Release definitions are part of the &quot;classic&quot; release pipelines.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/devopsiconpipelines96.Oa2ZrJjs.svg&quot; alt=&quot;Azure Pipelines logo&quot; /&gt; If you want to list all the Azure Pipelines Release Definitions for all projects in an Azure DevOps organisation, this script will return a list of their names, the date of the latest release and a link to view the definition within the web UI.&lt;/p&gt;
&lt;p&gt;Note that Release definitions are part of the &quot;classic&quot; &lt;a href=&quot;https://learn.microsoft.com/azure/devops/pipelines/release/?view=azure-devops&quot;&gt;release pipelines&lt;/a&gt;. If you&apos;re using YAML-based deployments then those will be viewable via the &lt;a href=&quot;/2021/06/azure-devops-list-pipelines&quot;&gt;Pipelines script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://learn.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Personal access tokens&lt;/a&gt; for instructions on how to create the personal access token.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;param (
    [string] $organisation,
    [string] $personalAccessToken
)

$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(&quot;:$($personalAccessToken)&quot;))
$headers = @{Authorization=(&quot;Basic {0}&quot; -f $base64AuthInfo)}

$result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/_apis/projects?api-version=6.0&quot; -Method Get -Headers $headers

$projectNames = $result.value.name

$projectNames | ForEach-Object {
    $project = $_

    $result = Invoke-RestMethod -Uri &quot;https://vsrm.dev.azure.com/$organisation/$project/_apis/release/definitions?api-version=6.1-preview.4&amp;amp;`$expand=lastRelease&quot; -Method Get -Headers $headers

    $result.value | Select-Object name, @{ Name=&quot;CreatedOn&quot;; Expression= { $_.lastRelease.createdOn }}, @{ Name = &quot;Url&quot;; Expression = { $_._links.web.href }}
} | Sort-Object 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It makes use of the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-6.0&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Definitions - List&lt;/a&gt; REST API. Note that the documentation for that API is slightly misleading in the examples. You &lt;em&gt;do&lt;/em&gt; need to pass in &lt;code&gt;$expand=lastRelease&lt;/code&gt; to get the &lt;code&gt;lastRelease&lt;/code&gt; property populated.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/devops-pipelines-logo.B5e7SH2Y.png" width="291" height="295"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/devops-pipelines-logo.B5e7SH2Y.png" width="291" height="295"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2021/06/azure-devops-list-pipelines</id>
    <updated>2021-06-10T09:00:00.000+09:30</updated>
    <title>Azure DevOps PowerShell Scripts - List all Azure Pipelines</title>
    <link href="https://david.gardiner.net.au/2021/06/azure-devops-list-pipelines" rel="alternate" type="text/html" title="Azure DevOps PowerShell Scripts - List all Azure Pipelines"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2021-06-10T09:00:00.000+09:30</published>
    <summary type="html">See Personal access tokens for instructions on how to create the personal access token.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/devopsiconpipelines96.Oa2ZrJjs.svg&quot; alt=&quot;Azure Pipelines logo&quot; /&gt; If you want to list all the Azure Pipelines for all projects in an Azure DevOps organisation, this script will return a list of their names.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://learn.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Personal access tokens&lt;/a&gt; for instructions on how to create the personal access token.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;param (
    [string] $organisation,
    [string] $personalAccessToken
)

$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(&quot;:$($personalAccessToken)&quot;))
$headers = @{Authorization=(&quot;Basic {0}&quot; -f $base64AuthInfo)}

$result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/_apis/projects?api-version=6.0&quot; -Method Get -Headers $headers

$projectNames = $result.value.name

$projectNames | ForEach-Object {
    $project = $_

    $result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/$project/_apis/pipelines?api-version=6.0-preview.1&quot; -Method Get -Headers $headers

    $result.value.name
} | Sort-Object
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It makes use of the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/pipelines/pipelines/list?view=azure-devops-rest-6.0&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Pipelines - List&lt;/a&gt; REST API, so you could ask for any of the other properties instead of or in addition to &lt;code&gt;name&lt;/code&gt; as well.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/devopsiconpipelines96.Oa2ZrJjs.svg" width="96" height="96"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/devopsiconpipelines96.Oa2ZrJjs.svg" width="96" height="96"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2021/06/azure-devops-list-git-repositories</id>
    <updated>2021-06-09T12:30:00.000+09:30</updated>
    <title>Azure DevOps PowerShell Scripts - List all Git repositories</title>
    <link href="https://david.gardiner.net.au/2021/06/azure-devops-list-git-repositories" rel="alternate" type="text/html" title="Azure DevOps PowerShell Scripts - List all Git repositories"/>
    <category term="Azure DevOps"/>
    <category term="PowerShell"/>
    <category term="Git"/>
    <published>2021-06-09T12:30:00.000+09:30</published>
    <summary type="html">See Personal access tokens for instructions on how to create the personal access token.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&quot;https://david.gardiner.net.au/_astro/devopsiconrepos96.B5OvG0Wx.svg&quot; alt=&quot;Azure Repos logo&quot; /&gt; If you want to list all the Git repositories for all projects in an Azure DevOps organisation, this script will return all the remote URLs.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://learn.microsoft.com/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Personal access tokens&lt;/a&gt; for instructions on how to create the personal access token.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;param (
    [string] $organisation,
    [string] $personalAccessToken
)

$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(&quot;:$($personalAccessToken)&quot;))
$headers = @{Authorization=(&quot;Basic {0}&quot; -f $base64AuthInfo)}

$result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/_apis/projects?api-version=6.0&quot; -Method Get -Headers $headers

$projectNames = $result.value.name

$projectNames | ForEach-Object {
    $project = $_

    $result = Invoke-RestMethod -Uri &quot;https://dev.azure.com/$organisation/$project/_apis/git/repositories?api-version=6.0&quot; -Method Get -Headers $headers

    $result.value.remoteUrl
} | Sort-Object
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It makes use of the &lt;a href=&quot;https://learn.microsoft.com/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-6.0&amp;amp;WT.mc_id=DOP-MVP-5001655&quot;&gt;Repositories - List&lt;/a&gt; REST API, so you could ask for any of the other properties instead of or in addition to &lt;code&gt;remoteUrl&lt;/code&gt; as well.&lt;/p&gt;
</content>
    <media:thumbnail url="https://david.gardiner.net.au/_astro/devopsiconrepos96.B5OvG0Wx.svg" width="96" height="96"/>
    <media:content medium="image" url="https://david.gardiner.net.au/_astro/devopsiconrepos96.B5OvG0Wx.svg" width="96" height="96"/>
  </entry>
  <entry>
    <id>https://david.gardiner.net.au/2020/04/azure-pipelines-powershell-7-errors</id>
    <updated>2020-04-30T15:00:00.000+09:30</updated>
    <title>Errors from PowerShell 7 in Azure Pipelines</title>
    <link href="https://david.gardiner.net.au/2020/04/azure-pipelines-powershell-7-errors" rel="alternate" type="text/html" title="Errors from PowerShell 7 in Azure Pipelines"/>
    <category term="Azure DevOps"/>
    <category term="Azure Pipelines"/>
    <category term="PowerShell"/>
    <published>2020-04-30T15:00:00.000+09:30</published>
    <summary type="html">I&apos;m aiming to use PowerShell 7 as much as possible, including in Azure Pipelines tasks.</summary>
    <content type="html">&lt;p&gt;I&apos;m aiming to use PowerShell 7 as much as possible, including in Azure Pipelines tasks. I was getting a bit frustrated recently though where I had a task failing, but the log gave absolutely no clue as to why.&lt;/p&gt;
&lt;p&gt;eg.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Starting: MyTask
==============================================================================
Task         : PowerShell
Description  : Run a PowerShell script on Linux, macOS, or Windows
Version      : 2.165.0
Author       : Microsoft Corporation
Help         : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/powershell
==============================================================================
Generating script.
========================== Starting Command Output ===========================
&quot;C:\Program Files\PowerShell\7\pwsh.exe&quot; -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command &quot;. &apos;D:\a\_temp\be69a030-2e55-4cb5-9f80-98968e90f3b2.ps1&apos;&quot;
##[error]PowerShell exited with code &apos;1&apos;.
Finishing: MyTask
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&apos;s going on? Turns out it&apos;s because &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-70?view=powershell-7#new-view-conciseview-and-cmdlet-get-error&quot;&gt;PowerShell 7 now defaults to the &apos;ConciseView&apos; for errors&lt;/a&gt; and so the error ends up being &lt;strong&gt;so&lt;/strong&gt; concise it isn&apos;t actually logged. I&apos;m &lt;a href=&quot;https://developercommunity.visualstudio.com/content/problem/959880/errors-from-powershell-7-do-not-show-up-in-azure-p.html&quot;&gt;not the first to experience this&lt;/a&gt;. The fact that the concise output is not logged at all &lt;a href=&quot;https://github.com/microsoft/azure-pipelines-tasks/issues/12799&quot;&gt;does look like a bug though&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;Set $ErrorView to &apos;NormalView&apos; as the first line of your script - then you&apos;ll see useful error details that will help with diagnosing what the problem is.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ErrorView = &apos;NormalView&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hopefully in the future, they&apos;ll fix the problem with no errors being logged, or enhance the pwsh task so you can set the ErrorView as a task property outside of the script.&lt;/p&gt;
&lt;h3&gt;Update 15th May&lt;/h3&gt;
&lt;p&gt;As Justin mentions in the comments, this problem &lt;a href=&quot;https://github.com/microsoft/azure-pipelines-tasks/pull/12809&quot;&gt;has been fixed&lt;/a&gt;, so no need to prepend your scripts with &lt;code&gt;$ErrorView = &apos;NormalView&apos;&lt;/code&gt; now!&lt;/p&gt;
</content>
  </entry>
</feed>
