vsteam.functions.ps1
# Apply types to the returned objects so format and type files can # identify the object and act on it. function _applyTypes { [CmdletBinding()] param( $item, $type ) $item.PSObject.TypeNames.Insert(0, $type) } function _applyTypesWorkItemType { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.WorkItemType') } function _applyTypesToWorkItem { [CmdletBinding()] param($item) # If there are ids in the list that don't map to a work item and empty # entry is returned in its place if ErrorPolicy is Omit. if ($item) { $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.WorkItem') } } function _applyTypesToWiql { [CmdletBinding()] param($item) if ($item) { $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.Wiql') } } function _applyTypesToTfvcBranch { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.TfvcBranch') } function _applyTypesToTeamMember { [CmdletBinding()] param( [Parameter(Mandatory = $true)] $item, [Parameter(Mandatory = $true)] $team, [Parameter(Mandatory = $true)] $ProjectName ) # Add the team name as a NoteProperty so we can use it further down the pipeline (it's not returned from the REST call) $item | Add-Member -MemberType NoteProperty -Name Team -Value $team $item | Add-Member -MemberType NoteProperty -Name ProjectName -Value $ProjectName $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.TeamMember') } function _applyTypesToApproval { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.Approval') } function _applyArtifactTypes { $item.PSObject.TypeNames.Insert(0, "vsteam_lib.Build.Artifact") if ($item.PSObject.Properties.Match('resource').count -gt 0 -and $null -ne $item.resource -and $item.resource.PSObject.Properties.Match('propeties').count -gt 0) { $item.resource.PSObject.TypeNames.Insert(0, 'vsteam_lib.Build.Artifact.Resource') $item.resource.properties.PSObject.TypeNames.Insert(0, 'vsteam_lib.Build.Artifact.Resource.Properties') } } function _applyTypesToAzureSubscription { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.AzureSubscription') } function _applyTypesToPolicy { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.Policy') } function _applyTypesToPolicyType { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.PolicyType') } function _applyTypesToPullRequests { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.PullRequest') } function _applyTypesToServiceEndpoint { param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.ServiceEndpoint') $item.createdBy.PSObject.TypeNames.Insert(0, 'vsteam_lib.User') $item.authorization.PSObject.TypeNames.Insert(0, 'vsteam_lib.authorization') $item.data.PSObject.TypeNames.Insert(0, 'vsteam_lib.ServiceEndpoint.Details') if ($item.PSObject.Properties.Match('operationStatus').count -gt 0 -and $null -ne $item.operationStatus) { # This is VSTS $item.operationStatus.PSObject.TypeNames.Insert(0, 'vsteam_lib.OperationStatus') } } function _applyTypesToServiceEndpointType { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.ServiceEndpointType') $item.inputDescriptors.PSObject.TypeNames.Insert(0, 'vsteam_lib.InputDescriptor[]') foreach ($inputDescriptor in $item.inputDescriptors) { $inputDescriptor.PSObject.TypeNames.Insert(0, 'vsteam_lib.InputDescriptor') } $item.authenticationSchemes.PSObject.TypeNames.Insert(0, 'vsteam_lib.AuthenticationScheme[]') foreach ($authenticationScheme in $item.authenticationSchemes) { $authenticationScheme.PSObject.TypeNames.Insert(0, 'vsteam_lib.AuthenticationScheme') } if ($item.PSObject.Properties.Match('dataSources').count -gt 0 -and $null -ne $item.dataSources) { $item.dataSources.PSObject.TypeNames.Insert(0, 'vsteam_lib.DataSource[]') foreach ($dataSource in $item.dataSources) { $dataSource.PSObject.TypeNames.Insert(0, 'vsteam_lib.DataSource') } } } function _applyTypesToVariableGroup { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.VariableGroup') $item.createdBy.PSObject.TypeNames.Insert(0, 'vsteam_lib.User') $item.modifiedBy.PSObject.TypeNames.Insert(0, 'vsteam_lib.User') if ($item.PSObject.Properties.Match('providerData').count -gt 0 -and $null -ne $item.providerData) { $item.providerData.PSObject.TypeNames.Insert(0, 'vsteam_lib.ProviderData') } $item.variables.PSObject.TypeNames.Insert(0, 'vsteam_lib.Variables') } function _applyTypesToYamlPipelineResultType { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.YamlPipelineResult') } function _applyTypesToBuildTimelineResultType { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.BuildTimeline') if ($item.PSObject.Properties.Match('records').count -gt 0 -and $null -ne $item.records) { $item.records.PSObject.TypeNames.Insert(0, 'vsteam_lib.BuildTimelineRecord[]') foreach ($records in $item.records) { $records.PSObject.TypeNames.Insert(0, 'vsteam_lib.BuildTimelineRecord') } } } function _applyTypesToAgentPoolMaintenance { [CmdletBinding()] param($item) $item.PSObject.TypeNames.Insert(0, 'vsteam_lib.AgentPoolMaintenance') } function _callMembershipAPI { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $Id, [ValidateSet('GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS', 'PUT', 'DEFAULT', 'HEAD', 'MERGE', 'TRACE')] [string] $Method = 'GET', [ValidateSet('', 'Up', 'Down')] [string] $Direction ) # This will throw if this account does not support the graph API _supportsGraph Write-Verbose "Getting members for $Id" $query = @{} if ($Direction) { $query['direction'] = $Direction } # Call the REST API $resp = _callAPI -Method $Method -SubDomain vssps ` -Area 'graph' ` -Resource 'memberships' ` -Id $Id ` -QueryString $query ` -Version $(_getApiVersion Graph) return $resp.value } $here = Split-Path -Parent $MyInvocation.MyCommand.Path [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'It is used in other files')] $profilesPath = "$HOME/vsteam_profiles.json" Add-Type -AssemblyName System.Web # This is the main function for calling TFS and VSTS. It handels the auth and format of the route. # If you need to call TFS or VSTS this is the function to use. function _callAPI { [CmdletBinding()] param( [string]$resource, [string]$area, [string]$id, [string]$version, [string]$subDomain, [ValidateSet('Get', 'Post', 'Patch', 'Delete', 'Options', 'Put', 'Default', 'Head', 'Merge', 'Trace')] [string]$method, [Parameter(ValueFromPipeline = $true)] [object]$body, [string]$InFile, [string]$OutFile, [string]$ContentType, [string]$ProjectName, [string]$Team, [string]$Url, [object]$QueryString, [hashtable]$AdditionalHeaders = @{ }, # Some API calls require the Project ID and not the project name. # However, the dynamic project name parameter only shows you names # and not the Project IDs. Using this flag the project name provided # will be converted to the Project ID when building the URI for the API # call. [switch]$UseProjectId, # This flag makes sure that even if a default project is set that it is # not used to build the URI for the API call. Not all API require or # allow the project to be used. Setting a default project would cause # that project name to be used in building the URI that would lead to # 404 because the URI would not be correct. [Alias('IgnoreDefaultProject')] [switch]$NoProject, # This flag makes sure that no specific account is used # some APIs do not have an account in their API uri because # they are not account specific in the url path itself. (e.g. user profile, pipeline billing) [switch]$NoAccount ) process { # If the caller did not provide a Url build it. if (-not $Url) { $buildUriParams = @{ } + $PSBoundParameters $extra = 'method', 'body', 'InFile', 'OutFile', 'ContentType', 'AdditionalHeaders' foreach ($x in $extra) { $buildUriParams.Remove($x) | Out-Null } $Url = _buildRequestURI @buildUriParams } elseif ($QueryString) { # If the caller provided the URL and QueryString we need # to add the querystring now $qs = [System.Web.HttpUtility]::ParseQueryString('') foreach ($key in $QueryString.keys) { if ($QueryString[$key]) { $qs.Add($key, $QueryString[$key]) } } $Url = _queryStringAppender -Url $Url -QueryString $qs } if ($body) { Write-Verbose "Body $body" } $params = $PSBoundParameters Write-Verbose "Calling: $Url" $params.Add('Uri', $Url) $params.Add('UserAgent', (_getUserAgent)) $params.Add('TimeoutSec', (_getDefaultTimeout)) # always use utf8 and json as default content type instead of xml if ($false -eq $PSBoundParameters.ContainsKey('ContentType')) { $params.Add('ContentType', 'application/json; charset=utf-8') } # do not use header when requested. Then bearer must be provided with additional headers $params.Add('Headers', @{ }) # checking if an authorization token is provided already with the additional headers # use case: sometimes other tokens for certain APIs have to be used (buying pipelines) in order to work # some parts of internal APIs use their own token based on the PAT if (!$AdditionalHeaders.ContainsKey('Authorization')) { if (_useWindowsAuthenticationOnPremise) { $params.Add('UseDefaultCredentials', $true) } elseif (_useBearerToken) { $params['Headers'].Add('Authorization', "Bearer $env:TEAM_TOKEN") } else { $params['Headers'].Add('Authorization', "Basic $env:TEAM_PAT") } } if ($AdditionalHeaders -and $AdditionalHeaders.PSObject.Properties.name -match 'Keys') { foreach ($key in $AdditionalHeaders.Keys) { $params['Headers'].Add($key, $AdditionalHeaders[$key]) } } # We have to remove any extra parameters not used by Invoke-RestMethod $extra = 'NoAccount', 'NoProject', 'UseProjectId', 'Area', 'Resource', 'SubDomain', 'Id', 'Version', 'JSON', 'ProjectName', 'Team', 'Url', 'QueryString', 'AdditionalHeaders', 'CustomBearer' foreach ($e in $extra) { $params.Remove($e) | Out-Null } try { $resp = Invoke-RestMethod @params if ($resp) { Write-Verbose "return type: $($resp.gettype())" Write-Verbose $resp } return $resp } catch { _handleException $_ throw } } } <# .SYNOPSIS appends a query string to a url .DESCRIPTION appends a query string to a url .PARAMETER Url the url to append the query string to .PARAMETER QueryString the query string to append .EXAMPLE _queryStringAppender -Url 'https://dev.azure.com/contoso/MyFirstProject/_apis/build/builds' -QueryString @{ definition = 1; statusFilter = 'completed' } #> function _queryStringAppender { param( [Parameter(Mandatory = $true)] [string]$Url, [Parameter(Mandatory = $true)] [Collections.Specialized.NameValueCollection]$QueryString ) if ($QueryString.HasKeys()) { # Do not assume that Url already contains a query string # Crude check, but this will do if ($Url.IndexOf('?') -gt 0) { $Url += '&' + $QueryString.ToString() } else { $Url += '?' + $QueryString.ToString() } } return $Url } # General function to manage API Calls that involve a paged response, # either with a ContinuationToken property in the body payload or # with a X-MS-ContinuationToken header # TODO: Add functionality to manage paged responses based on X-MS-ContinuationToken header # TODO: This would need to be integrated probably into the _callAPI function? function _callAPIContinuationToken { [CmdletBinding()] param( [string]$Url, # If present, or $true, the function will manage the pages using the header # specified in $ContinuationTokenName. # If not present, or $false, the function will manage the pages using the # continuationToken property specified in $ContinuationTokenName. [switch]$UseHeader, # Allows to specify a header or continuation token property different of the default values. # If this parameter is not specified, the default value is X-MS-ContinuationToken or continuationToken # depending if $UseHeader is present or not, respectively [string]$ContinuationTokenName, # Property in the response body payload that contains the collecion of objects to return to the calling function [string]$PropertyName, # Number of pages to be retrieved. If 0, or not specified, it will return all the available pages [int]$MaxPages ) if ($MaxPages -le 0) { $MaxPages = [int32]::MaxValue } if ([string]::IsNullOrEmpty($ContinuationTokenName)) { if ($UseHeader.IsPresent) { $ContinuationTokenName = 'X-MS-ContinuationToken' } else { $ContinuationTokenName = 'continuationToken' } } $i = 0 $obj = @() $apiParameters = $url do { if ($UseHeader.IsPresent) { throw 'Continuation token from response headers not supported in this version' } else { $resp = _callAPI -url $apiParameters $continuationToken = $resp."$ContinuationTokenName" $i++ Write-Verbose "page $i" $obj += $resp."$PropertyName" if (-not [String]::IsNullOrEmpty($continuationToken)) { $continuationToken = [uri]::EscapeDataString($continuationToken) $apiParameters = "${url}&continuationToken=$continuationToken" } } } while (-not [String]::IsNullOrEmpty($continuationToken) -and $i -lt $MaxPages) return $obj } # Not all versions support the name features. function _supportsGraph { _hasAccount if ($false -eq $(_testGraphSupport)) { throw 'This account does not support the graph API.' } } function _testGraphSupport { (_getApiVersion Graph) -as [boolean] } function _supportsHierarchyQuery { _hasAccount if ($false -eq $(_testHierarchyQuerySupport)) { throw 'This account does not support the hierarchy query API.' } } function _testHierarchyQuerySupport { (_getApiVersion HierarchyQuery) -as [boolean] } function _supportsBilling { _hasAccount if ($false -eq $(_testBillingSupport)) { throw 'This account does not support the billing API.' } } function _testBillingSupport { (_getApiVersion Billing) -as [boolean] } function _supportVariableGroups { _hasAccount if ($false -eq $(_testVariableGroupsSupport)) { throw 'This account does not support the variable groups.' } } function _testVariableGroupsSupport { (_getApiVersion VariableGroups) -as [boolean] } function _supportsSecurityNamespace { _hasAccount if (([vsteam_lib.Versions]::Version -ne 'VSTS') -and ([vsteam_lib.Versions]::Version -ne 'AzD')) { throw 'Security Namespaces are currently only supported in Azure DevOps Service (Online)' } } function _supportsMemberEntitlementManagement { [CmdletBinding(DefaultParameterSetName = 'upto')] param( [parameter(ParameterSetName = 'upto')] [string]$UpTo = $null, [parameter(ParameterSetName = 'onwards')] [string]$Onwards = $null ) _hasAccount $apiVer = _getApiVersion MemberEntitlementManagement if (-not $apiVer) { throw 'This account does not support Member Entitlement.' } elseif (-not [string]::IsNullOrEmpty($UpTo) -and $apiVer -gt $UpTo) { throw "EntitlementManagemen version must be equal or lower than $UpTo for this call, current value $apiVer" } elseif (-not [string]::IsNullOrEmpty($Onwards) -and $apiVer -lt $Onwards) { throw "EntitlementManagemen version must be equal or greater than $Onwards for this call, current value $apiVer" } } function _testAdministrator { $user = [Security.Principal.WindowsIdentity]::GetCurrent() (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } # When you mock this in tests be sure to add a Parameter Filter that matches # the Service that should be used. # Mock _getApiVersion { return '1.0-gitUnitTests' } -ParameterFilter { $Service -eq 'Git' } # Also test in the Assert-MockCalled that the correct version was used in the URL that was # built for the API call. function _getApiVersion { [CmdletBinding(DefaultParameterSetName = 'Service')] [OutputType([string])] param ( [parameter(ParameterSetName = 'Service', Mandatory = $true, Position = 0)] [ValidateSet('Build', 'Release', 'Core', 'Git', 'DistributedTask', 'DistributedTaskReleased', 'VariableGroups', 'Tfvc', 'Packaging', 'MemberEntitlementManagement', 'Version', 'ExtensionsManagement', 'ServiceEndpoints', 'Graph', 'TaskGroups', 'Policy', 'Processes', 'HierarchyQuery', 'Pipelines', 'Billing', 'Wiki', 'WorkItemTracking')] [string] $Service, [parameter(ParameterSetName = 'Target')] [switch] $Target ) if ($Target.IsPresent) { return [vsteam_lib.Versions]::GetApiVersion('Version') } else { return [vsteam_lib.Versions]::GetApiVersion($Service) } } function _getInstance { return [vsteam_lib.Versions]::Account } function _getDefaultTimeout { if ($Global:PSDefaultParameterValues['*-vsteam*:vsteamApiTimeout']) { return $Global:PSDefaultParameterValues['*-vsteam*:vsteamApiTimeout'] } else { return 60 } } function _getDefaultProject { return $Global:PSDefaultParameterValues['*-vsteam*:projectName'] } function _hasAccount { if (-not $(_getInstance)) { throw 'You must call Set-VSTeamAccount before calling any other functions in this module.' } } function _buildRequestURI { [CmdletBinding()] param( [string]$team, [string]$resource, [string]$area, [string]$id, [string]$version, [string]$subDomain, [object]$queryString, [Parameter(Mandatory = $false)] [vsteam_lib.ProjectValidateAttribute($false)] $ProjectName, [switch]$UseProjectId, [switch]$NoProject, [switch]$NoAccount ) process { _hasAccount $sb = New-Object System.Text.StringBuilder $qs = [System.Web.HttpUtility]::ParseQueryString('') $instance = 'https://dev.azure.com' if ($NoAccount.IsPresent -eq $false) { $instance = _getInstance } $sb.Append($(_addSubDomain -subDomain $subDomain -instance $instance)) | Out-Null # There are some APIs that must not have the project added to the URI. # However, if they caller set the default project it will be passed in # here and added to the URI by mistake. Functions that need the URI # created without the project even if the default project is set needs # to pass the -NoProject switch. if ($ProjectName -and $NoProject.IsPresent -eq $false -and $NoAccount.IsPresent -eq $false) { if ($UseProjectId.IsPresent) { $projectId = (Get-VSTeamProject -Name $ProjectName | Select-Object -ExpandProperty id) $sb.Append("/$projectId") | Out-Null } else { $sb.Append("/$projectName") | Out-Null } } if ($team) { $sb.Append("/$team") | Out-Null } $sb.Append('/_apis') | Out-Null if ($area) { $sb.Append("/$area") | Out-Null } if ($resource) { $sb.Append("/$resource") | Out-Null } if ($id) { $sb.Append("/$id") | Out-Null } if ($version) { $qs.Add('api-version', $version) } if ($queryString) { foreach ($key in $queryString.keys) { if ($QueryString[$key]) { $qs.Add($key, $queryString[$key]) } } } if ($qs.HasKeys()) { $sb.Append('?') | Out-Null $sb.Append($qs.ToString()) | Out-Null } $url = $sb.ToString() return $url } } function _handleException { param( [Parameter(Position = 1)] $ex ) $handled = $false if ($ex.Exception.PSObject.Properties.Match('Response').count -gt 0 -and $null -ne $ex.Exception.Response -and $ex.Exception.Response.StatusCode -ne 'BadRequest') { $handled = $true $msg = "An error occurred: $($ex.Exception.Message)" Write-Warning -Message $msg } try { $e = (ConvertFrom-Json $ex.ToString()) $hasValueProp = $e.PSObject.Properties.Match('value') if (0 -eq $hasValueProp.count) { $handled = $true Write-Warning -Message $e.message } else { $handled = $true Write-Warning -Message $e.value.message } } catch { $msg = "An error occurred: $($ex.Exception.Message)" } if (-not $handled) { throw $ex } } function _isVSTS { param( [parameter(Mandatory = $true)] [string] $instance ) return $instance -like '*.visualstudio.com*' -or $instance -like 'https://dev.azure.com/*' } function _getVSTeamAPIVersion { param( [parameter(Mandatory = $true)] [string] $instance, [string] $Version ) if ($Version) { return $Version } else { if (_isVSTS $instance) { return 'VSTS' } else { return 'TFS2017' } } } function _isOnWindows { $os = Get-OperatingSystem return $os -eq 'Windows' } function _addSubDomain { param( [string] $subDomain, [string] $instance ) # For VSTS Entitlements is under .vsaex if ($subDomain -and $instance.ToLower().Contains('dev.azure.com')) { $instance = $instance.ToLower().Replace('dev.azure.com', "$subDomain.dev.azure.com") } return $instance } function _getUserAgent { [CmdletBinding()] param() $os = Get-OperatingSystem $result = "Team Module/$([vsteam_lib.Versions]::ModuleVersion) ($os) PowerShell/$($PSVersionTable.PSVersion.ToString())" Write-Verbose $result return $result } function _useWindowsAuthenticationOnPremise { return (_isOnWindows) -and (!$env:TEAM_PAT) -and -not ($(_getInstance) -like '*visualstudio.com') -and -not ($(_getInstance) -like 'https://dev.azure.com/*') } function _useBearerToken { return (!$env:TEAM_PAT) -and ($env:TEAM_TOKEN) } function _getWorkItemTypes { param( [Parameter(Mandatory = $true)] [string] $ProjectName ) if (-not $(_getInstance)) { Write-Output @() return } # Call the REST API try { $resp = _callAPI -ProjectName $ProjectName ` -area wit ` -resource workitemtypes ` -version $(_getApiVersion Core) # This call returns JSON with "": which causes the ConvertFrom-Json to fail. # To replace all the "": with "_end": $resp = $resp.Replace('"":', '"_end":') | ConvertFrom-Json if ($resp.count -gt 0) { Write-Output ($resp.value).name } } catch { Write-Verbose $_ Write-Output @() } } function _buildLevelDynamicParam { param () # # Only add these options on Windows Machines if (_isOnWindows) { $ParameterName = 'Level' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $false $ParameterAttribute.HelpMessage = 'On Windows machines allows you to store the default project at the process, user or machine level. Not available on other platforms.' # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) # Generate and set the ValidateSet if (_testAdministrator) { $arrSet = 'Process', 'User', 'Machine' } else { $arrSet = 'Process', 'User' } $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) return $RuntimeParameterDictionary } } function _buildProjectNameDynamicParam { param( [string] $ParameterName = 'ProjectName', [string] $ParameterSetName, [bool] $Mandatory = $true, [string] $AliasName, [int] $Position = 0 ) # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $Mandatory $ParameterAttribute.Position = $Position if ($ParameterSetName) { $ParameterAttribute.ParameterSetName = $ParameterSetName } $ParameterAttribute.ValueFromPipelineByPropertyName = $true $ParameterAttribute.HelpMessage = 'The name of the project. You can tab complete from the projects in your Team Services or TFS account when passed on the command line.' # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) if ($AliasName) { $AliasAttribute = New-Object System.Management.Automation.AliasAttribute(@($AliasName)) $AttributeCollection.Add($AliasAttribute) } # Generate and set the ValidateSet $arrSet = [vsteam_lib.ProjectCache]::GetCurrent($false) if ($arrSet) { Write-Verbose "arrSet = $arrSet" $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) } # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) return $RuntimeParameterDictionary <# Builds a dynamic parameter that can be used to tab complete the ProjectName parameter of functions from a list of projects from the added TFS Account. You must call Set-VSTeamAccount before trying to use any function that relies on this dynamic parameter or you will get an error. This can only be used in Advanced Fucntion with the [CmdletBinding()] attribute. The function must also have a begin block that maps the value to a common variable like this. DynamicParam { # Generate and set the ValidateSet $arrSet = Get-VSTeamProjects | Select-Object -ExpandProperty Name _buildProjectNameDynamicParam -arrSet $arrSet } process { # Bind the parameter to a friendly variable $ProjectName = $PSBoundParameters[$ParameterName] } #> } function _buildProcessNameDynamicParam { param( [string] $ParameterName = 'ProcessName', [string] $ParameterSetName, [bool] $Mandatory = $true, [string] $AliasName, [int] $Position = 0 ) # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $Mandatory $ParameterAttribute.Position = $Position if ($ParameterSetName) { $ParameterAttribute.ParameterSetName = $ParameterSetName } $ParameterAttribute.ValueFromPipelineByPropertyName = $true $ParameterAttribute.HelpMessage = 'The name of the process. You can tab complete from the processes in your Team Services or TFS account when passed on the command line.' # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) if ($AliasName) { $AliasAttribute = New-Object System.Management.Automation.AliasAttribute(@($AliasName)) $AttributeCollection.Add($AliasAttribute) } # Generate and set the ValidateSet $arrSet = [vsteam_lib.ProcessTemplateCache]::GetCurrent() if ($arrSet) { Write-Verbose "arrSet = $arrSet" $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) } # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) return $RuntimeParameterDictionary <# Builds a dynamic parameter that can be used to tab complete the ProjectName parameter of functions from a list of projects from the added TFS Account. You must call Set-VSTeamAccount before trying to use any function that relies on this dynamic parameter or you will get an error. This can only be used in Advanced Fucntion with the [CmdletBinding()] attribute. The function must also have a begin block that maps the value to a common variable like this. DynamicParam { # Generate and set the ValidateSet $arrSet = Get-VSTeamProjects | Select-Object -ExpandProperty Name _buildProjectNameDynamicParam -arrSet $arrSet } process { # Bind the parameter to a friendly variable $ProjectName = $PSBoundParameters[$ParameterName] } #> } function _buildDynamicParam { param( [string] $ParameterName = 'QueueName', [array] $arrSet, [bool] $Mandatory = $false, [string] $ParameterSetName, [int] $Position = -1, [type] $ParameterType = [string], [bool] $ValueFromPipelineByPropertyName = $true, [string] $AliasName, [string] $HelpMessage ) # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER ParameterName Parameter description .PARAMETER ParameterSetName Parameter description .PARAMETER Mandatory Parameter description .PARAMETER AliasName Parameter description .PARAMETER Position Parameter description .EXAMPLE An example .NOTES General notes #> # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $Mandatory $ParameterAttribute.ValueFromPipelineByPropertyName = $ValueFromPipelineByPropertyName if ($Position -ne -1) { $ParameterAttribute.Position = $Position } if ($ParameterSetName) { $ParameterAttribute.ParameterSetName = $ParameterSetName } if ($HelpMessage) { $ParameterAttribute.HelpMessage = $HelpMessage } # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) if ($AliasName) { $AliasAttribute = New-Object System.Management.Automation.AliasAttribute(@($AliasName)) $AttributeCollection.Add($AliasAttribute) } if ($arrSet) { # Generate and set the ValidateSet $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) } # Create and return the dynamic parameter return New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, $ParameterType, $AttributeCollection) } function _convertSecureStringTo_PlainText { [CmdletBinding()] param( [parameter(ParameterSetName = 'Secure', Mandatory = $true, HelpMessage = 'Secure String')] [securestring] $SecureString ) # Convert the securestring to a normal string # this was the one technique that worked on Mac, Linux and Windows $credential = New-Object System.Management.Automation.PSCredential 'unknown', $SecureString return $credential.GetNetworkCredential().Password } function _trackProjectProgress { param( [Parameter(Mandatory = $true)] $Resp, [string] $Title, [string] $Msg ) $i = 0 $x = 1 $y = 10 $status = $resp.status # Track status while ($status -ne 'failed' -and $status -ne 'succeeded') { $status = (_callAPI -Url $resp.url).status # oscillate back a forth to show progress $i += $x Write-Progress -Activity $title -Status $msg -PercentComplete ($i / $y * 100) if ($i -eq $y -or $i -eq 0) { $x *= -1 } } } $iTracking = 0 $xTracking = 1 $yTracking = 10 $statusTracking = $null function _trackServiceEndpointProgress { param( [Parameter(Mandatory = $true)] [string] $projectName, [Parameter(Mandatory = $true)] $resp, [string] $title, [string] $msg ) $iTracking = 0 $xTracking = 1 $yTracking = 10 $isReady = $false # Track status while (-not $isReady) { $statusTracking = _callAPI -ProjectName $projectName -Area 'distributedtask' -Resource 'serviceendpoints' -Id $resp.id ` -Version $(_getApiVersion ServiceEndpoints) $isReady = $statusTracking.isReady if (-not $isReady) { $state = $statusTracking.operationStatus.state if ($state -eq 'Failed') { throw $statusTracking.operationStatus.statusMessage } } # oscillate back a forth to show progress $iTracking += $xTracking Write-Progress -Activity $title -Status $msg -PercentComplete ($iTracking / $yTracking * 100) if ($iTracking -eq $yTracking -or $iTracking -eq 0) { $xTracking *= -1 } } } function _getModuleVersion { # Read the version from the psd1 file. # $content = (Get-Content -Raw "./VSTeam.psd1" | Out-String) $content = (Get-Content -Raw "$here\VSTeam.psd1" | Out-String) $r = [regex]"ModuleVersion += +'([^']+)'" $d = $r.Match($content) return $d.Groups[1].Value } function _setEnvironmentVariables { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] param ( [string] $Level = 'Process', [string] $Pat, [string] $Acct, [string] $BearerToken, [string] $Version ) # You always have to set at the process level or they will Not # be seen in your current session. $env:TEAM_PAT = $Pat $env:TEAM_ACCT = $Acct $env:TEAM_VERSION = $Version $env:TEAM_TOKEN = $BearerToken [vsteam_lib.Versions]::Account = $Acct # This is so it can be loaded by default in the next session if ($Level -ne 'Process') { [System.Environment]::SetEnvironmentVariable('TEAM_PAT', $Pat, $Level) [System.Environment]::SetEnvironmentVariable('TEAM_ACCT', $Acct, $Level) [System.Environment]::SetEnvironmentVariable('TEAM_VERSION', $Version, $Level) } } # If you remove an account the current default project needs to be cleared as well. function _clearEnvironmentVariables { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] param ( [string] $Level = 'Process' ) $env:TEAM_PROJECT = $null $env:TEAM_TIMEOUT = $null [vsteam_lib.Versions]::DefaultProject = '' [vsteam_lib.Versions]::DefaultTimeout = '' $Global:PSDefaultParameterValues.Remove('*-vsteam*:projectName') $Global:PSDefaultParameterValues.Remove('*-vsteam*:vsteamApiTimeout') # This is so it can be loaded by default in the next session if ($Level -ne 'Process') { [System.Environment]::SetEnvironmentVariable('TEAM_PROJECT', $null, $Level) [System.Environment]::SetEnvironmentVariable('TEAM_TIMEOUT', $null, $Level) } _setEnvironmentVariables -Level $Level -Pat '' -Acct '' -UseBearerToken '' -Version '' } function _convertToHex() { [cmdletbinding()] param( [parameter(Mandatory = $true)] [string]$Value ) $bytes = $Value | Format-Hex -Encoding Unicode $hexString = ($bytes.Bytes | ForEach-Object ToString X2) -join '' return $hexString.ToLowerInvariant() } function _getVSTeamIdFromDescriptor { [cmdletbinding()] param( [parameter(Mandatory = $true)] [string]$Descriptor ) $identifier = $Descriptor.Split('.')[1] # We need to Pad the string for FromBase64String to work reliably (AzD Descriptors are not padded) $ModulusValue = ($identifier.length % 4) Switch ($ModulusValue) { '0' { $Padded = $identifier } '1' { $Padded = $identifier.Substring(0, $identifier.Length - 1) } '2' { $Padded = $identifier + ('=' * (4 - $ModulusValue)) } '3' { $Padded = $identifier + ('=' * (4 - $ModulusValue)) } } return [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($Padded)) } function _getPermissionInheritanceInfo { [cmdletbinding()] [OutputType([System.Collections.Hashtable])] param( [parameter(Mandatory = $true)] [string] $projectName, [parameter(Mandatory = $true)] [string] $resourceName, [Parameter(Mandatory = $true)] [ValidateSet('Repository', 'BuildDefinition', 'ReleaseDefinition')] [string] $resourceType ) $projectId = (Get-VSTeamProject -Name $projectName | Select-Object -ExpandProperty id) Switch ($resourceType) { 'Repository' { $securityNamespaceID = '2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87' $repositoryId = (Get-VSTeamGitRepository -Name "$resourceName" -ProjectName $projectName | Select-Object -ExpandProperty id ) if ($null -eq $repositoryId) { Write-Error 'Unable to retrieve repository information. Ensure that the resourceName provided matches a repository name exactly.' return } $token = "repoV2/$($projectId)/$repositoryId" } 'BuildDefinition' { $securityNamespaceID = '33344d9c-fc72-4d6f-aba5-fa317101a7e9' $buildDefinitionId = (Get-VSTeamBuildDefinition -ProjectName $projectName | Where-Object name -EQ "$resourceName" | Select-Object -ExpandProperty id) if ($null -eq $buildDefinitionId) { Write-Error 'Unable to retrieve build definition information. Ensure that the resourceName provided matches a build definition name exactly.' return } $token = "$($projectId)/$buildDefinitionId" } 'ReleaseDefinition' { $securityNamespaceID = 'c788c23e-1b46-4162-8f5e-d7585343b5de' $releaseDefinition = (Get-VSTeamReleaseDefinition -ProjectName $projectName | Where-Object -Property name -EQ "$resourceName") if ($null -eq $releaseDefinition) { Write-Error 'Unable to retrieve release definition information. Ensure that the resourceName provided matches a release definition name exactly.' return } if (($releaseDefinition).path -eq '/') { $token = "$($projectId)/$($releaseDefinition.id)" } else { $token = "$($projectId)" + "$($releaseDefinition.path -replace '\\','/')" + "/$($releaseDefinition.id)" } } } return @{ Token = $token ProjectID = $projectId SecurityNamespaceID = $securityNamespaceID } } function _getDescriptorForACL { [cmdletbinding()] param( [parameter(Mandatory = $true, ParameterSetName = 'ByUser')] [vsteam_lib.User]$User, [parameter(MAndatory = $true, ParameterSetName = 'ByGroup')] [vsteam_lib.Group]$Group ) if ($User) { switch ($User.Origin) { 'vsts' { $sid = _getVSTeamIdFromDescriptor -Descriptor $User.Descriptor if ($User.Descriptor.StartsWith('svc.')) { $descriptor = "Microsoft.TeamFoundation.ServiceIdentity;$sid" } else { $descriptor = "Microsoft.TeamFoundation.Identity;$sid" } } 'aad' { $descriptor = "Microsoft.IdentityModel.Claims.ClaimsIdentity;$($User.Domain)\\$($User.PrincipalName)" } default { throw 'User type not handled yet for ACL. Please report this as an issue on the VSTeam Repository: https://github.com/MethodsAndPractices/vsteam/issues' } } } if ($Group) { switch ($Group.Origin) { 'vsts' { $sid = _getVSTeamIdFromDescriptor -Descriptor $Group.Descriptor $descriptor = "Microsoft.TeamFoundation.Identity;$sid" } default { throw 'Group type not handled yet for Add-VSTeamGitRepositoryPermission. Please report this as an issue on the VSTeam Repository: https://github.com/MethodsAndPractices/vsteam/issues' } } } return $descriptor } function _getBillingToken { # get a billing access token by using the given PAT. # this token can be used for buying pipelines or artifacts # or other things used for billing (except user access levels) [CmdletBinding()] param ( #billing token can have different scopes. They are defined by named token ids. #They should be validated to be specific by it's name [Parameter(Mandatory = $true)] [string] [ValidateSet('AzCommDeploymentProfile', 'CommerceDeploymentProfile')] $NamedTokenId ) $sessionToken = @{ appId = 00000000 - 0000 - 0000 - 0000 - 000000000000 force = $false tokenType = 0 namedTokenId = $NamedTokenId } $billingToken = _callAPI ` -NoProject ` -method POST ` -ContentType 'application/json' ` -area 'WebPlatformAuth' ` -resource 'SessionToken' ` -version '3.2-preview.1' ` -body ($sessionToken | ConvertTo-Json -Depth 50 -Compress) return $billingToken } # pin if github is availabe and client has access to github function _pinpGithub { Write-Verbose 'Checking if client is online' $pingGh = [System.Net.NetworkInformation.Ping]::new() $replyStatus = $null try { [System.Net.NetworkInformation.PingReply]$reply = $pingGh.Send('github.com', 150) $replyStatus = $reply.Status } catch { $replyStatus = [System.Net.NetworkInformation.IPStatus]::Unknown Write-Debug $_.Exception.InnerException } return $replyStatus } function _showModuleLoadingMessages { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'False positive. Parameter is being used, but not catched when used in script blocks. see: https://github.com/PowerShell/PSScriptAnalyzer/issues/1472')] [CmdletBinding()] param ( # version of the module [Parameter(Mandatory = $true)] [version] $ModuleVersion ) process { if ((_pinpGithub) -eq [System.Net.NetworkInformation.IPStatus]::Success) { # catch if web request fails. Invoke-WebRequest does not have a ErrorAction parameter try { $moduleMessagesRes = (Invoke-RestMethod 'https://raw.githubusercontent.com/MethodsAndPractices/vsteam/trunk/.github/moduleMessages.json') # don't show messages if module has not the specified version $filteredMessages = $moduleMessagesRes | Where-Object { $returnMessage = $true if (-not [String]::IsNullOrEmpty($_.displayFromVersion)) { $returnMessage = ([version]$_.displayFromVersion) -le $ModuleVersion } if (-not [String]::IsNullOrEmpty($_.displayToVersion) -and $returnMessage -eq $true) { $returnMessage = ([version]$_.displayToVersion) -ge $ModuleVersion } return $returnMessage } # dont show messages if display until date is in the past $currentDate = Get-Date $filteredMessages = $filteredMessages | Where-Object { $currentDate -le ([DateTime]::ParseExact($_.toDate, 'dd/MM/yyyy HH:mm:ss', [cultureInfo]::InvariantCulture)) } # stop processing if no messages left if ($null -eq $filteredMessages -or $filteredMessages.Count -eq 0) { return } $filteredMessages | ForEach-Object { $messageFormat = '{0}: {1}' Write-Information ($messageFormat -f $_.type.ToUpper(), $_.msg) -InformationAction Continue } } catch { Write-Debug "Error: $_" } } else { Write-Information 'Client is offline or blocked by a firewall. Skipping module messages' } } } function _checkForModuleUpdates { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'False positive. Parameter is being used, but not catched when used in script blocks. see: https://github.com/PowerShell/PSScriptAnalyzer/issues/1472')] [CmdletBinding()] param ( # version of the module [Parameter(Mandatory = $true)] [version] $ModuleVersion ) process { # check if client has online access if ((_pinpGithub) -eq [System.Net.NetworkInformation.IPStatus]::Success) { # catch if web request fails. Invoke-WebRequest does not have a ErrorAction parameter try { Write-Verbose 'Checking if module is up to date' $ghLatestRelease = Invoke-RestMethod 'https://api.github.com/repos/MethodsAndPractices/vsteam/releases/latest' [version]$latestVersion = $ghLatestRelease.tag_name -replace 'v', '' [version]$currentVersion = $ModuleVersion if ($currentVersion -lt $latestVersion) { Write-Information "New version available: $latestVersion" -InformationAction Continue Write-Information "Run to update: Update-Module -Name VSTeam -RequiredVersion $latestVersion `n" -InformationAction Continue } } catch { Write-Debug "Error: $_" } } else { Write-Information 'Client is offline or blocked by a firewall. Skipping module updates check' } } } function _countParameters() { param( $BoundParameters ) $counter = 0 $advancedPameters = @('Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable') foreach ($p in $BoundParameters.GetEnumerator()) { if ($p.Key -notin $advancedPameters) { $counter++ } } Write-Verbose "Found $counter parameters" $counter } function _invalidate() { [vsteam_lib.ProjectCache]::Invalidate() } # Create a team in a team project. # # Get-VSTeamOption 'core' 'teams' # id : d30a3dd1-f8ba-442a-b86a-bd0c0c383e59 # area : core # resourceName : teams # routeTemplate : _apis/projects/{projectId}/{resource}/{*teamId} # https://bit.ly/Add-VSTeam function Add-VSTeam { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeam')] param( [Parameter(Mandatory = $true, Position = 0)] [Alias('TeamName')] [string] $Name, [Parameter(Position = 1)] [string] $Description = '', [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $body = '{ "name": "' + $Name + '", "description": "' + $Description + '" }' $resp = _callAPI -Method POST -NoProject ` -Resource "projects/$ProjectName/teams" ` -Body $body ` -Version $(_getApiVersion Core) $team = [vsteam_lib.Team]::new($resp, $ProjectName) Write-Output $team } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamAccessControlEntry { [CmdletBinding(DefaultParameterSetName = 'ByNamespace', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamAccessControlEntry')] param( [Parameter(ParameterSetName = 'ByNamespace', Mandatory = $true, ValueFromPipeline = $true)] [vsteam_lib.SecurityNamespace] $SecurityNamespace, [Parameter(ParameterSetName = 'ByNamespaceId', Mandatory = $true)] [guid] $SecurityNamespaceId, [Parameter(Mandatory = $true)] [string] $Token, [Parameter(Mandatory = $true)] [string] $Descriptor, [Parameter(Mandatory = $true)] [ValidateRange(0, [int]::MaxValue)] [int] $AllowMask, [Parameter(Mandatory = $true)] [ValidateRange(0, [int]::MaxValue)] [int] $DenyMask, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { if ($AllowMask -eq 0 -and $DenyMask -eq 0 -and $OverwriteMask -eq $false) { Write-Warning "Permission masks for Allow and Deny do not inlude any permission. No Permission will change!" } if ($SecurityNamespace) { $SecurityNamespaceId = $SecurityNamespace.ID } [string]$merge = (!$OverwriteMask) $merge = $merge.ToLowerInvariant() $body = @" { "token": "$Token", "merge": $merge, "accessControlEntries": [ { "descriptor": "$Descriptor", "allow": $AllowMask, "deny": $DenyMask, "extendedinfo": {} } ] } "@ $resp = _callAPI -Method POST -NoProject ` -Resource "accesscontrolentries" ` -Id $SecurityNamespaceId ` -Body $body ` -Version $(_getApiVersion Core) if ($resp.count -ne 1) { throw "Expected 1 result, but got $($rep.count)" } # Storing the object before you return it cleaned up the pipeline. # When I just write the object from the constructor each property # seemed to be written $acl = [vsteam_lib.AccessControlEntry]::new($resp.value[0]) Write-Output $acl } } # Create new classification node. # # Get-VSTeamOption 'wit' 'classificationNodes' # id : 5a172953-1b41-49d3-840a-33f79c3ce89f # area : wit # resourceName : classificationNodes # routeTemplate : {project}/_apis/{area}/{resource}/{structureGroup}/{*path} # https://bit.ly/Add-VSTeamClassificationNode function Add-VSTeamArea { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamArea')] param( [Parameter(Mandatory = $true, Position = 0)] [string] $Name, [Parameter(Mandatory = $false)] [string] $Path, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $resp = Add-VSTeamClassificationNode -ProjectName $ProjectName ` -Name $Name ` -StructureGroup areas ` -Path $Path Write-Output $resp } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamAzureRMServiceEndpoint { [CmdletBinding(DefaultParameterSetName = 'Automatic', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamAzureRMServiceEndpoint')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias('displayName')] [string] $SubscriptionName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $SubscriptionId, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $SubscriptionTenantId, [Parameter(ParameterSetName = 'Manual', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $ServicePrincipalId, [Parameter(ParameterSetName = 'Manual', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $ServicePrincipalKey, [string] $EndpointName, [string] $Description, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { if (-not $endpointName) { $endpointName = $subscriptionName } if (-not $servicePrincipalId) { $creationMode = 'Automatic' } else { $creationMode = 'Manual' } $obj = @{ authorization = @{ parameters = @{ serviceprincipalid = $servicePrincipalId serviceprincipalkey = $servicePrincipalKey tenantid = $subscriptionTenantId } scheme = 'ServicePrincipal' } data = @{ subscriptionId = $subscriptionId subscriptionName = $subscriptionName creationMode = $creationMode } url = 'https://management.azure.com/' description = $Description } return Add-VSTeamServiceEndpoint -ProjectName $ProjectName ` -endpointName $endpointName ` -endpointType azurerm ` -object $obj } } function Add-VSTeamBanner { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $true)] [ValidateSet('info', 'warning', 'error')] [string]$Level, [Parameter(Mandatory = $true)] [DateTime]$ExpirationDate, [string]$Id = (New-Guid).ToString() ) process { $bannerBody = @{ "GlobalMessageBanners/$Id" = @{ 'level' = $Level 'message' = $Message 'expirationDate' = $ExpirationDate.ToString("yyyy-MM-ddTHH:mm:ss") } } | ConvertTo-Json -Depth 3 Invoke-VSTeamRequest -method PATCH -area 'settings' -resource 'entries/host' -version '3.2-preview' -body $bannerBody } } # Queues a build. # # Get-VSTeamOption 'build' 'Builds' # id : 0cd358e1-9217-4d94-8269-1c1ee6f93dcf # area : Build # resourceName : Builds # routeTemplate : {project}/_apis/build/{resource}/{buildId} # https://bit.ly/Add-VSTeamBuild function Add-VSTeamBuild { [CmdletBinding(DefaultParameterSetName = 'ByName', HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamBuild')] param( [Parameter(ParameterSetName = 'ByID', ValueFromPipelineByPropertyName = $true)] [Int32] $BuildDefinitionId, [Parameter(Mandatory = $false)] [string] $SourceBranch, [Parameter(Mandatory = $false)] [hashtable] $BuildParameters, [Parameter(Mandatory = $false)] [hashtable] $TemplateParameters, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName, [ArgumentCompleter([vsteam_lib.QueueCompleter])] [string] $QueueName, [ArgumentCompleter([vsteam_lib.BuildDefinitionCompleter])] [string] $BuildDefinitionName ) begin { if ($BuildDefinitionId) { $body = @{ definition = @{ id = $BuildDefinitionId } } } elseif ($BuildDefinitionName) { # Find the BuildDefinition id from the name $id = (Get-VSTeamBuildDefinition -ProjectName "$ProjectName" -Filter $BuildDefinitionName).id if (-not $id) { throw "'$BuildDefinitionName' is not a valid build definition. Use Get-VSTeamBuildDefinition to get a list of build names" return } $body = @{ definition = @{ id = $id } } } else { throw "'No build definition was given. Use Get-VSTeamBuildDefinition to get a list of builds" return } if ($QueueName) { $queueId = (Get-VSTeamQueue -ProjectName "$ProjectName" -queueName "$QueueName").id if (-not $queueId) { throw "'$QueueName' is not a valid Queue. Use Get-VSTeamQueue to get a list of queues" return } else { $body["queue"] = @{id = $queueId } } } } process { if ($SourceBranch) { $body.Add('sourceBranch', $SourceBranch) } if ($BuildParameters) { $body.Add('parameters', ($BuildParameters | ConvertTo-Json -Depth 100 -Compress)) } if ($TemplateParameters) { $body.Add('templateParameters', $TemplateParameters) } $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "build" ` -Resource "builds" ` -Body ($body | ConvertTo-Json -Compress -Depth 100) ` -Version $(_getApiVersion Build) $build = [vsteam_lib.Build]::new($resp, $ProjectName) Write-Output $build } } # Creates a new definition. # # Get-VSTeamOption 'build' 'Definitions' # id : dbeaf647-6167-421a-bda9-c9327b25e2e6 # area : Build # resourceName : Definitions # routeTemplate : {project}/_apis/build/{resource}/{definitionId} # https://bit.ly/Add-VSTeamBuildDefinition function Add-VSTeamBuildDefinition { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamBuildDefinition')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $InFile, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { return _callAPI -Method POST -ProjectName $ProjectName ` -Area "build" ` -Resource "definitions" ` -infile $InFile ` -Version $(_getApiVersion Build) } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamBuildPermission { [CmdletBinding(DefaultParameterSetName = 'ByProjectAndUser', HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamBuildPermission')] param( [parameter(Mandatory = $true)] [string]$ProjectID, [parameter(Mandatory = $false)] [string]$BuildID, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndDescriptor")] [string]$Descriptor, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndGroup")] [object]$Group, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndUser")] [object]$User, [parameter(Mandatory = $false)] [vsteam_lib.BuildPermissions]$Allow, [parameter(Mandatory = $false)] [vsteam_lib.BuildPermissions]$Deny, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { # SecurityNamespaceID: 33344d9c-fc72-4d6f-aba5-fa317101a7e9 # Token: <projectId>/<pipelineId> $securityNamespaceId = "33344d9c-fc72-4d6f-aba5-fa317101a7e9" # Resolve Group to Descriptor if ($Group) { $Descriptor = _getDescriptorForACL -Group $Group } # Resolve User to Descriptor if ($User) { $Descriptor = _getDescriptorForACL -User $User } $token = $null if ($BuildID) { $token = "$ProjectID/$($BuildID)" } else { $token = "$ProjectID" } Add-VSTeamAccessControlEntry -SecurityNamespaceId $securityNamespaceId ` -Descriptor $Descriptor ` -Token $token ` -AllowMask ([int]$Allow) ` -DenyMask ([int]$Deny) ` -OverwriteMask:$OverwriteMask.IsPresent } } # Adds a tag to a build. # # Get-VSTeamOption 'build' 'tags' # id : 6e6114b2-8161-44c8-8f6c-c5505782427f # area : build # resourceName : tags # routeTemplate : {project}/_apis/{area}/builds/{buildId}/{resource}/{*tag} # http://bit.ly/Add-VSTeamBuildTag function Add-VSTeamBuildTag { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low", HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamBuildTag')] param( [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [string[]] $Tags, [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('BuildID')] [int[]] $Id, [switch] $Force, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { foreach ($item in $id) { if ($Force -or $pscmdlet.ShouldProcess($item, "Add-VSTeamBuildTag")) { foreach ($tag in $tags) { _callAPI -Method PUT -ProjectName $projectName ` -Area "build/builds/$id" ` -Resource "tags" ` -id $tag ` -Version $(_getApiVersion Build) | Out-Null } } } } } # Create new classification node. # # Get-VSTeamOption 'wit' 'classificationNodes' # id : 5a172953-1b41-49d3-840a-33f79c3ce89f # area : wit # resourceName : classificationNodes # routeTemplate : {project}/_apis/{area}/{resource}/{structureGroup}/{*path} # https://bit.ly/Add-VSTeamClassificationNode function Add-VSTeamClassificationNode { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamClassificationNode')] param( [Parameter(Mandatory = $true)] [string] $Name, [ValidateSet("areas", "iterations")] [Parameter(Mandatory = $true)] [string] $StructureGroup, [Parameter(Mandatory = $false)] [string] $Path = $null, [Parameter(Mandatory = $false)] [Nullable[datetime]] $StartDate, [Parameter(Mandatory = $false)] [Nullable[datetime]] $FinishDate, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $id = $StructureGroup $Path = [uri]::UnescapeDataString($Path) if ($Path) { $Path = [uri]::EscapeUriString($Path) $Path = $Path.TrimStart("/") $id += "/$Path" } $body = @{ name = $Name } if ($StructureGroup -eq "iterations") { $body.attributes = @{ startDate = $StartDate finishDate = $FinishDate } } $bodyAsJson = $body | ConvertTo-Json -Compress -Depth 100 # Call the REST API $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "wit" ` -Resource "classificationnodes" ` -id $id ` -body $bodyAsJson ` -Version $(_getApiVersion Core) $resp = [vsteam_lib.ClassificationNode]::new($resp, $ProjectName) Write-Output $resp } } # Install the specified extension into the account / project collection. # # Get-VSTeamOption 'extensionmanagement' 'installedextensionsbyname' -subDomain 'extmgmt' # id : fb0da285-f23e-4b56-8b53-3ef5f9f6de66 # area : ExtensionManagement # resourceName : InstalledExtensionsByName # routeTemplate : _apis/{area}/{resource}/{publisherName}/{extensionName}/{version} # http://bit.ly/Add-VSTeamExtension function Add-VSTeamExtension { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamExtension')] param( [parameter(Mandatory = $true)] [string] $PublisherId, [parameter(Mandatory = $true)] [string] $ExtensionId, [parameter(Mandatory = $false)] [string] $Version ) Process { $id = "$PublisherId/$ExtensionId" if ($version) { $id += '/' + $Version } $resp = _callAPI -Method POST -SubDomain 'extmgmt' ` -Area "extensionmanagement" ` -Resource "installedextensionsbyname" ` -Id $id ` -Version $(_getApiVersion ExtensionsManagement) $item = [vsteam_lib.Extension]::new($resp) Write-Output $item } } # Adds a new feed to package management. # # Get-VSTeamOption 'packaging' 'feeds' -subDomain 'feeds' # id : c65009a7-474a-4ad1-8b42-7d852107ef8c # area : Packaging # resourceName : Feeds # routeTemplate : {project}/_apis/{area}/{resource}/{feedId} # http://bit.ly/Add-VSTeamFeed function Add-VSTeamFeed { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamFeed')] param ( [Parameter(Position = 0, Mandatory = $true)] [string] $Name, [Parameter(Position = 1)] [string] $Description, [switch] $EnableUpstreamSources, [switch] $showDeletedPackageVersions, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $body = @{ name = $Name description = $Description hideDeletedPackageVersions = $true } if ($showDeletedPackageVersions.IsPresent) { $body.hideDeletedPackageVersions = $false } if ($EnableUpstreamSources.IsPresent) { $body.upstreamEnabled = $true $body.upstreamSources = @( @{ id = [System.Guid]::NewGuid() name = 'npmjs' protocol = 'npm' location = 'https://registry.npmjs.org/' upstreamSourceType = 1 }, @{ id = [System.Guid]::NewGuid() name = 'nuget.org' protocol = 'nuget' location = 'https://api.nuget.org/v3/index.json' upstreamSourceType = 1 } ) } $bodyAsJson = $body | ConvertTo-Json -Compress -Depth 100 $commonArgs = @{ Method = 'POST' subDomain = 'feeds' Area = 'packaging' Resource = 'feeds' body = $bodyAsJson Id = $item Version = $(_getApiVersion Packaging) } if ($ProjectName) { $commonArgs.ProjectName = $ProjectName }else{ $commonArgs.NoProject = $true } # Call the REST API $resp = _callAPI @commonArgs Write-Output [vsteam_lib.Feed]::new($resp) } } # Adds a Git repository to your Azure DevOps or Team Foundation Server account. # # Get-VSTeamOption 'git' 'repositories' # id : 225f7195-f9c7-4d14-ab28-a83f7ff77e1f # area : git # resourceName : repositories # routeTemplate : {project}/_apis/{area}/{resource}/{repositoryId} # http://bit.ly/Add-VSTeamGitRepository function Add-VSTeamGitRepository { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamGitRepository')] param( [parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $body = '{"name": "' + $Name + '"}' try { # Call the REST API $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "git" ` -Resource "repositories" ` -Body $body ` -Version $(_getApiVersion Git) # Storing the object before you return it cleaned up the pipeline. # When I just write the object from the constructor each property # seemed to be written $repo = [vsteam_lib.GitRepository]::new($resp, $ProjectName) Write-Output $repo } catch { _handleException $_ } } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamGitRepositoryPermission { [CmdletBinding(DefaultParameterSetName = 'ByProjectAndUser', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamGitRepositoryPermission')] param( [parameter(Mandatory = $true)] [vsteam_lib.Project]$Project, [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndDescriptor")] [guid]$RepositoryId, [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndDescriptor")] [string]$RepositoryName, [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryIdAndGroup")] [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryIdAndUser")] [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryNameAndGroup")] [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryNameAndUser")] [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryIdAndDescriptor")] [parameter(Mandatory = $false, ParameterSetName = "ByRepositoryNameAndDescriptor")] [string]$BranchName, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndDescriptor")] [string]$Descriptor, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndGroup")] [vsteam_lib.Group]$Group, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryIdAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByRepositoryNameAndUser")] [vsteam_lib.User]$User, [parameter(Mandatory = $false)] [vsteam_lib.GitRepositoryPermissions]$Allow, [parameter(Mandatory = $false)] [vsteam_lib.GitRepositoryPermissions]$Deny, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { # SecurityNamespaceID: 2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87 # Token: repoV2/<projectId>" <-- Whole project # Token: repoV2/<projectId>/<repositoryId>" <-- Whole repository # Token: repoV2/<projectId>/<repositoryId>/refs/heads/<branchName>" <-- Single branch $securityNamespaceId = "2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87" # Resolve Repository Name to ID if ($RepositoryName) { $repo = Get-VSTeamGitRepository -ProjectName $Project.Name -Name $RepositoryName if (!$repo) { throw "Repository not found" } $RepositoryId = $repo.ID } # Resolve Group to Descriptor if ($Group) { $Descriptor = _getDescriptorForACL -Group $Group } # Resolve User to Descriptor if ($User) { $Descriptor = _getDescriptorForACL -User $User } $token = "repoV2/$($Project.ID)" if ($RepositoryId) { $token += "/$($RepositoryId)" } if ($BranchName) { $branchHex = _convertToHex($BranchName) $token += "/refs/heads/$($branchHex)" } Add-VSTeamAccessControlEntry -SecurityNamespaceId $securityNamespaceId ` -Descriptor $Descriptor ` -Token $token ` -AllowMask ([int]$Allow) ` -DenyMask ([int]$Deny) ` -OverwriteMask:$OverwriteMask.IsPresent } } # Create new classification node. # # Get-VSTeamOption 'wit' 'classificationNodes' # id : 5a172953-1b41-49d3-840a-33f79c3ce89f # area : wit # resourceName : classificationNodes # routeTemplate : {project}/_apis/{area}/{resource}/{structureGroup}/{*path} # https://bit.ly/Add-VSTeamClassificationNode function Add-VSTeamIteration { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamIteration')] param( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $false)] [string] $Path, [Parameter(Mandatory = $false)] [Nullable[datetime]] $StartDate, [Parameter(Mandatory = $false)] [Nullable[datetime]] $FinishDate, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $resp = Add-VSTeamClassificationNode -ProjectName $ProjectName ` -Name $Name ` -StructureGroup iterations ` -Path $Path ` -StartDate $StartDate ` -FinishDate $FinishDate Write-Output $resp } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamKubernetesEndpoint { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamKubernetesEndpoint')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $endpointName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $kubeconfig, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $kubernetesUrl, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $clientCertificateData, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $clientKeyData, [switch] $acceptUntrustedCerts, [switch] $generatePfx, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { # Process switch parameters $untrustedCerts = $false if ($acceptUntrustedCerts.IsPresent) { $untrustedCerts = $true } $pfx = $false if ($generatePfx.IsPresent) { $pfx = $true } $obj = @{ authorization = @{ parameters = @{ clientCertificateData = $clientCertificateData clientKeyData = $clientKeyData generatePfx = $pfx kubeconfig = $Kubeconfig }; scheme = 'None' }; data = @{ acceptUntrustedCerts = $untrustedCerts }; url = $kubernetesUrl } return Add-VSTeamServiceEndpoint -ProjectName $ProjectName ` -endpointName $endpointName ` -endpointType kubernetes ` -object $obj } } # Adds a membership to a container. # # Get-VSTeamOption 'graph' 'memberships' -subDomain 'vssps' # id : 3fd2e6ca-fb30-443a-b579-95b19ed0934c # area : Graph # resourceName : Memberships # routeTemplate : _apis/{area}/{resource}/{subjectDescriptor}/{containerDescriptor} # http://bit.ly/Add-VSTeamMembership function Add-VSTeamMembership { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamMembership')] param( [Parameter(Mandatory = $true)] [string] $MemberDescriptor, [Parameter(Mandatory = $true)] [string] $ContainerDescriptor ) process { return _callMembershipAPI -Method PUT ` -Id "$MemberDescriptor/$ContainerDescriptor" } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamNuGetEndpoint { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] [CmdletBinding(DefaultParameterSetName = 'SecureApiKey')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $EndpointName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $NuGetUrl, [Parameter(ParameterSetName = 'ClearToken', Mandatory = $true, HelpMessage = 'Personal Access Token')] [string] $PersonalAccessToken, [Parameter(ParameterSetName = 'ClearApiKey', Mandatory = $true, HelpMessage = 'ApiKey')] [string] $ApiKey, [Parameter(ParameterSetName = 'SecurePassword', Mandatory = $true, HelpMessage = 'Username')] [string] $Username, [Parameter(ParameterSetName = 'SecureToken', Mandatory = $true, HelpMessage = 'Personal Access Token')] [securestring] $SecurePersonalAccessToken, [Parameter(ParameterSetName = 'SecureApiKey', Mandatory = $true, HelpMessage = 'ApiKey')] [securestring] $SecureApiKey, [Parameter(ParameterSetName = 'SecurePassword', Mandatory = $true, HelpMessage = 'Password')] [securestring] $SecurePassword, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { if ($PersonalAccessToken) { $Authentication = 'Token' $token = $PersonalAccessToken } elseif ($ApiKey) { $Authentication = 'ApiKey' $token = $ApiKey } elseif ($SecureApiKey) { $Authentication = 'ApiKey' $credential = New-Object System.Management.Automation.PSCredential "ApiKey", $SecureApiKey $token = $credential.GetNetworkCredential().Password } elseif ($SecurePassword) { $Authentication = 'UsernamePassword' $credential = New-Object System.Management.Automation.PSCredential "Password", $SecurePassword $token = $credential.GetNetworkCredential().Password } else { $Authentication = 'Token' $credential = New-Object System.Management.Automation.PSCredential "token", $securePersonalAccessToken $token = $credential.GetNetworkCredential().Password } $obj = @{ data = @{ } url = $NuGetUrl } if ($Authentication -eq 'ApiKey') { $obj['authorization'] = @{ parameters = @{ nugetkey = $token } scheme = 'None' } } elseif ($Authentication -eq 'Token') { $obj['authorization'] = @{ parameters = @{ apitoken = $token } scheme = 'Token' } } else { $obj['authorization'] = @{ parameters = @{ username = $Username password = $token } scheme = 'UsernamePassword' } } return Add-VSTeamServiceEndpoint -ProjectName $ProjectName ` -endpointName $endpointName ` -endpointType externalnugetfeed ` -object $obj } } # Adds a new policy to the specified project. # # Get-VSTeamOption 'policy' 'configurations' # id : dad91cbe-d183-45f8-9c6e-9c1164472121 # area : policy # resourceName : configurations # routeTemplate : {project}/_apis/{area}/{resource}/{configurationId} # http://bit.ly/Add-VSTeamPolicy function Add-VSTeamPolicy { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamPolicy')] param( [Parameter(Mandatory = $true)] [guid] $type, [switch] $enabled, [switch] $blocking, [Parameter(Mandatory = $true)] [hashtable] $settings, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $body = @{ isEnabled = $enabled.IsPresent; isBlocking = $blocking.IsPresent; type = @{ id = $type }; settings = $settings } | ConvertTo-Json -Depth 10 -Compress try { # Call the REST API $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "policy" ` -Resource "configurations" ` -Body $body ` -Version $(_getApiVersion Policy) Write-Output $resp } catch { _handleException $_ } } } function Add-VSTeamPool { [CmdletBinding( HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamPool')] param( [Parameter(Mandatory = $true, Position = 1)] [string] $Name, [Parameter(Mandatory = $false)] [string] $Description, [Parameter(Mandatory = $false)] [switch] $AutoProvision, [Parameter(Mandatory = $false)] [switch] $AutoAuthorize, [Parameter(Mandatory = $false)] [switch] $NoAutoUpdates ) process { $body = @{ name = $Name autoProvision = $AutoProvision.IsPresent autoUpdate = !$NoAutoUpdates.IsPresent properties = @{ "System.AutoAuthorize" = $AutoAuthorize.IsPresent } } $bodyAsJson = $body | ConvertTo-Json -Compress $resp = _callAPI -Method Post -NoProject -Area distributedtask -Resource pools -Version $(_getApiVersion DistributedTask) -Body $bodyAsJson $pool = [vsteam_lib.AgentPool]::new($resp) if ($resp -and $Description) { $descriptionAsJson = $Description | ConvertTo-Json -Compress $null = _callAPI -Method Put -NoProject -Area distributedtask -Resource pools -Id "$($pool.id)/poolmetadata" -Version $(_getApiVersion DistributedTask) -Body $descriptionAsJson } Write-Output $pool } } # Stores your account name and personal access token as a profile for use with # the Add-TeamAccount function in this module. function Add-VSTeamProfile { [CmdletBinding(DefaultParameterSetName = 'Secure', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamProfile')] param( [parameter(ParameterSetName = 'Windows', Mandatory = $true, Position = 1)] [parameter(ParameterSetName = 'Secure', Mandatory = $true, Position = 1)] [Parameter(ParameterSetName = 'Plain')] [string] $Account, [parameter(ParameterSetName = 'Plain', Mandatory = $true, Position = 2, HelpMessage = 'Personal Access Token')] [string] $PersonalAccessToken, [parameter(ParameterSetName = 'Secure', Mandatory = $true, HelpMessage = 'Personal Access Token')] [securestring] $SecurePersonalAccessToken, [string] $Name, [ValidateSet('TFS2017', 'TFS2018', 'AzD2019', 'VSTS', 'AzD', 'TFS2017U1', 'TFS2017U2', 'TFS2017U3', 'TFS2018U1', 'TFS2018U2', 'TFS2018U3', 'AzD2019U1')] [string] $Version, [switch] $UseBearerToken ) DynamicParam { # Only add these options on Windows Machines if (_isOnWindows) { # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $ParameterName2 = 'UseWindowsAuthentication' # Create the collection of attributes $AttributeCollection2 = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute2 = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute2.Mandatory = $true $ParameterAttribute2.ParameterSetName = "Windows" $ParameterAttribute2.HelpMessage = "On Windows machines allows you to use the active user identity for authentication. Not available on other platforms." # Add the attributes to the attributes collection $AttributeCollection2.Add($ParameterAttribute2) # Create and return the dynamic parameter $RuntimeParameter2 = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName2, [switch], $AttributeCollection2) $RuntimeParameterDictionary.Add($ParameterName2, $RuntimeParameter2) return $RuntimeParameterDictionary } } process { if ($SecurePersonalAccessToken) { # Even when promoted for a Secure Personal Access Token you can just # press enter. This leads to an empty PAT so error below. # Convert the securestring to a normal string # this was the one technique that worked on Mac, Linux and Windows $_pat = _convertSecureStringTo_PlainText -SecureString $SecurePersonalAccessToken } else { $_pat = $PersonalAccessToken } if (_isOnWindows) { # Bind the parameter to a friendly variable $UsingWindowsAuth = $PSBoundParameters[$ParameterName2] if (!($_pat) -and !($UsingWindowsAuth)) { Write-Error 'Personal Access Token must be provided if you are not using Windows Authentication; please see the help.' return } } # If they only gave an account name add https://dev.azure.com if ($Account -notlike "*/*") { if (-not $Name) { $Name = $Account } $Account = "https://dev.azure.com/$($Account)" } # If they gave https://dev.azure.com extract Account and Profile name if ($Account -match "(?<protocol>https\://)?(?<domain>dev\.azure\.com/)(?<account>[A-Z0-9][-A-Z0-9]*[A-Z0-9])") { if (-not $Name) { $Name = $matches.account } $Account = "https://dev.azure.com/$($matches.account)" } # If they gave https://xxx.visualstudio.com extract Account and Profile name, convert to new URL if ($Account -match "(?<protocol>https?\://)?(?<account>[A-Z0-9][-A-Z0-9]*[A-Z0-9])(?<domain>\.visualstudio\.com)") { if (-not $Name) { $Name = $matches.account } $Account = "https://dev.azure.com/$($matches.account)" } if ($UseBearerToken.IsPresent) { $authType = 'Bearer' $token = $_pat $encodedPat = '' } else { $token = '' $authType = 'Pat' $encodedPat = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":$_pat")) } # If no SecurePersonalAccessToken is entered, and on windows, are we using default credentials for REST calls if ((!$_pat) -and (_isOnWindows) -and ($UsingWindowsAuth)) { Write-Verbose 'Using Default Windows Credentials for authentication; no Personal Access Token required' $encodedPat = '' $token = '' $authType = 'OnPremise' } if (-not $Name) { $Name = $Account } # See if this item is already in there # I am testing URL because the user may provide a different # name and I don't want two with the same URL. # The @() forces even 1 item to an array $profiles = @(Get-VSTeamProfile | Where-Object URL -ne $Account) $newProfile = [PSCustomObject]@{ Name = $Name URL = $Account Type = $authType Pat = $encodedPat Token = $token Version = (_getVSTeamAPIVersion -Instance $Account -Version $Version) } $profiles += $newProfile $contents = ConvertTo-Json $profiles -Depth 100 Set-Content -Path $profilesPath -Value $contents } } # Adds a Team Project to your account. # # id : 603fe2ac-9723-48b9-88ad-09305aa6c6e1 # area : core # resourceName : projects # routeTemplate : _apis/{resource}/{*projectId} # http://bit.ly/Add-VSTeamProject function Add-VSTeamProject { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamProject')] param( [parameter(Mandatory = $true)] [Alias('Name')] [string] $ProjectName, [string] $Description, [validateset('private','public')] [string] $Visibility = 'private', [switch] $TFVC, [vsteam_lib.ProcessTemplateValidateAttribute()] [ArgumentCompleter([vsteam_lib.ProcessTemplateCompleter])] [string] $ProcessTemplate ) process { if ($TFVC.IsPresent) { $srcCtrl = "Tfvc" } else { $srcCtrl = 'Git' } if ($ProcessTemplate) { Write-Verbose "Finding $ProcessTemplate id" $templateTypeId = (Get-VSTeamProcess -Name $ProcessTemplate).Id } else { # Default to Scrum Process Template $ProcessTemplate = 'Scrum' $templateTypeId = '6b724908-ef14-45cf-84f8-768b5384da45' } $body = @{ name = $ProjectName description = $Description capabilities = @{ versioncontrol = @{ sourceControlType = $srcCtrl } processTemplate = @{ templateTypeId = $templateTypeId } } visibility = $Visibility } try { # Call the REST API $resp = _callAPI -Method POST ` -Resource "projects" ` -Body ($body | ConvertTo-Json -Compress -Depth 100) ` -Version $(_getApiVersion Core) _trackProjectProgress -resp $resp -title 'Creating team project' -msg "Name: $($ProjectName), Template: $($processTemplate), Src: $($srcCtrl)" # Invalidate any cache of projects. _invalidate Start-Sleep -Seconds 5 return Get-VSTeamProject $ProjectName } catch { _handleException $_ } } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamProjectPermission { [CmdletBinding(DefaultParameterSetName = 'ByProjectAndUser', HelpUri = 'https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamProjectPermission')] param( [parameter(Mandatory = $true)] [vsteam_lib.Project]$Project, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndDescriptor")] [string]$Descriptor, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndGroup")] [vsteam_lib.Group]$Group, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndUser")] [vsteam_lib.User]$User, [parameter(Mandatory = $false)] [vsteam_lib.ProjectPermissions]$Allow, [parameter(Mandatory = $false)] [vsteam_lib.ProjectPermissions]$Deny, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { # SecurityNamespaceID: 52d39943-cb85-4d7f-8fa8-c6baac873819 # Token: $PROJECT:vstfs:///Classification/TeamProject/<projectId> $securityNamespaceId = "52d39943-cb85-4d7f-8fa8-c6baac873819" # Resolve Group to Descriptor if ($Group) { $Descriptor = _getDescriptorForACL -Group $Group } # Resolve User to Descriptor if ($User) { $Descriptor = _getDescriptorForACL -User $User } $token = "`$PROJECT:vstfs:///Classification/TeamProject/$($Project.ID)" Add-VSTeamAccessControlEntry -SecurityNamespaceId $securityNamespaceId ` -Descriptor $Descriptor ` -Token $token ` -AllowMask ([int]$Allow) ` -DenyMask ([int]$Deny) ` -OverwriteMask:$OverwriteMask.IsPresent } } # Creates a new Pull Request. # # id : 88aea7e8-9501-45dd-ac58-b069aa73b926 # area : git # resourceName : repositories # routeTemplate : _apis/{area}/{projectId}/{resource}/{repositoryId} # http://bit.ly/Add-VSTeamPullRequest function Add-VSTeamPullRequest { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low", HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamPullRequest')] param( [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)] [Alias('Id')] [Guid] $RepositoryId, [Parameter(Mandatory = $true, HelpMessage = "Should be a ref like refs/heads/MyBranch")] [ValidatePattern('^refs/.*')] [string] $SourceRefName, [Parameter(Mandatory = $true, HelpMessage = "Should be a ref like refs/heads/MyBranch")] [ValidatePattern('^refs/.*')] [string] $TargetRefName, [Parameter(Mandatory = $true)] [string] $Title, [Parameter(Mandatory = $true)] [string] $Description, [Parameter()] [switch] $Draft, [Parameter()] [switch] $Force, [Parameter(ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { Write-Verbose "Add-VSTeamPullRequest" $body = '{"sourceRefName": "' + $SourceRefName + '", "targetRefName": "' + $TargetRefName + '", "title": "' + $Title + '", "description": "' + $Description + '", "isDraft": ' + $Draft.ToString().ToLower() + '}' Write-Verbose $body # Call the REST API if ($force -or $pscmdlet.ShouldProcess($Title, "Add Pull Request")) { try { Write-Debug 'Add-VSTeamPullRequest Call the REST API' $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "git" ` -Resource "repositories" ` -Id "$RepositoryId/pullrequests" ` -Body $body ` -Version $(_getApiVersion Git) _applyTypesToPullRequests -item $resp Write-Output $resp } catch { _handleException $_ } } } } # Queues a new release. # # Get-VSTeamOption 'release' 'releases' -subDomain 'vsrm' # id : a166fde7-27ad-408e-ba75-703c2cc9d500 # area : Release # resourceName : releases # routeTemplate : {project}/_apis/{area}/{resource}/{releaseId} # http://bit.ly/Add-VSTeamRelease function Add-VSTeamRelease { [CmdletBinding(DefaultParameterSetName = 'ById', SupportsShouldProcess = $true, ConfirmImpact = "Medium", HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamRelease')] param( [Parameter(ParameterSetName = 'ById', Mandatory = $true)] [int] $DefinitionId, [Parameter(Mandatory = $false)] [string] $Description, [Parameter(ParameterSetName = 'ById', Mandatory = $true)] [string] $ArtifactAlias, [Parameter()] [string] $Name, [Parameter(ParameterSetName = 'ById', Mandatory = $true)] [string] $BuildId, [ArgumentCompleter([vsteam_lib.BuildCompleter])] [Parameter(ParameterSetName = 'ByName', Mandatory = $true)] [string] $BuildNumber, [Parameter()] [string] $SourceBranch, [switch] $Force, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName, [ArgumentCompleter([vsteam_lib.ReleaseDefinitionCompleter])] [string] $DefinitionName ) begin { if ($BuildNumber) { $buildID = (Get-VSTeamBuild -ProjectName $ProjectName -BuildNumber $BuildNumber).id if (-not $buildID) { throw "'$BuildnNumber' is not a valid build Use Get-VsTeamBuild to get a list of valid build numbers." } } if ($DefinitionName -and -not $artifactAlias) { $def = Get-VSTeamReleaseDefinition -ProjectName $ProjectName | Where-Object { $_.name -eq $DefinitionName } $DefinitionId = $def.id $artifactAlias = $def.artifacts[0].alias } } process { $body = '{"definitionId": ' + $DefinitionId + ', "description": "' + $description + '", "artifacts": [{"alias": "' + $artifactAlias + '", "instanceReference": {"id": "' + $buildId + '", "name": "' + $Name + '", "sourceBranch": "' + $SourceBranch + '"}}]}' Write-Verbose $body # Call the REST API if ($force -or $pscmdlet.ShouldProcess($description, "Add Release")) { try { Write-Debug 'Add-VSTeamRelease Call the REST API' $resp = _callAPI -Method POST -SubDomain "vsrm" -ProjectName $ProjectName ` -Area "release" ` -Resource "releases" ` -Body $body ` -Version $(_getApiVersion Release) Write-Output $([vsteam_lib.Release]::new($resp, $ProjectName)) } catch { _handleException $_ } } } } # Creates a new release definition from a JSON file. # # Get-VSTeamOption 'release' 'definitions' -subDomain vsrm # id : d8f96f24-8ea7-4cb6-baab-2df8fc515665 # area : Release # resourceName : definitions # routeTemplate : {project}/_apis/{area}/{resource}/{definitionId} # http://bit.ly/Add-VSTeamReleaseDefinition function Add-VSTeamReleaseDefinition { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamReleaseDefinition')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $inFile, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $resp = _callAPI -Method POST -subDomain "vsrm" -ProjectName $ProjectName ` -Area "release" ` -Resource "definitions" ` -inFile $inFile ` -Version $(_getApiVersion Release) Write-Output $resp } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamServiceEndpoint { [CmdletBinding(DefaultParameterSetName = 'Secure', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamServiceEndpoint')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $endpointName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $endpointType, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [hashtable] $object, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $object['name'] = $endpointName $object['type'] = $endpointType $body = $object | ConvertTo-Json -Depth 100 # Call the REST API $resp = _callAPI -Method POST -ProjectName $projectName ` -Area "distributedtask" ` -Resource "serviceendpoints" ` -body $body ` -Version $(_getApiVersion ServiceEndpoints) _trackServiceEndpointProgress -projectName $projectName -resp $resp -title 'Creating Service Endpoint' -msg "Creating $endpointName" return Get-VSTeamServiceEndpoint -ProjectName $ProjectName -id $resp.id } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamServiceFabricEndpoint { [CmdletBinding(DefaultParameterSetName = 'Certificate', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamServiceFabricEndpoint')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias('displayName')] [string] $endpointName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $url, [parameter(ParameterSetName = 'Certificate', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $certificate, [Parameter(ParameterSetName = 'Certificate', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [securestring] $certificatePassword, [parameter(ParameterSetName = 'Certificate', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [parameter(ParameterSetName = 'AzureAd', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $serverCertThumbprint, [Parameter(ParameterSetName = 'AzureAd', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $username, [Parameter(ParameterSetName = 'AzureAd', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [securestring] $password, [Parameter(ParameterSetName = 'None', Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [string] $clusterSpn, [Parameter(ParameterSetName = 'None', Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [bool] $useWindowsSecurity, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { switch ($PSCmdlet.ParameterSetName) { "Certificate" { # copied securestring usage from Set-VSTeamAccount # while we don't actually have a username here, PSCredential requires that a non empty string is provided $credential = New-Object System.Management.Automation.PSCredential $serverCertThumbprint, $certificatePassword $certPass = $credential.GetNetworkCredential().Password $authorization = @{ parameters = @{ certificate = $certificate certificatepassword = $certPass servercertthumbprint = $serverCertThumbprint } scheme = 'Certificate' } } "AzureAd" { # copied securestring usage from Set-VSTeamAccount $credential = New-Object System.Management.Automation.PSCredential $username, $password $pass = $credential.GetNetworkCredential().Password $authorization = @{ parameters = @{ password = $pass servercertthumbprint = $serverCertThumbprint username = $username } scheme = 'UsernamePassword' } } Default { $authorization = @{ parameters = @{ ClusterSpn = $clusterSpn UseWindowsSecurity = $useWindowsSecurity } scheme = 'None' } } } $obj = @{ authorization = $authorization data = @{} url = $url } return Add-VSTeamServiceEndpoint -ProjectName $ProjectName ` -endpointName $endpointName ` -endpointType servicefabric ` -object $obj } } # Create a service endpoint. # # Get-VSTeamOption 'distributedtask' 'serviceendpoints' # id : dca61d2f-3444-410a-b5ec-db2fc4efb4c5 # area : distributedtask # resourceName : serviceendpoints # routeTemplate : {project}/_apis/{area}/{resource}/{endpointId} # https://bit.ly/Add-VSTeamServiceEndpoint function Add-VSTeamSonarQubeEndpoint { [CmdletBinding(DefaultParameterSetName = 'Secure', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamSonarQubeEndpoint')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $endpointName, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $sonarqubeUrl, [parameter(ParameterSetName = 'Plain', Mandatory = $true, Position = 2, HelpMessage = 'Personal Access Token')] [string] $personalAccessToken, [parameter(ParameterSetName = 'Secure', Mandatory = $true, HelpMessage = 'Personal Access Token')] [securestring] $securePersonalAccessToken, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { if ($personalAccessToken) { $token = $personalAccessToken } else { $credential = New-Object System.Management.Automation.PSCredential "nologin", $securePersonalAccessToken $token = $credential.GetNetworkCredential().Password } # Bind the parameter to a friendly variable $ProjectName = $PSBoundParameters['ProjectName'] $obj = @{ authorization = @{ parameters = @{ username = $token; password = '' }; scheme = 'UsernamePassword' }; data = @{ }; url = $sonarqubeUrl } try { return Add-VSTeamServiceEndpoint -ProjectName $ProjectName ` -endpointName $endpointName ` -endpointType sonarqube ` -object $obj } catch [System.Net.WebException] { if ($_.Exception.status -eq 'ProtocolError') { $errorDetails = ConvertFrom-Json $_.ErrorDetails $message = $errorDetails.message # The error message is different on TFS and VSTS if ($message.StartsWith("Endpoint type couldn't be recognized 'sonarqube'") -or $message.StartsWith("Unable to find service endpoint type 'sonarqube'")) { Write-Error -Message 'The Sonarqube extension not installed. Please install from https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarqube' return } } throw } } } # Adds a task group. # # Get-VSTeamOption 'distributedtask' 'taskgroups' # id : 6c08ffbf-dbf1-4f9a-94e5-a1cbd47005e7 # area : distributedtask # resourceName : taskgroups # routeTemplate : {project}/_apis/{area}/{resource}/{taskGroupId} # http://bit.ly/Add-VSTeamTaskGroup function Add-VSTeamTaskGroup { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamTaskGroup')] param( [Parameter(ParameterSetName = 'ByFile', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $InFile, [Parameter(ParameterSetName = 'ByBody', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Body, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { $commonArgs = @{ Method = 'Post' Area = 'distributedtask' Resource = 'taskgroups' ProjectName = $ProjectName Version = $(_getApiVersion TaskGroups) } if ($InFile) { $resp = _callAPI @commonArgs -InFile $InFile } else { $resp = _callAPI @commonArgs -Body $Body } return $resp } } # Add a user, assign license and extensions and make them a member of a # project group in an account. # # Get-VSTeamOption 'MemberEntitlementManagement' 'UserEntitlements' -subDomain 'vsaex' # id : 387f832c-dbf2-4643-88e9-c1aa94dbb737 # area : MemberEntitlementManagement # resourceName : UserEntitlements # routeTemplate : _apis/{resource}/{userDescriptor} # http://bit.ly/Add-VSTeamUserEntitlement function Add-VSTeamUserEntitlement { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamUserEntitlement')] param( [Parameter(Mandatory = $true)] [Alias('UserEmail')] [string]$Email, [ValidateSet('Advanced', 'EarlyAdopter', 'Express', 'None', 'Professional', 'StakeHolder')] [string]$License = 'EarlyAdopter', [ValidateSet('Custom', 'ProjectAdministrator', 'ProjectContributor', 'ProjectReader', 'ProjectStakeholder')] [string]$Group = 'ProjectContributor', [ValidateSet('account', 'auto', 'msdn', 'none', 'profile', 'trial')] [string]$LicensingSource = "account", [ValidateSet('eligible', 'enterprise', 'none', 'platforms', 'premium', 'professional', 'testProfessional', 'ultimate')] [string]$MSDNLicenseType = "none", [Parameter(ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { # Thi swill throw if this account does not support MemberEntitlementManagement _supportsMemberEntitlementManagement $obj = @{ accessLevel = @{ accountLicenseType = $License licensingSource = $LicensingSource msdnLicenseType = $MSDNLicenseType } user = @{ principalName = $email subjectKind = 'user' } projectEntitlements = @{ group = @{ groupType = $Group } projectRef = @{ id = $ProjectName } } } $body = $obj | ConvertTo-Json -Depth 100 # Call the REST API _callAPI -Method POST -SubDomain "vsaex" ` -Resource "userentitlements" ` -Body $body ` -Version $(_getApiVersion MemberEntitlementManagement) } } # Adds a variable group. # # Get-VSTeamOption 'distributedtask' 'variablegroups' # id : f5b09dd5-9d54-45a1-8b5a-1c8287d634cc # area : distributedtask # resourceName : variablegroups # routeTemplate : {project}/_apis/{area}/{resource}/{groupId} # http://bit.ly/Add-VSTeamVariableGroup function Add-VSTeamVariableGroup { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamVariableGroup')] param( [Parameter(ParameterSetName = 'ByHashtable', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Name, [Parameter(ParameterSetName = 'ByHashtable', Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [string] $Description, [Parameter(ParameterSetName = 'ByHashtable', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [hashtable] $Variables, [Parameter(ParameterSetName = 'ByBody', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $Body, [Parameter(ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) DynamicParam { $dp = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary if ([vsteam_lib.Versions]::Version -ne "TFS2017" -and $PSCmdlet.ParameterSetName -eq "ByHashtable") { $ParameterName = 'Type' $rp = _buildDynamicParam -ParameterName $ParameterName -arrSet ('Vsts', 'AzureKeyVault') -Mandatory $true $dp.Add($ParameterName, $rp) $ParameterName = 'ProviderData' $rp = _buildDynamicParam -ParameterName $ParameterName -Mandatory $false -ParameterType ([hashtable]) $dp.Add($ParameterName, $rp) } return $dp } Process { # This will throw if this account does not support the variable groups _supportVariableGroups if ([string]::IsNullOrWhiteSpace($Body)) { $bodyAsHashtable = @{ name = $Name description = $Description variables = $Variables } if ([vsteam_lib.Versions]::Version -ne "TFS2017") { $Type = $PSBoundParameters['Type'] $bodyAsHashtable.Add("type", $Type) $ProviderData = $PSBoundParameters['ProviderData'] if ($null -ne $ProviderData) { $bodyAsHashtable.Add("providerData", $ProviderData) } } $body = $bodyAsHashtable | ConvertTo-Json -Depth 100 } # Call the REST API $resp = _callAPI -Method POST -ProjectName $projectName ` -Area "distributedtask" ` -Resource "variablegroups" ` -body $body ` -Version $(_getApiVersion VariableGroups) return Get-VSTeamVariableGroup -ProjectName $ProjectName -id $resp.id } } function Add-VSTeamWiki { [CmdletBinding(DefaultParameterSetName = 'projectWiki', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamWiki')] param( [Parameter(Mandatory = $true)] [Alias('WikiName')] [string] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'codeWiki')] [string] $RepositoryId, [Parameter(Mandatory = $true, ParameterSetName = 'codeWiki')] [string] $Branch, [Parameter(Mandatory = $true, ParameterSetName = 'codeWiki')] [string] $MappedPath, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName ) process { # Create the body for a projectWiki type $body = [ordered]@{ type = $PSCmdlet.ParameterSetName name = $Name projectId = (Get-VSTeamProject -Name $ProjectName).id } # if its a code wiki include additional properties if ($PSCmdlet.ParameterSetName -eq 'codeWiki'){ $body+= [ordered]@{ Version = @{ Version = $Branch } repositoryId = $RepositoryId mappedPath = $MappedPath } } $commonArgs = [ordered]@{ Method = 'POST' Area = 'wiki' Resource = 'wikis' Body = $($body | ConvertTo-Json -Depth 100 -compress) ContentType = 'application/json' Version = $(_getApiVersion Wiki) } $resp = _callAPI @commonArgs Write-Output $resp } } # Adds a work item to your project. # # Get-VSTeamOption 'wit' 'workItems' # id : 62d3d110-0047-428c-ad3c-4fe872c91c74 # area : wit # resourceName : workItems # routeTemplate : {project}/_apis/{area}/{resource}/${type} # http://bit.ly/Add-VSTeamWorkItem function Add-VSTeamWorkItem { [CmdletBinding(HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamWorkItem')] param( [Parameter(Mandatory = $true)] [string] $Title, [Parameter(Mandatory = $false)] [string] $Description, [Parameter(Mandatory = $false)] [string] $IterationPath, [Parameter(Mandatory = $false)] [string] $AssignedTo, [Parameter(Mandatory = $false)] [int] $ParentId, [Parameter(Mandatory = $false)] [hashtable] $AdditionalFields, [Parameter(ValueFromPipelineByPropertyName = $true)] [vsteam_lib.ProjectValidateAttribute($false)] [ArgumentCompleter([vsteam_lib.ProjectCompleter])] [string] $ProjectName, [Parameter(Mandatory = $true)] [vsteam_lib.WorkItemTypeValidateAttribute()] [ArgumentCompleter([vsteam_lib.WorkItemTypeCompleter])] [string] $WorkItemType ) Process { # The type has to start with a $ # We can't reasign to $WorkItemType because the validate attribute # above will fire again and throw exception. $fullWorkItemType = '$' + $WorkItemType # Constructing the contents to be send. # Empty parameters will be skipped when converting to json. [Array]$body = @( @{ op = "add" path = "/fields/System.Title" value = $Title } @{ op = "add" path = "/fields/System.Description" value = $Description } @{ op = "add" path = "/fields/System.IterationPath" value = $IterationPath } @{ op = "add" path = "/fields/System.AssignedTo" value = $AssignedTo }) | Where-Object { $_.value } if ($ParentId) { $parentUri = _buildRequestURI -ProjectName $ProjectName -Area 'wit' -Resource 'workitems' -id $ParentId $body += @{ op = "add" path = "/relations/-" value = @{ "rel" = "System.LinkTypes.Hierarchy-Reverse" "url" = $parentURI } } } #this loop must always come after the main work item fields defined in the function parameters if ($AdditionalFields) { foreach ($fieldName in $AdditionalFields.Keys) { #check that main properties are not added into the additional fields hashtable $foundFields = $body | Where-Object { $null -ne $_ -and $_.path -like "*$fieldName" } if ($null -ne $foundFields) { throw "Found duplicate field '$fieldName' in parameter AdditionalFields, which is already a parameter. Please remove it." } else { $body += @{ op = "add" path = "/fields/$fieldName" value = $AdditionalFields[$fieldName] } } } } # It is very important that even if the user only provides # a single value above that the item is an array and not # a single object or the call will fail. # You must call ConvertTo-Json passing in the value and not # not using pipeline. # https://stackoverflow.com/questions/18662967/convertto-json-an-array-with-a-single-item $json = ConvertTo-Json @($body) -Compress -Depth 100 # Call the REST API $resp = _callAPI -Method POST -ProjectName $ProjectName ` -Area "wit" ` -Resource "workitems" ` -id $fullWorkItemType ` -Body $json ` -ContentType 'application/json-patch+json; charset=utf-8' ` -Version $(_getApiVersion Core) _applyTypesToWorkItem -item $resp return $resp } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamWorkItemAreaPermission { [CmdletBinding(DefaultParameterSetName = 'ByProjectAndAreaIdAndUser', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamWorkItemAreaPermission')] param( [parameter(Mandatory = $true)] [vsteam_lib.Project]$Project, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndUser")] [int]$AreaID, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndGroup")] [string]$AreaPath, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndDescriptor")] [string]$Descriptor, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndGroup")] [vsteam_lib.Group]$Group, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaPathAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndAreaIdAndUser")] [vsteam_lib.User]$User, [parameter(Mandatory = $false)] [vsteam_lib.WorkItemAreaPermissions]$Allow, [parameter(Mandatory = $false)] [vsteam_lib.WorkItemAreaPermissions]$Deny, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { # SecurityID: 83e28ad4-2d72-4ceb-97b0-c7726d5502c3 # Token: vstfs:///Classification/Node/862eb45f-3873-41d7-89c8-4b2f8802eaa9 (https://dev.azure.com/<organization>/<project>/_apis/wit/classificationNodes/Areas) # "token": "vstfs:///Classification/Node/ae76de05-8b53-4e02-9205-e73e2012585e:vstfs:///Classification/Node/f8c5b667-91dd-4fe7-bf23-3138c439d07e", $securityNamespaceId = "83e28ad4-2d72-4ceb-97b0-c7726d5502c3" if ($AreaID) { $area = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Id $AreaID } if ($AreaPath) { $area = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Path $AreaPath -StructureGroup "areas" } if (-not $area) { throw "Area not found" } if ($area.StructureType -ne "area") { throw "This is not an Area" } $nodes = @() $nodes += $area while ($area.ParentUrl) { $path = $area.ParentUrl -ireplace ".*(classificationNodes/Areas)\/?" if ($path.length -gt 0) { # We have a Path to resolve $area = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Path $path -StructureGroup "Areas" } else { # We need to get the "root" node now $area = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -StructureGroup "Areas" } $nodes += $area } # Build Token from Path [array]::Reverse($nodes) $token = ($nodes | ForEach-Object { "vstfs:///Classification/Node/$($_.Identifier)" }) -join ":" # Resolve Group to Descriptor if ($Group) { $Descriptor = _getDescriptorForACL -Group $Group } # Resolve User to Descriptor if ($User) { $Descriptor = _getDescriptorForACL -User $User } Add-VSTeamAccessControlEntry -SecurityNamespaceId $securityNamespaceId ` -Descriptor $Descriptor ` -Token $token ` -AllowMask ([int]$Allow) ` -DenyMask ([int]$Deny) ` -OverwriteMask:$OverwriteMask.IsPresent } } # Add or update ACEs in the ACL for the provided token. The request body # contains the target token, a list of ACEs and a optional merge parameter. # In the case of a collision (by identity descriptor) with an existing ACE # in the ACL, the "merge" parameter determines the behavior. If set, the # existing ACE has its allow and deny merged with the incoming ACE's allow # and deny. If unset, the existing ACE is displaced. # # Get-VSTeamOption 'Security' 'AccessControlEntries' # id : ac08c8ff-4323-4b08-af90-bcd018d380ce # area : Security # resourceName : AccessControlEntries # routeTemplate : _apis/{resource}/{securityNamespaceId} # https://bit.ly/Add-VSTeamAccessControlEntry function Add-VSTeamWorkItemIterationPermission { [CmdletBinding(DefaultParameterSetName = 'ByProjectAndIterationIdAndUser', HelpUri='https://methodsandpractices.github.io/vsteam-docs/docs/modules/vsteam/commands/Add-VSTeamWorkItemIterationPermission')] param( [parameter(Mandatory = $true)] [vsteam_lib.Project]$Project, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndUser")] [int]$IterationID, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndGroup")] [string]$IterationPath, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndDescriptor")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndDescriptor")] [string]$Descriptor, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndGroup")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndGroup")] [vsteam_lib.Group]$Group, [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationPathAndUser")] [parameter(Mandatory = $true, ParameterSetName = "ByProjectAndIterationIdAndUser")] [vsteam_lib.User]$User, [parameter(Mandatory = $false)] [vsteam_lib.WorkItemIterationPermissions]$Allow, [parameter(Mandatory = $false)] [vsteam_lib.WorkItemIterationPermissions]$Deny, [Parameter(Mandatory = $false)] [switch] $OverwriteMask ) process { # SecurityID: bf7bfa03-b2b7-47db-8113-fa2e002cc5b1 # Token: vstfs:///Classification/Node/862eb45f-3873-41d7-89c8-4b2f8802eaa9 (https://dev.azure.com/<organization>/<project>/_apis/wit/classificationNodes/Iterations) # "token": "vstfs:///Classification/Node/ae76de05-8b53-4e02-9205-e73e2012585e:vstfs:///Classification/Node/f8c5b667-91dd-4fe7-bf23-3138c439d07e", $securityNamespaceId = "bf7bfa03-b2b7-47db-8113-fa2e002cc5b1" if ($IterationID) { $iteration = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Id $IterationID } if ($IterationPath) { $iteration = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Path $IterationPath -StructureGroup "iterations" } if (-not $iteration) { throw "Iteration not found" } if ($iteration.StructureType -ne "iteration") { throw "This is not an Iteration" } $nodes = @() $nodes += $iteration while ($iteration.ParentUrl) { $path = $iteration.ParentUrl -ireplace ".*(classificationNodes/Iterations)\/?" if ($path.length -gt 0) { # We have a Path to resolve $iteration = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -Path $path -StructureGroup "Iterations" } else { # We need to get the "root" node now $iteration = Get-VSTeamClassificationNode -ProjectName $Project.Name -Depth 0 -StructureGroup "Iterations" } $nodes += $iteration } # Build Token from Path [array]::Reverse($nodes) $token = ($nodes | ForEach-Object { "vstfs:///Classification/Node/$($_.Identifier)" }) -join ":" # Resolve Group to Descriptor if ($Group) { $Descriptor = _getDescriptorForACL -Group $Group } # Resolve User to Descriptor if ($User) { $Descriptor = _getDescriptorForACL -User $User } Add-VSTeamAccessControlEntry -SecurityNamespaceId $securityNamespaceId ` -Descriptor $Descriptor ` -Token $token ` -AllowMask ([int]$Allow) ` -DenyMask ([int]$Deny) ` -OverwriteMask:$OverwriteMask.IsPresent } } function Clear-VSTeamDefaultAPITimeout { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [CmdletBinding()] param() DynamicParam { # # Only add these options on Windows Machines if (_isOnWindows) { $ParameterName = 'Level' # Create the dictionary $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary # Create the collection of attributes $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] # Create and set the parameters' attributes $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute $ParameterAttribute.Mandatory = $false $ParameterAttribute.HelpMessage = "On Windows machines allows you to store the default timeout at the process, user or machine level. Not available on other platforms." # Add the attributes to the attributes collection $AttributeCollection.Add($ParameterAttribute) # Generate and set the ValidateSet if (_testAdministrator) { $arrSet = "Process", "User", "Machine" } else { $arrSet = "Process", "User" } $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) # Add the ValidateSet to the attributes collection $AttributeCollection.Add($ValidateSetAttribute) # Create and return the dynamic parameter $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection) $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter) return $RuntimeParameterDictionary } } begin { if (_isOnWindows) { # Bind the parameter to a friendly variable $Level = $PSBoundParameters[$ParameterName] } } process { if (_isOnWindows) { if (-not $Level) { $Level = "Process" } } else { $Level = "Process" } # You always have to set at the process level or they will Not # be seen in your current session. $env:TEAM_TIMEOUT = $null if (_isOnWindows) { [System.Environment]::SetEnvironmentVariable("TEAM_TIMEOUT", $null, $Level) } [vsteam_lib.Versions]::DefaultTimeout = '' $Global:PSDefaultParameterValues.Remove("*-vsteam*:vsteamApiTimeout") Write-Output "Removed default timeout" } } function Clear-VSTeamDefaultProject { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] [CmdletBinding()] param() DynamicParam { _buildLevelDynamicParam } begin { if (_isOnWindows) { # Bind the parameter to a friendly variable $Level = $PSBoundParameters['Level'] } } process { if (_isOnWindows) { if (-not $Level) { $Level = "Process" } } else { $Level = "Process" } # You always have to set at the process level or they will Not # be seen in your current session. $env:TEAM_PROJECT = $null if (_isOnWindows) { [System.Environment]::SetEnvironmentVariable("TEAM_PROJECT", $null, $Level) } [vsteam_lib.Versions]::DefaultProject = '' $Global:PSDefaultParameterValues.Remove("*-vsteam*:projectName") Write-Output "Removed default project" |