<# .SYNOPSIS Comprehensive Azure RBAC assignment analysis across entire Azure tenant. .DESCRIPTION This script analyzes RBAC assignments across all Azure resources in an Azure tenant, providing a complete inventory of role assignments at every level of the Azure resource hierarchy. The script generates a detailed CSV report with comprehensive metadata including resource information, role assignments, and organizational tags. Features: • Recursive analysis of Management Groups, Subscriptions, Resource Groups, and Resources • Complete RBAC assignment enumeration with role and principal details • Organizational metadata collection (tags, locations, resource types) • Hierarchical resource context (management group → subscription → resource group → resource) • Timestamped CSV output for historical tracking and compliance reporting • Comprehensive error handling to continue processing despite individual failures .PARAMETER SubscriptionIds Optional array of specific subscription IDs to process. If not provided, all active subscriptions across all management groups will be processed. When specified, only the listed subscriptions will be analyzed for RBAC assignments. .PARAMETER OutputPath Optional custom path for the output CSV file. If not provided, the file will be created in the current directory with a timestamped filename. .EXAMPLE .\AzureRBAC.ps1 Process all management groups and subscriptions to generate complete RBAC inventory. .EXAMPLE .\AzureRBAC.ps1 -SubscriptionIds @("a134faf1-7a89-4f2c-8389-06d00bd5e2a7", "30ce4e64-4299-4b93-91b8-4c953f63678e") Process only the specified subscriptions for RBAC analysis. .EXAMPLE .\AzureRBAC.ps1 -SubscriptionIds @("12345678-1234-1234-1234-123456789012") -OutputPath "C:\Reports\rbac-analysis.csv" Process a specific subscription and save results to a custom location. .OUTPUTS CSV file: [YYYY-MM-DD HHMM] azure_rbac_assignments.csv The output file contains the following columns: - ResourceId: Azure resource identifier - Id: Resource ID (for individual resources) - Kind: Resource type (ManagementGroup, Subscription, ResourceGroup, Resource) - Location: Azure region/location - ResourceName: Name of the resource - ResourceGroupName: Resource group containing the resource - ResourceType: Azure resource type (e.g., Microsoft.Storage/storageAccounts) - ManagementGroupId: Parent management group identifier - ManagementGroupName: Parent management group display name - SubscriptionId: Subscription identifier - SubscriptionName: Subscription display name - Tag_Team: Team tag value for organizational tracking - Tag_Product: Product tag value for product alignment - Tag_Environment: Environment tag value (Dev, Test, Prod, etc.) - Tag_Data: Data classification tag value - Tag_Delete: Deletion schedule tag value - Tag_Split: Cost allocation split tag value - RBAC_RoleAssignmentId: Unique role assignment identifier - RBAC_Scope: Scope where the role assignment is effective - RBAC_DisplayName: Display name of the assigned principal - RBAC_SignInName: Sign-in name/UPN of the assigned principal - RBAC_RoleDefinitionName: Name of the assigned Azure role .NOTES Requires PowerShell modules: Az.Accounts, Az.Resources Requires appropriate Azure RBAC permissions: • Reader access or higher on Management Groups • Reader access or higher on Subscriptions • Reader access or higher on Resource Groups and Resources • Microsoft.Authorization/roleAssignments/read permission at appropriate scopes Authentication: • Must be authenticated to Azure (Connect-AzAccount) before running • Service Principal or Managed Identity authentication supported • Requires appropriate tenant-level permissions for comprehensive analysis Performance Considerations: • Processing time scales with number of resources and role assignments • Large tenants may require several minutes to hours for complete analysis • Network connectivity and API throttling may affect processing speed • Memory usage scales with number of resources processed Compatibility: • PowerShell 5.1 and PowerShell 7.x • Azure PowerShell module version 8.0 or later • Windows, macOS, and Linux support Output Management: • CSV files are appended to support incremental data collection • Timestamped filenames prevent overwrites • Large result sets may generate substantial file sizes • Consider data retention and storage management policies .LINK https://docs.microsoft.com/en-us/azure/role-based-access-control/ https://docs.microsoft.com/en-us/powershell/azure/ AUTHOR: Cloud Engineering Team CREATED: Azure governance and compliance toolkit VERSION: 2.0 - Simplified RBAC analysis without PIM detection UPDATED: Focused on core RBAC assignment enumeration and organizational metadata #> # Script parameters param( [Parameter(Mandatory = $false, HelpMessage = "Array of subscription IDs to process. If not specified, all subscriptions will be processed.")] [string[]]$SubscriptionIds = @(), [Parameter(Mandatory = $false, HelpMessage = "Custom output path for the CSV file. If not specified, uses timestamped filename in current directory.")] [string]$OutputPath = "" ) #Connect-AzAccount Import-Module Az.Accounts Import-Module Az.Resources # PowerShell class to represent Azure resource and RBAC assignment data class ResourceCheck { # Resource identification and metadata [string] $ResourceId = "" # Azure resource identifier (full ARM path) [string] $Id = "" # Resource ID (used for individual resources) [string] $Kind = "" # Resource level (ManagementGroup, Subscription, ResourceGroup, Resource) [string] $Location = "" # Azure region/location where resource is deployed [string] $ResourceName = "" # Name of the resource [string] $ResourceGroupName = "" # Resource group containing the resource [string] $ResourceType = "" # Azure resource type (e.g., Microsoft.Storage/storageAccounts) # Organizational hierarchy context [string] $ManagementGroupId = "" # Parent management group identifier [string] $ManagementGroupName = "" # Parent management group display name [string] $SubscriptionId = "" # Subscription identifier [string] $SubscriptionName = "" # Subscription display name # Organizational metadata tags (customize based on organizational tagging strategy) [string] $Tag_Team = "" # Team responsible for the resource [string] $Tag_Product = "" # Product/application alignment [string] $Tag_Environment = "" # Environment classification (Dev, Test, Prod, etc.) [string] $Tag_Data = "" # Data classification level [string] $Tag_Delete = "" # Scheduled deletion information [string] $Tag_Split = "" # Cost allocation split information # RBAC assignment details [string] $RBAC_RoleAssignmentId = "" # Unique identifier for the role assignment [string] $RBAC_Scope = "" # Scope where the role assignment is effective [string] $RBAC_DisplayName = "" # Display name of the assigned principal [string] $RBAC_SignInName = "" # Sign-in name/UPN of the assigned principal [string] $RBAC_RoleDefinitionName = "" # Name of the assigned Azure role } # Display script execution banner Write-Host "========================================================================================================================================================================" Write-Host "Creating resource RBAC assignment overview." Write-Host "========================================================================================================================================================================" Write-Host "Azure RBAC Assignment Analysis" Write-Host "========================================================================================================================================================================" # Display processing scope information if ($SubscriptionIds.Count -gt 0) { Write-Host "SCOPE: Processing specific subscriptions only" Write-Host "Subscription IDs to process:" foreach ($subId in $SubscriptionIds) { Write-Host " - $subId" } } else { Write-Host "SCOPE: Processing ALL active subscriptions across all management groups" } Write-Host "" # Generate filename for output CSV (use custom path or default timestamped filename) if ($OutputPath -ne "") { $fileName = $OutputPath Write-Host "Using custom output file: $fileName" } else { [string] $date = Get-Date -Format "yyyy-MM-dd HHmm" $fileName = ".\$date azure_rbac_assignments.csv" Write-Host "Using default timestamped filename: $fileName" } # Discover all management groups in the tenant # This provides the top-level organizational structure for Azure resources $managementGroups = Get-AzManagementGroup # Process each management group in the tenant foreach ($managementGroup in $managementGroups) { Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------" Write-Host "Management group [$($managementGroup.Name)]" # Initialize collection for management group level role assignments [ResourceCheck[]]$Result = @() try { # Get role assignments directly assigned to this management group (not inherited) $roleAssignments = Get-AzRoleAssignment -Scope $managementGroup.Id | Where-Object Scope -eq $managementGroup.Id # Process each role assignment at the management group level foreach($roleAssignment in $roleAssignments) { [ResourceCheck] $resourceCheck = [ResourceCheck]::new() # Set management group context (no individual resource context at this level) $resourceCheck.ResourceId = "" # No specific resource for MG assignments $resourceCheck.Kind = "ManagementGroup" # Indicates this is a management group level assignment $resourceCheck.Location = "" # Management groups don't have locations $resourceCheck.ResourceGroupName = "" # No resource group context $resourceCheck.ManagementGroupId = $managementGroup.Id $resourceCheck.ManagementGroupName = $managementGroup.DisplayName $resourceCheck.SubscriptionId = "" # No subscription context at MG level $resourceCheck.SubscriptionName = "" # Management groups don't have tags in the same way resources do $resourceCheck.Tag_Team = "" $resourceCheck.Tag_Product = "" $resourceCheck.Tag_Environment = "" $resourceCheck.Tag_Data = "" $resourceCheck.Tag_Delete = "" $resourceCheck.Tag_Split = "" # Populate RBAC assignment details $resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId $resourceCheck.RBAC_Scope = $roleAssignment.Scope $resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName $resourceCheck.RBAC_SignInName = $roleAssignment.SignInName $resourceCheck.RBAC_RoleDefinitionName = $roleAssignment.RoleDefinitionName $Result += $resourceCheck } } catch { } $Result | Export-Csv -Path $fileName -Append -NoTypeInformation # Get all active subscriptions under this management group $allSubscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name | Where-Object State -eq "Active" # Filter subscriptions if specific subscription IDs were provided if ($SubscriptionIds.Count -gt 0) { $subscriptions = $allSubscriptions | Where-Object { $subscriptionId = $_.Id.Split('/')[-1] # Extract subscription ID from resource path $subscriptionId -in $SubscriptionIds } if ($subscriptions.Count -eq 0) { Write-Host "No matching subscriptions found in management group '$($managementGroup.DisplayName)' for the specified subscription IDs." continue } Write-Host "Processing $($subscriptions.Count) filtered subscription(s) in management group '$($managementGroup.DisplayName)'" } else { $subscriptions = $allSubscriptions Write-Host "Processing all $($subscriptions.Count) active subscription(s) in management group '$($managementGroup.DisplayName)'" } # Process each subscription under the current management group foreach ($subscription in $subscriptions) { Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------" # Extract subscription ID from the full resource path $scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length) $subscriptionId = $scope.Replace("/subscriptions/", "") Write-Host "Subscription [$($subscription.DisplayName) - $subscriptionId]" # Set Azure PowerShell context to the current subscription Set-AzContext -SubscriptionId $subscriptionId | Out-Null Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------" # Initialize result array for this subscription's role assignments [ResourceCheck[]]$Result = @() # Get role assignments directly assigned to this subscription scope try { $roleAssignments = Get-AzRoleAssignment -Scope $scope | Where-Object Scope -eq $scope # Process each subscription-level role assignment foreach($roleAssignment in $roleAssignments) { [ResourceCheck] $resourceCheck = [ResourceCheck]::new() # Set subscription context (no individual resource context at this level) $resourceCheck.ResourceId = "" # No specific resource for subscription assignments $resourceCheck.Kind = "Subscription" # Indicates this is a subscription level assignment $resourceCheck.Location = "" # Subscriptions don't have specific locations $resourceCheck.ResourceGroupName = "" # No resource group context at subscription level $resourceCheck.ManagementGroupId = $managementGroup.Id $resourceCheck.ManagementGroupName = $managementGroup.DisplayName $resourceCheck.SubscriptionId = $subscription.Id $resourceCheck.SubscriptionName = $subscription.DisplayName # Extract subscription-level tags if available $resourceCheck.Tag_Team = $subscription.Tags.team $resourceCheck.Tag_Product = $subscription.Tags.product $resourceCheck.Tag_Environment = $subscription.Tags.environment $resourceCheck.Tag_Data = $subscription.Tags.data $resourceCheck.Tag_Delete = $subscription.Tags.delete $resourceCheck.Tag_Split = $subscription.Tags.split # Populate RBAC assignment details $resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId $resourceCheck.RBAC_Scope = $roleAssignment.Scope $resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName $resourceCheck.RBAC_SignInName = $roleAssignment.SignInName $resourceCheck.RBAC_RoleDefinitionName = $roleAssignment.RoleDefinitionName $Result += $resourceCheck } } catch { # Silently handle any errors during subscription-level role assignment processing } # Export subscription-level role assignments to CSV $Result | Export-Csv -Path $fileName -Append -NoTypeInformation # Get all resource groups within the current subscription $resourceGroups = Get-AzResourceGroup # Process each resource group for RBAC assignments foreach ($resourceGroup in $resourceGroups) { # Initialize result array for this resource group's role assignments [ResourceCheck[]]$Result = @() # Get role assignments at resource group level and below try { $roleAssignments = Get-AzRoleAssignment -Scope $resourceGroup.ResourceId | Where-Object Scope -Like "$($resourceGroup.ResourceId)*" # Process each resource group-level role assignment foreach($roleAssignment in $roleAssignments) { [ResourceCheck] $resourceCheck = [ResourceCheck]::new() # Set resource group context $resourceCheck.ResourceId = $resourceGroup.ResourceId $resourceCheck.Kind = "ResourceGroup" # Indicates this is a resource group level assignment $resourceCheck.Location = $resourceGroup.Location $resourceCheck.ResourceGroupName = $resourceGroup.ResourceGroupName $resourceCheck.ManagementGroupId = $managementGroup.Id $resourceCheck.ManagementGroupName = $managementGroup.DisplayName $resourceCheck.SubscriptionId = $subscription.Id $resourceCheck.SubscriptionName = $subscription.DisplayName # Extract resource group-level tags if available $resourceCheck.Tag_Team = $resourceGroup.Tags.team $resourceCheck.Tag_Product = $resourceGroup.Tags.product $resourceCheck.Tag_Environment = $resourceGroup.Tags.environment $resourceCheck.Tag_Data = $resourceGroup.Tags.data $resourceCheck.Tag_Delete = $resourceGroup.Tags.delete $resourceCheck.Tag_Split = $resourceGroup.Tags.split # Populate RBAC assignment details $resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId $resourceCheck.RBAC_Scope = $roleAssignment.Scope $resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName $resourceCheck.RBAC_SignInName = $roleAssignment.SignInName $resourceCheck.RBAC_RoleDefinitionName = $roleAssignment.RoleDefinitionName $Result += $resourceCheck } } catch { # Silently handle any errors during resource group-level role assignment processing } # Export resource group-level role assignments to CSV $Result | Export-Csv -Path $fileName -Append -NoTypeInformation } # Get all individual resources within the current subscription $allResources = Get-AzResource # Process each individual resource for RBAC assignments foreach ($resource in $allResources) { # Initialize result array for this resource's role assignments [ResourceCheck[]]$Result = @() # Get role assignments directly assigned to this specific resource try { $roleAssignments = Get-AzRoleAssignment -Scope $resource.ResourceId | Where-Object Scope -eq $resource.ResourceId # Process each resource-level role assignment foreach($roleAssignment in $roleAssignments) { [ResourceCheck] $resourceCheck = [ResourceCheck]::new() # Set individual resource context with full metadata $resourceCheck.ResourceId = $resource.ResourceId $resourceCheck.Id = $resource.Id # Additional resource identifier $resourceCheck.Kind = "Resource" # Indicates this is an individual resource assignment $resourceCheck.Location = $resource.Location $resourceCheck.ResourceName = $resource.ResourceName $resourceCheck.ResourceGroupName = $resource.ResourceGroupName $resourceCheck.ResourceType = $resource.ResourceType # e.g., Microsoft.Storage/storageAccounts $resourceCheck.ManagementGroupId = $managementGroup.Id $resourceCheck.ManagementGroupName = $managementGroup.DisplayName $resourceCheck.SubscriptionId = $subscription.Id $resourceCheck.SubscriptionName = $subscription.DisplayName # Extract resource-level tags if available $resourceCheck.Tag_Team = $resource.Tags.team $resourceCheck.Tag_Product = $resource.Tags.product $resourceCheck.Tag_Environment = $resource.Tags.environment $resourceCheck.Tag_Data = $resource.Tags.data $resourceCheck.Tag_Delete = $resource.Tags.delete $resourceCheck.Tag_Split = $resource.Tags.split # Populate RBAC assignment details $resourceCheck.RBAC_RoleAssignmentId = $roleAssignment.RoleAssignmentId $resourceCheck.RBAC_Scope = $roleAssignment.Scope $resourceCheck.RBAC_DisplayName = $roleAssignment.DisplayName $resourceCheck.RBAC_SignInName = $roleAssignment.SignInName $resourceCheck.RBAC_RoleDefinitionName = $roleAssignment.RoleDefinitionName $Result += $resourceCheck } } catch { # Silently handle any errors during individual resource-level role assignment processing } # Export individual resource-level role assignments to CSV $Result | Export-Csv -Path $fileName -Append -NoTypeInformation } } } # Final completion message Write-Host "========================================================================================================================================================================" if ($SubscriptionIds.Count -gt 0) { Write-Host "RBAC analysis complete for $($SubscriptionIds.Count) specified subscription(s)." } else { Write-Host "RBAC analysis complete for all active subscriptions across all management groups." } Write-Host "Results exported to: $fileName" Write-Host "Done."