Search notes:

TFS Web API

Some simple investigations on using the TFS Web API with PowerShell.
Function to request a JSON object from the WebAPI endpoint:
function get-webApiJson {
   param (
      [string] $url
   )
 
   $res = invoke-webRequest -useDefaultCredential $url
   convertFrom-json $res.Content
}
Define some global variables that specify a TFS collection's WebAPI URL:
$rootUrl    = 'https://devops.foo.xyz/'
$collection = 'DefaultCollection'
$collUrl    = "$rootUrl$collection"
Iterate over available projects
$projects = get-webApiJson "$collUrl/_apis/projects"

write-host ''
foreach ($project in $projects.value) {
   write-host "$($project.name): $($project.description)"
 
   write-host "  id:          $($project.id)"
   write-host "  url:         $($project.url)"
   write-host "  revision:    $($project.revision) ($($project.lastUpdateTime))"
   write-host ''
}
Choose a project URL and ID from output shown above:
$project_id  = '01234567-89ab-cdef-0123-456789abcdef'
$project_url = "$collUrl/_apis/projects/$project_id"
$projectDetail = get-webApiJson $project_url
$defaultTeam = $projectDetail.defaultTeam
 
write-host "Default Team name: $($defaultTeam.name)"
 
$releases = get-webApiJson "$collUrl/_apis/release/releases"
write-host "Number of releases: $($releases.count)"
$lastRelease = $releases.value[$releases.count-1]
$releaseModifier = $lastRelease.modifiedBy
 
write-host "Last release id            : $($lastRelease.id)"
write-host "Last release name          : $($lastRelease.name)"
 
write-host "Last relase modified by    : $($releaseModifier.displayName) ($($releaseModifier.uniqueName)), url = $($releaseModifier.url)"
 
$releaseDetail = get-webApiJson $lastRelease.url
 
$processes = get-webApiJson "$collUrl/_apis/process/processes"
write-host "count of processes:  $($processes.count)"
write-host "first process:       $($processes.value[0].description)"
$repoRoot = get-webApiJson "$collUrl/_apis/tfvc/items?scopePath=$/&recursionLevel=oneLevel"
$repoRoot

List items in a TFS directory

A simple function to list the items in a TFS directory:
function ls-TFSDirectory {
   param (
      [string] $urlRoot,
      [string] $path
   )
   $dir = get-webApiJson "$urlRoot/_apis/tfvc/items?scopePath=$path&recursionLevel=oneLevel"
   foreach ($item in $dir.value) {
      if ($item.psobject.Properties.name -match 'isFolder') {
         $dir_file = 'd'
         ts
         $size     = ''
      }
      else {
         $dir_file = 'f'
         $size     = $item.size
      }
      write-host ( '{0} {1,6} {2,4} {3,-40}' -f $dir_file, $size, $item.version, $item.path)
   }
}
Using the function:
ls-TFSDirectory $collUrl $/path/to/directory

Show change sets

Function to render a change set in a readable form:
function format-changeSet ($v) {
    $comment = ''
    if ($v.PSObject.properties.item("comment") -ne $null) { $comment = $v.comment}
 
   [dateTime] $createDt = $v.createdDate
 
   '{0,5}  {1,-15}  {2,-30} {3}' -f $v.changeSetId, ($createDt.ToString('yyyy-MM-dd HH:mm')), $v.author.displayName,  $comment
}
Iterate over the change sets that affected a given file:
$path='$/PROJECT/path/to/file.txt'
 
$versions=(get-webApiJson  "$collUrl/_apis/tfvc/changesets?searchCriteria.itemPath=$path").value

foreach ($v in $versions){
   format-changeSet $v
}
Iterate over the last n changesets that were checked in by given person under a given path:
#
#  https://docs.microsoft.com/en-us/rest/api/azure/devops/tfvc/changesets/get-changesets?view=azure-devops-rest-6.1
#
$top_n  = 100
$path   = '$/TRAUM/DWH'
$author = 'Nyffenegger, René'
 
foreach ($changeSet in (get-webApiJson ("$collUrl/_apis/tfvc/changesets?project=$project_id" +
                      "&`$top=$top_n"                       +
                      "&`searchCriteria.itemPath=$path"     +
                      "&`searchCriteria.author=$author"
                      )).value) {
 
   format-changeset $changeSet
}

Pipelines

List available pipelines

foreach ($pipeline in (get-webApiJson "$collUrl/$project_id/_apis/pipelines").value ) {
   '{0,3}: {1,-40} {2,-30} {3}' -f $pipeline.id, $pipeline.name, $pipeline.folder, $pipeline.url
}
The first column printed is the pipeline's id. It might be stored in a variable to inspect details of a pipeline:
$pipeline_id = 48
Store the data of an entire pipeline in a variable:
$pipeline = get-webApiJson "$collUrl/$project_id/_apis/pipelines/$pipeline_id"

Show variables of a pipeline

$pipeline_variables = $pipeline.configuration.designerJson.variables

foreach ($var in $pipeline_variables | get-member -type noteProperty) {
    $var_name = $var.name
    '{0,-30}: {1,-90}' -f $var_name, $pipeline_variables.$var_name.value # , ($pipeline_variables.$var_name.allowOverride)
}

Run a pipeline

The following POST request runs the pipeline with ID $pipeline_id and assigns the value fooBarBaz to the variable VersionFileName:
invoke-webRequest `
  -useDefaultCredentials `
  -method post `
  -headers  @{ 'Accept'        = 'application/json' ;
               'Content-Type'  = 'application/json' } `
  "$collUrl/$project_id/_apis/pipelines/$pipeline_id/runs?api-version=6.0-preview" `
  -body '{
    "variables":  {
          "VersionFileName": {
              "secret": false,
              "value": "fooBarBaz"
           }
      },
}'

Show runs of a pipeline

foreach ($run in (get-webApiJson "$collUrl/$project_id/_apis/pipelines/$pipeline_id/runs").value) {

    $finishedDate = $run.psObject.members.item('finishedDate')

    if ($finishedDate -ne $null) {
        $finishedDate = ([datetime] $run.finishedDate).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss')
    }
    else {
        $finishedDate = '-'
    }
    $result = $run.psObject.members.item('result')

    if ($result -ne $null) {
        $result = $run.result
    }
    else {
        $result = '-'
    }

   '{0}: {1:yyyy-MM-dd HH:mm:ss} {2,-19}  {3,-60} {4,-10} {5,-10}' -f `
      $run.id,
      (([datetime] $run.createdDate ).ToLocalTime()),
                   $finishedDate,
                   $run.name,
                   $run.state,
                   $result
}

Builds

List builds

List the 20 most recent builds ($top=…)
$top = 20
foreach ($build in (get-webApiJson "$collUrl/$project_id/_apis/build/builds?`$top=$top").value) {

  #
  # result is empty if status is 'In Progress'
  #
    $result = $build.psObject.members.item('result')
    if ($result -ne $null) {
        $result = $result.value
    }
    else {
        $result = 'n/a'
    }

    '{0,5} | {1:yyyy-MM-dd HH:mm:ss} | {2,-40} {3,-30} {4,-11} {5,-11}' -f `
       $build.id,
       ([datetime] $build.queueTime).ToLocalTime(),
       $build.buildNumber,
       $build.requestedFor.displayName,
       $build.status,
       $result
}

Show details about a build with a given id

$build_id = 7141
$bld_det = get-webApiJson "$collUrl/$project_id/_apis/build/builds/$build_id"
write-host "Build Rev Nr.    $($bld_det.buildNumberRevision)"
write-host "Source version:  $($bld_det.sourceVersion)"
write-host "Queue:           $($bld_det.queue.name) | Pool: $($bld_det.queue.pool.id) $($bld_det.queue.pool.name)"
write-host "Definition id:   $($bld_det.definition.id)"
write-host "           url:  $($bld_det.definition.url)"
write-host "Logs:            $($bld_det.logs.url)"
write-host "Parameters:"
$params_ = $bld_det.parameters |convertFrom-json
foreach ($param in ($params_ | get-member -type noteProperty)) {
   write-host "    $($param.name): $($params_.($param.name))"
}
write-host "orchestrationPlan: $($bld_det.orchestrationPlan.planid)"

Build definitions

foreach ($def in (get-webApiJson "$collUrl/$project_id/_apis/build/definitions").value) {

   write-host ( '{0,3}: {1,-50} {2,-8} | {3,-2} {4,-20} | {5,-10}' -f `
      $def.id,
      $def.name,
      $def.type,        # build ...?
      $def.queue.id,
      $def.queue.name,
      $def.queueStatus  # enabled, paused, ...?
   )

}

Releases

Show definitions

foreach ($rel in (get-webApiJson "$collUrl/$project_id/_apis/release/definitions").value) {
   '{0,3}: {1,-60} {2,-20} {3}' -f $rel.id, $rel.name, $rel.path, $rel.releaseNameFormat
}
$release_definition_id = 18

Print details of a release definition

function print-release-definition {
   param (
       [int] $rel_def_id
   )
   $rel_def = get-webApiJson "$collUrl/$project_id/_apis/release/definitions/$rel_def_id"
   #
   write-host "id              : $($rel_def.id)"
   #
   write-host "Last release: $( ([dateTime] $rel_def.lastRelease.createdOn).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss'))  ($($rel_def.lastRelease.id))"
#  write-host "Last release"
   #
   write-host "Environments"
   foreach ($env in $rel_def.environments) {
      write-host ('   {0,3}: {1}' -f $env.id, $env.name  )
      write-host ("        Current release: $($env.currentRelease.id) [$($env.currentRelease.url)]")
      write-host ("        Deploy step    : $($env.deployStep.id)")
      write-host ("        Badge URL      : $($env.badgeUrl)")
      #
      write-host ("        deploy phases  : $($env.deployPhases)")
      #
      write-host
   }
   #
   write-host "Artifacts"
   foreach ($art in $rel_def.artifacts) {
      write-host ("        $($art.sourceId)")
      write-host ("        $($art.type)")
      write-host ("        $($art.alias)")
      write-host ("        $($art.isPrimary)")
      #
      write-host
   }
}

print-release-definition $release_definition_id

Show «executed» releases

foreach ($rel in (get-webApiJson "$collUrl/$project_id/_apis/release/releases").value) {
    '{0,4}: {1,-60} {2,-7} {3:yyyy-MM-dd HH:mm:ss}  {4,3}  {5,-30} {6,-20}' -f `
        $rel.id,
        $rel.name,
        $rel.status,
       ( ([datetime] $rel.createdOn).ToLocalTime()),
        $rel.releaseDefinition.id,
        $rel.releaseDefinition.name,
        $rel.createdBy.displayName
}

Show details of a given release

function print-release-details {

    param (
        [int] $rel_id
    )
 
    $rel_det = get-webApiJson "$collUrl/$project_id/_apis/release/releases/$rel_id"
 
    write-host "Release Details for $($rel_det.name) ($($rel_det.id))"
    write-host "Log container URL: $($rel_det.logsContainerUrl)"
    write-host "URL:               $($rel_det.url)"
    foreach ($env in $rel_det.environments) {
       write-host ('   {0,3}: {1}' -f $env.id, $env.name  )
       write-host ("        Status             : $($env.status)")
       write-host ("        Rank               : $($env.rank)")
       write-host ("        Release            : $($env.release.id)")
       write-host ("        Release definition : $($env.releaseDefinition.id)")
       write-host ("        Deploy Steps")
       foreach ($step in $env.deploySteps) {
          write-host ("           $($step.id) - $($step.status)")
          foreach ($phase in $step.releaseDeployPhases) {
          write-host ("              phase:      $($phase.id)")
          write-host ("              runPlandId: $($phase.runPlanId)")
          }
       }
    }
}

Download and compare versioned files

Function to download a file from a web service:
function download-file {
   param (
      [string] $url,
      [string] $localFile
   )
 
   $res = invoke-webRequest -useDefaultCredential $url
 
  [System.IO.File]::WriteAllBytes($localFile, $res.Content)
}
This function uses download-file to download a file that is versioned in TFVC:
function download-tfvcFile {
    param (
        [string] $path,
        [int]    $version
    )
 
    $leaf = split-path -leaf $path
    $filename = "$pwd/$version.$leaf"
    download-file "$collUrl/_apis/tfvc/items?path=$path&versionDescriptor.version=$version" $filename
 
    return $filename
}
This function uses compare-spreadsheets to download two Excel Workbook files and compare them:
function compare-tfvcExcelSheets {
    param (
        [string] $path,
        [int]    $version_1,
        [int]    $version_2
    )
 
    $filename_1 = download-tfvcFile $path $version_1
    $filename_2 = download-tfvcFile $path $version_2
 
    compare-spreadsheets $filename_1 $filename_2
}
 
compare-tfvcExcelSheets $path  15310  15656

See also

Web APIs

Index