RBAC Role Assignment
Assigns an Azure RBAC role to a security group across all resource groups matching an environment naming convention.
Naming Convention
Resource groups must follow: rg-<app>-<environment>-<location>-<instance>
Examples: rg-func-dev-uks-01, rg-api-qa-uks-01
Usage
# Assign Reader role (default) to a group for all dev resource groups
.\Perms-Add.ps1 -SecurityGroupName "DevTeam" -Environment dev
# Assign Contributor role for QA
.\Perms-Add.ps1 -SecurityGroupName "QATeam" -Environment qa -RoleDefinitionName "Contributor"Parameters
-SecurityGroupName(required) — Display name of the Entra ID security group-Environment(required) — One of:dev,qa,prod-RoleDefinitionName(optional) — Azure RBAC role, defaults toReader
How it works
- Looks up the security group via
Get-AzADGroup,Get-MgGroup, oraz ad group list - Finds all resource groups matching the regex
^rg-[^-]+-<environment>-[^-]+-\d+$ - Assigns the specified RBAC role to the group on each matching resource group
- Skips existing assignments gracefully
Script
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$SecurityGroupName,
[Parameter(Mandatory = $true)]
[ValidateSet("dev", "qa", "prod")]
[string]$Environment,
[Parameter(Mandatory = $false)]
[string]$RoleDefinitionName = "Reader"
)
# Naming standard: rg-<app>-<environment>-<location>-<instance>
# Examples: rg-func-dev-uks-01, rg-api-dev-uks-01
# This regex targets the environment segment specifically.
$resourceGroupRegex = "^rg-[^-]+-$([regex]::Escape($Environment))-[^-]+-\d+$"
Write-Host "Using regex: $resourceGroupRegex"
Write-Host "Finding security group '$SecurityGroupName'..."
$escapedGroupName = $SecurityGroupName.Replace("'", "''")
$securityGroupMatches = @()
$hasAzAdGroupCmd = [bool](Get-Command -Name Get-AzADGroup -ErrorAction SilentlyContinue)
$hasMgGroupCmd = [bool](Get-Command -Name Get-MgGroup -ErrorAction SilentlyContinue)
$hasAzRgCmd = [bool](Get-Command -Name Get-AzResourceGroup -ErrorAction SilentlyContinue)
$hasAzRoleCmd = [bool](Get-Command -Name New-AzRoleAssignment -ErrorAction SilentlyContinue)
$hasAzCli = [bool](Get-Command -Name az -ErrorAction SilentlyContinue)
if ($hasAzAdGroupCmd) {
$securityGroupMatches = @(Get-AzADGroup -Filter "displayName eq '$escapedGroupName'" -First 2)
}
elseif ($hasMgGroupCmd) {
$securityGroupMatches = @(Get-MgGroup -Filter "displayName eq '$escapedGroupName'" -Top 2)
}
elseif ($hasAzCli) {
$cliGroups = & az ad group list --display-name $SecurityGroupName --query "[].{Id:id,DisplayName:displayName}" -o json 2>$null
if ($cliGroups) {
$securityGroupMatches = @($cliGroups | ConvertFrom-Json)
}
}
else {
throw "No supported group lookup available. Install/import Az.Resources (Get-AzADGroup), Microsoft.Graph.Groups (Get-MgGroup), or Azure CLI (az)."
}
if (-not $securityGroupMatches -or $securityGroupMatches.Count -eq 0) {
Write-Warning "Security group '$SecurityGroupName' was not found."
return
}
if ($securityGroupMatches.Count -gt 1) {
Write-Warning "More than one group matched '$SecurityGroupName'. Please use a unique group name."
return
}
$securityGroup = $securityGroupMatches[0]
Write-Host "Finding resource groups for environment '$Environment'..."
$resourceGroups = @()
if ($hasAzRgCmd) {
$resourceGroups = @(Get-AzResourceGroup | Where-Object { $_.ResourceGroupName -match $resourceGroupRegex })
}
elseif ($hasAzCli) {
$cliResourceGroups = & az group list --query "[].name" -o json 2>$null
if ($cliResourceGroups) {
$resourceGroups = @(
($cliResourceGroups | ConvertFrom-Json) |
Where-Object { $_ -match $resourceGroupRegex } |
ForEach-Object { [pscustomobject]@{ ResourceGroupName = $_ } }
)
}
}
else {
throw "No supported resource group lookup available. Install/import Az.Resources (Get-AzResourceGroup) or Azure CLI (az)."
}
if (-not $resourceGroups) {
Write-Warning "No matching resource groups found for environment '$Environment'."
return
}
Write-Host "Using security group: $($securityGroup.DisplayName)"
Write-Host "Found $($resourceGroups.Count) matching resource group(s):"
$resourceGroups.ResourceGroupName | Sort-Object | ForEach-Object { Write-Host " - $_" }
$subscriptionId = $null
if ($hasAzCli) {
$subscriptionId = (& az account show --query id -o tsv 2>$null).Trim()
if (-not $subscriptionId) {
throw "Azure CLI is available but no active subscription is selected. Run 'az login' and 'az account set --subscription <name-or-id>'."
}
}
foreach ($resourceGroup in $resourceGroups) {
Write-Host "Assigning '$RoleDefinitionName' to '$($securityGroup.DisplayName)' on RG '$($resourceGroup.ResourceGroupName)'..."
if ($hasAzRoleCmd) {
try {
New-AzRoleAssignment -ObjectId $securityGroup.Id -RoleDefinitionName $RoleDefinitionName -ResourceGroupName $resourceGroup.ResourceGroupName -ErrorAction Stop
}
catch {
if ($_.Exception.Message -match "already exists") {
Write-Host " - Skipping existing assignment."
}
else {
Write-Warning " - Failed: $($_.Exception.Message)"
}
}
}
elseif ($hasAzCli) {
$scope = "/subscriptions/$subscriptionId/resourceGroups/$($resourceGroup.ResourceGroupName)"
$cliAssignOutput = & az role assignment create --assignee-object-id $securityGroup.Id --assignee-principal-type Group --role $RoleDefinitionName --scope $scope --only-show-errors -o none 2>&1
if ($LASTEXITCODE -ne 0) {
$errorText = "$cliAssignOutput"
if ($errorText -match "already exists|exists") {
Write-Host " - Skipping existing assignment."
}
else {
Write-Warning " - Failed: $errorText"
}
}
else {
Write-Host " - Assigned."
}
}
else {
throw "No supported role assignment command available. Install/import Az.Resources (New-AzRoleAssignment) or Azure CLI (az)."
}
}
Write-Host "Done."Last updated on