Last active
September 20, 2024 05:53
-
-
Save JohanSelmosson/85c22a11debe1929fbd4dd0d49d900b3 to your computer and use it in GitHub Desktop.
Get-AzureRoleAssignments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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