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 = @("[email protected]", "[email protected]")
#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.