ADOPS.psm1
#region SkipTest class SkipTest : Attribute { [string[]]$TestNames SkipTest([string[]]$Name) { $this.TestNames = $Name } } #endregion SkipTest #region GetADOPSHeader function GetADOPSHeader { [CmdletBinding()] param ( [string]$Organization ) $Res = @{} if (-not [string]::IsNullOrEmpty($Organization)) { $HeaderObj = $Script:ADOPSCredentials[$Organization] $res.Add('Organization', $Organization) } else { $r = $script:ADOPSCredentials.Keys | Where-Object {$script:ADOPSCredentials[$_].Default -eq $true} $HeaderObj = $script:ADOPSCredentials[$r] $res.Add('Organization', $r) } $UserName = $HeaderObj.Credential.UserName $Password = $HeaderObj.Credential.GetNetworkCredential().Password $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $UserName, $Password))) $Header = @{ Authorization = ("Basic {0}" -f $base64AuthInfo) } $Res.Add('Header',$Header) $Res } #endregion GetADOPSHeader #region InvokeADOPSRestMethod function InvokeADOPSRestMethod { param ( [Parameter(Mandatory)] [URI]$Uri, [Parameter()] [Microsoft.PowerShell.Commands.WebRequestMethod]$Method, [Parameter()] [string]$Body, [Parameter()] [string]$Organization, [Parameter()] [string]$ContentType = 'application/json', [Parameter()] [switch]$FullResponse ) if (-not [string]::IsNullOrEmpty($Organization)) { $CallHeaders = GetADOPSHeader -Organization $Organization } else { $CallHeaders = GetADOPSHeader } $InvokeSplat = @{ 'Uri' = $Uri 'Method' = $Method 'Headers' = $CallHeaders.Header 'ContentType' = $ContentType } if (-not [string]::IsNullOrEmpty($Body)) { $InvokeSplat.Add('Body', $Body) } if ($FullResponse) { $InvokeSplat.Add('ResponseHeadersVariable', 'ResponseHeaders') $InvokeSplat.Add('StatusCodeVariable', 'ResponseStatusCode') } $Result = Invoke-RestMethod @InvokeSplat if ($Result -like "*Azure DevOps Services | Sign In*") { throw 'Failed to call Azure DevOps API. Please login before using.' } elseif ($FullResponse) { @{ Content = $Result; Headers = $ResponseHeaders; StatusCode = $ResponseStatusCode } } else { $Result } } #endregion InvokeADOPSRestMethod #region Connect-ADOPS function Connect-ADOPS { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$Username, [Parameter(Mandatory)] [string]$PersonalAccessToken, [Parameter(Mandatory)] [string]$Organization, [Parameter()] [switch]$Default ) $Credential = [pscredential]::new($Username, (ConvertTo-SecureString -String $PersonalAccessToken -AsPlainText -Force)) $ShouldBeDefault = $Default.IsPresent if ($script:ADOPSCredentials.Count -eq 0) { $ShouldBeDefault = $true $Script:ADOPSCredentials = @{} } elseif ($default.IsPresent) { $r = $script:ADOPSCredentials.Keys | Where-Object { $ADOPSCredentials[$_].Default -eq $true } $ADOPSCredentials[$r].Default = $false } $OrgData = @{ Credential = $Credential Default = $ShouldBeDefault } $Script:ADOPSCredentials[$Organization] = $OrgData $URI = "https://vssps.dev.azure.com/$Organization/_apis/profile/profiles/me?api-version=7.1-preview.3" try { InvokeADOPSRestMethod -Method Get -Uri $URI } catch { $Script:ADOPSCredentials.Remove($Organization) throw $_ } } #endregion Connect-ADOPS #region Disconnect-ADOPS function Disconnect-ADOPS { [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) # Only require $Organization if several connections if ($Script:ADOPSCredentials.Count -eq 0) { throw "There are no current connections!" } # Allow not specifying organization if there's only one connection elseif ($Script:ADOPSCredentials.Count -eq 1 -and [string]::IsNullOrWhiteSpace($Organization)) { $Script:ADOPSCredentials = @{} # Make sure to exit script after clearing hashtable return } elseif (-not $Script:ADOPSCredentials.ContainsKey($Organization)) { throw "No connection made for organization $Organization!" } # If the connection to be removed is set as default, set another one $ChangeDefault = $Script:ADOPSCredentials[$Organization].Default $Script:ADOPSCredentials.Remove($Organization) # If there are any connections left and we removed the default if ($Script:ADOPSCredentials.Count -gt 0 -and $ChangeDefault) { # Set another one to default $Name = ($Script:ADOPSCredentials.GetEnumerator() | Select-Object -First 1).Name $Script:ADOPSCredentials[$Name].Default = $true } } #endregion Disconnect-ADOPS #region Get-ADOPSConnection function Get-ADOPSConnection { [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $Script:ADOPSCredentials.GetEnumerator() | Where-Object { $_.Key -eq $Organization} } else { $Script:ADOPSCredentials } } #endregion Get-ADOPSConnection #region Get-ADOPSElasticPool function Get-ADOPSElasticPool { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter()] [int32]$PoolId ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } if ($PSBoundParameters.ContainsKey('PoolId')) { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools/$PoolId?api-version=7.1-preview.1" } else { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools?api-version=7.1-preview.1" } $Method = 'GET' $ElasticPoolInfo = InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization -Body $Body if ($ElasticPoolInfo.psobject.properties.name -contains 'value') { Write-Output $ElasticPoolInfo.value } else { Write-Output $ElasticPoolInfo } } #endregion Get-ADOPSElasticPool #region Get-ADOPSNode function Get-ADOPSNode { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [int32]$PoolId ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools/$PoolId/nodes?api-version=7.1-preview.1" $Method = 'GET' $NodeInfo = InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization if ($NodeInfo.psobject.properties.name -contains 'value') { Write-Output $NodeInfo.value } else { Write-Output $NodeInfo } } #endregion Get-ADOPSNode #region Get-ADOPSPipeline function Get-ADOPSPipeline { [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/pipelines?api-version=7.1-preview.1" $InvokeSplat = @{ Method = 'Get' Uri = $URI Organization = $Organization } $AllPipelines = (InvokeADOPSRestMethod @InvokeSplat).value if ($PSBoundParameters.ContainsKey('Name')) { $Pipelines = $AllPipelines | Where-Object {$_.name -eq $Name} if (-not $Pipelines) { throw "The specified PipelineName $Name was not found amongst pipelines: $($AllPipelines.name -join ', ')!" } } else { $Pipelines = $AllPipelines } $return = @() foreach ($Pipeline in $Pipelines) { $InvokeSplat = @{ Method = 'Get' Uri = $Pipeline.url Organization = $Organization } $result = InvokeADOPSRestMethod @InvokeSplat $return += $result } return $return } #endregion Get-ADOPSPipeline #region Get-ADOPSPool function Get-ADOPSPool { [CmdletBinding(DefaultParameterSetName = 'All')] param ( [Parameter(ParameterSetName = 'PoolId')] [Parameter(ParameterSetName = 'PoolName')] [Parameter(ParameterSetName = 'All')] [string]$Organization, [Parameter( Mandatory = $true, ParameterSetName = 'PoolId' )] [int32]$PoolId, [Parameter( Mandatory = $true, ParameterSetName = 'PoolName' )] [string]$PoolName, # Include legacy pools [Parameter(ParameterSetName = 'All')] [switch] $IncludeLegacy ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } switch ($PSCmdlet.ParameterSetName) { 'PoolId' { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/pools/$PoolId`?api-version=7.1-preview.1" } 'PoolName' { $uri = "https://dev.azure.com/$Organization/_apis/distributedtask/pools?poolName=$PoolName`&api-version=7.1-preview.1" } 'All' { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/pools?api-version=7.1-preview.1" } } $Method = 'GET' $PoolInfo = InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization if ($PoolInfo.psobject.properties.name -contains 'value') { $PoolInfo = $PoolInfo.value } if ((-not ($IncludeLegacy.IsPresent)) -and $PSCmdlet.ParameterSetName -eq 'All') { $PoolInfo = $PoolInfo | Where-Object { $_.IsLegacy -eq $false } } Write-Output $PoolInfo } #endregion Get-ADOPSPool #region Get-ADOPSProject function Get-ADOPSProject { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter()] [string]$Project ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $Uri = "https://dev.azure.com/$Organization/_apis/projects?api-version=7.1-preview.4" $Method = 'GET' $ProjectInfo = (InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization).value if (-not [string]::IsNullOrWhiteSpace($Project)) { $ProjectInfo = $ProjectInfo | Where-Object -Property Name -eq $Project } Write-Output $ProjectInfo } #endregion Get-ADOPSProject #region Get-ADOPSRepository function Get-ADOPSRepository { [CmdletBinding()] param( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [string]$Repository ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } if ($PSBoundParameters.ContainsKey('Repository')) { $Uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$Repository`?api-version=7.1-preview.1" } else { $Uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=7.1-preview.1" } $result = InvokeADOPSRestMethod -Uri $Uri -Method Get -Organization $Organization if ($result.psobject.properties.name -contains 'value') { Write-Output -InputObject $result.value } else { Write-Output -InputObject $result } } #endregion Get-ADOPSRepository #region Get-ADOPSServiceConnection function Get-ADOPSServiceConnection { [CmdletBinding()] param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/serviceendpoint/endpoints?api-version=7.1-preview.4" $InvokeSplat = @{ Method = 'Get' Uri = $URI Organization = $Organization } $AllPipelines = (InvokeADOPSRestMethod @InvokeSplat).value if ($PSBoundParameters.ContainsKey('Name')) { $Pipelines = $AllPipelines | Where-Object {$_.name -eq $Name} if (-not $Pipelines) { throw "The specified ServiceConnectionName $Name was not found amongst Connections: $($AllPipelines.name -join ', ')!" } } else { $Pipelines = $AllPipelines } return $Pipelines } #endregion Get-ADOPSServiceConnection #region Get-ADOPSUser function Get-ADOPSUser { [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)] [string]$Name, [Parameter(Mandatory, ParameterSetName = 'Descriptor', Position = 0)] [string]$Descriptor, [Parameter()] [string]$Organization, [Parameter(ParameterSetName = 'Default', DontShow)] [string]$ContinuationToken ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } if ($PSCmdlet.ParameterSetName -eq 'Default') { $Uri = "https://vssps.dev.azure.com/$Organization/_apis/graph/users?api-version=6.0-preview.1" $Method = 'GET' if(-not [string]::IsNullOrEmpty($ContinuationToken)) { $Uri += "&continuationToken=$ContinuationToken" } $Response = (InvokeADOPSRestMethod -FullResponse -Uri $Uri -Method $Method -Organization $Organization) $Users = [System.Collections.ArrayList]::new(1000) $Users.AddRange($Response.Content.value) Write-Verbose "Found $($Response.Content.count) users" if($Response.Headers.ContainsKey('X-MS-ContinuationToken')) { Write-Verbose "Found continuationToken. Will fetch more users." $parameters = [hashtable]$PSBoundParameters $parameters.Add('ContinuationToken', $Response.Headers['X-MS-ContinuationToken']?[0]) $Users.AddRange((Get-ADOPSUser @parameters)) } Write-Output $Users } elseif ($PSCmdlet.ParameterSetName -eq 'Name') { $Uri = "https://vsaex.dev.azure.com/$Organization/_apis/UserEntitlements?`$filter=name eq '$Name'&`$orderBy=name Ascending&api-version=6.0-preview.3" $Method = 'GET' $Users = (InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization).members.user Write-Output $Users } elseif ($PSCmdlet.ParameterSetName -eq 'Descriptor') { $Uri = "https://vssps.dev.azure.com/$Organization/_apis/graph/users/$Descriptor`?api-version=6.0-preview.1" $Method = 'GET' $User = (InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization) Write-Output $User } } #endregion Get-ADOPSUser #region Get-ADOPSWiki function Get-ADOPSWiki { param ( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [Parameter()] [string]$WikiId ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } $BaseUri = "https://dev.azure.com/$Organization/$Project/_apis/wiki/wikis" if ($WikiId) { $Uri = "${BaseUri}/${WikiId}?api-version=7.1-preview.2" } else { $Uri = "${BaseUri}?api-version=7.1-preview.2" } $Method = 'Get' $res = InvokeADOPSRestMethod -Uri $URI -Method $Method -Organization $Organization if ($res.psobject.properties.name -contains 'value') { Write-Output -InputObject $res.value } else { Write-Output -InputObject $res } } #endregion Get-ADOPSWiki #region Import-ADOPSRepository function Import-ADOPSRepository { [CmdLetBinding(DefaultParameterSetName='RepositoryName')] param ( [Parameter(ParameterSetName = 'RepositoryName')] [Parameter(ParameterSetName = 'RepositoryId')] $Organization, [Parameter(Mandatory, ParameterSetName = 'RepositoryName')] [Parameter(Mandatory, ParameterSetName = 'RepositoryId')] [string]$Project, [Parameter(Mandatory, ParameterSetName = 'RepositoryName')] [Parameter(Mandatory, ParameterSetName = 'RepositoryId')] $GitSource, [Parameter(Mandatory, ParameterSetName = 'RepositoryId')] $RepositoryId, [Parameter(Mandatory, ParameterSetName = 'RepositoryName')] $RepositoryName ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } switch ($PSCmdlet.ParameterSetName) { 'RepositoryName' { $RepoIdentifier = $RepositoryName} 'RepositoryId' { $RepoIdentifier = $RepositoryId} Default {} } $InvokeSplat = @{ URI = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$RepoIdentifier/importRequests?api-version=7.1-preview.1" Method = 'Post' Body = "{""parameters"":{""gitSource"":{""url"":""$GitSource""}}}" Organization = $Organization } InvokeADOPSRestMethod @InvokeSplat } #endregion Import-ADOPSRepository #region New-ADOPSElasticpool function New-ADOPSElasticPool { [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$PoolName, [Parameter(Mandatory)] $ElasticPoolObject, [string]$Organization, [Parameter()] [string] $ProjectId, [Parameter()] [boolean] $AuthorizeAllPipelines = $false, [Parameter()] [boolean] $AutoProvisionProjectPools = $false ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } if ($PSBoundParameters.ContainsKey('ProjectId')) { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools?poolName=$PoolName`&authorizeAllPipelines=$AuthorizeAllPipelines`&autoProvisionProjectPools=$AutoProvisionProjectPools&projectId=$ProjectId&api-version=7.1-preview.1" } else { $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools?poolName=$PoolName`&authorizeAllPipelines=$AuthorizeAllPipelines`&autoProvisionProjectPools=$AutoProvisionProjectPools&api-version=7.1-preview.1" } if ($ElasticPoolObject.gettype().name -eq 'String') { $Body = $ElasticPoolObject } else { try { $Body = $ElasticPoolObject | ConvertTo-Json -Depth 100 } catch { throw "Unable to convert the content of the ElasticPoolObject to json." } } $Method = 'POST' $ElasticPoolInfo = InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization -Body $Body Write-Output $ElasticPoolInfo } #endregion New-ADOPSElasticpool #region New-ADOPSElasticPoolObject function New-ADOPSElasticPoolObject { [SkipTest('HasOrganizationParameter')] [CmdletBinding()] param ( # Service Endpoint Id [Parameter(Mandatory)] [guid] $ServiceEndpointId, # Service Endpoint Scope [Parameter(Mandatory)] [guid] $ServiceEndpointScope, # Azure Id [Parameter(Mandatory)] [string] $AzureId, # Operating System Type [Parameter()] [ValidateSet('linux', 'windows')] [string] $OsType = 'linux', # MaxCapacity [Parameter()] [int] $MaxCapacity = 1, # DesiredIdle [Parameter()] [int] $DesiredIdle = 0, # Recycle VM after each use [Parameter()] [boolean] $RecycleAfterEachUse = $false, # Desired Size of pool [Parameter()] [int] $DesiredSize = 0, # Agent Interactive UI [Parameter()] [boolean] $AgentInteractiveUI = $false, # Time before scaling down [Parameter()] [int] $TimeToLiveMinues = 15, # maxSavedNodeCount [Parameter()] [int] $MaxSavedNodeCount = 0, # Output Type [Parameter()] [ValidateSet('json','pscustomobject')] [string] $OutputType = 'pscustomobject' ) if ($DesiredIdle -gt $MaxCapacity) { throw "The desired idle count cannot be larger than the max capacity." } $ElasticPoolObject = [PSCustomObject]@{ serviceEndpointId = $ServiceEndpointId serviceEndpointScope = $ServiceEndpointScope azureId = $AzureId maxCapacity = $MaxCapacity desiredIdle = $DesiredIdle recycleAfterEachUse = $RecycleAfterEachUse maxSavedNodeCount = $MaxSavedNodeCount osType = $OsType desiredSize = $DesiredSize agentInteractiveUI = $AgentInteractiveUI timeToLiveMinutes = $TimeToLiveMinues } if ($OutputType -eq 'json') { $ElasticPoolObject = $ElasticPoolObject | ConvertTo-Json -Depth 100 } Write-Output $ElasticPoolObject } #endregion New-ADOPSElasticPoolObject #region New-ADOPSPipeline function New-ADOPSPipeline { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter(Mandatory)] [ValidateScript( { $_ -like '*.yaml' }, ErrorMessage = "Path must be to a yaml file in your repository like: folder/file.yaml")] [string]$YamlPath, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Repository, [Parameter()] [ValidateNotNullOrEmpty()] [string]$FolderPath, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/pipelines?api-version=7.1-preview.1" try { $RepositoryID = (Get-ADOPSRepository -Organization $Organization -Project $Project -Repository $Repository -ErrorAction Stop).id } catch { throw "The specified Repository $Repository was not found." } $Body = @{ "name" = $Name "folder" = "\$FolderPath" "configuration" = @{ "type" = "yaml" "path" = $YamlPath "repository" = @{ "id" = $RepositoryID "type" = "azureReposGit" } } } $Body = $Body | ConvertTo-Json -Compress $InvokeSplat = @{ Method = 'Post' Uri = $URI Organization = $Organization Body = $Body } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSPipeline #region New-ADOPSProject function New-ADOPSProject { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Description, [Parameter(Mandatory)] [ValidateSet('Private', 'Public')] [string]$Visibility, [Parameter()] [ValidateSet('Git', 'Tfvc')] [string]$SourceControlType = 'Git', # The process type for the project, such as Basic, Agile, Scrum or CMMI [Parameter()] [ValidateNotNullOrEmpty()] [string]$ProcessTypeName, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } # Get organization process templates $URI = "https://dev.azure.com/$Organization/_apis/process/processes?api-version=7.1-preview.1" $InvokeSplat = @{ Method = 'Get' Uri = $URI Organization = $Organization } $ProcessTemplates = (InvokeADOPSRestMethod @InvokeSplat).value if ([string]::IsNullOrWhiteSpace($ProcessTypeName)) { $ProcessTemplateTypeId = $ProcessTemplates | Where-Object isDefault -eq $true | Select-Object -ExpandProperty id } else { $ProcessTemplateTypeId = $ProcessTemplates | Where-Object name -eq $ProcessTypeName | Select-Object -ExpandProperty id if ([string]::IsNullOrWhiteSpace($ProcessTemplateTypeId)) { throw "The specified ProcessTypeName was not found amongst options: $($ProcessTemplates.name -join ', ')!" } } # Create project endpoint $URI = "https://dev.azure.com/$Organization/_apis/projects?api-version=7.1-preview.4" $Body = @{ 'name' = $Name 'visibility' = $Visibility 'capabilities' = @{ 'versioncontrol' = @{ 'sourceControlType' = $SourceControlType } 'processTemplate' = @{ 'templateTypeId' = $ProcessTemplateTypeId } } } if (-not [string]::IsNullOrEmpty($Description)) { $Body.Add('description', $Description) } $Body = $Body | ConvertTo-Json -Compress $InvokeSplat = @{ Method = 'Post' Uri = $URI Body = $Body Organization = $Organization } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSProject #region New-ADOPSRepository function New-ADOPSRepository { param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $ProjectID = (Get-ADOPSProject -Project $Project).id $URI = "https://dev.azure.com/$Organization/_apis/git/repositories?api-version=7.1-preview.1" $Body = "{""name"":""$Name"",""project"":{""id"":""$ProjectID""}}" $InvokeSplat = @{ Uri = $URI Method = 'Post' Body = $Body Organization = $Organization } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSRepository #region New-ADOPSServiceConnection function New-ADOPSServiceConnection { [cmdletbinding()] param( [parameter(Mandatory = $true)] [string]$TenantId, [parameter(Mandatory = $true)] [string]$SubscriptionName, [parameter(Mandatory = $true)] [string]$SubscriptionId, [parameter(Mandatory = $true)] [string]$Project, [parameter()] [string]$ConnectionName, [parameter()] [string]$Organization, [Parameter(mandatory)] [pscredential]$ServicePrincipal ) # Set organization if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } # Get ProjectId $ProjectInfo = Get-ADOPSProject -Organization $Organization -Project $Project # Set connection name if not set by parameter if (-not $ConnectionName) { $ConnectionName = $SubscriptionName -replace " " } # Create body for the API call $Body = @{ data = @{ subscriptionId = $SubscriptionId subscriptionName = $SubscriptionName environment = "AzureCloud" scopeLevel = "Subscription" creationMode = "Manual" } name = ($SubscriptionName -replace " ") type = "AzureRM" url = "https://management.azure.com/" authorization = @{ parameters = @{ tenantid = $TenantId serviceprincipalid = $ServicePrincipal.UserName authenticationType = "spnKey" serviceprincipalkey = $ServicePrincipal.GetNetworkCredential().Password } scheme = "ServicePrincipal" } isShared = $false isReady = $true serviceEndpointProjectReferences = @( @{ projectReference = @{ id = $ProjectInfo.Id name = $Project } name = $ConnectionName } ) } | ConvertTo-Json -Depth 10 # Run function $URI = "https://dev.azure.com/$Organization/$Project/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4" $InvokeSplat = @{ Uri = $URI Method = "POST" Body = $Body Organization = $Organization } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSServiceConnection #region New-ADOPSUserStory function New-ADOPSUserStory { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = "Default")] [string]$Organization, [Parameter(Mandatory, ParameterSetName = "Default")] [string]$ProjectName, [Parameter(Mandatory, ParameterSetName = "Default")] [string]$Title, [Parameter(ParameterSetName = "Default")] [string]$Description, [Parameter(ParameterSetName = "Default")] [string]$Tags, [Parameter(ParameterSetName = "Default")] [string]$Priority ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $URI = "https://dev.azure.com/$Organization/$ProjectName/_apis/wit/workitems/`$User Story?api-version=5.1" $Method = 'POST' $desc = $Description.Replace('"', "'") $Body = "[ { `"op`": `"add`", `"path`": `"/fields/System.Title`", `"value`": `"$($Title)`" }, { `"op`": `"add`", `"path`": `"/fields/System.Description`", `"value`": `"$($desc)`" }, { `"op`": `"add`", `"path`": `"/fields/System.Tags`", `"value`": `"$($Tags)`" }, { `"op`": `"add`", `"path`": `"/fields/Microsoft.VSTS.Common.Priority`", `"value`": `"$($Priority)`" }, ]" $InvokeSplat = @{ Uri = $URI ContentType = "application/json-patch+json" Method = $Method Body = $Body Organization = $Organization } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSUserStory #region New-ADOPSVariableGroup function New-ADOPSVariableGroup { [CmdletBinding()] param ( [Parameter(ParameterSetName = 'VariableSingle')] [Parameter(ParameterSetName = 'VariableHashtable')] [string]$Organization, [Parameter(Mandatory, ParameterSetName = 'VariableSingle')] [Parameter(Mandatory, ParameterSetName = 'VariableHashtable')] [string]$Project, [Parameter(Mandatory, ParameterSetName = 'VariableSingle')] [Parameter(Mandatory, ParameterSetName = 'VariableHashtable')] [string]$VariableGroupName, [Parameter(Mandatory, ParameterSetName = 'VariableSingle')] [string]$VariableName, [Parameter(Mandatory, ParameterSetName = 'VariableSingle')] [string]$VariableValue, [Parameter(ParameterSetName = 'VariableSingle')] [switch]$IsSecret, [Parameter()] [string]$Description, [Parameter(Mandatory, ParameterSetName = 'VariableHashtable')] [ValidateScript( { $_ | ForEach-Object { $_.Keys -Contains 'Name' -and $_.Keys -Contains 'IsSecret' -and $_.Keys -Contains 'Value' -and $_.Keys.count -eq 3 } }, ErrorMessage = 'The hashtable must contain the following keys: Name, IsSecret, Value')] [hashtable[]]$VariableHashtable ) if ([string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader $Organization = $Org['Organization'] } else { $Org = GetADOPSHeader -Organization $Organization } $ProjectInfo = Get-ADOPSProject -Organization $Organization -Project $Project $URI = "https://dev.azure.com/${Organization}/_apis/distributedtask/variablegroups?api-version=7.1-preview.2" $method = 'POST' if ($VariableName) { $Body = @{ Name = $VariableGroupName Description = $Description Type = 'Vsts' variableGroupProjectReferences = @(@{ Name = $VariableGroupName Description = $Description projectReference = @{ Id = $ProjectInfo.Id } }) variables = @{ $VariableName = @{ isSecret = $IsSecret.IsPresent value = $VariableValue } } } | ConvertTo-Json -Depth 10 } else { $Variables = @{} foreach ($Hashtable in $VariableHashtable) { $Variables.Add( $Hashtable.Name, @{ isSecret = $Hashtable.IsSecret value = $Hashtable.Value } ) } $Body = @{ Name = $VariableGroupName Description = $Description Type = 'Vsts' variableGroupProjectReferences = @(@{ Name = $VariableGroupName Description = $Description projectReference = @{ Id = $($ProjectInfo.Id) } }) variables = $Variables } | ConvertTo-Json -Depth 10 } InvokeADOPSRestMethod -Uri $Uri -Method $Method -Body $Body -Organization $Organization } #endregion New-ADOPSVariableGroup #region New-ADOPSWiki function New-ADOPSWiki { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [Parameter(Mandatory)] [string]$WikiName, [Parameter(Mandatory)] [string]$WikiRepository, [Parameter()] [string]$WikiRepositoryPath = '/', [Parameter()] [string]$GitBranch = 'main' ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $ProjectId = (Get-ADOPSProject -Project $Project).id $RepositoryId = (Get-ADOPSRepository -Project $Project -Repository $WikiRepository).id $URI = "https://dev.azure.com/$Organization/_apis/wiki/wikis?api-version=7.1-preview.2" $Method = 'Post' $Body = [ordered]@{ 'type' = 'codeWiki' 'name' = $WikiName 'projectId' = $ProjectId 'repositoryId' = $RepositoryId 'mappedPath' = $WikiRepositoryPath 'version' = @{'version' = $GitBranch} } $InvokeSplat = @{ Uri = $URI Method = $Method Body = $Body | ConvertTo-Json -Compress } InvokeADOPSRestMethod @InvokeSplat } #endregion New-ADOPSWiki #region Remove-ADOPSRepository function Remove-ADOPSRepository { [CmdletBinding()] param( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [Parameter(Mandatory)] [string]$RepositoryID ) if (-not [string]::IsNullOrEmpty($Organization)) { $OrgInfo = GetADOPSHeader -Organization $Organization } else { $OrgInfo = GetADOPSHeader $Organization = $OrgInfo['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$RepositoryID`?api-version=7.1-preview.1" $result = InvokeADOPSRestMethod -Uri $Uri -Method Delete -Organization $Organization if ($result.psobject.properties.name -contains 'value') { Write-Output -InputObject $result.value } else { Write-Output -InputObject $result } } #endregion Remove-ADOPSRepository #region Remove-ADOPSVariableGroup function Remove-ADOPSVariableGroup { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [Parameter(Mandatory)] [string]$VariableGroupName ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/distributedtask/variablegroups?api-version=7.1-preview.2" $VariableGroups = (InvokeADOPSRestMethod -Uri $Uri -Method 'Get' -Organization $Organization).value $GroupToRemove = $VariableGroups | Where-Object name -eq $VariableGroupName if ($null -eq $GroupToRemove) { throw "Could not find group $VariableGroupName! Groups found: $($VariableGroups.name -join ', ')." } $ProjectId = (Get-ADOPSProject -Organization $Organization -Project $Project).id $URI = "https://dev.azure.com/$Organization/_apis/distributedtask/variablegroups/$($GroupToRemove.id)?projectIds=$ProjectId&api-version=7.1-preview.2" $null = InvokeADOPSRestMethod -Uri $Uri -Method 'Delete' -Organization $Organization } #endregion Remove-ADOPSVariableGroup #region Set-ADOPSElasticPool function Set-ADOPSElasticPool { [CmdletBinding()] param ( [Parameter(Mandatory)] [int]$PoolId, [Parameter(Mandatory)] $ElasticPoolObject, [string]$Organization ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $Uri = "https://dev.azure.com/$Organization/_apis/distributedtask/elasticpools/$PoolId`?api-version=7.1-preview.1" if ($ElasticPoolObject.gettype().name -eq 'String') { $Body = $ElasticPoolObject } else { try { $Body = $ElasticPoolObject | ConvertTo-Json -Depth 100 } catch { throw "Unable to convert the content of the ElasticPoolObject to json." } } $Method = 'PATCH' $ElasticPoolInfo = InvokeADOPSRestMethod -Uri $Uri -Method $Method -Organization $Organization -Body $Body Write-Output $ElasticPoolInfo } #endregion Set-ADOPSElasticPool #region Start-ADOPSPipeline function Start-ADOPSPipeline { param ( [Parameter(Mandatory)] [string]$Name, [Parameter(Mandatory)] [string]$Project, [Parameter()] [string]$Organization, [Parameter()] [string]$Branch = 'main' ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader } $AllPipelinesURI = "https://dev.azure.com/$($Org['Organization'])/$Project/_apis/pipelines?api-version=7.1-preview.1" $AllPipelines = InvokeADOPSRestMethod -Method Get -Uri $AllPipelinesURI -Organization $Org['Organization'] $PipelineID = ($AllPipelines.value | Where-Object -Property Name -EQ $Name).id if ([string]::IsNullOrEmpty($PipelineID)) { throw "No pipeline with name $Name found." } $URI = "https://dev.azure.com/$($Org['Organization'])/$Project/_apis/pipelines/$PipelineID/runs?api-version=7.1-preview.1" $Body = '{"stagesToSkip":[],"resources":{"repositories":{"self":{"refName":"refs/heads/' + $Branch + '"}}},"variables":{}}' $InvokeSplat = @{ Method = 'Post' Uri = $URI Body = $Body Organization = $Org['Organization'] } InvokeADOPSRestMethod @InvokeSplat } #endregion Start-ADOPSPipeline #region Test-ADOPSYamlFile function Test-ADOPSYamlFile { [CmdletBinding()] param ( [Parameter()] [string]$Organization, [Parameter(Mandatory)] [string]$Project, [Parameter(Mandatory)] [ValidateScript({ $_ -match '.*\.y[aA]{0,1}ml$' }, ErrorMessage = 'Fileextension must be ".yaml" or ".yml"')] [string]$File, [Parameter(Mandatory)] [int]$PipelineId ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['Organization'] } $Uri = "https://dev.azure.com/$Organization/$Project/_apis/pipelines/$PipelineId/runs?api-version=7.1-preview.1" $FileData = Get-Content $File -Raw $Body = @{ previewRun = $true templateParameters = @{} resources = @{} yamlOverride = $FileData } | ConvertTo-Json -Depth 10 -Compress $InvokeSplat = @{ Uri = $URI Method = 'Post' Body = $Body Organization = $Organization } try { $Result = InvokeADOPSRestMethod @InvokeSplat Write-Output "$file validation success." } catch [Microsoft.PowerShell.Commands.HttpResponseException] { if ($_.ErrorDetails.Message) { $r = $_.ErrorDetails.Message | ConvertFrom-Json if ($r.typeName -like '*PipelineValidationException*') { Write-Warning "Validation failed:`n$($r.message)" } else { throw $_ } } } } #endregion Test-ADOPSYamlFile |