mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 10:45:02 +01:00
371 lines
19 KiB
PowerShell
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 "======================================================================================================================================================================"
|
|
|