Files
Cloud-20Engineering/Powershell/Lists/Azure/AzurePIM.ps1
Jurjen Ladenius a226ca97ac added documetation
2025-11-03 08:12:01 +01:00

371 lines
19 KiB
PowerShell

<#
.SYNOPSIS
Exports Azure Privileged Identity Management (PIM) role eligible assignments across all management groups, subscriptions, resource groups, and resources.
.DESCRIPTION
This script comprehensively inventories all PIM eligible role assignments across the entire Azure environment hierarchy.
It traverses management groups, subscriptions, resource groups, and individual resources to collect detailed information
about role eligibility schedules. The script uses Azure REST API calls to retrieve PIM data and exports results to CSV.
The script performs a complete audit of:
- Management Group level PIM assignments
- Subscription level PIM assignments
- Resource Group level PIM assignments
- Individual Resource level PIM assignments
All data is consolidated into a single CSV file with detailed scope and principal information.
.PARAMETER None
This script does not accept parameters. It processes all accessible management groups and their child resources.
.OUTPUTS
CSV file named with timestamp pattern: "yyyy-MM-dd HHmm azure_pim_assignments.csv"
Contains columns for scope hierarchy, role definitions, principals, and assignment metadata.
.EXAMPLE
.\AzurePIM.ps1
Exports all PIM eligible assignments to a timestamped CSV file in the current directory.
.NOTES
Author: Cloud Engineering Team
Version: 1.0
Created: 2024
Prerequisites:
- Azure PowerShell module (Az) must be installed
- User must be authenticated (Connect-AzAccount)
- Requires appropriate permissions to read PIM assignments across all scopes
- Tenant ID is hardcoded and may need adjustment for different environments
Security Considerations:
- Script uses Azure access tokens for REST API authentication
- Requires elevated permissions to access PIM data across all scopes
- Output file contains sensitive role assignment information
Performance Notes:
- Processing time varies significantly based on environment size
- Script processes resources sequentially which may take considerable time
- Consider running during off-peak hours for large environments
.LINK
https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/
https://docs.microsoft.com/en-us/rest/api/authorization/roleeligibilityscheduleinstances
#>
#Requires -Modules Az
#Connect-AzAccount
# Class definition for structured PIM assignment data
class ResourceCheck {
# Hierarchy level indicators
[string] $Level = "" # Management Group, Subscription, Resource Group, or Resource
[string] $ManagementGroupId = "" # Management group identifier
[string] $ManagementGroupName = "" # Management group display name
[string] $SubscriptionId = "" # Azure subscription GUID
[string] $SubscriptionName = "" # Subscription display name
# Resource identification
[string] $ResourceId = "" # Full Azure resource identifier
[string] $ResourceGroup = "" # Resource group name
[string] $ResourceName = "" # Individual resource name
[string] $ResourceType = "" # Azure resource type
# PIM assignment details
[string] $RoleEligibilityScheduleId = "" # Unique PIM schedule identifier
[string] $Scope = "" # Assignment scope path
[string] $RoleDefinitionId = "" # Azure RBAC role definition ID
[string] $RoleDefinitionName = "" # Human-readable role name
[string] $RoleDefinitionType = "" # Role definition type
# Principal (user/group/service principal) information
[string] $PrincipalId = "" # Principal object ID
[string] $PrincipalName = "" # Principal display name
[string] $PrincipalType = "" # User, Group, or ServicePrincipal
# Assignment metadata
[string] $Status = "" # Assignment status (Active, Eligible, etc.)
[string] $StartDateTime = "" # Assignment start date/time
[string] $EndDateTime = "" # Assignment expiration date/time
[string] $CreatedOn = "" # Assignment creation timestamp
}
<#
.SYNOPSIS
Retrieves PIM eligible role assignments for a specified scope using Azure Management REST API.
.DESCRIPTION
This function queries the Azure Management REST API to retrieve role eligibility schedule instances
for a given scope. It handles authentication using Azure access tokens and filters results to
exclude inherited assignments.
.PARAMETER scope
The Azure resource scope path to query for PIM assignments. Can be management group, subscription,
resource group, or individual resource scope.
.OUTPUTS
Returns an array of PIM assignment objects with properties containing assignment details,
or empty string if no assignments found.
.EXAMPLE
GetEligibleAssignments -scope "subscriptions/12345678-1234-1234-1234-123456789012"
Retrieves all PIM eligible assignments for the specified subscription.
.NOTES
- Uses hardcoded tenant ID which may need adjustment for different environments
- REST API version 2020-10-01 is used for compatibility
- Filters out inherited assignments to show only direct assignments
#>
function GetEligibleAssignments {
param (
[Parameter(Mandatory = $true)]
[string] $scope
)
try {
# Get Azure access token for the specified tenant
# Note: Tenant ID is hardcoded and should be updated for different environments
$access_token_secure = (Get-AzAccessToken -TenantId "e9792fd7-4044-47e7-a40d-3fba46f1cd09").Token
$access_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($access_token_secure))
# Construct REST API URL for role eligibility schedule instances
$url = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01&`$filter=atScope()"
# Create authorization header with bearer token
$head = @{ Authorization = " Bearer $access_token" }
# Execute REST API call to retrieve PIM assignments
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
# Process response and filter out inherited assignments
$response | ForEach-Object {
$responseValue = $_.value
if ($responseValue.Length -gt 0) {
# Return only direct assignments (exclude inherited)
return $responseValue | ForEach-Object {
return ($_.properties | Where-Object MemberType -NE "Inherited")
}
}
else {
# Return empty string if no assignments found
return ""
}
}
}
catch {
Write-Warning "Failed to retrieve PIM assignments for scope: $scope. Error: $($_.Exception.Message)"
return ""
}
}
# Main script execution begins
Write-Host "======================================================================================================================================================================"
Write-Host "Creating comprehensive PIM assignments overview across all Azure scopes."
Write-Host "======================================================================================================================================================================"
# Generate timestamped filename for CSV export
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date azure_pim_assignments.csv"
Write-Host "Output file: $fileName"
# Retrieve all management groups accessible to the current user
Write-Host "Retrieving management groups..."
$managementGroups = Get-AzManagementGroup
Write-Host "Found $($managementGroups.Count) management group(s) to process."
# Process each management group for PIM assignments
foreach ($managementGroup in $managementGroups)
{
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
Write-Host "Processing Management Group: [$($managementGroup.Name)] - $($managementGroup.DisplayName)"
# Retrieve PIM assignments at management group level
$assignments = GetEligibleAssignments -scope "providers/Microsoft.Management/managementGroups/$($managementGroup.Name)"
# Process management group level assignments
[ResourceCheck[]]$Result = @()
foreach ($assignment in $assignments) {
# Create structured object for management group assignment
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.Level = "Management Group"
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.RoleEligibilityScheduleId = $assignment.roleEligibilityScheduleId
$resourceCheck.Scope = $assignment.scope
$resourceCheck.RoleDefinitionId = $assignment.roleDefinitionId
$resourceCheck.RoleDefinitionName = $assignment.expandedProperties.roleDefinition.displayName
$resourceCheck.RoleDefinitionType = $assignment.expandedProperties.roleDefinition.type
$resourceCheck.PrincipalId = $assignment.principalId
$resourceCheck.PrincipalName = $assignment.expandedProperties.principal.displayName
$resourceCheck.PrincipalType = $assignment.principalType
$resourceCheck.Status = $assignment.status
$resourceCheck.StartDateTime = $assignment.startDateTime
$resourceCheck.EndDateTime = $assignment.endDateTime
$resourceCheck.CreatedOn = $assignment.createdOn
$Result += $resourceCheck
}
# Export management group assignments to CSV
if ($Result.Count -gt 0) {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
Write-Host " Exported $($Result.Count) management group assignment(s)"
}
# Retrieve active subscriptions within the management group
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name | Where-Object State -eq "Active"
Write-Host " Found $($subscriptions.Count) active subscription(s) in management group"
# Process each subscription for PIM assignments
foreach ($subscription in $subscriptions)
{
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
# Extract subscription ID from the full path
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
$subscriptionId = $scope.Replace("/subscriptions/", "")
Write-Host "Processing Subscription: [$($subscription.DisplayName)] - $subscriptionId"
# Set Azure context to the current subscription
Set-AzContext -SubscriptionId $subscriptionId | Out-Null
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
# Retrieve PIM assignments at subscription level
$assignments = GetEligibleAssignments -scope $scope
# Process subscription level assignments
[ResourceCheck[]]$Result = @()
foreach ($assignment in $assignments) {
# Create structured object for subscription assignment
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.Level = "Subscription"
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscriptionId
$resourceCheck.SubscriptionName = $subscription.DisplayName
$resourceCheck.RoleEligibilityScheduleId = $assignment.roleEligibilityScheduleId
$resourceCheck.Scope = $assignment.scope
$resourceCheck.RoleDefinitionId = $assignment.roleDefinitionId
$resourceCheck.RoleDefinitionName = $assignment.expandedProperties.roleDefinition.displayName
$resourceCheck.RoleDefinitionType = $assignment.expandedProperties.roleDefinition.type
$resourceCheck.PrincipalId = $assignment.principalId
$resourceCheck.PrincipalName = $assignment.expandedProperties.principal.displayName
$resourceCheck.PrincipalType = $assignment.principalType
$resourceCheck.Status = $assignment.status
$resourceCheck.StartDateTime = $assignment.startDateTime
$resourceCheck.EndDateTime = $assignment.endDateTime
$resourceCheck.CreatedOn = $assignment.createdOn
$Result += $resourceCheck
}
# Export subscription assignments to CSV
if ($Result.Count -gt 0) {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
Write-Host " Exported $($Result.Count) subscription assignment(s)"
}
# Retrieve all resource groups in the current subscription
$allResourceGroups = Get-AzResourceGroup
Write-Host " Found $($allResourceGroups.Count) resource group(s) to process"
# Process each resource group for PIM assignments
foreach ($group in $allResourceGroups) {
Write-Host " Processing Resource Group: $($group.ResourceGroupName)"
# Retrieve PIM assignments at resource group level
$assignments = GetEligibleAssignments -scope $group.ResourceId
# Process resource group level assignments
[ResourceCheck[]]$Result = @()
foreach ($assignment in $assignments) {
# Create structured object for resource group assignment
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.Level = "Resource Group"
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscriptionId
$resourceCheck.SubscriptionName = $subscription.DisplayName
$resourceCheck.ResourceGroup = $group.ResourceGroupName
$resourceCheck.RoleEligibilityScheduleId = $assignment.roleEligibilityScheduleId
$resourceCheck.Scope = $assignment.scope
$resourceCheck.RoleDefinitionId = $assignment.roleDefinitionId
$resourceCheck.RoleDefinitionName = $assignment.expandedProperties.roleDefinition.displayName
$resourceCheck.RoleDefinitionType = $assignment.expandedProperties.roleDefinition.type
$resourceCheck.PrincipalId = $assignment.principalId
$resourceCheck.PrincipalName = $assignment.expandedProperties.principal.displayName
$resourceCheck.PrincipalType = $assignment.principalType
$resourceCheck.Status = $assignment.status
$resourceCheck.StartDateTime = $assignment.startDateTime
$resourceCheck.EndDateTime = $assignment.endDateTime
$resourceCheck.CreatedOn = $assignment.createdOn
$Result += $resourceCheck
}
# Export resource group assignments to CSV
if ($Result.Count -gt 0) {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
Write-Host " Exported $($Result.Count) resource group assignment(s)"
}
# Retrieve all resources within the current resource group
$allResources = Get-AzResource -ResourceGroupName $group.ResourceGroupName
# Process each individual resource for PIM assignments
foreach ($resource in $allResources)
{
# Retrieve PIM assignments at individual resource level
$assignments = GetEligibleAssignments -scope $resource.ResourceId
# Process individual resource level assignments
[ResourceCheck[]]$Result = @()
foreach ($assignment in $assignments) {
# Create structured object for resource assignment
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.Level = "Resource"
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscriptionId
$resourceCheck.SubscriptionName = $subscription.DisplayName
$resourceCheck.ResourceGroup = $group.ResourceGroupName
$resourceCheck.ResourceId = $resource.ResourceId
$resourceCheck.ResourceName = $resource.Name
$resourceCheck.ResourceType = $resource.ResourceType
$resourceCheck.RoleEligibilityScheduleId = $assignment.roleEligibilityScheduleId
$resourceCheck.Scope = $assignment.scope
$resourceCheck.RoleDefinitionId = $assignment.roleDefinitionId
$resourceCheck.RoleDefinitionName = $assignment.expandedProperties.roleDefinition.displayName
$resourceCheck.RoleDefinitionType = $assignment.expandedProperties.roleDefinition.type
$resourceCheck.PrincipalId = $assignment.principalId
$resourceCheck.PrincipalName = $assignment.expandedProperties.principal.displayName
$resourceCheck.PrincipalType = $assignment.principalType
$resourceCheck.Status = $assignment.status
$resourceCheck.StartDateTime = $assignment.startDateTime
$resourceCheck.EndDateTime = $assignment.endDateTime
$resourceCheck.CreatedOn = $assignment.createdOn
$Result += $resourceCheck
}
# Export individual resource assignments to CSV
if ($Result.Count -gt 0) {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
Write-Host " Exported $($Result.Count) assignment(s) for resource: $($resource.Name)"
}
}
}
}
}
# Script completion summary
Write-Host "======================================================================================================================================================================"
Write-Host "PIM assignments export completed successfully."
Write-Host "Results saved to: $fileName"
Write-Host ""
Write-Host "Summary:"
Write-Host "- Processed $($managementGroups.Count) management group(s)"
Write-Host "- Traversed all subscriptions, resource groups, and individual resources"
Write-Host "- Exported all PIM eligible role assignments to CSV format"
Write-Host ""
Write-Host "Note: Review the CSV file for comprehensive PIM assignment details across all Azure scopes."
Write-Host "======================================================================================================================================================================"