Skip to content

Instantly share code, notes, and snippets.

@JohanSelmosson
Last active September 20, 2024 05:53
Show Gist options
  • Select an option

  • Save JohanSelmosson/85c22a11debe1929fbd4dd0d49d900b3 to your computer and use it in GitHub Desktop.

Select an option

Save JohanSelmosson/85c22a11debe1929fbd4dd0d49d900b3 to your computer and use it in GitHub Desktop.
Get-AzureRoleAssignments
function Get-AzureRoleAssignments {
<#
.SYNOPSIS
Retrieves Azure Role Assignments for specified scopes.
.DESCRIPTION
The Get-AzureRoleAssignments function retrieves Azure Role Assignments for either a specific subscription, a management group, or all accessible subscriptions. It can optionally include group members for assignments made to groups.
.PARAMETER SubscriptionId
Specifies the ID of the subscription to retrieve role assignments from. If not specified, and ManagementGroupId is also not specified, the function will retrieve role assignments from all accessible subscriptions.
.PARAMETER ManagementGroupId
Specifies the ID of the management group to retrieve role assignments from. If specified, the function will retrieve role assignments for this management group and its child subscriptions.
.PARAMETER IncludeGroupMembers
If specified, the function will also retrieve and output the members of groups that have role assignments.
.OUTPUTS
This function outputs custom objects with the following properties:
- EntraIDName: The name of the Azure AD tenant (Entra ID).
- SubscriptionName: The name of the subscription where the role assignment exists.
- SubscriptionId: The ID of the subscription where the role assignment exists.
- PrincipalDisplayName: The display name of the principal (user, group, or service principal) that has the role assignment.
- PrincipalId: The object ID of the principal.
- PrincipalType: The type of principal (User, Group, ServicePrincipal, etc.).
- RoleDefinitionName: The name of the role that is assigned.
- RoleDefinitionId: The ID of the role that is assigned.
- Scope: The full scope at which the role assignment applies.
- InheritedFromType: The type of Azure resource from which the role assignment is inherited (e.g., "Subscription", "Resource Group", etc.).
- InheritedFromName: The name of the specific Azure resource from which the role assignment is inherited.
- IsCustomRole: A boolean indicating whether the assigned role is a custom role.
- GroupName: If the principal is a group, this will be the group's name. Otherwise, it will be null.
- GroupId: If the principal is a group, this will be the group's ID. Otherwise, it will be null.
When IncludeGroupMembers is specified, additional objects will be output for each group member, with similar properties to above, but PrincipalType will indicate that it's a group member.
.EXAMPLE
Get-AzureRoleAssignments -Verbose
This example retrieves role assignments for all accessible subscriptions and outputs them to the console.
.EXAMPLE
Get-AzureRoleAssignments -SubscriptionId "12345678-1234-1234-1234-123456789012" -IncludeGroupMembers
This example retrieves role assignments for the specified subscription, including members of groups that have role assignments.
.EXAMPLE
Get-AzureRoleAssignments -ManagementGroupId "mg-finance" -IncludeGroupMembers
This example retrieves role assignments for the specified management group and its child subscriptions, including members of groups that have role assignments.
.EXAMPLE
$date = Get-Date -Format "yyyy-MM-dd"
$fileName = "AzureRoleAssignments_$date.csv"
Get-AzureRoleAssignments -IncludeGroupMembers | Export-Csv -Path $fileName -NoTypeInformation
This example retrieves role assignments for all accessible subscriptions, including group members, and exports the results to a CSV file with a date-stamped filename.
.NOTES
This function requires the Az and Microsoft.Graph PowerShell modules to be installed and for you to be authenticated to both Azure and Microsoft Graph before running.
Lots of Inspiration and code was lent/stolen from this blog post, thanks Morten Pedholt!
https://pedholtlab.com/export-role-assignments-for-all-azure-subscriptions/
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[string]$SubscriptionId,
[Parameter(Mandatory = $false)]
[string]$ManagementGroupId,
[Parameter(Mandatory = $false)]
[switch]$IncludeGroupMembers
)
begin {
Write-Verbose "Checking Azure PowerShell connection..."
try {
$context = Get-AzContext
if (-not $context) {
throw "Not connected to Azure. Please connect using Connect-AzAccount."
}
Write-Verbose "Connected as: [$($context.Account)]"
# Get Entra ID (Azure AD) name
$tenantDetails = Get-AzTenant -TenantId $context.Tenant.Id
$entraIdName = $tenantDetails.Name
Write-Verbose "Entra ID (Azure AD) Name: [$entraIdName]"
if ($IncludeGroupMembers) {
Write-Verbose "Checking Microsoft Graph connection for group member retrieval..."
try {
Get-MgContext -ErrorAction Stop
}
catch {
Write-Warning "Not connected to Microsoft Graph. Connecting now..."
Connect-MgGraph -Scopes "Directory.Read.All" -ErrorAction Stop
}
}
# Initialize caches
$userCache = @{}
$groupCache = @{}
$spCache = @{}
function Get-CachedDirectoryObject {
param (
[string]$ObjectId,
[string]$ObjectType
)
$cache = switch ($ObjectType) {
'User' { $userCache }
'Group' { $groupCache }
'ServicePrincipal' { $spCache }
default { $null }
}
if ($null -eq $cache -or -not $cache.ContainsKey($ObjectId)) {
try {
$objectDetails = Get-MgDirectoryObject -DirectoryObjectId $ObjectId
if ($null -ne $cache) {
$cache[$ObjectId] = $objectDetails
}
return $objectDetails
}
catch {
Write-Warning ("Error fetching details for object [{0}]: [{1}]" -f $ObjectId, $PSItem.Exception.Message)
return $null
}
}
return $cache[$ObjectId]
}
}
catch {
throw ("Error during setup: [{0}]" -f $PSItem.Exception.Message)
}
# Cache management group names
$mgCache = @{}
}
process {
if ($ManagementGroupId) {
Write-Verbose "Retrieving role assignments for Management Group: [$ManagementGroupId]"
$roleAssignments = Get-AzRoleAssignment -Scope "/providers/Microsoft.Management/managementGroups/$ManagementGroupId"
$subscriptions = Get-AzSubscription -TenantId $context.Tenant.Id | Where-Object { $_.ManagementGroupId -eq $ManagementGroupId }
}
elseif ($SubscriptionId) {
Write-Verbose "Retrieving role assignments for Subscription: [$SubscriptionId]"
Set-AzContext -SubscriptionId $SubscriptionId | Out-Null
$roleAssignments = Get-AzRoleAssignment
$subscriptions = @(Get-AzSubscription -SubscriptionId $SubscriptionId)
}
else {
Write-Verbose "Retrieving Azure subscriptions..."
$subscriptions = Get-AzSubscription
Write-Verbose "Found [$($subscriptions.Count)] subscriptions."
$roleAssignments = @()
foreach ($sub in $subscriptions) {
try {
$context = Set-AzContext -SubscriptionId $sub.Id -ErrorAction Stop
if ($context.Subscription.State -eq 'Enabled') {
Write-Verbose "Processing subscription: [$($sub.Name)] (ID: [$($sub.Id)])"
$roleAssignments += Get-AzRoleAssignment -ErrorAction Stop
}
else {
Write-Warning "Skipping disabled subscription: [$($sub.Name)] (ID: [$($sub.Id)])"
}
}
catch {
Write-Warning "Error processing subscription [$($sub.Name)] (ID: [$($sub.Id)]): [$($PSItem.Exception.Message)]"
}
}
}
Write-Verbose "Processing [$($roleAssignments.Count)] role assignments."
foreach ($assignment in $roleAssignments) {
$checkForCustomRole = Get-AzRoleDefinition -Name $assignment.RoleDefinitionName
$isCustomRole = $checkForCustomRole.IsCustom
# Determine the scope of inheritance
$inheritedFromType = ''
$inheritedFromName = ''
switch -Regex ($assignment.Scope) {
'/providers/Microsoft.Management/managementGroups/(.+)' {
$mgId = $matches[1]
if (-not $mgCache.ContainsKey($mgId)) {
$mgCache[$mgId] = (Get-AzManagementGroup -GroupId $mgId -ErrorAction SilentlyContinue).DisplayName
}
$inheritedFromType = "Management Group"
$inheritedFromName = $mgCache[$mgId]
}
'/subscriptions/[^/]+$' {
$inheritedFromType = "Subscription"
$inheritedFromName = ($subscriptions | Where-Object { $_.Id -eq $assignment.Scope.Split('/')[2] }).Name
}
'/subscriptions/[^/]+/resourceGroups/([^/]+)(/providers/([^/]+)/([^/]+)/([^/]+))?$' {
if ($matches[3]) {
$inheritedFromType = "Resource"
$inheritedFromName = "$($matches[1])/$($matches[3])/$($matches[4])/$($matches[5])"
}
else {
$inheritedFromType = "Resource Group"
$inheritedFromName = $matches[1]
}
}
'^/$' {
$inheritedFromType = "Root"
$inheritedFromName = "/"
}
default {
$inheritedFromType = "Unknown"
$inheritedFromName = $assignment.Scope
}
}
$subName = ($subscriptions | Where-Object { $_.Id -eq $assignment.Scope.Split('/')[2] }).Name
$reportItem = [PSCustomObject]@{
EntraIDName = $entraIdName
SubscriptionName = $subName
SubscriptionId = $assignment.Scope.Split('/')[2]
PrincipalDisplayName = $assignment.DisplayName
PrincipalId = $assignment.ObjectId
PrincipalType = $assignment.ObjectType
RoleDefinitionName = $assignment.RoleDefinitionName
RoleDefinitionId = $assignment.RoleDefinitionId
Scope = $assignment.Scope
InheritedFromType = $inheritedFromType
InheritedFromName = $inheritedFromName
IsCustomRole = $isCustomRole
GroupName = if ($assignment.ObjectType -eq 'Group') { $assignment.DisplayName } else { $null }
GroupId = if ($assignment.ObjectType -eq 'Group') { $assignment.ObjectId } else { $null }
}
# Emit the object immediately
$reportItem
if ($IncludeGroupMembers -and $assignment.ObjectType -eq 'Group') {
Write-Verbose "Fetching members for group: [$($assignment.DisplayName)]"
try {
$groupMembers = Get-MgGroupMember -GroupId $assignment.ObjectId -All
foreach ($member in $groupMembers) {
$memberItem = $reportItem.PSObject.Copy()
$objectDetails = Get-CachedDirectoryObject -ObjectId $member.Id -ObjectType 'Unknown'
if ($null -ne $objectDetails) {
$memberItem.PrincipalDisplayName = $objectDetails.AdditionalProperties.displayName
$memberItem.PrincipalId = $objectDetails.Id
$memberItem.PrincipalType = switch ($objectDetails.AdditionalProperties.'@odata.type') {
'#microsoft.graph.user' { "User (Group Member)" }
'#microsoft.graph.group' { "Group (Nested Group Member)" }
'#microsoft.graph.servicePrincipal' { "Service Principal (Group Member)" }
default { "Unknown (Group Member)" }
}
}
else {
$memberItem.PrincipalDisplayName = "Error Fetching Member"
$memberItem.PrincipalId = $member.Id
$memberItem.PrincipalType = "Unknown (Group Member)"
}
# Emit the group member object immediately
$memberItem
}
}
catch {
Write-Warning ("Error fetching members for group [{0}]: [{1}]" -f $assignment.DisplayName, $PSItem.Exception.Message)
}
}
}
}
end {
# No specific cleanup required
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment