diff --git a/CHANGELOG.md b/CHANGELOG.md
index a176154..c583b75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,19 @@ All notable changes to this project will be documented in this file.
-->
+## [UNRELEASED]
+
+### Summary
+- _None_
+
+### What's Changed
+- feat(Get-AdoMembership): Add list support
+
+### Breaking Changes
+- _None_
+
+
+
## [0.4.1] - 2026-02-17
### Summary
diff --git a/docs/Get-AdoMembership.md b/docs/Get-AdoMembership.md
index e420dad..5f97637 100644
--- a/docs/Get-AdoMembership.md
+++ b/docs/Get-AdoMembership.md
@@ -13,17 +13,24 @@ title: Get-AdoMembership
## SYNOPSIS
-Get the membership relationship between a subject and a container in Azure DevOps.
+Get membership relationships
## SYNTAX
-### __AllParameterSets
+### GetMembership
```text
Get-AdoMembership [[-CollectionUri] ] [-SubjectDescriptor]
[-ContainerDescriptor] [[-Version] ] []
```
+### ListMemberships
+
+```text
+Get-AdoMembership [[-CollectionUri] ] [-SubjectDescriptor]
+ [[-Depth] ] [[-Direction] ] [[-Version] ] []
+```
+
## ALIASES
This cmdlet has the following aliases,
@@ -31,7 +38,8 @@ This cmdlet has the following aliases,
## DESCRIPTION
-This cmdlet retrieves the membership relationship between a specified subject and container in Azure DevOps.
+This cmdlet retrieves the membership relationships between a specified subject and container in Azure DevOps or
+get all the memberships where this descriptor is a member in the relationship.
## EXAMPLES
@@ -64,6 +72,38 @@ $params = @{
Retrieves the membership relationships for multiple subjects demonstrating pipeline input.
+### EXAMPLE 3
+
+#### PowerShell
+
+```powershell
+$params = @{
+ CollectionUri = 'https://vssps.dev.azure.com/my-org'
+ SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000'
+ Depth = 2
+ Direction = 'up'
+}
+Get-AdoMembership @params
+```
+
+Retrieves all groups for a user with a depth of 2.
+
+### EXAMPLE 4
+
+#### PowerShell
+
+```powershell
+$params = @{
+ CollectionUri = 'https://vssps.dev.azure.com/my-org'
+ SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000'
+ Depth = 2
+ Direction = 'down'
+}
+Get-AdoMembership @params
+```
+
+Retrieves all memberships of a group with a depth of 2.
+
## PARAMETERS
### -CollectionUri
@@ -112,7 +152,7 @@ HelpMessage: ''
### -ContainerDescriptor
-Mandatory.
+Optional.
A descriptor to the container in the relationship.
```yaml
@@ -121,9 +161,32 @@ DefaultValue: ''
SupportsWildcards: false
Aliases: []
ParameterSets:
-- Name: (All)
+- Name: GetMembership
Position: Named
- IsRequired: true
+ IsRequired: false
+ ValueFromPipeline: false
+ ValueFromPipelineByPropertyName: true
+ ValueFromRemainingArguments: false
+DontShow: false
+AcceptedValues: []
+HelpMessage: ''
+```
+
+### -Depth
+
+Optional.
+The depth of memberships to retrieve when ContainerDescriptor is not specified.
+Default is 1.
+
+```yaml
+Type: System.Int32
+DefaultValue: ''
+SupportsWildcards: false
+Aliases: []
+ParameterSets:
+- Name: ListMemberships
+ Position: Named
+ IsRequired: false
ValueFromPipeline: false
ValueFromPipelineByPropertyName: true
ValueFromRemainingArguments: false
@@ -132,15 +195,42 @@ AcceptedValues: []
HelpMessage: ''
```
+### -Direction
+
+Optional.
+The direction of memberships to retrieve when ContainerDescriptor is not specified.
+
+The default value for direction is 'up' meaning return all memberships where the subject is a member (e.g. all groups the subject is a member of).
+Alternatively, passing the direction as 'down' will return all memberships where the subject is a container (e.g. all members of the subject group).
+
+```yaml
+Type: System.String
+DefaultValue: ''
+SupportsWildcards: false
+Aliases: []
+ParameterSets:
+- Name: ListMemberships
+ Position: Named
+ IsRequired: false
+ ValueFromPipeline: false
+ ValueFromPipelineByPropertyName: true
+ ValueFromRemainingArguments: false
+DontShow: false
+AcceptedValues:
+- up
+- down
+HelpMessage: ''
+```
+
### -Version
Optional.
The API version to use for the request.
-Default is '7.2-preview.1'.
+Default is '7.1-preview.1'.
```yaml
Type: System.String
-DefaultValue: 7.2-preview.1
+DefaultValue: 7.1-preview.1
SupportsWildcards: false
Aliases:
- ApiVersion
@@ -153,6 +243,7 @@ ParameterSets:
ValueFromRemainingArguments: false
DontShow: false
AcceptedValues:
+- 7.1-preview.1
- 7.2-preview.1
HelpMessage: ''
```
@@ -180,3 +271,4 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable
## RELATED LINKS
-
+-
diff --git a/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1 b/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1
index 33af535..1d0c7b4 100644
--- a/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1
+++ b/src/Azure.DevOps.PSModule/Public/Graph/Memberships/Get-AdoMembership.ps1
@@ -1,10 +1,11 @@
function Get-AdoMembership {
<#
.SYNOPSIS
- Get the membership relationship between a subject and a container in Azure DevOps.
+ Get membership relationships
.DESCRIPTION
- This cmdlet retrieves the membership relationship between a specified subject and container in Azure DevOps.
+ This cmdlet retrieves the membership relationships between a specified subject and container in Azure DevOps or
+ get all the memberships where this descriptor is a member in the relationship.
.PARAMETER CollectionUri
Optional. The collection URI of the Azure DevOps collection/organization, e.g., https://vssps.dev.azure.com/my-org.
@@ -13,16 +14,26 @@
Mandatory. A descriptor to the child subject in the relationship.
.PARAMETER ContainerDescriptor
- Mandatory. A descriptor to the container in the relationship.
+ Optional. A descriptor to the container in the relationship.
+
+ .PARAMETER Depth
+ Optional. The depth of memberships to retrieve when ContainerDescriptor is not specified. Default is 1.
+
+ .PARAMETER Direction
+ Optional. The direction of memberships to retrieve when ContainerDescriptor is not specified.
+
+ The default value for direction is 'up' meaning return all memberships where the subject is a member (e.g. all groups the subject is a member of).
+ Alternatively, passing the direction as 'down' will return all memberships where the subject is a container (e.g. all members of the subject group).
.PARAMETER Version
- Optional. The API version to use for the request. Default is '7.1'.
+ Optional. The API version to use for the request. Default is '7.1-preview.1'.
.OUTPUTS
PSCustomObject
.LINK
- https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/get
+ - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/get
+ - https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/list
.EXAMPLE
$params = @{
@@ -42,6 +53,28 @@
@('aadgp.00000000-0000-0000-0000-000000000002', 'aadgp.00000000-0000-0000-0000-000000000003') | Get-AdoMembership @params
Retrieves the membership relationships for multiple subjects demonstrating pipeline input.
+
+ .EXAMPLE
+ $params = @{
+ CollectionUri = 'https://vssps.dev.azure.com/my-org'
+ SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000'
+ Depth = 2
+ Direction = 'up'
+ }
+ Get-AdoMembership @params
+
+ Retrieves all groups for a user with a depth of 2.
+
+ .EXAMPLE
+ $params = @{
+ CollectionUri = 'https://vssps.dev.azure.com/my-org'
+ SubjectDescriptor = 'aadgp.00000000-0000-0000-0000-000000000000'
+ Depth = 2
+ Direction = 'down'
+ }
+ Get-AdoMembership @params
+
+ Retrieves all memberships of a group with a depth of 2.
#>
[CmdletBinding()]
param (
@@ -52,9 +85,16 @@
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
[string[]]$SubjectDescriptor,
- [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
+ [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'GetMembership')]
[string]$ContainerDescriptor,
+ [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListMemberships')]
+ [int32]$Depth,
+
+ [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ListMemberships')]
+ [ValidateSet('up', 'down')]
+ [string]$Direction,
+
[Parameter()]
[Alias('ApiVersion')]
[ValidateSet('7.1-preview.1', '7.2-preview.1')]
@@ -66,6 +106,8 @@
Write-Debug ("CollectionUri: $CollectionUri")
Write-Debug ("SubjectDescriptor: $($SubjectDescriptor -join ',')")
Write-Debug ("ContainerDescriptor: $ContainerDescriptor")
+ Write-Debug ("Depth: $Depth")
+ Write-Debug ("Direction: $Direction")
Write-Debug ("Version: $Version")
Confirm-Default -Defaults ([ordered]@{
@@ -79,18 +121,38 @@
process {
try {
+ $queryParameters = [List[string]]::new()
+
+ if ($ContainerDescriptor) {
+ $uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor/$ContainerDescriptor"
+ } else {
+ $uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor"
+
+ if ($Depth) {
+ $queryParameters.Add("depth=$Depth")
+ }
+ if ($Direction) {
+ $queryParameters.Add("direction=$Direction")
+ }
+ }
+
$params = @{
- Uri = "$CollectionUri/_apis/graph/memberships/$SubjectDescriptor/$ContainerDescriptor"
- Version = $Version
- Method = 'GET'
+ Uri = $uri
+ Version = $Version
+ QueryParameters = if ($queryParameters.Count -gt 0) { $queryParameters -join '&' } else { $null }
+ Method = 'GET'
}
- $result = Invoke-AdoRestMethod @params
+ $results = Invoke-AdoRestMethod @params
+ $memberships = if ($ContainerDescriptor) { @($results) } else { $results.value }
- [PSCustomObject]@{
- memberDescriptor = $result.memberDescriptor
- containerDescriptor = $result.containerDescriptor
- collectionUri = $CollectionUri
+ foreach ($m_ in $memberships) {
+ $obj = [ordered]@{
+ containerDescriptor = $m_.containerDescriptor
+ memberDescriptor = $m_.memberDescriptor
+ collectionUri = $CollectionUri
+ }
+ [PSCustomObject]$obj
}
} catch {
diff --git a/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1 b/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1
index 469c795..fa642fb 100644
--- a/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1
+++ b/src/Azure.DevOps.PSModule/Tests/Graph/Memberships/Get-AdoMembership.Tests.ps1
@@ -21,6 +21,19 @@ Describe 'Get-AdoMembership' {
memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001'
containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000002'
}
+
+ $mockMembershipsList = [PSCustomObject]@{
+ value = @(
+ [PSCustomObject]@{
+ memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001'
+ containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000003'
+ }
+ [PSCustomObject]@{
+ memberDescriptor = 'aadgp.00000000-0000-0000-0000-000000000001'
+ containerDescriptor = 'vssgp.00000000-0000-0000-0000-000000000004'
+ }
+ )
+ }
}
Context 'Core Functionality Tests' {
@@ -137,6 +150,83 @@ Describe 'Get-AdoMembership' {
}
}
+ Context 'ListMemberships Parameter Set Tests' {
+ BeforeEach {
+ Mock -ModuleName Azure.DevOps.PSModule Invoke-AdoRestMethod { return $mockMembershipsList }
+ Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { }
+ Mock -ModuleName Azure.DevOps.PSModule Start-Sleep { }
+ }
+
+ It 'Should retrieve memberships without ContainerDescriptor' {
+ # Act
+ $result = Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up'
+
+ # Assert
+ $result | Should -Not -BeNullOrEmpty
+ $result | Should -HaveCount 2
+ }
+
+ It 'Should construct API URI correctly without ContainerDescriptor' {
+ # Act
+ Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up'
+
+ # Assert
+ Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter {
+ $Uri -eq "$mockCollectionUri/_apis/graph/memberships/$mockSubjectDescriptor"
+ }
+ }
+
+ It 'Should include Depth query parameter when specified' {
+ # Act
+ Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2
+
+ # Assert
+ Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter {
+ $QueryParameters -eq 'depth=2'
+ }
+ }
+
+ It 'Should include Direction query parameter when specified' {
+ # Act
+ Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up'
+
+ # Assert
+ Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter {
+ $QueryParameters -eq 'direction=up'
+ }
+ }
+
+ It 'Should include both Depth and Direction query parameters when specified' {
+ # Act
+ Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2 -Direction 'down'
+
+ # Assert
+ Should -Invoke Invoke-AdoRestMethod -ModuleName Azure.DevOps.PSModule -Times 1 -ParameterFilter {
+ $QueryParameters -eq 'depth=2&direction=down'
+ }
+ }
+
+ It 'Should accept Direction value "up"' {
+ # Act & Assert
+ { Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'up' } | Should -Not -Throw
+ }
+
+ It 'Should accept Direction value "down"' {
+ # Act & Assert
+ { Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Direction 'down' } | Should -Not -Throw
+ }
+
+ It 'Should return multiple memberships from value array' {
+ # Act
+ $result = Get-AdoMembership -CollectionUri $mockCollectionUri -SubjectDescriptor $mockSubjectDescriptor -Depth 2
+
+ # Assert
+ $result | Should -HaveCount 2
+ $result[0].memberDescriptor | Should -Be $mockSubjectDescriptor
+ $result[1].memberDescriptor | Should -Be $mockSubjectDescriptor
+ }
+ }
+
Context 'Error Handling Tests' {
BeforeEach {
Mock -ModuleName Azure.DevOps.PSModule Confirm-Default { }
@@ -225,12 +315,73 @@ Describe 'Get-AdoMembership' {
$param.Attributes.Mandatory | Should -Contain $true
}
- It 'Should have ContainerDescriptor as mandatory parameter' {
+ It 'Should have ContainerDescriptor as optional parameter' {
# Arrange
$param = (Get-Command Get-AdoMembership).Parameters['ContainerDescriptor']
# Assert
- $param.Attributes.Mandatory | Should -Contain $true
+ $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false
+ }
+
+ It 'Should have Depth as optional parameter' {
+ # Arrange
+ $param = (Get-Command Get-AdoMembership).Parameters['Depth']
+
+ # Assert
+ $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false
+ }
+
+ It 'Should have Direction as optional parameter' {
+ # Arrange
+ $param = (Get-Command Get-AdoMembership).Parameters['Direction']
+
+ # Assert
+ $param.Attributes.Where({ $_.GetType().Name -eq 'ParameterAttribute' }).Mandatory | Should -Contain $false
+ }
+ }
+
+ Context 'Parameter Set Tests' {
+ It 'Should have GetMembership parameter set' {
+ # Arrange
+ $paramSets = (Get-Command Get-AdoMembership).ParameterSets
+
+ # Assert
+ $paramSets.Name | Should -Contain 'GetMembership'
+ }
+
+ It 'Should have ListMemberships parameter set' {
+ # Arrange
+ $paramSets = (Get-Command Get-AdoMembership).ParameterSets
+
+ # Assert
+ $paramSets.Name | Should -Contain 'ListMemberships'
+ }
+
+ It 'Should have ContainerDescriptor in GetMembership parameter set' {
+ # Arrange
+ $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'GetMembership' }
+ $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'ContainerDescriptor' }
+
+ # Assert
+ $param | Should -Not -BeNullOrEmpty
+ }
+
+ It 'Should have Depth in ListMemberships parameter set' {
+ # Arrange
+ $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'ListMemberships' }
+ $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'Depth' }
+
+ # Assert
+ $param | Should -Not -BeNullOrEmpty
+ }
+
+ It 'Should have Direction in ListMemberships parameter set' {
+ # Arrange
+ $paramSet = (Get-Command Get-AdoMembership).ParameterSets | Where-Object { $_.Name -eq 'ListMemberships' }
+ $param = $paramSet.Parameters | Where-Object { $_.Name -eq 'Direction' }
+
+ # Assert
+ $param | Should -Not -BeNullOrEmpty
}
}
}