From 4b250a1d0e0ed33fa901f68d6357787e81d4a843 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sun, 30 Jan 2022 21:26:05 +0100 Subject: [PATCH 01/12] Update help with new syntax --- .docs/Get-VSTeamUserEntitlement.md | 114 +++++++++++++++++++- .docs/synopsis/Get-VSTeamUserEntitlement.md | 7 +- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/.docs/Get-VSTeamUserEntitlement.md b/.docs/Get-VSTeamUserEntitlement.md index 03dba95e8..0ff92657b 100644 --- a/.docs/Get-VSTeamUserEntitlement.md +++ b/.docs/Get-VSTeamUserEntitlement.md @@ -14,6 +14,44 @@ ## EXAMPLES +### Example 1: Get user by Id + +```powershell +Get-VSTeamUserEntitlement -Id f1ef22eb-5dd6-4e26-907c-986a0311b106 +``` + +This command gets the user entitlement of the user identified by id. + +### Example 2: Get users by name + +```powershell +Get-VSTeamUserEntitlement -Name username +``` + +This command gets a list of users which mail or user name contains 'username'. +Filtering by Name, License, or UserType is available only when MemberEntitlementManagement service version is 6.0 or upper. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + +### Example 3: Filter with some conditions + +```powershell +Get-VSTeamUserEntitlement -Filter "licenseId eq 'Account-Express' and licenseStatus eq 'Disabled'" +``` + +This command gets a list of users that match the license status and license type conditions. +The -Filter parameter is available only when MemberEntitlementManagement service version is 6.0 or upper. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + +### Example 4: List paged users + +```powershell +Get-VSTeamUserEntitlement -Skip 100 -Top 100 +``` + +This command list the from the user in the 101 position, the next 100 users +Filtering using the -Top -Skip parameters only works when MemberEntitlementManagement service version is below 6.0. See Get-VSTeamAPIVersion and Set-VSTeamAPIVersion commands + + ## PARAMETERS ### Skip @@ -53,11 +91,85 @@ Comma (",") separated list of properties to select in the result entitlements. ```yaml Type: String -Parameter Sets: List +Parameter Sets: List,PagedFilter,PagedParams Required: True Default value: None ``` +### MaxPages + +User entlitement API returs a paged result. This parameter allows to limit the number of pages to be retrieved. Default is 0 = all pages. + +```yaml +Type: int +Parameter Sets: PagedFilter,PagedParams +Required: False +Default value: $null +``` + +### Filter + +Equality operators relating to searching user entitlements seperated by and clauses. Valid filters include: licenseId, licenseStatus, userType, and name. +- licenseId: filters based on license assignment using license names. i.e. licenseId eq 'Account-Stakeholder' or licenseId eq 'Account-Express'. +- licenseStatus: filters based on license status. currently only supports disabled. i.e. licenseStatus eq 'Disabled'. To get disabled basic licenses, you would pass (licenseId eq 'Account-Express' and licenseStatus eq 'Disabled') +- userType: filters off identity type. Suppored types are member or guest i.e. userType eq 'member'. +- name: filters on if the user's display name or email contians given input. i.e. get all users with "test" in email or displayname is "name eq 'test'". + +A valid query could be: (licenseId eq 'Account-Stakeholder' or (licenseId eq 'Account-Express' and licenseStatus eq 'Disabled')) and name eq 'test' and userType eq 'guest'. + +```yaml +Type: string +Parameter Sets: PagedFilter +Required: False +Default value: None +``` + +### License + +Filters based on license assignment using license names + +The acceptable values for this parameter are: +- Stakeholder: Stakeholder +- Express: Basic +- Advanced: Basic + Test Plans + +Other licenses which source (licenseSource) is MSDN cannot be filtered here + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +``` + +### UserType + +Filters based on user type + +The acceptable values for this parameter are: +- Member +- Guest + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +``` + +### Name + +Filters on if the user's display name or email contains given input + +```yaml +Type: string +Parameter Sets: PagedParams +Required: False +Default value: None +Alias: Mail +``` + + ## INPUTS ## OUTPUTS diff --git a/.docs/synopsis/Get-VSTeamUserEntitlement.md b/.docs/synopsis/Get-VSTeamUserEntitlement.md index 65b573ce3..d5ccc573d 100644 --- a/.docs/synopsis/Get-VSTeamUserEntitlement.md +++ b/.docs/synopsis/Get-VSTeamUserEntitlement.md @@ -1 +1,6 @@ -Get User Entitlement for a user. \ No newline at end of file +Get User Entitlement for a user, or a paged list of users matching the specified filter + +Please note that Filter, Name, UserType and License parameters only works when MemberEntitlementManagement module version is 6.0 or upper +In the same way Top and Skip paramerers only works up to version 5.1 + +You can setup the specific version for the MemberEntitlementManagement calling Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version VersionNumberYouNeed. \ No newline at end of file From 11621b744e01a53ad1942895f1de564051ccccbc Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Mon, 28 Feb 2022 23:44:36 +0100 Subject: [PATCH 02/12] update Get-VSTeamUserEntitlement to support new 6.0 API version * added unit tests for /Get-VSTeamUserEntitlement * updated documentation --- .docs/Get-VSTeamUserEntitlement.md | 17 ++- Source/Private/common.ps1 | 12 +- Source/Public/Get-VSTeamUserEntitlement.ps1 | 134 ++++++++++++++++-- .../tests/Get-VSTeamUserEntitlement.Tests.ps1 | 101 ++++++++++--- 4 files changed, 224 insertions(+), 40 deletions(-) diff --git a/.docs/Get-VSTeamUserEntitlement.md b/.docs/Get-VSTeamUserEntitlement.md index 0ff92657b..561e25b94 100644 --- a/.docs/Get-VSTeamUserEntitlement.md +++ b/.docs/Get-VSTeamUserEntitlement.md @@ -117,6 +117,10 @@ Equality operators relating to searching user entitlements seperated by and clau A valid query could be: (licenseId eq 'Account-Stakeholder' or (licenseId eq 'Account-Express' and licenseStatus eq 'Disabled')) and name eq 'test' and userType eq 'guest'. +Currently, filter names and values must match exactly the case. i.e.: +* LicenseID will throw Invalid filter message. +* licenseId eq 'account-stakeholder' will return an empty list + ```yaml Type: string Parameter Sets: PagedFilter @@ -129,11 +133,12 @@ Default value: None Filters based on license assignment using license names The acceptable values for this parameter are: -- Stakeholder: Stakeholder -- Express: Basic -- Advanced: Basic + Test Plans +- Account-Stakeholder: Stakeholder +- Account-Express: Basic +- Account-Advanced: Basic + Test Plans Other licenses which source (licenseSource) is MSDN cannot be filtered here +Parameter values are case sensitive ```yaml Type: string @@ -147,8 +152,10 @@ Default value: None Filters based on user type The acceptable values for this parameter are: -- Member -- Guest +- member +- guest + +Parameter values are case sensitive ```yaml Type: string diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index 0df28211e..a4791aeee 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -175,9 +175,19 @@ function _supportsSecurityNamespace { } function _supportsMemberEntitlementManagement { + [CmdletBinding()] + param( + [string]$expression = $null + ) _hasAccount - if (-not $(_getApiVersion MemberEntitlementManagement)) { + $apiVer = _getApiVersion MemberEntitlementManagement + if (-not $apiVer) { throw 'This account does not support Member Entitlement.' + } elseif ($null -ne $expression) { + $isMatch = Invoke-Expression "'$apiVer' $expression" + if (-not $isMatch) { + throw "EntitlementManagemen version must match $expression for this call, current value $apiVer" + } } } diff --git a/Source/Public/Get-VSTeamUserEntitlement.ps1 b/Source/Public/Get-VSTeamUserEntitlement.ps1 index 735a0f795..9e69bac77 100644 --- a/Source/Public/Get-VSTeamUserEntitlement.ps1 +++ b/Source/Public/Get-VSTeamUserEntitlement.ps1 @@ -9,22 +9,58 @@ function Get-VSTeamUserEntitlement { [int] $Skip = 0, [Parameter(ParameterSetName = 'List')] + [Parameter(ParameterSetName = 'PagedFilter')] + [Parameter(ParameterSetName = 'PagedParams')] [ValidateSet('Projects', 'Extensions', 'Grouprules')] [string[]] $Select, [Parameter(ParameterSetName = 'ByID')] [Alias('UserId')] - [string[]] $Id + [string[]] $Id, + + [Parameter(ParameterSetName = 'PagedFilter')] + [Parameter(ParameterSetName = 'PagedParams')] + [int] $MaxPages = 0, + + [Parameter(ParameterSetName = 'PagedFilter')] + [string] $Filter, + + [Parameter(ParameterSetName = 'PagedParams')] + [ValidateSet('Account-Stakeholder', 'Account-Express', 'Account-Advanced', IgnoreCase = $false)] + [Alias('License')] + [string] $LicenseId, #license name is case sensitive + + [Parameter(ParameterSetName = 'PagedParams')] + [ValidateSet('guest', 'member', IgnoreCase = $false)] #userType is case sensitive: values in lowercase + [string] $UserType, + + [Parameter(ParameterSetName = 'PagedParams')] + [Alias('UserName')] + [Alias('Mail')] + [string] $Name ) + process { - # This will throw if this account does not support MemberEntitlementManagement - _supportsMemberEntitlementManagement + # This will throw if this account does not support MemberEntitlementManagement + # or supported version is not correct with the type of API call + $paramCounter = _countParameters -BoundParameters $PSBoundParameters + $paramset = 'PagedParams', 'PagedFilter' + if ($paramCounter -eq 0) { + $memberEntitlementManagementRequirement = $null + } elseif ($paramset -contains $PSCmdlet.ParameterSetName) { + $memberEntitlementManagementRequirement = "-ge '6.0'" + } else { + $memberEntitlementManagementRequirement = "-le '5.1'" + } + _supportsMemberEntitlementManagement $memberEntitlementManagementRequirement + + $apiVersion = _getApiVersion MemberEntitlementManagement $commonArgs = @{ subDomain = 'vsaex' resource = 'userentitlements' - version = $(_getApiVersion MemberEntitlementManagement) + version = $apiVersion } if ($Id) { @@ -37,23 +73,91 @@ function Get-VSTeamUserEntitlement { } } else { - # Build the url to list the teams + # use the appropiate syntax depending on the API version + $useContinuationToken = $false + if ($paramCounter -eq 0) { + if ($apiVersion -gt '6.0') { + $useContinuationToken = $true + } + } else { + if ($paramset -contains $PSCmdlet.ParameterSetName) { + $useContinuationToken = $true + } + } + $listurl = _buildRequestURI @commonArgs + $objs = @() + Write-Verbose "Use continuation token: $useContinuationToken" + if ($useContinuationToken) { + if ($psCmdLet.ParameterSetName -eq 'PagedParams') { + #parameter names must be lowercase, parameter values depends on the parameter + if ($name) { + $filter += "name eq '$name' and " + } + if ($LicenseId) { - $listurl += _appendQueryString -name "top" -value $top -retainZero - $listurl += _appendQueryString -name "skip" -value $skip -retainZero - $listurl += _appendQueryString -name "select" -value ($select -join ",") + $filter += "licenseId eq '$LicenseId' and " + } + if ($UserType) { + $filter += "userType eq '$UserType' and " + } + $filter = $filter.SubString(0, $filter.Length - 5) + } + $listurl += _appendQueryString -name "`$filter" -value $filter + $listurl += _appendQueryString -name "select" -value ($select -join ",") - # Call the REST API - $resp = _callAPI -url $listurl + # Call the REST API + if ($MaxPages -le 0){ + $MaxPages = [int32]::MaxValue + } + $i = 0 + $apiParameters = $listurl + Write-Verbose "API params: $listurl" + do { + $resp = _callAPI -url $apiParameters + $continuationToken = $resp.continuationToken + $i++ + Write-Verbose "page $i" + foreach ($item in $resp.members) { + $objs += [vsteam_lib.UserEntitlement]::new($item) + } + if (-not [String]::IsNullOrEmpty($continuationToken)) { + $continuationToken = [uri]::EscapeDataString($continuationToken) + $apiParameters = "${listurl}&continuationToken=$continuationToken" + } + } while (-not [String]::IsNullOrEmpty($continuationToken) -and $i -lt $MaxPages) - $objs = @() + } else { + $listurl += _appendQueryString -name "top" -value $top -retainZero + $listurl += _appendQueryString -name "skip" -value $skip -retainZero + $listurl += _appendQueryString -name "select" -value ($select -join ",") - foreach ($item in $resp.members) { - $objs += [vsteam_lib.UserEntitlement]::new($item) - } + # Call the REST API + Write-Verbose "API params: $listurl" + $resp = _callAPI -url $listurl + foreach ($item in $resp.members) { + $objs += [vsteam_lib.UserEntitlement]::new($item) + } + } Write-Output $objs + + } + } +} + + +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++ } } -} \ No newline at end of file + Write-Verbose "Found $counter parameters" + $counter +} diff --git a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 index 66bdae0ad..ac6e598c4 100644 --- a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 +++ b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 @@ -32,7 +32,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { BeforeAll { Mock _getApiVersion { return 'VSTS' } Mock _getInstance { return 'https://dev.azure.com/test' } - Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement-Id.json' } -ParameterFilter { @@ -40,35 +40,98 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { } } - It 'no parameters should return users' { - $users = Get-VSTeamUserEntitlement + Context 'Get-VSTeamUserEntitlement_UpToVersion6.0' { + BeforeAll { + Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + } - $users.count | Should -Be 3 - $users[0].UserName | Should -Be 'Math lastName' + It 'no parameters should return users' { + $users = Get-VSTeamUserEntitlement + + $users.count | Should -Be 3 + $users[0].UserName | Should -Be 'Math lastName' + + # Make sure it was called with the correct URI + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0" + } + } + + It 'by Id should return users with projects' { + $user = Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' + + $user.Email | Should -Be 'test@test.com' -Because 'email is from type' + $user.userName | Should -Be 'Donovan Brown' -Because 'userName is from type' + + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements/00000000-0000-0000-0000-000000000000?api-version=$(_getApiVersion MemberEntitlementManagement)" + } + } - # Make sure it was called with the correct URI - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0" + It 'with select for projects should return users with projects' { + Get-VSTeamUserEntitlement -Select Projects + + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0&select=Projects" + } } + + It 'should throw with paged parameterset and version below 6.0 ' { + { Get-VSTeamUserEntitlement -Name 'Math' } | Should -Throw + } + } - It 'by Id should return users with projects' { - $user = Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' + Context 'Get-VSTeamUserEntitlement_Version6.0_Onwards' { + BeforeAll { + Mock _getApiVersion { return '6.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } + # Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { + # $Uri -like "*filter=*" + # } + } - $user.Email | Should -Be 'test@test.com' -Because 'email is from type' - $user.userName | Should -Be 'Donovan Brown' -Because 'userName is from type' + It 'should throw by ID and version 6.0 onwards' { + { Get-VSTeamUserEntitlement -Id '00000000-0000-0000-0000-000000000000' } | Should -Throw + } + + It "with incorrect case in license parameter should throw" { + { Get-VSTeamUserEntitlement -License account-Advanced} | Should -Throw + } - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements/00000000-0000-0000-0000-000000000000?api-version=$(_getApiVersion MemberEntitlementManagement)" + It "with incorrect case in usertype parameter should throw" { + { Get-VSTeamUserEntitlement -UserType Member} | Should -Throw + } + + It 'no parameters should return users' { + $users = Get-VSTeamUserEntitlement + + $users.count | Should -Be 3 + $users[0].UserName | Should -Be 'Math lastName' + + # Make sure it was called with the correct URI + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)" + } + } + + It 'by filter should return users that match that filter' { + Get-VSTeamUserEntitlement -Filter "name eq 'Math'" + + # Make sure it was called with the correct URI parameters + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&filter=name eq 'Math'" + } } - } - It 'with select for projects should return users with projects' { - Get-VSTeamUserEntitlement -Select Projects + It 'by name should translate to filter' { + Get-VSTeamUserEntitlement -Name "Math" -License Account-Advanced -UserType member - Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&top=100&skip=0&select=Projects" + # Make sure it was called with the correct URI parameters. Filter parameter names are case sensitive + Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&filter=name eq 'Math' and licenseId eq 'Account-Advanced' and userType eq 'member'" + } } + } } } From 8ee2a5676e976f6cc740d86522c88fbdd29b2c41 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Thu, 3 Mar 2022 20:55:16 +0100 Subject: [PATCH 03/12] Fix VSTeamUserEntitlement test using filter parameter --- Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 index ac6e598c4..8f0d30eed 100644 --- a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 +++ b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 @@ -119,7 +119,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { # Make sure it was called with the correct URI parameters Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&filter=name eq 'Math'" + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&`$filter=name eq 'Math'" } } @@ -128,7 +128,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { # Make sure it was called with the correct URI parameters. Filter parameter names are case sensitive Should -Invoke Invoke-RestMethod -Exactly -Times 1 -Scope It -ParameterFilter { - $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&filter=name eq 'Math' and licenseId eq 'Account-Advanced' and userType eq 'member'" + $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&`$filter=name eq 'Math' and licenseId eq 'Account-Advanced' and userType eq 'member'" } } From 1434d1166a83b4f097c68eab83c0ffd9d1d8ef64 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Fri, 4 Mar 2022 23:23:45 +0100 Subject: [PATCH 04/12] Avoid analysis code error with Invoke-Expression --- Source/Private/common.ps1 | 17 ++++++++++------- Source/Public/Get-VSTeamUserEntitlement.ps1 | 9 +++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index 208ca5157..5db53d667 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -175,19 +175,22 @@ function _supportsSecurityNamespace { } function _supportsMemberEntitlementManagement { - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName="upto")] param( - [string]$expression = $null + [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 ($null -ne $expression) { - $isMatch = Invoke-Expression "'$apiVer' $expression" - if (-not $isMatch) { - throw "EntitlementManagemen version must match $expression for this call, current value $apiVer" - } + } 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" } } diff --git a/Source/Public/Get-VSTeamUserEntitlement.ps1 b/Source/Public/Get-VSTeamUserEntitlement.ps1 index 9e69bac77..2c386f7e5 100644 --- a/Source/Public/Get-VSTeamUserEntitlement.ps1 +++ b/Source/Public/Get-VSTeamUserEntitlement.ps1 @@ -47,13 +47,12 @@ function Get-VSTeamUserEntitlement { $paramCounter = _countParameters -BoundParameters $PSBoundParameters $paramset = 'PagedParams', 'PagedFilter' if ($paramCounter -eq 0) { - $memberEntitlementManagementRequirement = $null + _supportsMemberEntitlementManagement } elseif ($paramset -contains $PSCmdlet.ParameterSetName) { - $memberEntitlementManagementRequirement = "-ge '6.0'" + _supportsMemberEntitlementManagement -Onwards '6.0' } else { - $memberEntitlementManagementRequirement = "-le '5.1'" + _supportsMemberEntitlementManagement -UpTo '5.1' } - _supportsMemberEntitlementManagement $memberEntitlementManagementRequirement $apiVersion = _getApiVersion MemberEntitlementManagement @@ -74,6 +73,8 @@ function Get-VSTeamUserEntitlement { } else { # use the appropiate syntax depending on the API version + $useContinuationToken = ($paramCounter -eq 0 -and $apiVersion -gt '6.0') -or ($paramset -contains $PSCmdlet.ParameterSetName) + $useContinuationToken = $false if ($paramCounter -eq 0) { if ($apiVersion -gt '6.0') { From c66d5d1d5a26d71ccc56d7551ea9d1ac691043b3 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sat, 5 Mar 2022 18:57:50 +0100 Subject: [PATCH 05/12] Fix error in help file --- .docs/Get-VSTeamUserEntitlement.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.docs/Get-VSTeamUserEntitlement.md b/.docs/Get-VSTeamUserEntitlement.md index bb334f87f..989496de4 100644 --- a/.docs/Get-VSTeamUserEntitlement.md +++ b/.docs/Get-VSTeamUserEntitlement.md @@ -173,7 +173,6 @@ Type: string Parameter Sets: PagedParams Required: False Default value: None -Alias: Mail ``` From 79d9801102d47e837f4992b664f52f0ba0f149a3 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sat, 5 Mar 2022 20:39:17 +0100 Subject: [PATCH 06/12] Test continuationToken usage --- ...TeamUserEntitlement-ContinuationToken.json | 288 ++++++++++++++++++ .../tests/Get-VSTeamUserEntitlement.Tests.ps1 | 16 +- 2 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json diff --git a/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json b/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json new file mode 100644 index 000000000..256a4185a --- /dev/null +++ b/Tests/SampleFiles/Get-VSTeamUserEntitlement-ContinuationToken.json @@ -0,0 +1,288 @@ +{ + "members": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "mlastName@test.com", + "mailAddress": "mlastName@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Math lastName", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T06:43:29.0769722Z", + "dateCreated": "2020-02-25T05:27:12.1277176Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "guest", + "directoryAlias": "dlbm3_test.com#EXT#", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "dlbm3@test.com", + "mailAddress": "dlbm3@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "pending", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "0001-01-01T00:00:00Z", + "dateCreated": "2020-09-07T18:29:19.290717Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "directoryAlias": "test", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "test@test.com", + "mailAddress": "test@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T15:38:08.1632123Z", + "dateCreated": "2017-12-24T16:41:16.743Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + } + ], + "continuationToken": "+RID:~I7MHAPrCvZ7TpEEAAAAAAA==#RT:1#TRC:100#ISV:2#IEO:65551#QCF:4#FPC:AggGAQAAAAAAAAIBAAAAJAAABgEAAAAAAAACANOkCQEAAAAAAAACAN6KFQEAAAAAAAACAKew/QAAAAAkAAACACCS/wAAAAAkAAACADmfAQEAAAAkAAACACK1AgEAAAAkAAAGAGO4kQAKAA==", + "totalCount": 0, + "items": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "mlastName@test.com", + "mailAddress": "mlastName@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Math lastName", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T06:43:29.0769722Z", + "dateCreated": "2020-02-25T05:27:12.1277176Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "guest", + "directoryAlias": "dlbm3_test.com#EXT#", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "dlbm3@test.com", + "mailAddress": "dlbm3@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "pending", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "0001-01-01T00:00:00Z", + "dateCreated": "2020-09-07T18:29:19.290717Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "user": { + "subjectKind": "user", + "metaType": "member", + "directoryAlias": "test", + "domain": "00000000-0000-0000-0000-000000000000", + "principalName": "test@test.com", + "mailAddress": "test@test.com", + "origin": "aad", + "originId": "00000000-0000-0000-0000-000000000000", + "displayName": "Donovan Brown", + "_links": { + "self": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted" + }, + "memberships": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Memberships/aad.redacted" + }, + "membershipState": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/MembershipStates/aad.redacted" + }, + "storageKey": { + "href": "https://vssps.dev.azure.com/toolTester/_apis/Graph/StorageKeys/aad.redacted" + }, + "avatar": { + "href": "https://dev.azure.com/toolTester/_apis/GraphProfile/MemberAvatars/aad.redacted" + } + }, + "url": "https://vssps.dev.azure.com/toolTester/_apis/Graph/Users/aad.redacted", + "descriptor": "aad.redacted" + }, + "accessLevel": { + "licensingSource": "account", + "accountLicenseType": "earlyAdopter", + "msdnLicenseType": "none", + "licenseDisplayName": "Early Adopter", + "status": "active", + "statusMessage": "", + "assignmentSource": "unknown" + }, + "lastAccessedDate": "2020-09-09T15:38:08.1632123Z", + "dateCreated": "2017-12-24T16:41:16.743Z", + "projectEntitlements": [], + "extensions": [], + "groupAssignments": [] + } + ] +} diff --git a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 index 8f0d30eed..362e31345 100644 --- a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 +++ b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 @@ -85,9 +85,12 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { Context 'Get-VSTeamUserEntitlement_Version6.0_Onwards' { BeforeAll { Mock _getApiVersion { return '6.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } - # Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { - # $Uri -like "*filter=*" - # } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement-ContinuationToken.json' } -ParameterFilter { + $Uri -match "filter=userType eq 'guest'$" + } + Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { + $Uri -like "*filter=userType eq 'guest'&continuationToken=*" + } } It 'should throw by ID and version 6.0 onwards' { @@ -131,6 +134,13 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { $Uri -eq "https://vsaex.dev.azure.com/test/_apis/userentitlements?api-version=$(_getApiVersion MemberEntitlementManagement)&`$filter=name eq 'Math' and licenseId eq 'Account-Advanced' and userType eq 'member'" } } + + It 'with many matches continuationToken is used' { + $users = Get-VSTeamUserEntitlement -UserType guest + $users.Count | Should -Be 6 + + Should -Invoke Invoke-RestMethod -Exactly -Times 2 + } } } From ee3437726826ba74b277c8f19574e49d37179a3e Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sun, 13 Mar 2022 19:33:01 +0100 Subject: [PATCH 07/12] Create _callAPIContinuationToken common function --- Source/Private/common.ps1 | 44 +++++++++++++++++++++ Source/Public/Get-VSTeamUserEntitlement.ps1 | 34 ++-------------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index 59452a16e..68dc89b49 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -121,6 +121,50 @@ function _callAPI { } } + +function _callAPIContinuationToken { + [CmdletBinding()] + param( + [string]$Url, + [switch]$UseHeader, + [string]$ContinuationTokenName, + [string]$PropertyName, + [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 { diff --git a/Source/Public/Get-VSTeamUserEntitlement.ps1 b/Source/Public/Get-VSTeamUserEntitlement.ps1 index 2c386f7e5..b520000a8 100644 --- a/Source/Public/Get-VSTeamUserEntitlement.ps1 +++ b/Source/Public/Get-VSTeamUserEntitlement.ps1 @@ -75,17 +75,6 @@ function Get-VSTeamUserEntitlement { # use the appropiate syntax depending on the API version $useContinuationToken = ($paramCounter -eq 0 -and $apiVersion -gt '6.0') -or ($paramset -contains $PSCmdlet.ParameterSetName) - $useContinuationToken = $false - if ($paramCounter -eq 0) { - if ($apiVersion -gt '6.0') { - $useContinuationToken = $true - } - } else { - if ($paramset -contains $PSCmdlet.ParameterSetName) { - $useContinuationToken = $true - } - } - $listurl = _buildRequestURI @commonArgs $objs = @() Write-Verbose "Use continuation token: $useContinuationToken" @@ -108,26 +97,11 @@ function Get-VSTeamUserEntitlement { $listurl += _appendQueryString -name "select" -value ($select -join ",") # Call the REST API - if ($MaxPages -le 0){ - $MaxPages = [int32]::MaxValue - } - $i = 0 - $apiParameters = $listurl Write-Verbose "API params: $listurl" - do { - $resp = _callAPI -url $apiParameters - $continuationToken = $resp.continuationToken - $i++ - Write-Verbose "page $i" - foreach ($item in $resp.members) { - $objs += [vsteam_lib.UserEntitlement]::new($item) - } - if (-not [String]::IsNullOrEmpty($continuationToken)) { - $continuationToken = [uri]::EscapeDataString($continuationToken) - $apiParameters = "${listurl}&continuationToken=$continuationToken" - } - } while (-not [String]::IsNullOrEmpty($continuationToken) -and $i -lt $MaxPages) - + $items = _callAPIContinuationToken -Url $listurl -PropertyName "members" + foreach ($item in $items) { + $objs += [vsteam_lib.UserEntitlement]::new($item) + } } else { $listurl += _appendQueryString -name "top" -value $top -retainZero $listurl += _appendQueryString -name "skip" -value $skip -retainZero From c31ba8dd4a0378e5fb667a17404e46239e897608 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Mon, 14 Mar 2022 22:18:59 +0100 Subject: [PATCH 08/12] updated changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db0e484c0..add13cf8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 7.7.0 +Merged [Pull Request](https://github.com/MethodsAndPractices/vsteam/pull/459) from [Miguel Nieto](https://github.com/mnieto) the following: + +- Enable API version 6.0 calls in Get-VSTeamUserEntitlement. This add new parameters only available starting from version 6.0. + - Top and Skip parameters are still valid on versions up to 5.1. But will throw if called from version 6.0. This preservers backwards compability, but can be a breaking change if you do not pay attention to the API version in your scripts + - New parameters will throw if called from version 5.1 + - Function behaviour can be changed from Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version $yourVersion + ## 7.6.1 Merged [Pull Request](https://github.com/MethodsAndPractices/vsteam/pull/456) from [Sebastian Schütze](https://github.com/SebastianSchuetze) the following: From c371e534bac1f6420b2f4988aa0d0b6fd49fa0e4 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sun, 20 Mar 2022 22:16:48 +0100 Subject: [PATCH 09/12] doc and comments clarification --- .docs/Get-VSTeamUserEntitlement.md | 5 +++++ .docs/synopsis/Get-VSTeamUserEntitlement.md | 7 +------ Source/Private/common.ps1 | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.docs/Get-VSTeamUserEntitlement.md b/.docs/Get-VSTeamUserEntitlement.md index 989496de4..ca9305a11 100644 --- a/.docs/Get-VSTeamUserEntitlement.md +++ b/.docs/Get-VSTeamUserEntitlement.md @@ -6,6 +6,11 @@ +Please note that Filter, Name, UserType and License parameters only works when MemberEntitlementManagement module version is 6.0 or upper +In the same way Top and Skip paramerers only works up to version 5.1 + +You can setup the specific version for the MemberEntitlementManagement calling Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version VersionNumberYouNeed. + ## SYNTAX ## DESCRIPTION diff --git a/.docs/synopsis/Get-VSTeamUserEntitlement.md b/.docs/synopsis/Get-VSTeamUserEntitlement.md index d5ccc573d..325fefd65 100644 --- a/.docs/synopsis/Get-VSTeamUserEntitlement.md +++ b/.docs/synopsis/Get-VSTeamUserEntitlement.md @@ -1,6 +1 @@ -Get User Entitlement for a user, or a paged list of users matching the specified filter - -Please note that Filter, Name, UserType and License parameters only works when MemberEntitlementManagement module version is 6.0 or upper -In the same way Top and Skip paramerers only works up to version 5.1 - -You can setup the specific version for the MemberEntitlementManagement calling Set-VSTeamAPIVersion -Service MemberEntitlementManagement -Version VersionNumberYouNeed. \ No newline at end of file +Get User Entitlement for a user, or a paged list of users matching the specified filter \ No newline at end of file diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index 68dc89b49..51fe8fc72 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -122,13 +122,27 @@ function _callAPI { } +# 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 ) From 235e0da22f249249e60161d98e99070562f45278 Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sun, 20 Mar 2022 22:18:00 +0100 Subject: [PATCH 10/12] Add tests for _callAPIContinuationToken --- Tests/function/tests/common.Tests.ps1 | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Tests/function/tests/common.Tests.ps1 b/Tests/function/tests/common.Tests.ps1 index 1a778f552..1ff8db6b5 100644 --- a/Tests/function/tests/common.Tests.ps1 +++ b/Tests/function/tests/common.Tests.ps1 @@ -37,6 +37,33 @@ Describe 'Common' { } } + Context '_callAPIContinuationToken' { + BeforeAll { + Mock _callAPI { Open-SampleFile 'Get-VSTeamUserEntitlement-ContinuationToken.json' } -ParameterFilter { + $Url -match "filter=userType eq 'guest'$" + } + Mock _callAPI { Open-SampleFile 'Get-VSTeamUserEntitlement.json' } -ParameterFilter { + $Url -like "*filter=userType eq 'guest'&continuationToken=*" + } + } + + # TODO: To be removed when support to manage X-MS-ContinuationToken header is added and replace it with specific tests + It 'not supported should throw with UseHeader parameter' { + { _callAPIContinuationToken -UseHeader } | Should -Throw + } + + It 'When MaxPages has default value, all pages are returned' { + _callAPIContinuationToken -Url "https://vsaex.dev.azure.com/test/_apis/userentitlements?`$filter=userType eq 'guest'" -PropertyName 'members' + Should -Invoke _callAPI -Exactly -Times 2 + } + + It 'When number of pages are greater than MaxMages, only MaxPages are returned' { + _callAPIContinuationToken -Url "https://vsaex.dev.azure.com/test/_apis/userentitlements?`$filter=userType eq 'guest'" -PropertyName 'members' -MaxPages 1 + Should -Invoke _callAPI -Exactly -Times 1 + } + + } + Context '_getPermissionInheritanceInfo' { BeforeAll { Mock Get-VSTeamBuildDefinition { Open-SampleFile 'Get-BuildDefinition_AzD.json' -ReturnValue } From b0a5151475149b0fa2a43503301e4cc710b7a9cd Mon Sep 17 00:00:00 2001 From: Miguel Nieto Date: Sun, 20 Mar 2022 22:18:25 +0100 Subject: [PATCH 11/12] fix naming in context --- Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 index 362e31345..17ded083d 100644 --- a/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 +++ b/Tests/function/tests/Get-VSTeamUserEntitlement.Tests.ps1 @@ -40,7 +40,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { } } - Context 'Get-VSTeamUserEntitlement_UpToVersion6.0' { + Context 'Get-VSTeamUserEntitlement up to version 6.0' { BeforeAll { Mock _getApiVersion { return '1.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } } @@ -82,7 +82,7 @@ Describe "VSTeamUserEntitlement" -Tag 'VSTeamUserEntitlement' { } - Context 'Get-VSTeamUserEntitlement_Version6.0_Onwards' { + Context 'Get-VSTeamUserEntitlement version 6.0 onwards' { BeforeAll { Mock _getApiVersion { return '6.0-unitTests' } -ParameterFilter { $Service -eq 'MemberEntitlementManagement' } Mock Invoke-RestMethod { Open-SampleFile 'Get-VSTeamUserEntitlement-ContinuationToken.json' } -ParameterFilter { From b333c80f054aa8bc9b12235c25d4102251c5d081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20S=20Sch=C3=BCtze?= Date: Sat, 3 Sep 2022 17:25:50 +0200 Subject: [PATCH 12/12] moved _countParameters function to common.ps1 --- Source/Private/common.ps1 | 19 ++++++++++++++-- Source/Public/Get-VSTeamUserEntitlement.ps1 | 25 +++++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Private/common.ps1 b/Source/Private/common.ps1 index 51fe8fc72..59194165a 100644 --- a/Source/Private/common.ps1 +++ b/Source/Private/common.ps1 @@ -122,7 +122,7 @@ function _callAPI { } -# General function to manage API Calls that involve a paged response, +# 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 @@ -133,7 +133,7 @@ function _callAPIContinuationToken { [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 + # 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. @@ -1251,4 +1251,19 @@ function _checkForModuleUpdates { } } +} + +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 } \ No newline at end of file diff --git a/Source/Public/Get-VSTeamUserEntitlement.ps1 b/Source/Public/Get-VSTeamUserEntitlement.ps1 index b520000a8..d88212e86 100644 --- a/Source/Public/Get-VSTeamUserEntitlement.ps1 +++ b/Source/Public/Get-VSTeamUserEntitlement.ps1 @@ -28,10 +28,10 @@ function Get-VSTeamUserEntitlement { [Parameter(ParameterSetName = 'PagedParams')] [ValidateSet('Account-Stakeholder', 'Account-Express', 'Account-Advanced', IgnoreCase = $false)] [Alias('License')] - [string] $LicenseId, #license name is case sensitive + [string] $LicenseId, [Parameter(ParameterSetName = 'PagedParams')] - [ValidateSet('guest', 'member', IgnoreCase = $false)] #userType is case sensitive: values in lowercase + [ValidateSet('guest', 'member', IgnoreCase = $false)] [string] $UserType, [Parameter(ParameterSetName = 'PagedParams')] @@ -42,9 +42,10 @@ function Get-VSTeamUserEntitlement { process { - # This will throw if this account does not support MemberEntitlementManagement + # This will throw if this account does not support MemberEntitlementManagement # or supported version is not correct with the type of API call $paramCounter = _countParameters -BoundParameters $PSBoundParameters + $paramset = 'PagedParams', 'PagedFilter' if ($paramCounter -eq 0) { _supportsMemberEntitlementManagement @@ -86,7 +87,7 @@ function Get-VSTeamUserEntitlement { } if ($LicenseId) { - $filter += "licenseId eq '$LicenseId' and " + $filter += "licenseId eq '$LicenseId' and " } if ($UserType) { $filter += "userType eq '$UserType' and " @@ -120,19 +121,3 @@ function Get-VSTeamUserEntitlement { } } } - - -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 -}