diff --git a/Formatting/PSDevOps.SharedQuery.format.ps1 b/Formatting/PSDevOps.SharedQuery.format.ps1
new file mode 100644
index 00000000..78326ab2
--- /dev/null
+++ b/Formatting/PSDevOps.SharedQuery.format.ps1
@@ -0,0 +1 @@
+Write-FormatView -TypeName PSDevOps.SharedQuery -Property IsPublic, Path, Wiql -Wrap -GroupByProperty Project
diff --git a/Get-ADOWorkItem.ps1 b/Get-ADOWorkItem.ps1
index 1044c3fa..ac79b474 100644
--- a/Get-ADOWorkItem.ps1
+++ b/Get-ADOWorkItem.ps1
@@ -93,6 +93,7 @@
# If provided, will only return the first N results from a query.
[Parameter(ParameterSetName='/{Organization}/{Project}/{Team}/_apis/wit/wiql',ValueFromPipelineByPropertyName)]
+ [Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
[Alias('Top')]
[uint32]
$First,
@@ -103,6 +104,33 @@
[switch]
$WorkItemType,
+ # If set, will return work item shared queries
+ [Parameter(Mandatory,ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
+ [switch]
+ $SharedQuery,
+
+ # If set, will return shared queries that have been deleted.
+ [Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
+ [switch]
+ $IncludeDeleted,
+
+ # If provided, will return shared queries up to a given depth.
+ [Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
+ [ValidateRange(0,2)]
+ [int]
+ $Depth,
+
+ # If provided, will filter the shared queries returned
+ [Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
+ [int]
+ $SharedQueryFilter,
+
+ # Determines how data from shared queries will be expanded. By default, expands all data.
+ [Parameter(ParameterSetName='/{Organization}/{Project}/_apis/wit/queries',ValueFromPipelineByPropertyName)]
+ [ValidateSet('All','Clauses','Minimal','None', 'Wiql')]
+ [string]
+ $ExpandSharedQuery = 'All',
+
# One or more fields.
[Alias('Fields','Select')]
[string[]]
@@ -170,6 +198,31 @@
}
#endregion Output Work Item
+
+ #region ExpandSharedQueries
+ $expandSharedQueries = {
+ param([Parameter(ValueFromPipeline)]$node)
+ process {
+ if (-not $node) { return }
+ $node.pstypenames.clear()
+ foreach ($typeName in "$organization.SharedQuery",
+ "$organization.$Project.SharedQuery",
+ "PSDevOps.SharedQuery"
+ ) {
+ $node.pstypenames.Add($typeName)
+ }
+ $node |
+ Add-Member NoteProperty Organization $organization -Force -PassThru |
+ Add-Member NoteProperty Project $Project -Force -PassThru |
+ Add-Member NoteProperty Server $Server -Force -PassThru
+ if ($node.haschildren) {
+ $node.children |
+ & $MyInvocation.MyCommand.ScriptBlock
+ }
+ }
+ }
+ #endregion ExpandSharedQueries
+
$allIDS = [Collections.ArrayList]::new()
}
@@ -181,6 +234,19 @@
$selfSplat.Query = "Select [System.ID] from WorkItems Where [System.Title] contains '$title'"
Get-ADOWorkItem @selfSplat
}
+ elseif ($psCmdlet.ParameterSetName -eq '/{Organization}/{Project}/_apis/wit/queries') {
+ $myInvokeParams = @{} + $invokeParams
+ $myInvokeParams.Url = "$Server".TrimEnd('/') + $psCmdlet.ParameterSetName
+
+ $myInvokeParams.QueryParameter = @{'$expand'= $ExpandSharedQuery}
+ $myInvokeParams.UrlParameter = @{} + $psBoundParameters
+ if ($IncludeDeleted) { $myInvokeParams.QueryParameter.'$includeDeleted' = $true }
+ if ($First) { $myInvokeParams.QueryParameter.'$top' = $First}
+ if ($Depth) { $myInvokeParams.QueryParameter.'$depth' = $Depth}
+ $myInvokeParams.Property = @{Organization = $Organization;Project=$Project}
+ Invoke-ADORestAPI @myInvokeParams | & $expandSharedQueries
+ return
+ }
elseif (
$PSCmdlet.ParameterSetName -in
'/{Organization}/{Project}/_apis/wit/workitems/{id}',
@@ -198,7 +264,7 @@
elseif ($PSCmdlet.ParameterSetName -eq '/{Organization}/{Project}/{Team}/_apis/wit/wiql')
{
$uri = "$Server".TrimEnd('/') + (. $ReplaceRouteParameter $PSCmdlet.ParameterSetName) + '?'
- $uri +=
+ $uri +=
@(if ($First) {
"`$top=$First"
}
@@ -214,7 +280,6 @@
$realQuery += ' AND '
}
-
$realQuery +=
@(
if ($Project) {
@@ -261,6 +326,7 @@
"api-version=$ApiVersion"
}
$invokeParams.Uri = $uri
+ $invokeParams.Property = @{Organization = $Organization}
$workItemTypes = Invoke-ADORestAPI @invokeParams
$workItemTypes -replace '"":', '"_blank":' |
ConvertFrom-Json |
diff --git a/New-ADOWorkItem.ps1 b/New-ADOWorkItem.ps1
index 9128a4a5..8478682b 100644
--- a/New-ADOWorkItem.ps1
+++ b/New-ADOWorkItem.ps1
@@ -11,22 +11,55 @@
.Link
Invoke-ADORestAPI
#>
- [CmdletBinding(DefaultParameterSetName='ByID',SupportsShouldProcess=$true)]
+ [CmdletBinding(DefaultParameterSetName='WorkItem',SupportsShouldProcess=$true)]
[OutputType('PSDevOps.WorkItem')]
param(
# The InputObject
- [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
+ [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[PSObject]
$InputObject,
# The type of the work item.
- [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
+ [Parameter(Mandatory, ParameterSetName='WorkItem',ValueFromPipelineByPropertyName)]
[Alias('WorkItemType')]
[string]
$Type,
+ # If set, will create a shared query for work items. The -InputObject will be passed to the body.
+ [Parameter(Mandatory,ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
+ [string]
+ $QueryName,
+
+ # If provided, will create shared queries beneath a given folder.
+ [Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
+ [Parameter(ParameterSetName='SharedQueryFolder',ValueFromPipelineByPropertyName)]
+ [string]
+ $QueryPath,
+
+ # If provided, create a shared query with a given WIQL.
+ [Parameter(Mandatory, ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
+ [string]
+ $WIQL,
+
+ # If provided, the shared query created may be hierchical
+ [Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
+ [ValidateSet('Flat','OneHop', 'Tree')]
+ [string]
+ $QueryType,
+
+ # The recursion option for use in a tree query.
+ [Parameter(ParameterSetName='SharedQuery',ValueFromPipelineByPropertyName)]
+ [ValidateSet('childFirst','parentFirst')]
+ [string]
+ $QueryRecursiveOption,
+
+ # If provided, create a shared query folder.
+ [Parameter(Mandatory, ParameterSetName='SharedQueryFolder',ValueFromPipelineByPropertyName)]
+ [string]
+ $FolderName,
+
# The work item ParentID
- [Parameter(ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[string]
$ParentID,
@@ -42,13 +75,13 @@
$Project,
# A collection of relationships for the work item.
- [Parameter(ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[Alias('Relationships')]
[Collections.IDictionary]
$Relationship,
# A list of comments to be added to the work item.
- [Parameter(ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[PSObject[]]
$Comment,
@@ -58,7 +91,7 @@
$Tag,
# If set, will not validate rules.
- [Parameter(ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[Alias('BypassRules','NoRules','NoRule')]
[switch]
$BypassRule,
@@ -70,7 +103,7 @@
$ValidateOnly,
# If set, will only validate rules, but will not update the work item.
- [Parameter(ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WorkItem')]
[Alias('SuppressNotifications','SkipNotification','SkipNotifications','NoNotify')]
[switch]
$SupressNotification,
@@ -160,6 +193,9 @@
}
}
#endregion Output Work Item
+
+
+
$q = [Collections.Queue]::new()
}
@@ -177,14 +213,89 @@
$c++
Write-Progress "Creating" "$type [$c/$t]" -PercentComplete ($c * 100 / $t) -Id $progId
-
+ $orgAndProject = @{Organization=$Organization;Project=$Project}
$validFields =
if ($script:ADOFieldCache.$uribase) {
$script:ADOFieldCache.$uribase
} else {
- Get-ADOField -Organization $Organization -Project $Project -Server $Server @invokeParams
+ Get-ADOField @orgAndProject -Server $Server @invokeParams
}
+ if ($psParameterSet -in 'SharedQuery', 'SharedQueryFolder') {
+ if ($Server -ne 'https://dev.azure.com/' -and
+ -not $PSBoundParameters.ApiVersion) {
+ $ApiVersion = '2.0'
+ }
+
+ $queryPathParts = @($QueryPath -split '/')
+ $sharedQueries = $null
+ foreach ($qp in $queryPathParts) {
+ if (-not $qp) { continue }
+ if (-not ($qp -as [guid])) {
+ $sharedQueries = Get-ADOWorkItem -SharedQuery @orgAndProject -Depth 2
+ break
+ }
+ }
+
+ if ($sharedQueries) {
+ $queryPathId = $sharedQueries |
+ Where-Object Path -eq $QueryPath |
+ Select-Object -ExpandProperty ID
+ if (-not $queryPathId) {
+ Write-Error "Unable to find Query Path '$QueryPath'"
+ continue
+ } else {
+ $QueryPath = $queryPathId
+ }
+ }
+
+ $uri = $uriBase, "_apis/wit/queries", $(if ($QueryPath) { $QueryPath }) -ne '' -join '/'
+ $uri = $uri.ToString().TrimEnd('/')
+ $uri += '?' +
+ (@(
+ if ($ApiVersion) { "api-version=$ApiVersion" }
+ if ($validateOnly) { "validateWiqlOnly=true" }
+ ) -join '&')
+ $invokeParams.uri = $uri
+
+ $queryObject = @{}
+ if ($psParameterSet -eq 'SharedQueryFolder') {
+ $queryObject['name'] = $FolderName
+ $queryObject['isFolder'] = $true
+ if ($QueryType) {
+ $queryObject['queryType'] = $QueryType
+ }
+ if ($queryRecursionOption) {
+ $queryObject['queryRecursionOption'] = $queryRecursionOption
+ }
+
+ } else {
+ $queryObject['name'] = $QueryName
+ $queryObject['wiql'] = $WIQL
+
+ }
+
+ $invokeParams.Body = ConvertTo-Json $queryObject -Depth 100
+ $invokeParams.Method = 'POST'
+ $invokeParams.ContentType = 'application/json'
+ $invokeParams.PSTypeName = @(
+ "$Organization.$psParameterSet"
+ "$Organization.$project.$psParameterSet"
+ "PSDevOps.$psParameterSet"
+ )
+ if ($WhatIfPreference) {
+ $invokeParams.Remove('PersonalAccessToken')
+ $invokeParams
+ continue
+ }
+
+ if (-not $PSCmdlet.ShouldProcess("POST $uri with $($invokeParams.body)")) { continue }
+ $restResponse = Invoke-ADORestAPI @invokeParams 2>&1
+ $restResponse
+ continue
+ }
+
+
$validFieldTable = $validFields | Group-Object ReferenceName -AsHashTable
$uri = $uriBase, "_apis/wit/workitems", "`$$($Type)?" -join '/'
if ($Server -ne 'https://dev.azure.com/' -and
diff --git a/PSDevOps.format.ps1xml b/PSDevOps.format.ps1xml
index 9b673974..fc1344da 100644
--- a/PSDevOps.format.ps1xml
+++ b/PSDevOps.format.ps1xml
@@ -973,6 +973,41 @@ $($ParentNode.CustomControl.CustomEntries.CustomEntry.CustomItem.ExpressionBindi
+
+ PSDevOps.SharedQuery
+
+ PSDevOps.SharedQuery
+
+
+ Project
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsPublic
+
+
+ Path
+
+
+ Wiql
+
+
+
+
+
+
PSDevOps.Team
diff --git a/PSDevOps.tests.ps1 b/PSDevOps.tests.ps1
index 33080cc3..58e5374a 100644
--- a/PSDevOps.tests.ps1
+++ b/PSDevOps.tests.ps1
@@ -1,4 +1,4 @@
-param(
+param(
[string]
$TestOrg = 'StartAutomating',
[string]
@@ -291,7 +291,7 @@ describe 'Calling REST APIs' {
}
it 'Can set team -DefaultAreaPath and -AreaPath' {
- $whatIf =
+ $whatIf =
Get-ADOTeam -Organization StartAutomating -Project PSDevOps -TeamID 'PSDevOps Team' -PersonalAccessToken $testPat |
Set-ADOTeam -DefaultAreaPath "MyAreaPath" -WhatIf -AreaPath "An\AreaPath", "Another\AreaPath"
@@ -299,8 +299,8 @@ describe 'Calling REST APIs' {
$whatIf.Uri | Should -BeLike '*teamFieldvalue*'
$whatIf.Body.defaultValue | Should -Be MyAreaPath
-
- }
+
+ }
}
context Repositories {
@@ -692,11 +692,9 @@ describe 'Working with Work Items' {
it 'Will not use workitemsbatch when using an old version of the REST api' {
$queryResults = Get-ADOWorkItem -Organization StartAutomating -Project PSDevOps -Query 'Select [System.ID] from WorkItems Where [System.WorkItemType] = "Epic"' -PersonalAccessToken $testPat -ApiVersion '3.0'
$queryResults[0].'System.WorkItemType' | should -be Epic
- }
+ }
}
-
-
it 'Can create, update, and remove a work item' {
$splat = @{Organization = $TestOrg; Project = $TestProject; PersonalAccessToken = $testPat }
$wi = New-ADOWorkItem -InputObject @{Title = 'Test-WorkItem' } -Type Issue -ParentID 1 @splat -Tag 'PSDevOpsUnitTest' -Comment 'Added while unit testing'
@@ -708,6 +706,8 @@ describe 'Working with Work Items' {
Remove-ADOWorkItem @splat -Query "select [System.ID] from WorkItems Where [System.Title] = 'Test-WorkItem'" -Confirm:$false
}
+
+
it 'Can get work proccesses' {
Get-ADOWorkProcess -Organization $TestOrg -PersonalAccessToken $testPat |
Select-Object -First 1 -ExpandProperty name |
diff --git a/PSDevOps.types.ps1xml b/PSDevOps.types.ps1xml
index 2fec89db..78d9f20f 100644
--- a/PSDevOps.types.ps1xml
+++ b/PSDevOps.types.ps1xml
@@ -1083,6 +1083,24 @@ $bitMask
+
+ PSDevOps.SharedQuery
+
+
+ QueryID
+ ID
+
+
+
+
+ Deserialized.PSDevOps.SharedQuery
+
+
+ QueryID
+ ID
+
+
+
PSDevOps.State
diff --git a/Remove-ADOWorkItem.ps1 b/Remove-ADOWorkItem.ps1
index 7f02eb33..84a13eb6 100644
--- a/Remove-ADOWorkItem.ps1
+++ b/Remove-ADOWorkItem.ps1
@@ -38,6 +38,12 @@
[string]
$Query,
+ # If set, will return work item shared queries
+ [Parameter(Mandatory,ParameterSetName='/{Organization}/{Project}/_apis/wit/queries/{QueryID}',ValueFromPipelineByPropertyName)]
+ [string]
+ $QueryID,
+
+
# The server. By default https://dev.azure.com/.
# To use against TFS, provide the tfs server URL (e.g. http://tfsserver:8080/tfs).
[Parameter(ValueFromPipelineByPropertyName)]
@@ -58,7 +64,12 @@
}
process {
- if ($PSCmdlet.ParameterSetName -eq 'ByID') { # If we're removing by ID
+ $psParameterSet = $PSCmdlet.ParameterSetName
+ $in = $_
+ if ($in.QueryID) {
+ $psParameterSet = '/{Organization}/{Project}/_apis/wit/queries/{QueryID}'
+ }
+ if ($psParameterSet -eq 'ByID') { # If we're removing by ID
$uriBase = "$Server".TrimEnd('/'), $Organization, $Project -join '/'
$uri = $uriBase, "_apis/wit/workitems", "${ID}?" -join '/'
@@ -75,7 +86,7 @@
$invokeParams.Method = 'DELETE'
if (-not $PSCmdlet.ShouldProcess("Remove Work Item $ID")) { return }
Invoke-ADORestAPI @invokeParams
- } elseif ($PSCmdlet.ParameterSetName -eq 'ByQuery') {
+ } elseif ($psParameterSet -eq 'ByQuery') {
$uri = "$Server".TrimEnd('/'), $Organization, $Project, "_apis/wit/wiql?" -join '/'
@@ -99,5 +110,15 @@
Write-Progress "Updating Work Items" "Complete" -Completed -Id $progId
}
+ elseif ($psParameterSet -eq '/{Organization}/{Project}/_apis/wit/queries/{QueryID}') {
+ $invokeParams.Method = "DELETE"
+ $invokeParams["Uri"] = "$Server".TrimEnd('/') + $psParameterSet
+ $invokeParams.QueryParameter = @{"api-version"="$ApiVersion"}
+ if ($WhatIfPreference) {
+ $invokeParams.Remove('PersonalAccessToken')
+ return $invokeParams
+ }
+ Invoke-ADORestAPi @invokeParams
+ }
}
}
diff --git a/Types/PSDevOps.SharedQuery/Alias.psd1 b/Types/PSDevOps.SharedQuery/Alias.psd1
new file mode 100644
index 00000000..6aa24e7d
--- /dev/null
+++ b/Types/PSDevOps.SharedQuery/Alias.psd1
@@ -0,0 +1,3 @@
+@{
+ QueryID = 'ID'
+}