diff --git a/Team.psd1 b/Team.psd1
index 485207de1..caeba8295 100644
--- a/Team.psd1
+++ b/Team.psd1
@@ -64,7 +64,8 @@
TypesToProcess = @('src\types.ps1xml')
# Format files (.ps1xml) to be loaded when importing this module
- FormatsToProcess = @('src\TeamTypes.format.ps1xml')
+ FormatsToProcess = @('src\TeamTypes.format.ps1xml',
+ 'src\TeamTypes.Artifacts.format.ps1xml')
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('src\team.psm1',
@@ -123,7 +124,12 @@
'Get-GitRepository',
'Add-GitRepository',
'Remove-GitRepository',
- 'Get-BuildLog')
+ 'Get-BuildLog',
+ 'Add-BuildTag',
+ 'Get-BuildTag',
+ 'Remove-BuildTag',
+ 'Get-BuildArtifact',
+ 'Update-Build')
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
# CmdletsToExport = @()
diff --git a/src/TeamTypes.Artifacts.format.ps1xml b/src/TeamTypes.Artifacts.format.ps1xml
new file mode 100644
index 000000000..46e813136
--- /dev/null
+++ b/src/TeamTypes.Artifacts.format.ps1xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+ Team.Build.Artifact.TableView
+
+ Team.Build.Artifact
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ name
+
+
+ type
+
+
+ downloadUrl
+
+
+
+
+
+
+
+
+ Team.Build.Artifact.WideView
+
+ Team.Build.Artifact
+
+
+
+
+
+ downloadUrl
+
+
+
+
+
+
+
+ Team.Build.Artifact.ListView
+
+ Team.Build.Artifact
+
+
+
+
+
+
+ id
+
+
+ name
+
+
+ type
+
+
+ data
+
+
+ downloadUrl
+
+
+
+
+
+
+
+
+ Team.Build.Artifact.Resource.TableView
+
+ Team.Build.Artifact.Resource
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ type
+
+
+ data
+
+
+ url
+
+
+ downloadUrl
+
+
+ properties
+
+
+
+
+
+
+
+
+ Team.Build.Artifact.Resource.WideView
+
+ Team.Build.Artifact.Resource
+
+
+
+
+
+ properties
+
+
+
+
+
+
+
+ Team.Build.Artifact.Resource.ListView
+
+ Team.Build.Artifact.Resource
+
+
+
+
+
+
+ type
+
+
+ data
+
+
+ url
+
+
+ downloadUrl
+
+
+ properties
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/builds.psm1 b/src/builds.psm1
index 2f0f1b772..65295cd92 100644
--- a/src/builds.psm1
+++ b/src/builds.psm1
@@ -4,6 +4,8 @@ Set-StrictMode -Version Latest
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
. "$here\common.ps1"
+$apiVersionQueryString = '?api-version=2.0'
+
function _buildURL {
param(
[parameter(Mandatory = $true)]
@@ -13,28 +15,69 @@ function _buildURL {
[int] $LogIndex
)
- if (-not $env:TEAM_ACCT) {
- throw 'You must call Add-TeamAccount before calling any other functions in this module.'
+ if ($Logs.IsPresent -eq $true) {
+ $rootUrl = _buildRootURL -ProjectName $ProjectName -Id $Id -Logs $Logs -LogIndex $LogIndex
}
-
- $version = '2.0'
- $resource = "/build/builds"
- $instance = $env:TEAM_ACCT
-
- if ($id) {
- $resource += "/$id"
+ else {
+ $rootUrl = _buildRootURL -ProjectName $ProjectName -Id $Id -LogIndex $LogIndex
}
- if ($Logs.IsPresent) {
- $resource += "/logs"
-
- if ($LogIndex) {
- $resource += "/$LogIndex"
- }
- }
# Build the url to list the projects
- return $instance + "/$projectName/_apis" + $resource + '?api-version=' + $version
+ return $rootUrl + $apiVersionQueryString
+}
+
+function _buildChildUrl {
+ param(
+ [parameter(Mandatory = $true)]
+ [string] $ProjectName,
+ [int] $Id,
+ [Switch] $Logs,
+ [int] $LogIndex,
+ [string] $Child
+ )
+
+ if ($Logs.IsPresent -eq $true) {
+ $rootUrl = _buildRootURL -ProjectName $ProjectName -Id $Id -Logs $Logs -LogIndex $LogIndex
+ }
+ else {
+ $rootUrl = _buildRootURL -ProjectName $ProjectName -Id $Id -LogIndex $LogIndex
+ }
+
+ # Build the url to list the projects
+ return $rootUrl + "/$Child" + $apiVersionQueryString
+}
+
+function _buildRootURL {
+ param(
+ [parameter(Mandatory = $true)]
+ [string] $ProjectName,
+ [int] $Id,
+ [Switch] $Logs,
+ [int] $LogIndex
+ )
+
+ if (-not $env:TEAM_ACCT) {
+ throw 'You must call Add-TeamAccount before calling any other functions in this module.'
+ }
+
+ $resource = "/build/builds"
+ $instance = $env:TEAM_ACCT
+
+ if ($id) {
+ $resource += "/$id"
+ }
+
+ if ($Logs.IsPresent) {
+ $resource += "/logs"
+
+ if ($LogIndex) {
+ $resource += "/$LogIndex"
+ }
+ }
+
+ # Build the url to list the projects
+ return $instance + "/$projectName/_apis" + $resource
}
# Apply types to the returned objects so format and type files can
@@ -61,6 +104,15 @@ function _applyTypes {
}
}
+function _applyArtifactTypes {
+ $item.PSObject.TypeNames.Insert(0, "Team.Build.Artifact")
+
+ if ($item.PSObject.Properties.Match('resource').count -gt 0 -and $null -ne $item.resource) {
+ $item.resource.PSObject.TypeNames.Insert(0, 'Team.Build.Artifact.Resource')
+ $item.resource.properties.PSObject.TypeNames.Insert(0, 'Team.Build.Artifact.Resource.Properties')
+ }
+}
+
function Get-Build {
[CmdletBinding(DefaultParameterSetName = 'List')]
param (
@@ -370,4 +422,202 @@ function Remove-Build {
}
}
-Export-ModuleMember -Alias * -Function Add-Build, Get-Build, Remove-Build, Get-BuildLog
\ No newline at end of file
+function Update-Build {
+ [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
+ param(
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [Int] $Id,
+
+ [parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [bool] $KeepForever,
+
+ [parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [string] $BuildNumber,
+
+ [switch] $Force
+ )
+
+ DynamicParam {
+ _buildProjectNameDynamicParam
+ }
+
+ Process {
+ $ProjectName = $PSBoundParameters["ProjectName"]
+
+ if ($Force -or $pscmdlet.ShouldProcess($Id, "Update-Build")) {
+
+ $updateUrl = _buildURL -ProjectName $ProjectName -Id $Id
+
+ $body = '{'
+
+ $items = New-Object System.Collections.ArrayList
+
+ if ($KeepForever -ne $null) {
+ $items.Add("`"keepForever`": $($KeepForever.ToString().ToLower())") > $null
+ }
+
+ if ($buildNumber -ne $null -and $buildNumber.Length -gt 0) {
+ $items.Add("`"buildNumber`": `"$BuildNumber`"") > $null
+ }
+
+ if ($items -ne $null -and $items.count -gt 0) {
+ $body += ($items -join ", ")
+ }
+
+ $body += '}'
+
+ # Call the REST API
+ if (_useWindowsAuthenticationOnPremise) {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Patch -ContentType "application/json" -Body $body -Uri $updateUrl -UseDefaultCredentials
+ }
+ else {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Patch -ContentType "application/json" -Body $body -Uri $updateUrl -Headers @{Authorization = "Basic $env:TEAM_PAT"}
+ }
+ }
+ }
+
+}
+
+function Get-BuildTag {
+ param(
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [int] $Id
+ )
+
+ DynamicParam {
+ _buildProjectNameDynamicParam
+ }
+
+ Process {
+ $ProjectName = $PSBoundParameters["ProjectName"]
+
+ $rootUrl = _buildChildUrl -projectName $ProjectName -id $Id -child "tags"
+
+ # Call the REST API
+ if (_useWindowsAuthenticationOnPremise) {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Get -Uri $rootUrl -UseDefaultCredentials
+ }
+ else {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Get -Uri $rootUrl -Headers @{Authorization = "Basic $env:TEAM_PAT"}
+ }
+
+ return $resp.value
+ }
+}
+
+function Add-BuildTag {
+ [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low")]
+ param(
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
+ [string[]] $Tags,
+
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [int[]] $Id,
+
+ [switch] $Force
+ )
+
+ DynamicParam {
+ _buildProjectNameDynamicParam
+ }
+
+ Process {
+ $ProjectName = $PSBoundParameters["ProjectName"]
+
+ foreach ($item in $id) {
+ if ($Force -or $pscmdlet.ShouldProcess($item, "Add-BuildTag")) {
+
+ $rootUrl = _buildChildUrl -projectName $ProjectName -id $item -child "tags"
+
+ foreach ($tag in $tags) {
+
+ $tagUrl = $rootUrl + "&tag=$tag"
+
+ # Call the REST API
+ if (_useWindowsAuthenticationOnPremise) {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Put -Uri $tagUrl -UseDefaultCredentials
+ }
+ else {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Put -Uri $tagUrl -Headers @{Authorization = "Basic $env:TEAM_PAT"}
+ }
+ }
+ }
+ }
+ }
+}
+
+function Remove-BuildTag {
+ [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Low")]
+ param(
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
+ [string[]] $Tags,
+
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [int[]] $Id,
+
+ [switch] $Force
+ )
+
+ DynamicParam {
+ _buildProjectNameDynamicParam
+ }
+
+ Process {
+ $ProjectName = $PSBoundParameters["ProjectName"]
+
+ foreach ($item in $id) {
+ if ($Force -or $pscmdlet.ShouldProcess($item, "Remove-BuildTag")) {
+
+ $rootUrl = _buildChildUrl -projectName $ProjectName -id $item -child "tags"
+
+ foreach ($tag in $tags)
+ {
+ $tagUrl = $rootUrl + "&tag=$tag"
+
+ # Call the REST API
+ if (_useWindowsAuthenticationOnPremise) {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Delete -Uri $tagUrl -UseDefaultCredentials
+ }
+ else {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Delete -Uri $tagUrl -Headers @{Authorization = "Basic $env:TEAM_PAT"}
+ }
+ }
+ }
+ }
+ }
+}
+
+function Get-BuildArtifact {
+ param(
+ [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
+ [int] $Id
+ )
+
+ DynamicParam {
+ _buildProjectNameDynamicParam
+ }
+
+ Process {
+ $ProjectName = $PSBoundParameters["ProjectName"]
+
+ $rootUrl = _buildChildUrl -projectName $ProjectName -id $Id -child "artifacts"
+
+ # Call the REST API
+ if (_useWindowsAuthenticationOnPremise) {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Get -Uri $rootUrl -UseDefaultCredentials
+ }
+ else {
+ $resp = Invoke-RestMethod -UserAgent (_getUserAgent) -Method Get -Uri $rootUrl -Headers @{Authorization = "Basic $env:TEAM_PAT"}
+ }
+
+ foreach ($item in $resp.value) {
+ _applyArtifactTypes -item $item
+ }
+
+ Write-Output $resp.value
+ }
+}
+
+Export-ModuleMember -Alias * -Function Add-Build, Get-Build, Remove-Build, Get-BuildLog,
+ Add-BuildTag, Get-BuildTag, Remove-BuildTag,
+ Get-BuildArtifact, Update-Build
\ No newline at end of file
diff --git a/src/types.ps1xml b/src/types.ps1xml
index 2e1be4650..dc02695bc 100644
--- a/src/types.ps1xml
+++ b/src/types.ps1xml
@@ -139,6 +139,40 @@ REMAINS WITH THE USER.
+
+
+ Team.Build.Artifact
+
+
+ type
+ $this.resource.type
+
+
+ data
+ $this.resource.data
+
+
+ url
+ $this.resource.url
+
+
+ downloadUrl
+ $this.resource.downloadUrl
+
+
+ PSStandardMembers
+
+
+ DefaultDisplayPropertySet
+
+ id
+ name
+
+
+
+
+
+
Team.Release
diff --git a/test/builds.Tests.ps1 b/test/builds.Tests.ps1
index aa025ab4a..37ec4f6b9 100644
--- a/test/builds.Tests.ps1
+++ b/test/builds.Tests.ps1
@@ -9,7 +9,7 @@ InModuleScope builds {
# Just a shell for the nest dynamic parameters
# Used as Mock for calls below. We can't use normal
- # Mock because the module where is lives is not loaded.
+ # Mock because the module where it lives is not loaded.
function Get-BuildDefinition {
return new-object psobject -Property @{
id=2
@@ -127,5 +127,88 @@ InModuleScope builds {
}
}
}
- }
+
+ Context 'Add-BuildTag' {
+ Mock Invoke-RestMethod -UserAgent(_getUserAgent)
+ $inputTags = "Test1", "Test2", "Test3"
+
+ It 'should add tags to Build' {
+ Add-BuildTag -ProjectName project -id 2 -Tags $inputTags
+
+ foreach ($inputTag in $inputTags) {
+ Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter {
+ $Method -eq 'Put' -and
+ $Uri -eq 'https://test.visualstudio.com/project/_apis/build/builds/2/tags?api-version=2.0' + "&tag=$inputTag"
+ }
+ }
+ }
+ }
+
+ Context 'Remove-BuildTag' {
+ Mock Invoke-RestMethod -UserAgent(_getUserAgent) {
+ return @{ value=$null }
+ }
+ [string[]] $inputTags = "Test1", "Test2", "Test3"
+
+ It 'should add tags to Build' {
+ Remove-BuildTag -ProjectName project -id 2 -Tags $inputTags
+
+ foreach ($inputTag in $inputTags) {
+ Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter {
+ $Method -eq 'Delete' -and
+ $Uri -eq 'https://test.visualstudio.com/project/_apis/build/builds/2/tags?api-version=2.0' + "&tag=$inputTag"
+ }
+ }
+ }
+ }
+
+ Context 'Get-BuildTag calls correct Url' {
+ Mock Invoke-RestMethod {
+ return @{ value='Tag1', 'Tag2'}
+ }
+
+ It 'should get all Build Tags for the Build.' {
+ Get-BuildTag -projectName project -id 2
+
+ Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter {
+ $Method -eq 'Get' -and
+ $Uri -eq 'https://test.visualstudio.com/project/_apis/build/builds/2/tags?api-version=2.0'
+ }
+ }
+ }
+
+ Context 'Get-BuildTag returns correct data' {
+ $tags = 'Tag1', 'Tag2'
+ Mock Invoke-RestMethod -UserAgent(_getUserAgent) {
+ return @{ value=$tags}
+ }
+
+ It 'should get all Build Tags for the Build.' {
+ $returndata = Get-BuildTag -projectName project -id 2
+
+ Compare-Object $tags $returndata |
+ Should Be $null
+ }
+ }
+
+ Context "Get-BuildArtifact calls correct Url" {
+ Mock Invoke-RestMethod -UserAgent(_getUserAgent) { return @{
+ value = @{
+ id = 150;
+ name = "Drop";
+ resource = @{type="filepath"; data="C:\Test"}
+ }
+ }
+ }
+
+ It 'should return the build artifact data' {
+ Get-BuildArtifact -projectName project -id 2
+
+ Assert-MockCalled Invoke-RestMethod -Exactly -Scope It -Times 1 -ParameterFilter {
+ $Method -eq 'Get' -and
+ $Uri -eq 'https://test.visualstudio.com/project/_apis/build/builds/2/artifacts?api-version=2.0'
+ }
+ }
+ }
+ }
}
\ No newline at end of file