ADOPS.psm1
#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' ) 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) } $Result = Invoke-RestMethod @InvokeSplat if ($Result -like "*Azure DevOps Services | Sign In*") { throw 'Failed to call Azure DevOps API. Please login before using.' } 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 { param () $Script:ADOPSCredentials } #endregion Get-ADOPSConnection #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-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 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-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')] [hashtable]$VariableHashtable ) if (-not [string]::IsNullOrEmpty($Organization)) { $Org = GetADOPSHeader -Organization $Organization } else { $Org = GetADOPSHeader $Organization = $Org['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 { $Body = @{ Name = $VariableGroupName Description = $Description Type = 'Vsts' variableGroupProjectReferences = @(@{ Name = $VariableGroupName Description = $Description projectReference = @{ Id = $($ProjectInfo.Id) } }) variables = $VariableHashtable } | ConvertTo-Json -Depth 10 } InvokeADOPSRestMethod -Uri $Uri -Method $Method -Body $Body -Organization $Organization } #endregion New-ADOPSVariableGroup #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 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 |