Giter Club home page Giter Club logo

azuredevopsstatistics's Introduction

Hi! I'm Sam Smith, a senior manager of Customer Success Architecture at GitHub, where I help customers accelerate their DevOps journeys with GitHub.

Top Langs

Learn how to make these profiles for your own account (Last refreshed: Aug 13, 2024, 8:14 PM EDT)

azuredevopsstatistics's People

Contributors

samsmithnz avatar

Watchers

 avatar  avatar  avatar

azuredevopsstatistics's Issues

todo

  • Add PRs
  • Handle orgs with minimal access
  • Count TFVC repos
  • Output to CSV
  • Harden/ bug fixes
  • Update Readme
  • Release

Recommended changes from Brad

.Notes
Author: GitHub Team
Modified By:
- Brad
Last Modified: 2021-11-05
Changes:
- Initial version.

.Synopsis
Collect  project information.

.Description
The Get-ProjectInfo script collects project information.

.Parameter Token
This is a personal access token that has full read access to all required Azure DevOps information.
This currently defaults to a personal access token.

.Example
Get-ProjectInfo;

#>
[CmdletBinding()]
param(
	[Parameter(Mandatory=$false)]
	[string]$Token = $ENV:SYSTEM_ACCESSTOKEN,
	[Parameter(Mandatory=$false)]
	[string]$InitialOrganizationName = $global:Account,
	[Parameter(Mandatory=$false)]
	[string]$ProjectName = $global:ProjectName,
	[Parameter(Mandatory=$false)]
	[string]$csvExportLocation = "C:\dev\test\AzureDevOps",
	[Parameter(Mandatory=$false)]
	[bool]$JustScanInitialOrganization = $true,
	[Parameter(Mandatory=$false)]
	[bool]$JustScanInitialProject = $true,
	[Parameter(Mandatory=$false)]
	[bool]$GetArtifacts = $false,
	[Parameter(Mandatory=$false)]
	[bool]$IncludeWorkItems = $false
)
if ($null -eq (Get-Module -Name DevOps)) {Import-Module -Name DevOps -Force -Verbose:$False};
Initialize-Script $PSCmdlet.MyInvocation -ShowStarting;
$authHeader = New-AuthorizationHeader $Token;
# Get all organizations
if (-not $JustScanInitialOrganization)
{
	#This is a little dirty, and may fail in the future if Azure DevOps changes the way it lists all organizations
	$orgRequestBody = "{
		""contributionIds"": [""ms.vss-features.my-organizations-data-provider""],
		""dataProviderContext"":
			{
				""properties"":{}
			}
	}"
	$uri = "https://dev.azure.com/$InitialOrganizationName/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1"
	$orgResponse = Invoke-RestMethod -Uri $uri -Body $orgRequestBody -ContentType $global:JsonContent -Headers $authHeader -Method Post -ErrorAction Stop
	$orgResponseDetails = $orgResponse.dataProviders
	$organzations = $orgResponseDetails."ms.vss-features.my-organizations-data-provider".organizations
}
else
{
	#Add just the initial organization to the collection (just looping once)
	$organzations = @()
	$newOrg = New-Object -TypeName PSObject -Property @{
			id = "0";
			Name = $InitialOrganizationName;
			url = "";
		}
	$organzations += $newOrg
}
$summary = @();
$artifacts = @();
[System.Collections.ArrayList]$builds = @();
[System.Collections.ArrayList]$releases = @();
$repos = @();
$prs = @();
[System.Collections.ArrayList]$workItems = @();
foreach ($organization in $organzations)
{
	$orgSummary = @()
	$orgName = $organization.name
	Write-PrefixedOutput -MessageFormat "Processing organization: {0}" -TokenValues $orgName;
	# Get Artifacts
	if ($GetArtifacts -eq $true)
	{
		#https://feeds.dev.azure.com/{organization}/{project}/_apis/packaging/feeds?api-version=6.0-preview.1
		$uri = "https://feeds.dev.azure.com/$orgName/_apis/packaging/feeds?api-version=6.0-preview.1"
		try
		{
			$artifactsJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop
			$artifacts += $artifactsJson | ConvertTo-Json | ConvertFrom-Json | Select-Object -Expand Value | Select name
		}
		catch
		{
			#do nothing
			Write-PrefixedOutput -Message "No access to $orgName artifacts"
		}
	}
	# Get projects for organization
	#https://dev.azure.com/{organization}/_apis/projects?api-version=5.1
	$uri = "https://dev.azure.com/$orgName/_apis/projects?api-version=5.1"
	try
	{
		$projectsJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop
		#Extract just the name and last updated date for each project into a new array
		$projects = $projectsJson.value | Foreach-Object {
			if ((-not $JustScanInitialProject) -or ($_.name -eq $ProjectName))
			{
				New-Object -TypeName PSObject -Property @{
					Name = $_.name
					LastUpdateTime = Get-Date $_.lastUpdateTime
				};
			}
		}
	}
	catch
	{
		#do nothing
		Write-PrefixedOutput -Message "No access to projects in organization $orgName"
		$projects = @{}
	}
	foreach ($project in $projects)
	{
		Write-PrefixedOutput -MessageFormat "Processing project: {0}" -TokenValues $project.name;
		# Builds for project
		$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/build/builds?api-version=5.1"
		try
		{
			Write-PrefixedOutput -Message "Processing builds";
			$buildRunsJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop;
			$buildRunsJson.value | Foreach-Object {
				$null = $builds.Add((New-Object -TypeName PSObject -Property @{
					Name = $_.definition.name
					BuildNumber = $_.buildNumber
					Status = $_.status
					Result = $_.result
					QueueTime = Get-Date $_.queueTime
					Organization = $orgName
					ProjectName = $($project.name)
				}));
			}
		}
		catch
		{
			#do nothing
			Write-PrefixedOutput -Message "No access to $orgName $($project.name) builds";
		}
		Write-ElapsedTime -ElapsedLabel ("Processed {0} builds" -f $builds.Count);
		# Releases for project
		# https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases?api-version=5.1
		# GET https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases?api-version=6.1-preview.8&$top={$top}&path={path}&sourceBranchFilter={sourceBranchFilter}
		$uri = "https://vsrm.dev.azure.com/$orgName/$($project.name)/_apis/release/releases?`$top=500&api-version=6.1-preview.8"
		Write-PrefixedOutput -MessageFormat "Processing releases: {0}" -TokenValues $uri;
		try
		{
			# default is 50 at a time
			$releasesJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop;
			#$releasesJson.value | ForEach-Object {
			foreach ($nextRelease in $releasesJson.value)
			{
				$uri = "https://vsrm.dev.azure.com/$orgName/$($project.name)/_apis/release/releases/$($nextRelease.id)?api-version=5.1";
				$releaseJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop;
				$environments = $releaseJson.environments | ConvertTo-Json -Depth 5 | Where-Object status -ne "notStarted";
				$environmentsObj = $environments | ConvertFrom-Json;
				$statuses = $environmentsObj | Select name, status | Where-Object status -ne "notStarted";
				$null =$releases.Add((New-Object -TypeName PSObject -Property @{
					Id = $nextRelease.id;
					Name = $nextRelease.name;
					ReleaseDefinition = $nextRelease.releaseDefinition.name;
					Status = $nextRelease.status;
					CreatedOn = if ($releaseJson -ne $null) {Get-Date $releaseJson.createdon} else { Get-Date };
					LastEnvironmentName = if($statuses -ne $null) { $statuses[-1].name } else { "none" };
					LastEnvironmentStatus = if($statuses -ne $null) { $statuses[-1].status } else { "none" };
					Organization = $orgName
					ProjectName = $($project.name)
				}));
			}
		}
		catch
		{
			#do nothing
			Write-PrefixedOutput -Message "No access to $orgName $($project.name) releases"
		}
		Write-ElapsedTime -ElapsedLabel ("Processed {0} releases." -f $releases.Count);
		if ($IncludeWorkItems)
		{
			# Work items for project
			[System.Collections.ArrayList]$projectWorkItems = @()
			$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/wit/reporting/workitemrevisions?api-version=5.1&includeDeleted=false"#&includeLatestOnly=true"
			Write-PrefixedOutput -Message "Processing work items";
			do
			{
				try
				{
					$workItemsJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop;
					foreach ($workItem in $workItemsJson.values)
					{
						$null = $projectWorkItems.Add(($workItem | ConvertTo-Json -Depth 10 | ConvertFrom-Json));
					}
					$uri = $workItemsJson.nextLink
				}
				catch
				{
					#do nothing
					Write-PrefixedOutput -Message "No access to $orgName $($project.name) work items"
				}
			} While ($workItemsJson.values.Length -gt 0) #Loop while there are items in the list. Once we reach the end of the list, we will have 0 items
			$workItems += $projectWorkItems.values | ConvertTo-Json -Depth 10 | ConvertFrom-Json | Get-Unique -AsString;
			Write-ElapsedTime -ElapsedLabel "Processed work items.";
		}
		Write-PrefixedOutput -Message "Processing repositories";
		#Repos for project
		#GET https://dev.azure.com/{organization}/{project}/_apis/git/repositories?api-version=6.0
		$projectRepos = @()
		$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/git/repositories?api-version=6.0"
		try
		{
			$reposJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop
			$projectRepos = $reposJson.value | ConvertTo-Json -Depth 10 | ConvertFrom-Json
		}
		catch
		{
			#do nothing
			Write-PrefixedOutput -Message "No access to $orgName $($project.name) repos"
		}
		$repos += $projectRepos | ConvertTo-Json -Depth 10 | ConvertFrom-Json | Get-Unique -AsString
		Write-ElapsedTime -ElapsedLabel ("Processed {0} repositories." -f $projectRepos.Count);
		Write-PrefixedOutput -Message "Processing TFVC repositories";
		#TFVCs in project (note there are no PRs - but should we be counting branches?)
		#GET https://dev.azure.com/{organization}/{project}/_apis/tfvc/items?api-version=6.0
		$tfvcRepoExists = $false
		try
		{
			$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/tfvc/items?api-version=6"
			$tfvcJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop
			$projectTFVCRepos = $tfvcJson.value | ConvertTo-Json -Depth 10 | ConvertFrom-Json
			if ($projectTFVCRepos.Count -gt 0)
			{
				$tfvcRepoExists = $true
			}
		}
		catch
		{
			#do nothing
			Write-PrefixedOutput -Message "No access to $orgName TVFC repos"
		}
		Write-ElapsedTime -ElapsedLabel "Processed TFVC repositories.";
		Write-PrefixedOutput -Message "Processing PRs";
		#PRs in each repo
		#GET https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repositoryId}/pullrequests?searchCriteria.status=completed&api-version=6.0
		#Loop through each Repo for PR's
		foreach ($projectRepo in $projectRepos)
		{
			$skipPRs = 0 # The number of pull requests to ignore. For example, to retrieve results 101-150, set top to 50 and skip to 100.
			$topPRs = 100 # The number of pull requests to retrieve.
			$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/git/repositories/$($projectRepo.id)/pullrequests?searchCriteria.status=completed&`$skip=$skipPRs&`$top=$topPRs&api-version=6.0"
			$projectReposPRs = @();
			$tmp = @();
			$doMore = $true;
			do
			{
				try
				{
					$prsJson = Invoke-RestMethod -Uri $uri -ContentType $global:JsonContent -Headers $authHeader -Method Get -ErrorAction Stop
					$projectRepoPRsJson = $prsJson.value | ConvertTo-Json -Depth 10 | ConvertFrom-Json
					$tmp = $projectRepoPRsJson | ConvertTo-Json -Depth 10 | ConvertFrom-Json | Get-Unique -AsString
					$projectReposPRs += $tmp
					$skipPRs += $topPRs
					$uri = "https://dev.azure.com/$orgName/$($project.name)/_apis/git/repositories/$($projectRepo.id)/pullrequests?searchCriteria.status=all&`$skip=$skipPRs&`$top=$topPRs&api-version=6.0"
				}
				catch
				{
					Write-PrefixedOutput -MessageFormat "{0} repository error: {1}" -TokenValues $projectRepo.id,$_.Exception.Message;
					$doMore = $false;
				}
			} while (($tmp.Count -ge 100) -and $doMore) #Loop while there are items in the list. Once we reach the end of the list, we will have 0 items
			$prs += $projectReposPRs;
			$orgSummary += (New-Object -TypeName PSObject -Property @{
					Organization = $orgName
					Project = $project.name
					WorkItemCount = $projectWorkItems.values.Count
					GitRepo = $projectRepo.name
					TVFCRepoExists = $tfvcRepoExists
					GitRepoCompressedSizeInMB = "{0:n2}" -f [math]::Round(($projectRepo.size / 1000000),2) # dividing by a million, not exact - but close enough
					PRsCount = $(if($projectReposPRs.Count -eq $null) {1} else {$projectReposPRs.Count})
					BuildsAndReleasesCount = $builds.Count + $releases.Count
				});
		}
		Write-PrefixedOutput -Message "Scanning project $($project.name)... (found $($repos.Length) Git repos, $(if($tfvcRepoExists -eq $true) {1} else {0}) TFVC repos, $($prs.Length) prs, $($builds.Length) builds, $($releases.Length) releases, and $($workItems.Length) work items found so far)"
	} # end Foreach ($project in $projects){
	if ($orgSummary.Count -gt 0)
	{
		$orgSummary | Select-Object Organization, Project, WorkItemCount, TVFCRepoExists, GitRepo, GitRepoCompressedSizeInMB, PRsCount | Export-Csv -Path "$csvExportLocation\AzureDevOpsStats_$orgName.csv" -Encoding ascii -NoTypeInformation
		$summary += $orgSummary
	}
} # end Foreach ($org in $organzationsJson)

Write-PrefixedOutput -Message "Total builds: $($builds.Count)"
#$builds | Select Name, Status, Result, QueueTime | Group-Object -Property Status, Result | Select Count, Name | ft

Write-PrefixedOutput -Message "Total releases: $($releases.Count)"
#$releases | Select LastEnvironmentStatus | Group-Object -Property LastEnvironmentStatus | Select Count, Name | ft

Write-PrefixedOutput -Message "Total work items: $($workItems.Count)"
#$workItems.fields | Select System.WorkItemType, System.ChangedDate | Group-Object -Property System.WorkItemType | Select Count, Name | ft

if ($GetArtifacts -eq $true)
{
	Write-PrefixedOutput -Message "Total artifact feeds: $($artifacts.Count)"
	#$artifacts | Select name
}

Write-PrefixedOutput -Message "Total Git repos: $($repos.Count)"
$TotalReposOver2GB = ($repos | Where-Object size -gt $(2 * ([Math]::Pow(1000,3)))) #1000^3 is a billion/or ~1 GB
Write-PrefixedOutput -Message "Total Git repos over 2GB: $($TotalReposOver2GB.Count)"
$TotalReposOver2GB | Select name, size | ft
Write-PrefixedOutput -Message "Total PRs: $($prs.Count)"
Write-PrefixedOutput -Message "Summary"
$summary | ft Organization, Project, WorkItemCount, TVFCRepoExists, GitRepo, @{n='GitRepoCompressedSizeInMB';e={$_.GitRepoCompressedSizeInMB};align='right'}, PRsCount #, BuildsAndReleasesCount
Write-ElapsedTime;
Write-Output ("{0}: Finishing: {1}" -f (Get-UtcTimestamp),$PSCmdlet.MyInvocation.Line);
Get-ProjectInfo.ps1.txt
Displaying Get-ProjectInfo.ps1.txt.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.