How to get all key secrets after rest api collect all secrets in Azure Key Vault - azure

Have a nice day everyone!
I have a VMware Windows Which has permission in key vault and I want to collect all key secrets but the code below when it finished just has ID + Attributes not consist value of Key secrets. Anyone can help me finish the code below to get all key secrets.
Many thanks for your help!
$RresourceUrl = 'dddd.vault.azure.net'
# Compose REST request.
$response = Invoke-WebRequest -Uri 'http://169.254.111.211/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net' -Method GET -Headers #{Metadata="true"}
$OAuth = $response.Content | ConvertFrom-Json
# Check if authentication was successfull.
if ($OAuth.access_token) {
#Format headers.
$HeaderParams = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $($OAuth.access_token)"
}
# Create an empty array to store the result.
$QueryResults = #()
$Uri = "https://$RresourceUrl/secrets?api-version=2016-10-01"
# Invoke REST method and fetch data until there are no pages left.
do {
$Results = Invoke-WebRequest -Uri $Uri -Method GET -Headers $HeaderParams | ConvertFrom-Json
$Results.nextLink
if ($Results.value) {
$QueryResults += $Results.value
}
else {
$QueryResults += $Results
}
$Uri = $Results.nextLink
} until (!($Uri))
# Return the result.
$QueryResults | Export-Csv -NoTypeInformatio *\Documents\Tesst.csv
}
else {
Write-Error "No Access Token"
}

This is my final code maybe isn't optimized but it worked.
$RresourceUrl = 'devakv01.vault.azure.net'
# Compose REST request.
$response = Invoke-WebRequest -Uri 'http://169.254.111.211/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net' -Method GET -Headers #{Metadata="true"}
$OAuth = $response.Content | ConvertFrom-Json
# Check if authentication was successfull.
if ($OAuth.access_token) {
#Format headers.
$HeaderParams = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $($OAuth.access_token)"
}
# Create an empty array to store the result.
$QueryResults = #()
$Uri = "https://$RresourceUrl/secrets?api-version=2016-10-01"
# Invoke REST method and fetch data until there are no pages left.
do {
$Results = Invoke-WebRequest -Uri $Uri -Method GET -Headers $HeaderParams | ConvertFrom-Json
$Results.nextLink
if ($Results.value) {
$QueryResults += $Results.value
}
else {
$QueryResults += $Results
}
$Uri = $Results.nextLink
} until (!($Uri))
# Return the result.
$QueryResults
}
else {
Write-Error "No Access Token"
}
# get Key after to have secrets name
$output = ForEach ($nameSecret in $QueryResults.id)
{
$Resultskey = Invoke-WebRequest -Uri $($nameSecret+'?api-version=2016-10-01') -Method GET -Headers $HeaderParams | ConvertFrom-Json
$Resultskey
}
$output | Export-Csv -NoTypeInformatio *\$RresourceUrl.csv

Related

Getting 'Bad Request' and 'Invalid query definition' when calling Azure REST API

I'm trying to call an Azure REST API endpoint to get information my daily usage cost.
I'm using Powershell 7.2 and here's my code:
$uri = 'https://management.azure.com/subscriptions/{subscription id}/providers/Microsoft.CostManagement/query?api-version=2021-10-01'
$token = '{generated bearer token string}'
$securetoken = ConvertTo-SecureString $token -AsPlainText -Force
$body = #{
type = 'Usage'
timeframe = 'MonthToDate'
dataset = #{
granularity = 'Daily'
aggregation = #{
totalCost = #{
name = 'PreTaxCost'
function = 'Sum'
}
}
grouping = #(
#{
type = 'Dimension'
name = 'ServiceName'
}
)
}
}
$costresponse = Invoke-WebRequest -Uri $uri -Method Post -Authentication Bearer -Token $securetoken -Body $body
Write-Host $costresponse
Here's the request body example from the Microsoft documentation I'm trying to emulate:
{
"type": "Usage",
"timeframe": "TheLastMonth",
"dataset": {
"granularity": "None",
"aggregation": {
"totalCost": {
"name": "PreTaxCost",
"function": "Sum"
}
},
"grouping": [
{
"type": "Dimension",
"name": "ResourceGroup"
}
]
}
}
When I run the code I get this error message:
Line |
27 | … tresponse = Invoke-WebRequest -Uri $uri -Method Post -Authentication …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| {"error":{"code":"BadRequest","message":"Invalid query definition: Missing dataset granularity; valid
| values: 'Daily'.\r\n\r\n (Request ID: c6ead005-85b3-4ebe-9b46-........)"}}
I think the error has to do with the body syntax but I can't figure out what is the issue. I'm following this Microsoft documentation: https://learn.microsoft.com/en-us/rest/api/cost-management/query/usage
EDIT:
I tried converting the body to JSON and adding a depth parameter like this:
$costresponse = Invoke-WebRequest -Uri $uri -Method Post -Authentication Bearer -Token $securetoken -Body ($body|ConvertTo-Json -Depth 5)
And now I get a slightly different error message:
Line |
26 | … tresponse = Invoke-WebRequest -Uri $uri -Method Post -Authentication …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| {"error":{"code":"BadRequest","message":"Invalid query definition, Dataset is invalid or not supplied.
| (Request ID: cf6a4b8f-88e8-4037-aa33-904......)"}}
I needed to add -ContentType 'application/json' to my Invoke-WebRequest function to get it to work. Thanks to #bluuf for the suggestion.

Azure REST API list key vault secrets has maxresults limited to 25

I'm trying to get a list of all the secrets in each of my key vaults and I'm using Microsofts documentation at this URL.
https://learn.microsoft.com/en-us/rest/api/keyvault/getsecrets/getsecrets#secretlistresult
It states that if you do not set maxresults it will default to 25. It does however throw this error in my powershell script when i try to set it higher than 25.
{"error":{"code":"BadParameter","message":"invalid maxresults"}}
From the documentation the endpoint does not appear to contain any pagination or way to get more than 25 random secrets. This seems to make the endpoint pretty useless as there are no ways to filter the listings.
The command I'm using to get the list is this.
$uri = ""https://$($Vault).vault.azure.net/secrets?api-version=7.1&maxresults=26""
Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
As Gaurav said in the comment, you need to use nextLink to get the result of next page.
There is my test code with do-until:
$LoginUrl = "https://login.microsoft.com"
$RresourceUrl = "https://vault.azure.net/.default"
$ClientID = ""
$ClientSecret = ""
$TenantName = ""
# Compose REST request.
$Body = #{ grant_type = "client_credentials"; scope = $RresourceUrl; client_id = $ClientID; client_secret = $ClientSecret }
$OAuth = Invoke-RestMethod -Method Post -Uri $LoginUrl/$TenantName/oauth2/v2.0/token -Body $Body
# Check if authentication was successfull.
if ($OAuth.access_token) {
# Format headers.
$HeaderParams = #{
'Content-Type' = "application\json"
'Authorization' = "Bearer $($OAuth.access_token)"
}
# Create an empty array to store the result.
$QueryResults = #()
$Uri = "https://<your-key-vault-name>.vault.azure.net/secrets?api-version=7.1"
# Invoke REST method and fetch data until there are no pages left.
do {
$Results = Invoke-RestMethod -Headers $HeaderParams -Uri $Uri -UseBasicParsing -Method "GET" -ContentType "application/json"
$Results.nextLink
if ($Results.value) {
$QueryResults += $Results.value
}
else {
$QueryResults += $Results
}
$Uri = $Results.nextLink
} until (!($Uri))
# Return the result.
$QueryResults
}
else {
Write-Error "No Access Token"
}

Filter Azure DevOps releases with RestAPIs

I have been working on a requirement with a release API to retain all the production releases.
Code -
param (
[string]$token="",
[string]$collection="",
[string]$projectName =""
)
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user, $token)))
$response = Invoke-RestMethod "https://vsrm.dev.azure.com/$collection/$projectName/_apis/release/definitions?`$expand=Environments&`$top=1000&api-version=6.0" -Method 'GET' -Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo)}
foreach ($releaseDefinition in $response.value){
write-host $releaseDefinition.name
write-host $releaseDefinition.id
$releaseDefinitionid = [convert]::ToInt32($releaseDefinition.id)
write-host "--------------------------------------------------"
[string] $releases = "https://vsrm.dev.azure.com/$collection/$projectName/_apis/release/releases?definitonid="
[string] $defid = $releaseDefinitionid
[string] $geturl = $releases + $defid + "&api-version=6.0"
$releaseresult = Invoke-RestMethod $geturl -Method 'GET' -Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo)}
#Write-Host $releaseresult.value
#Write-host $releaseresult.keepForever
foreach ($retain in $releaseresult.value)
{
Write-host $retain.id
$temp = $retain.id.ToString()
$id = "https://vsrm.dev.azure.com/$collection/$projectName/_apis/release/releases/$temp/?api-version=6.0"
$retainrelease = Invoke-RestMethod $id -Method 'GET' -Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo)}
if( $retainrelease.environments.name -eq 'PRD' -and $retainrelease.environments.status -eq 'succeeded')
{
if([string]$retainrelease.keepForever -eq 'False')
{
$keepforever = #{
keepforever='true'}
$jsonKeepForever = $keepforever | ConvertTo-Json -Depth 100
$uriForBuildUpdate = "https://vsrm.dev.azure.com/$collection/$projectName/_apis/release/releases/$temp/?api-version=6.0"
$patchreq = Invoke-RestMethod -Uri $uriForBuildUpdate -Method Patch -Body $jsonKeepForever -ContentType "application/json" -Headers #{Authorization = ("Basic {0}" -f $base64AuthInfo)}
Write-Verbose "Result: $patchreq" -Verbose
}
}
else
{
}
}
}
It retains the releases, but the requirement is to retain only release definitions created in the last 60 days (or a particular date) and retain only releases happened in last 10 days to prod.
I couldn't find any querystring param to filter it by. How can I do this?
I found that you can get a list of associated environments like prod/stage/dev (for each definition) by adding the $expand=environments to query string.
https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/deployments?$expand=environments&api-version=6.0
So you'll get the list of all environments and their ids for each definition. The ids can be used as definitionEnvironmentId for the other endpoints that seem to have what you're looking for (e.g.: maxCreatedTime or minCreatedTime) like this one for Releases:
https://learn.microsoft.com/en-us/rest/api/azure/devops/release/releases/list?view=azure-devops-rest-6.0#uri-parameters
Reference:
List of properties that can be expanded - https://learn.microsoft.com/en-us/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-6.0#releasedefinitionexpands

Azure Repos API Commits - GetChanges returning empty list

I have an Azure build pipeline with the following powershell task running on the agent to get the commits of my PR:
$id = $(System.PullRequest.PullRequestId)
$uri = "$(System.TeamFoundationCollectionUri)NPI/_apis/git/repositories/$(Build.Repository.ID)/pullRequests/$id/commits?api-version=5.1"
Write-Host "Getting all commits for PR" $id ":" $uri
$J = Invoke-WebRequest -URI $uri -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
} | ConvertFrom-Json
And then loop through the reply like this:
foreach ($c in $J.value)
{
$id = $c.commitId
$uri = "$(System.TeamFoundationCollectionUri)NPI/_apis/git/repositories/$(Build.Repository.ID)/commits/$id/changes?api-version=5.1"
Write-Host "Getting details for commit" $id ":" $uri
$J = Invoke-WebRequest -URI $uri -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
} | ConvertFrom-Json
Write-Host $J
}
The log file of the Azure task contains this:
Getting all commits for PR 1026 : https://dev.azure.com/xxx/xxx/_apis/git/repositories/xxx/pullRequests/1026/commits?api-version=5.1
Getting details for commit xxx : https://dev.azure.com/xxx/xxx/_apis/git/repositories/xxx/commits/xxx/changes?api-version=5.1
#{changeCounts=; changes=System.Object[]}
As you can see, there are no changes listed. However, if I click on the logged url for getting the commit details, I get a non-empty list. What's going on here?
Oops, turns out it does actually work but the Write-Host output is broken.
This runs fine:
foreach ($cc in $J.changes)
{
Write-Host $cc.item.path
}
You need a variable to store it before convert to json. This is work correctly to me.
"$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/git/repositories/$($pipeline.repository.id)/commits/$(Build.BuildId)/changes?api-version=5.0"
Write-Host $commitUrl
$rsCommit = Invoke-RestMethod -Uri $commitUrl -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"}
Write-Host "CommitInfo = $($rsCommit | ConvertTo-Json -Depth 100)"

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