Is there a way to filter issues in GitLab by whether or not they have a related merge request? - gitlab

I have a number of issues assigned to me to work on, and I've made merge requests to fix many of them. I am looking for a query that would let me filter out issues with a related merge request.
The GitLab instance I'm using is at v14.4.2.
I've looked in the docs for both basic search and advanced search, but can't figure out a good query for this.
The equivalent query in GitHub would be is:issue is:open assignee:legowerewolf -linked:pr

As of writing, that's not possible.
Generally, if a feature is not listed in the docs, then it hasn't been implemented. You can search for and existing feature request in the GitLab issue tracker (though I didn't find one) or file a feature request.

As per #Arty-chan above, there's no built-in way to do this. After futzing around in PowerShell and with the API for a bit, I came up with this:
$GitlabToken = '[your GitLab PAT with API read access]'
$APIRoot = 'https://[your GitLab server here]/api/v4'
$TokenHeader = #{
"PRIVATE-TOKEN" = $GitlabToken
}
$User = Invoke-RestMethod -Method Get -Uri "$APIRoot/user" -Headers $TokenHeader
$UserOpenIssues = Invoke-RestMethod -Method Get -Uri "$APIRoot/issues" -Headers $TokenHeader -Body #{
assignee_id = $User.id
state = "opened"
scope = "all"
}
foreach ($Issue in $UserOpenIssues) {
$Issue | Add-Member -MemberType NoteProperty -Name "related_merge_requests" -Value (
Invoke-RestMethod -Method Get -Uri "$APIRoot/projects/$($Issue.project_id)/issues/$($Issue.iid)/related_merge_requests" -Headers $TokenHeader | Where-Object { $_.state -eq "opened" }
)
}
$UserOpenIssues = $UserOpenIssues | Sort-Object -Property #{Expression = { $_.labels[0] } }
Write-Host "Open issues without a merge request assigned to $($User.name):"
$UserOpenIssues
| Where-Object { $_.related_merge_requests.Count -eq 0 }
| Format-Table -Property #(
#{name = "Reference"; expression = { $_.references.full } }
#{name = "Title"; expression = { $_.title } }
#{name = "Labels"; expression = { $_.labels } }
)

Related

how to list all NPM package and its version available in azure feed?

I have requested to list out all the NPM packages available in the Azure Artifact location.
Able to list from project scoped feed and unable to list NPM packages from Organization scoped feeds. Please help
As per this MSDoc - Get Packages
To list out all the packages available in the Azure Artifact location, use the below REST API
GET https://feeds.dev.azure.com/{organization}/{project}/_apis/packaging/Feeds/{feedId}/packages?api-version=6.0-preview.1
As per this MSDoc - Get Package Versions
To get the list of Package Versions, use the below API
GET https://feeds.dev.azure.com/{organization}/{project}/_apis/packaging/Feeds/{feedId}/Packages/{packageId}/versions/{packageVersionId}?api-version=6.0-preview.1
To get the list of npm packages Version, use
GET https://pkgs.dev.azure.com/{organization}/{project}/_apis/packaging/feeds/{feedId}/npm/{packageName}/versions/{packageVersion}?api-version=6.0-preview.1
Please refer Artifacts - Npm for more information
Below PowerShell script helped me to pull the list using PAT.
function GetUrl() {
param(
[string]$orgUrl,
[hashtable]$header,
[string]$AreaId
)
# Area ids
# https://learn.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http&viewFallbackFrom=vsts#resource-area-ids-reference
# Build the URL for calling the org-level Resource Areas REST API for the RM APIs
$orgResourceAreasUrl = [string]::Format("{0}/_apis/resourceAreas/{1}?api-preview=5.0-preview.1", $orgUrl, $AreaId)
# Do a GET on this URL (this returns an object with a "locationUrl" field)
$results = Invoke-RestMethod -Uri $orgResourceAreasUrl -Headers $header
# The "locationUrl" field reflects the correct base URL for RM REST API calls
if ("null" -eq $results) {
$areaUrl = $orgUrl
}
else {
$areaUrl = $results.locationUrl
}
return $areaUrl
}
$orgUrl = "https://dev.azure.com/myorg"
$personalToken = "mytoken"
Write-Host "Initialize authentication context" -ForegroundColor Yellow
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($personalToken)"))
$header = #{authorization = "Basic $token"}
DEMO 1 List of projects
Write-Host "Project List"
#packaging ID refer https://learn.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http&viewFallbackFrom=vsts
$coreAreaId = "7ab4e64e-c4d8-4f50-ae73-5ef2e21642a5"
$tfsBaseUrl = GetUrl -orgUrl $orgUrl -header $header -AreaId $coreAreaId
https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=azure-devops-rest-5.1
project name should be specified for project based feed
#$projectsUrl = "$($tfsBaseUrl)Project/_apis/packaging/Feeds/{FeedName}/packages?api-version=6.0-preview.1"
#$projectsUrl = "$($tfsBaseUrl)/_apis/packaging/Feeds/platform-template/packages?includeAllVersions=True&api-version=6.0-preview.1"
No project name should be specified for Organization based feed
$projectsUrl = "$($tfsBaseUrl)/_apis/packaging/Feeds/{FeedName}/packages?api-version=6.0-preview.1"
$projects = Invoke-RestMethod -Uri $projectsUrl -Method Get -ContentType "application/json" -Headers $header
$projects.value | ForEach-Object {
write-output "Package Name:" $.name | Out-File "D:\File.txt" -Append
$projects2Url = "https://feeds.dev.azure.com/pdidev/apis/packaging/Feeds/{FeedName}/Packages/$($.id)/versions?api-version=6.0-preview.1"
$project2s = Invoke-RestMethod -Uri $projects2Url -Method Get -ContentType "application/json" -Headers $header
$project2s.value | ForEach-Object {
write-output $.version `n| Out-File "D:\file.txt" -Append
}
}

Can we add label to tiles on Azure Dashboard [duplicate]

Is it possible to show custom information in azure dashboards?
I was searching on how to add custom content in azure dashboards but did not find anything. The only thing close was the markdown tile which allows html to be displayed.
With this in mind and after a lot of digging I found a solution:
Basically we needed a custom tile that displays data retrieved from our REST api.
1. Create a new, empty 'Markdown' tile on a new or existing dashboard, give it a 'Title'
2. Share the dashboard
3. Navigate to All services, Filter by 'dashboards' in the ResourceGroup filter
- Click on the dashboard which contains the 'Markdown' tile
- Take a note of the 'RESOURCE ID'
In our scenario, we used Azure Automation Runbooks. In this scenario we utilized the Azure Resource Manager REST api.
4. Create a new RunBook [Powershell Runbook]
The following steps concern the following:
- Login to the ResourceManagerAPI
- Get Azure Resource by ID [The Resource ID above]
- Update Azure Resource by ID [The Resource ID above]
Before we continue, we need to get our client credentials. To do so:
- Click on Cloud Shell in the Portal Menu bar
- Type 'az'
- Type 'az ad sp create-for-rpac -n "runbooks"' //runbooks is just a name, feel free to input a different string
- The above command should list out the credentials needed. If an error occurs, kindly contact your Azure admin and run it from their account.
5. In your empty powershell runbook, add the following 2 variables:
$ExpectedTileName = "Extension/HubsExtension/PartType/MarkdownPart"
$MarkdownTileTitle = "<The Markdown title you've set in the first step>"
6. Getting the Access_Token [The variables <> represent the values retrieved from the previous step]
#Get Bearer Token
$TenantId = '<Your tenantID>'
$LoginUri = "https://login.microsoftonline.com/"+$TenantId+"/oauth2/token"
$params = #{
"grant_type"="client_credentials";
"client_id"="<appId>";
"client_secret"="<password>";
"resource"="https://management.azure.com";
}
$LoginResponse = Invoke-RestMethod -Uri $LoginUri -Method Post -Body $params
$Access_Token = $LoginResponse.access_token;
$Access_TokenString = "Bearer " + $Access_Token
7. Getting the DashboardResource by ResourceID:
#Get Resource
$RMUri = "https://management.azure.com/"+ $DashboardId +"?api-version=2015-08-01-preview"
$DashboardResource = (Invoke-RestMethod -Uri $RMUri -Method Get -Headers #{'Authorization'=$Access_TokenString}) | ConvertTo-Json -Depth 100 | ConvertFrom-Json
8. Looping through all tiles within the dashboard. Please note that tiles are not contained within an array, thus you may need to increase/decrease the length of the for loop.
#Loop through all tiles within the dashboard
$Parts = $DashboardResource.properties.lenses.0.0.parts
For ($i=0; $i -lt 200; $i++)
{
$Part = $Parts | Select-Object -Property $i.toString()
if($Part.$i)
{
if($Part.$i.metadata.type -eq $ExpectedTileName -And $Part.$i.metadata.settings.content.settings.title -eq $MarkdownTileTitle)
{
$Part.$i.metadata.settings.content.settings.content = <CustomValue ex: invoke a get request to your api>
}
}
else
{
break
}
}
9. Finally we need to update the dashboard resource
#Update Resource
$UpdateUri = "https://management.azure.com/"+ $DashboardId +"?api-version=2015-08-01-preview"
$JsonValue = $DashboardResource | ConvertTo-Json -Depth 100
Invoke-RestMethod -Uri $UpdateUri -Method Put -Headers #{'Authorization'=$Access_TokenString; 'Content-type'='application/json'} -Body $JsonValue
To sum it up:
$ExpectedTileName = "Extension/HubsExtension/PartType/MarkdownPart"
$MarkdownTileTitle = "<The Markdown title you've set in the first step>"
#Get Bearer Token
$TenantId = '<Your subscriptionID>'
$LoginUri = "https://login.microsoftonline.com/"+$TenantId+"/oauth2/token"
$params = #{
"grant_type"="client_credentials";
"client_id"="<appId>";
"client_secret"="<password>";
"resource"="https://management.azure.com";
}
$LoginResponse = Invoke-RestMethod -Uri $LoginUri -Method Post -Body $params
$Access_Token = $LoginResponse.access_token;
$Access_TokenString = "Bearer " + $Access_Token
#Get Resource
$RMUri = "https://management.azure.com/"+ $DashboardId +"?api-version=2015-08-01-preview"
$DashboardResource = (Invoke-RestMethod -Uri $RMUri -Method Get -Headers #{'Authorization'=$Access_TokenString}) | ConvertTo-Json -Depth 100 | ConvertFrom-Json
#Loop through all tiles within the dashboard
$Parts = $DashboardResource.properties.lenses.0.0.parts
For ($i=0; $i -lt 200; $i++)
{
$Part = $Parts | Select-Object -Property $i.toString()
if($Part.$i)
{
if($Part.$i.metadata.type -eq $ExpectedTileName -And $Part.$i.metadata.settings.content.settings.title -eq $MarkdownTileTitle)
{
$Part.$i.metadata.settings.content.settings.content = <CustomValue ex: invoke a get request to your api>
}
}
else
{
break
}
}
#Update Resource
$UpdateUri = "https://management.azure.com/"+ $DashboardId +"?api-version=2015-08-01-preview"
$JsonValue = $DashboardResource | ConvertTo-Json -Depth 100
Invoke-RestMethod -Uri $UpdateUri -Method Put -Headers #{'Authorization'=$Access_TokenString; 'Content-type'='application/json'} -Body $JsonValue
Conclusion
With this newly created runbook we can now schedule it to run every 1 hour. In our case, 1 hour was too much. The following article shows how we can schedule the runbook to run every 1 minute.
https://blogs.technet.microsoft.com/stefan_stranger/2017/06/21/azure-scheduler-schedule-your-runbooks-more-often-than-every-hour/

Assigning Intune scope tags to Azure Azure AD group

I cannot figure this on out. I need a way to assign Endpoint Manager's Scope tags to an Azure AD group using Microsoft Graph and PowerShell.
Under the portal this is done under Endpoint Manager\Tenant Administration\Roles\Scope (Tags). Then clicking on the Tag and tgo to assignments and browse to Azure AD group.
Since its under Roles, I'm assuming it falls under the roleAssignment or roleScopeTag resource types?
I have thoroughly read all documentation for the REST api and I have also attempted to do this via Microsoft.Graph.Intune modules but still cannot find a suitable cmdlet that will do this. Am I missing something?
Here is the current code I have built following this document
FIRST let's assume I have the correct tag id and azure ad group object id.
$ScopeTagId = 2
$TargetGroupIds = #()
$TargetGroupIds += '687c08f1-e78f-4506-b4a6-dfe35a05d138'
$graphApiVersion = "beta"
$Resource = "deviceManagement/roleScopeTags"
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name 'assignments' -Value #($TargetGroupIds)
$JSON = $object | ConvertTo-Json
$uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)/$ScopeTagId/assign"
Invoke-RestMethod -Method Post -Uri $uri -Headers $global:authToken -Body $JSON
No success. I then thought that since the create roleScopeTag API doesn't have an assignment property in the request body, this must be done using the update method, but that doesn't have it in there either. The only one I read was to use the assign action and in the documentation example it shows the roleScopeTagAutoAssignment URI, so I went down that rabbit hole:
$ScopeTagId = 2
$TargetGroupIds = #()
$TargetGroupIds += '687c08f1-e78f-4506-b4a6-dfe35a05d138'
$graphApiVersion = "beta"
$Resource = "deviceManagement/roleScopeTags"
$AutoTagObject = #()
foreach ($TargetGroupId in $TargetGroupIds) {
#Build custom object for assignment
$AssignmentProperties = "" | Select '#odata.type',id,target
$AssignmentProperties.'#odata.type' = '#microsoft.graph.roleScopeTagAutoAssignment'
$AssignmentProperties.id = $TargetGroupId
#Build custom object for target
$targetProperties = "" | Select "#odata.type",deviceAndAppManagementAssignmentFilterId,deviceAndAppManagementAssignmentFilterType
$targetProperties."#odata.type" = "microsoft.graph.deviceAndAppManagementAssignmentTarget"
$targetProperties.deviceAndAppManagementAssignmentFilterId = $TargetGroupId
$targetProperties.deviceAndAppManagementAssignmentFilterType = 'include'
#add target object to assignment
$AssignmentProperties.target = $targetProperties
$AutoTagObject += $AssignmentProperties
}
#build body object
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name 'assignments' -Value #($AutoTagObject)
$JSON = $object | ConvertTo-Json
$uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)/$ScopeTagId/assign"
Invoke-RestMethod -Method Post -Uri $uri -Headers $global:authToken -Body $JSON
The error I get back is something like: "Property target in payload has a value that does not match schema.","innerError":{"date":"2022-01-27T16:28:34","request-id":"75626c4d-f09b-438e-8b0f-0b2928ac23ce","client-request-id":"75626c4d-f09b-438e-8b0f-0b2928ac23ce" which I assume is the odata.type object i'm calling "microsoft.graph.deviceAndAppManagementAssignmentTarget"
There is a second post method in the documentations via roledefinition URI which seems like an unnecessary step, but I tried that too with no success.
I do not know if any of this is correct. I understand the API calls for others pretty well and I have been able to successfully add Tags for Custom Roles and their assignments using graph; I just can't seem to find the right combination of URI and JSON body for scope tags themselves...if it even exists. :(
Any ideas, please share some code snippets if you can. THANKS!
I found the correct URI and request body
If can be generated like this:
$ScopeTagId = 2
$TargetGroupIds = #()
$TargetGroupIds += '687c08f1-e78f-4506-b4a6-dfe35a05d138'
$graphApiVersion = "beta"
$Resource = "deviceManagement/roleScopeTags"
$AutoTagObject = #()
#TEST $TargetGroupId = $TargetGroupIds[0]
foreach ($TargetGroupId in $TargetGroupIds) {
#Build custom object for assignment
$AssignmentProperties = "" | Select id,target
$AssignmentProperties.id = ($TargetGroupId + '_' + $ScopeTagId)
#Build custom object for target
$targetProperties = "" | Select "#odata.type",deviceAndAppManagementAssignmentFilterId,deviceAndAppManagementAssignmentFilterType,groupId
$targetProperties."#odata.type" = "microsoft.graph.groupAssignmentTarget"
$targetProperties.deviceAndAppManagementAssignmentFilterId = $null
$targetProperties.deviceAndAppManagementAssignmentFilterType = 'none'
$targetProperties.groupId = $TargetGroupId
#add target object to assignment
$AssignmentProperties.target = $targetProperties
$AutoTagObject += $AssignmentProperties
}
#build body object
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name 'assignments' -Value #($AutoTagObject)
$JSON = $object | ConvertTo-Json -Depth 10
$uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)/$ScopeTagId/assign"
Invoke-RestMethod -Method Post -Uri $uri -Headers $global:authToken -Body $JSON -ErrorAction Stop
The Json request body would look like this:
{
"assignments": [
{
"id": "b25c80e3-78cc-4b7c-888e-fc50dcc6b582_2",
"target": {
"#odata.type": "microsoft.graph.groupAssignmentTarget",
"deviceAndAppManagementAssignmentFilterId": null,
"deviceAndAppManagementAssignmentFilterType": "none",
"groupId": "b25c80e3-78cc-4b7c-888e-fc50dcc6b582"
}
}
]
}
Nowhere is this documented...

How to make Invoke-RestMethod GET and PUT requests to Azure table storage using SAS key

This is a two-part question. I am in the process of automating tasks that a) require information from my Azure table and b) need to update specific entities in my Azure table. I've currently been able to accomplish this by using either of the 2 provided access keys but think this is an unsafe practice and want to define individual policies for different groups and so want to transition into using generated SAS keys.
a) I can currently use SAS policies to retrieve the whole table and find the information I need but I think a better method is to perform an individual query that only pulls the single entity that matches a specific property I'm looking for (e.g. pull all properties of an entity that matches a customer ID: "000000001"). How can I change my code to accomplish this?
$tableName = "accountTD"
$sasReadToken = '<SAS token here>'
$tableUri = "https://$storageAccount.table.core.windows.net/$tableName$sasReadToken"
$GMTTime = (Get-Date).ToUniversalTime().toString('R')
$header = #{
'x-ms-date' = $GMTTime;
Accept = 'application/json;odata=nometadata'
}
$finalResult = Invoke-WebRequest -Uri $tableUri -Headers $header -UseBasicParsing
$finalResult = $finalResult.Content | ConvertFrom-Json
$finalResult.value
b) I also need to update the same entity in the table and can't seem to figure out how to authorize it with my generated SAS key. I'm not sure whether to use Invoke-WebRequest or Invoke-RestMethod or how to go about either of them. Here's what I have so far based on my research.
function addUpdateEntity ($tableName, $PartitionKey, $RowKey, $entity){
$sasReadToken = '<SAS token here>'
$resource = "$tableName(PartitionKey='$PartitionKey',RowKey='$Rowkey')"
$tableUri = "https://$storageAccount.table.core.windows.net/$tableName$sasReadToken"
$GMTTime = (Get-Date).ToUniversalTime().toString('R')
$header = #{
'x-ms-date' = $GMTTime;
Accept = 'application/json;odata=nometadata'
}
$body = $entity | ConvertTo-Json
$item = Invoke-RestMethod -Method PUT -Uri $tableUri -Headers $headers -Body $body -ContentType application/json
}
$mBody = #{
PartitionKey = "MPS02000"
RowKey = "2019-000101"
appUpdateMode = "1"
m_CustID = "000000001"
}
addUpdateEntity -TableName "atdMachines" -PartitionKey $mBody.PartitionKey -RowKey $mBody.RowKey -entity $mBody
Q1. Pull all properties of an entity that matches a customer ID
Answer: You can use $filter query expression. For example, I have 2 entities in my testTable:
I can get the entity whose Id equals to 00001 by making a request as following:
GET https://storagetest789.table.core.windows.net/testTable?{sastoken}&$filter=(Id eq '00001')
$storageAccount = "storagetest789"
$tableName = "testTable"
$sasReadToken = "?sv=2019-02-02&ss=t&sr***************D"
$filter = "`$filter=(Id eq '00001')"
$tableUri = "https://$storageAccount.table.core.windows.net/$tableName$sasReadToken&$filter"
$GMTTime = (Get-Date).ToUniversalTime().toString('R')
$header = #{
'x-ms-date' = $GMTTime;
Accept = 'application/json;odata=nometadata'
}
$finalResult = Invoke-WebRequest -Uri $tableUri -Headers $header -UseBasicParsing
$finalResult = $finalResult.Content | ConvertFrom-Json
$finalResult.value
Result:
Q2. Update the same entity in the table
Answer: Both Invoke-WebRequest and Invoke-RestMethod are suitable for making a HTTP request here. I find some mistakes in your scripts, here is the fixed one:
function addUpdateEntity ($tableName, $PartitionKey, $RowKey, $entity){
$storageAccount = "storagetest789"
$tableName = "testTable"
# Need write access
$sasWriteToken = "?sv=2019-02-02&ss=t&s*****************************D"
$resource = "$tableName(PartitionKey='$PartitionKey',RowKey='$Rowkey')"
# should use $resource, not $tableNmae
$tableUri = "https://$storageAccount.table.core.windows.net/$resource$sasWriteToken"
# should be headers, because you use headers in Invoke-RestMethod
$headers = #{
Accept = 'application/json;odata=nometadata'
}
$body = $entity | ConvertTo-Json
$item = Invoke-RestMethod -Method PUT -Uri $tableUri -Headers $headers -Body $body -ContentType application/json
}
$mBody = #{
PartitionKey = "p1"
RowKey = "r1"
Id = "00001"
Value = "new value"
}
addUpdateEntity -TableName "atdMachines" -PartitionKey $mBody.PartitionKey -RowKey $mBody.RowKey -entity $mBody
Result:

How to add team members in Azure Devops via API? Also Groups API does not work

I am new to Azure Devops and currently migrating to it. I want to add team members for my azure project via REST API. I referred the following documentation, but there is no mention of it. 'Teams' API has no functionality to add Members to it, rather only to create a Team with the Team Name of your choice.
https://learn.microsoft.com/en-us/rest/api/azure/devops/?view=azure-devops-rest-5.1
I encountered another problem in the Group Entitlements API:
https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/group%20entitlements/list?view=azure-devops-rest-5.1
I am unable to hit this particular URL: https://vsaex.dev.azure.com.
In the other API examples, they have used only https://dev.azure.com which works perfectly fine for me. I do not understand what the vsaex stands for. Adding 'vsaex' or ignoring it did not work either. I could not find any documentation regarding this.
Same problem arises for vsaex.dev.azure.com for Users API.
Solutions to any of these would be helpful. Thanks in advance :)
I recently write a PowerShell Script to solve your first problem, but it is only tested on a local azure devops server.
class REST {
#PROPERTIES
[string]$ContentType = "application/json;charset=utf-8"
[string]$PAT
[System.Collections.IDictionary]$Headers
[string]$Url
[string]$Collection
[string]$_Project
#STATIC PROPERTIES
static [int]$Timeout = 30
#CONSTRUCTOR
REST([string]$PAT, [string]$Url, [string]$Collection, [string]$Project) { $this.Init($PAT, $Url, $Collection, $Project) }
REST([string]$PAT, [string]$Url, [string]$Collection) { $this.Init($PAT, $Url, $Collection, $null) }
REST([string]$PAT, [string]$Url) { $this.Init($PAT, $Url, $null, $null) }
REST([string]$PAT) { $this.Init($PAT, $null, $null, $null) }
#INITIALIZE
[void]Init([string]$PAT, [string]$Url, [string]$Collection, [string]$Project) {
$this.PAT = $PAT
$this.Url = $Url
$this.Collection = $Collection
$this._Project = $Project
$this.Headers = $(Headers -PAT $PAT)
}
#GET
[PSCustomObject]Get([string]$Uri) { return Invoke-RestMethod -Uri $Uri -Method GET -ContentType $this.ContentType -Headers $this.Headers -TimeoutSec $([REST]::Timeout) -Verbose }
#PUT
[PSCustomObject]Put([string]$Uri, $Body) { return Invoke-RestMethod -Uri $Uri -Method PUT -ContentType $this.ContentType -Headers $this.Headers -Body $Body -TimeoutSec $([REST]::Timeout) -Verbose }
#POST
[PSCustomObject]Post([string]$Uri, $Body) { return Invoke-RestMethod -Uri $Uri -Method POST -ContentType $this.ContentType -Headers $this.Headers -Body $Body -TimeoutSec $([REST]::Timeout) -Verbose }
#DELETE
[PSCustomObject]Delete([string]$Uri) { return Invoke-RestMethod -Uri $Uri -Method DELETE -ContentType $this.ContentType -Headers $this.Headers -TimeoutSec $([REST]::Timeout) -Verbose }
#TEAMS
[PSCustomObject]Teams([string]$Url, [string]$Collection, [string]$Project) { return $($this.Get($(Combine #($Url, $Collection, $Project, "_settings/teams?__rt=fps&__ver=2")))).fps.dataProviders.data.'ms.vss-tfs-web.team-data' }
[PSCustomObject]Teams([string]$Collection, [string]$Project) { return $this.Teams($this.Url, $Collection, $Project) }
[PSCustomObject]Teams([string]$Project) { return $this.Teams($this.Url, $this.Collection, $Project) }
[PSCustomObject]Teams() { return $this.Teams($this.Url, $this.Collection, $this._Project) }
#TEAM MEMBERS
[PSCustomObject]TeamMembers([string]$Url, [string]$Collection, [string]$Project, [string]$TeamId) { return $this.Get($(Combine #($Url, $Collection, $Project, "_api/_identity/ReadGroupMembers?__v=5&scope=$($TeamId)&readMembers=true&scopedMembershipQuery=1"))) }
[PSCustomObject]TeamMembers([string]$Collection, [string]$Project, [string]$TeamId) { return $this.TeamMembers($this.Url, $Collection, $Project, $TeamId) }
[PSCustomObject]TeamMembers([string]$Project, [string]$TeamId) { return $this.TeamMembers($this.Url, $this.Collection, $Project, $TeamId) }
[PSCustomObject]TeamMembers([string]$TeamId) { return $this.TeamMembers($this.Url, $this.Collection, $this._Project, $TeamId) }
#TEAM MEMBER POST
[PSCustomObject]TeamMemberPost([string]$Url, [string]$Collection, [string]$Project, [string]$TeamId, [string]$Domain, [string]$Name) { $body = '{{''newUsersJson'':''[\''{0}\\\\{1}\'']'',''existingUsersJson'':''[]'',''groupsToJoinJson'':''[\''{2}\'']'',''aadGroupsJson'':''[]''}}' -f ($Domain, $Name, $TeamId); return $this.Post($(Combine #($Url, $Collection, $Project, "_api/_identity/AddIdentities?__v=5")), $body) }
[PSCustomObject]TeamMemberPost([string]$Collection, [string]$Project, [string]$TeamId, [string]$Domain, [string]$Name) { return $this.TeamMemberPost($this.Url, $Collection, $Project, $TeamId, $Domain, $Name) }
[PSCustomObject]TeamMemberPost([string]$Project, [string]$TeamId, [string]$Domain, [string]$Name) { return $this.TeamMemberPost($this.Url, $this.Collection, $Project, $TeamId, $Domain, $Name) }
[PSCustomObject]TeamMemberPost([string]$TeamId, [string]$Domain, [string]$Name) { return $this.TeamMemberPost($this.Url, $this.Collection, $this._Project, $TeamId, $Domain, $Name) }
}
These are the REST-API calls I used for.
#TEAMS returns all teams of a project as json. The call also gives you the $TeamId
#TEAM MEMBERS give you all members of a team
#TEAM MEMBER POST allows you to add you new members. Important: the members must be known by Azure DevOps, that means they need to be in your domain (I don't know how it is organized in azure devops service)
How to use: (but this in the same file like the REST class or load the REST class as module or file before)
#ADD = LIST OF VALID AND KNOWN MEMBERS OF YOUR AZURE DEVOPS SERVICE (STORE IT IN A .TXT FILE OR SOMETHING)
$ADD = #("member1#xyz.com", "member2#xyz.com")
#INITIALIZE REST API
$REST = [REST]::new($PAT, $Uri, $Collection, $Project) #$PAT ~ "atfghfrhfdgdwnx6jnyrculcmaas2g5j6rrogpmn7aza266hrudsahq"; $Uri = https://server.com
#REQUEST TEAMS
$result = $REST.Teams()
$team = $result.team
#REQUEST TEAM MEMBERS
$result = $REST.TeamMembers($team.id)
$members = $result.identities.MailAddress
#ADD MISSING MEMBERS TO TEAM
foreach ($item in $ADD) {
if (-not $members.Contains($item)) {
Write-Host "[ps1] add: '$item'" -ForegroundColor Yellow
#POST ADD MEMBER
$name = $item.Replace($mail, "")
$result = $REST.TeamMemberPost($team.id, $domain, $name)
if ("AddedIdentities" -in $result.PSobject.Properties.Name) { Write-Host "[ps1] successful added: $($result.AddedIdentities.DisplayName) ($($result.AddedIdentities.TeamFoundationId))" -ForegroundColor Green }
else { Write-Host "[ps1] fail to add: '$name'" -ForegroundColor Red }
}
}
I take the snippts from my script. I don't have the time to test this stuff, so please expect errors.
How to find out the correct URLs by your self:
Open Browser (I used Edge)
Press F12
Go to Network
Navigate to the event you want to observe
Clear the list
Execute the event (click button)
Check out the GET/POST with application/json like in sceen shot:
If it is a GET/POST Event you can display the the transfered json under text
{
"newUsersJson": "[\"Domain\\\\user\"]",
"existingUsersJson": "[]",
"groupsToJoinJson": "[\"2d1dfa03-a108-4421-958a-bdsfdsf161696\"]",
"aadGroupsJson": "[]"
}
Hope this helps.
You can use member add api to user to team members.
PUT https://vsaex.dev.azure.com/{organization}/_apis/GroupEntitlements/{groupId}/members/{memberId}?api-version=5.1-preview.1
When you go to the Permissions under Project Settings, You will find the team is actually listed as a group. So i tried using team Id for the groupId in above api. And it worked.
After testing,the memeberId is actually the user id.
You can get the user id with below Get User Entitlements api: check here for details.
GET https://vsaex.dev.azure.com/{organization}/_apis/userentitlements?top={top}&skip={skip}&filter={filter}&sortOption={sortOption}&api-version=5.1-preview.2
Then you can call above member add api to add user to teams.
$uri ="https://vsaex.dev.azure.com/{ORG}/_apis/GroupEntitlements/{teamid}/members/{userid}?api-version=5.1-preview.1"
$connectionToken="PAT"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
# Invoke the REST call and capture the results (notice this uses the PATCH methodg
$result = Invoke-RestMethod -Uri $group -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method put
If you cannot hit https://vsaex.dev.azure.com. You may need to check if your {PAT} has the all the permission scopes to perform add member action. Check here for more information about PAT.
There is a lack of information about vsaex. but i guess vsaex is the server domain for user ad data. As Microsoft manage user ad data information in a separate server from other data.
Not 100% an answer to your question, but maybe it can help you or other people.
Recently I had to add user to a team in an AzDo project. I had the email for the users and the name of the team.
I used the following Powershell code in AzDo version M183_20210320.1:
$PAT = "my-path"; # get your Personal access token https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
$Headers += #{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($PAT)")) };
$Collection = 'my-organisation';
$Project = 'my-project';
$Timeout = 30;
$TeamToAddUser = "my-team-name>";
$EmailToAddToTeam = "my#email.com";
# get project id
$ProjectId = (Invoke-RestMethod -Uri "https://dev.azure.com/$($Collection)/_apis/projects/$($Project)?api-version=6.0" -Method GET -ContentType "application/json;charset=utf-8" -Headers $Headers -TimeoutSec $Timeout).id;
Write-Host "ProjectId: $ProjectId";
# get project descriptor
$ProjecDescriptor = (Invoke-RestMethod -Uri "https://vssps.dev.azure.com/$($Collection)/_apis/graph/descriptors/$($ProjectId)?api-version=6.0-preview" -Method GET -ContentType "application/json;charset=utf-8" -Headers $Headers -TimeoutSec $Timeout).value;
Write-Host "ProjecDescriptor: $ProjecDescriptor";
# get all teams in project
$TeamsInProject = (Invoke-RestMethod -Uri "https://vssps.dev.azure.com/$($Collection)/_apis/graph/groups?scopeDescriptor=$($ProjecDescriptor)&api-version=6.0-preview" -Method GET -ContentType "application/json;charset=utf-8" -Headers $Headers -TimeoutSec $Timeout).value;
Write-Host "TeamsInProject: $($TeamsInProject | forEach { "`n - $($_.displayName) : $($_.descriptor)" }) `n";
# get the team
$Team = $TeamsInProject | Where-Object { $_.displayName -eq $TeamToAddUser }
Write-Host "Team: $($Team.displayName) : $($Team.descriptor)";
# get user id from email
$User = (Invoke-RestMethod -Uri "https://vsaex.dev.azure.com/$($Collection)/_apis/userentitlements?api-version=6.0-preview.3&`$filter=name eq '$(${EmailToAddToTeam})'" -Method GET -ContentType "application/json;charset=utf-8" -Headers $Headers -TimeoutSec $Timeout).members[0];
Write-Host "User to add user: $($User.user.displayName) : $($User.user.originId)";
# add user to team
$body = #{
"originId" = $User.user.originId
};
$result = (Invoke-RestMethod -Uri "https://vssps.dev.azure.com/$($Collection)/_apis/graph/Users?groupDescriptors=$($Team.descriptor)&api-version=6.0-preview" -Method POST -ContentType "application/json;charset=utf-8" -Headers $Headers -Body $($body | ConvertTo-Json -Depth 10 -Compress) -TimeoutSec $Timeout)
After spending hours to do this via API I found a solution, You can use
POST https://vssps.dev.azure.com/{organization}/_apis/graph/users?groupDescriptors={groupDescriptor}&api-version=6.0-preview.1
you can add email in a requestbody, JSON will look like
{
"principalName": "yourmail#goeshere.com"
}
For getting the group descriptor use below GET call
GET https://vssps.dev.azure.com/{organization}/_apis/graph/groups?api-version=6.0-preview.1
Check for group with "displayName" as "{your project name} Team" in the response and take group descriptor of that group.
If you are using Postman to make this call, select basic Auth as authorization and give username empty and password as the PAT token.

Resources