Functions/Add-AzDoGroupMember.ps1
|
Function Add-AzDoGroupMember { <# .SYNOPSIS Adds an Azure Entra ID group or user to an Azure DevOps project group. .DESCRIPTION Adds an Azure Entra ID group or user to a project group (e.g., Contributors, Readers) in Azure DevOps. This function uses the Graph API Memberships endpoint to create the membership link. .EXAMPLE Add-AzDoGroupMember -MemberOriginId "11111111-2222-3333-4444-555555555555" -ContainerDescriptor "vssgp..." -Organization "MyOrg" .EXAMPLE Add-AzDoGroupMember -MemberPrincipalName "user@domain.com" -Project "MyProject" -GroupName "Contributors" .PARAMETER MemberDescriptor The descriptor of the Azure Entra ID group or user to add. You can get this from Graph API's Groups/Users endpoint. .PARAMETER MemberOriginId The Origin ID (Object ID) of the Azure Entra ID group to add. This is the most reliable method for mail-disabled security groups. .PARAMETER MemberPrincipalName The principal name (email) of the user or group to add. This will be used to look up the descriptor if MemberDescriptor is not provided. Note: This may not work for mail-disabled security groups. .PARAMETER ContainerDescriptor The descriptor of the Azure DevOps project group to add the member to. This is typically the target group like "Contributors" or "Project Administrators". .PARAMETER ContainerName The name of the project group (e.g., "Contributors"). This will be used with Project to look up the descriptor if ContainerDescriptor is not provided. .PARAMETER Project The name of the Azure DevOps project where the group exists. .PARAMETER Organization The name of your Azure DevOps organization. .INPUTS Input is from command line or called from a script. .OUTPUTS Returns the membership object for the added member. .NOTES Author: Lars Panzerbjørn Creation Date: 2025.05.20 #> [CmdletBinding(DefaultParameterSetName = 'ByDescriptor')] param( [Parameter(ParameterSetName = 'ByDescriptor', Mandatory)] [string]$MemberDescriptor, [Parameter(ParameterSetName = 'ByOriginId', Mandatory)] [string]$MemberOriginId, [Parameter(ParameterSetName = 'ByName', Mandatory)] [string]$MemberPrincipalName, [Parameter(ParameterSetName = 'ByDescriptor', Mandatory)] [Parameter(ParameterSetName = 'ByOriginId', Mandatory)] [string]$ContainerDescriptor, [Parameter(ParameterSetName = 'ByName', Mandatory)] [string]$ContainerName, [Parameter(ParameterSetName = 'ByName', Mandatory)] [string]$Project, [Parameter()] [string]$Organization = $Script:Organisation ) BEGIN{ Write-Verbose "Beginning $($MyInvocation.Mycommand)" $ApiVersion = "7.1-preview.1" } PROCESS{ Write-Verbose "Processing $($MyInvocation.Mycommand)" # Resolve Member Descriptor if Origin ID is provided IF($PSCmdlet.ParameterSetName -eq 'ByOriginId') { Write-Verbose "Looking up descriptor for member Origin ID: $MemberOriginId" # Use the Users endpoint with a filter or the Groups endpoint # The Graph API can search by originId using the subjectTypes parameter $MemberUri = "https://vssps.dev.azure.com/$Organization/_apis/graph/groups?api-version=$ApiVersion&subjectTypes=group" $Groups = Invoke-RestMethod -Uri $MemberUri -Method GET -Headers $Header $Group = $Groups.value | Where-Object { $_.originId -eq $MemberOriginId } IF(-not $Group) { # Try as a user instead $MemberUri = "https://vssps.dev.azure.com/$Organization/_apis/graph/users?api-version=$ApiVersion" $Users = Invoke-RestMethod -Uri $MemberUri -Method GET -Headers $Header $Group = $Users.value | Where-Object { $_.originId -eq $MemberOriginId } } IF(-not $Group) { Write-Error "Could not find member with Origin ID: $MemberOriginId" RETURN } $MemberDescriptor = $Group.descriptor Write-Verbose "Found Member Descriptor: $MemberDescriptor" } # Resolve Member Descriptor if Principal Name is provided (may not work for mail-disabled groups) IF($PSCmdlet.ParameterSetName -eq 'ByName') { Write-Verbose "Looking up descriptor for member: $MemberPrincipalName" $MemberUri = "https://vssps.dev.azure.com/$Organization/_apis/graph/users?api-version=$ApiVersion" $Members = Invoke-RestMethod -Uri $MemberUri -Method GET -Headers $Header $Member = $Members.value | Where-Object { $_.principalName -eq $MemberPrincipalName -or $_.displayName -eq $MemberPrincipalName } IF(-not $Member) { Write-Error "Could not find member: $MemberPrincipalName" RETURN } $MemberDescriptor = $Member.descriptor Write-Verbose "Found Member Descriptor: $MemberDescriptor" } # Resolve Container Descriptor if not provided IF($PSCmdlet.ParameterSetName -eq 'ByName') { Write-Verbose "Looking up descriptor for group: $ContainerName in project: $Project" $GroupUri = "https://vssps.dev.azure.com/$Organization/_apis/graph/groups?api-version=$ApiVersion&scopeDescriptor=$Project" $Groups = Invoke-RestMethod -Uri $GroupUri -Method GET -Headers $Header $Group = $Groups.value | Where-Object { $_.displayName -eq $ContainerName } IF(-not $Group) { Write-Error "Could not find group: $ContainerName in project: $Project" RETURN } $ContainerDescriptor = $Group.descriptor Write-Verbose "Found Container Descriptor: $ContainerDescriptor" } # Build URI for adding membership $Uri = "https://vssps.dev.azure.com/$Organization/_apis/graph/memberships/$MemberDescriptor/$ContainerDescriptor`?api-version=$ApiVersion" Write-Verbose "Membership URI: $Uri" # The API expects a PUT request with no body $Result = Invoke-RestMethod -Uri $Uri -Method PUT -Headers $Header -ContentType "application/json" Write-Verbose "Successfully added member to group." } END{ Write-Verbose "Ending $($MyInvocation.Mycommand)" $Result } } |