๐ฐ Automating Access Package Creation for Entra ID roles with PowerShell
Discover the magic of automating Entra ID roles with PowerShell and GraphAPI in Azure.
๐ฐ Automating Access Package Creation for Entra ID roles with PowerShell
Greetings, fellow wizards of the tech realm! ๐งโโ๏ธ Today, we embark on a mystical journey into the enchanted forest of PowerShell scripting and Microsoft Graph. Our quest? To automate the creation and management of Access Packages for Entra ID roles. So grab your wands (or keyboards) and letโs dive in!
๐ญ The Quest Begins: Understanding Our Mission
In the ever-evolving landscape of cloud security, managing access to Entra ID (formerly Azure AD) admin roles efficiently is crucial. Microsoft has recently empowered us with the ability to assign Entra ID roles through Access Packages in Identity Governance. This new feature allows for more granular control and easier management of administrative access.
Our mission today is to create a set of Access Packages designed to assign Entra ID Admin roles automatically. By automating this process, weโll:
- Save valuable time for IT administrators
- Reduce human error in role assignments
- Ensure access reviews on critical admin roles
- Improve security by standardizing the approval process for admin roles
Note: The script had a small bug so I updated the full version of the script. The script is now fully functional and ready for use. 03-10-2024
You can find the script here on GitHub: Create-Access-Package - Github Create-Access-Package - RAW
๐ Gathering Our Magical Artifacts
Before we begin our incantations, we must gather the essential magical artifacts โ our PowerShell modules. These modules are the conduits through which weโll channel the power of Microsoft Graph to bend Entra ID to our will.
1
2
3
4
5
6
7
8
9
10
11
# Import required modules
Write-Host "๐ฎ Summoning PowerShell modules..."
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Beta.Identity.Governance
Import-Module Microsoft.Graph.Identity.DirectoryManagement
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Groups
# Authenticate to Microsoft Graph
Write-Host "๐งโโ๏ธ Authenticating to Microsoft Graph..."
Connect-MgGraph -Scopes "Directory.Read.All", "EntitlementManagement.ReadWrite.All"
Letโs break down these modules and their purposes:
Microsoft.Graph.Authentication
: This is our key to the kingdom, allowing us to authenticate with Microsoft Graph.Microsoft.Graph.Beta.Identity.Governance
: This module grants access to Identity Governance features, crucial for working with Access Packages.Microsoft.Graph.Identity.DirectoryManagement
: Weโll use this to interact with directory objects, including roles and role assignments.Microsoft.Graph.Users
andMicrosoft.Graph.Groups
: These modules help us work with user and group objects in Entra ID.
๐ฐ The Arcane Knowledge: Permissions Required
Beware, young apprentice! To perform this powerful magic, you must possess the right scrolls of power. You need to wield the Global Administrator role to add Entra ID roles to the Catalog. However, our script will exclude the Global Administrator role itself from the Access Packages for security reasons.
๐ Pro Tip: Always test your spells in a demo realm before unleashing them upon your production kingdom. This ensures you donโt accidentally turn your users into digital toads!
โ๏ธ Selecting the Chosen Ones: The Cloud Admin Group
In the realm of best practices, we use dedicated Cloud Admin accounts. These accounts are the keepers of the cloud, governed by different policy sets. Weโll use an interactive selection spell to choose the appropriate group for these cloud admin accounts.
1
2
3
4
5
6
7
8
9
10
11
# Function to select a group
function Select-Group {
Write-Host "๐ Fetching dynamic groups for selection..."
$groups = Get-MgGroup -Filter "groupTypes/any(g:g eq 'DynamicMembership')" | Select-Object DisplayName, Id
Write-Host "๐ฎ Select the Cloud Admin Group..."
$selectedGroup = $groups | Out-ConsoleGridView -Title "Select the Cloud Admin Group" -OutputMode Single
if ($null -eq $selectedGroup) {
throw "No group selected. Exiting script."
}
return $selectedGroup
}
This function fetches all dynamic groups in your Entra ID and presents them in a grid view. You can then select the group that contains your cloud admin accounts. This approach ensures flexibility and allows you to easily update the script if your admin group changes in the future.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# Function to get the current user's Object ID
function Get-CurrentUserObjectId {
$currentUser = Get-MgContext
$user = Get-MgUser -UserId $currentUser.Account
return $user.Id
}
# Function to check and activate PIM roles
function Ensure-PimRolesActive {
param (
[string[]]$RequiredRoles = @(
"Global Administrator",
"Identity Governance Administrator"
)
)
$currentUserObjectId = Get-CurrentUserObjectId
Write-Host "Current user Object ID: $currentUserObjectId"
$activationPerformed = $false
foreach ($roleName in $RequiredRoles) {
# Get the role definition
$roleDefinition = Get-MgRoleManagementDirectoryRoleDefinition -Filter "displayName eq '$roleName'"
if ($null -eq $roleDefinition) {
Write-Host "โ Role definition for $roleName not found. Exiting script." -ForegroundColor Red
return $false
}
$roleId = $roleDefinition.Id
# Check if the role is already active
$activeAssignments = Get-MgRoleManagementDirectoryRoleAssignmentScheduleInstance -Filter "roleDefinitionId eq '$roleId' and principalId eq '$currentUserObjectId'"
if ($activeAssignments) {
Write-Host "โ
PIM role $roleName is already active." -ForegroundColor Green
} else {
Write-Host "๐งโโ๏ธ Activating PIM role $roleName for 1 hour..."
$params = @{
action = "selfActivate"
principalId = $currentUserObjectId
roleDefinitionId = $roleId
directoryScopeId = "/"
justification = "Script execution: Activating $roleName role"
scheduleInfo = @{
startDateTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
expiration = @{
type = "AfterDuration"
duration = "PT1H"
}
}
}
try {
New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params
Write-Host "โจ PIM role $roleName activation requested." -ForegroundColor Green
$activationPerformed = $true
} catch {
Write-Host "โ Failed to activate PIM role $roleName. Error: $_" -ForegroundColor Red
return $false
}
}
}
if ($activationPerformed) {
Write-Host "๐ Reconnecting to Microsoft Graph to apply new permissions..." -ForegroundColor Yellow
Disconnect-MgGraph | Out-Null
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory", "Directory.Read.All"
Write-Host "โ
Reconnected to Microsoft Graph. New permissions are now active." -ForegroundColor Green
}
return $true
}
This function activates the correct PIM roles in order to execute the script
๐จ Crafting the Access Package Spell
Our Get-OrCreateAccessPackage
function is the cornerstone of our automation. It ensures we donโt duplicate our efforts by checking if the package already exists before creating a new one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function Get-OrCreateAccessPackage {
param (
[Parameter(Mandatory=$true)]
[string]$RoleName,
[Parameter(Mandatory=$true)]
[string]$RoleId,
[Parameter(Mandatory=$true)]
[string]$CatalogId
)
$displayName = "Access Package - $RoleName"
# Check if the access package already exists
Write-Host "๐ Searching for existing access package: $displayName..."
$existingPackage = Get-MgBetaEntitlementManagementAccessPackage -Filter "displayName eq '$displayName'" -All | Where-Object { $_.CatalogId -eq $CatalogId }
if ($existingPackage) {
Write-Host "โ ๏ธ Access package for $RoleName already exists. Skipping creation." -ForegroundColor Yellow
return $existingPackage
}
$params = @{
DisplayName = $displayName
Description = "Access package for $RoleName role"
CatalogId = $CatalogId
IsHidden = $false
}
Write-Host "โจ Creating new access package: $displayName..."
$newPackage = New-MgBetaEntitlementManagementAccessPackage -BodyParameter $params
Write-Host "โ
Access package created successfully for $RoleName" -ForegroundColor Green
return $newPackage
}
This function performs the following magical feats:
- Checks if an Access Package for the given role already exists
- If it doesnโt exist, creates a new Access Package with a standardized name and description
- Returns the existing or newly created Access Package
By using this function, we ensure our script is idempotent โ it can be run multiple times without creating duplicate packages.
๐ Adding Role Scope: The Arcane Ritual
The Add-RoleScopeToAccessPackage
function is where the real magic happens. It binds the Entra ID role to the access package, ensuring the role resource exists in the catalog.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
function Add-RoleScopeToAccessPackage {
param (
[Parameter(Mandatory=$true)]
[string]$AccessPackageId,
[Parameter(Mandatory=$true)]
[string]$CatalogId,
[Parameter(Mandatory=$true)]
[object]$Role
)
Write-Host "๐ Attempting to add role scope for $($Role.DisplayName) to access package $AccessPackageId" -ForegroundColor Yellow
try {
$roleResource = Get-MgEntitlementManagementCatalogResource -AccessPackageCatalogId $CatalogId -Filter "originId eq '$($Role.RoleTemplateId)' and originSystem eq 'DirectoryRole'"
if (-not $roleResource) {
Write-Host "โ ๏ธ Role resource not found in catalog. Attempting to add it..." -ForegroundColor Yellow
Add-EntraRoleToCatalog -CatalogId $CatalogId -Role $Role
Start-Sleep -Seconds 10 # Wait for a bit to ensure the role is added
$roleResource = Get-MgEntitlementManagementCatalogResource -AccessPackageCatalogId $CatalogId -Filter "originId eq '$($Role.RoleTemplateId)' and originSystem eq 'DirectoryRole'"
}
if (-not $roleResource) {
throw "โ Failed to find or add role resource to catalog"
}
Write-Host "โ
Role resource found in catalog: $($roleResource.DisplayName)" -ForegroundColor Green
}
catch {
Write-Host "โ Failed to retrieve or add role resource: $_" -ForegroundColor Red
return
}
$params = @{
AccessPackageId = $AccessPackageId
BodyParameter = @{
accessPackageResourceRole = @{
originId = "Eligible"
displayName = "Eligible Member"
originSystem = "DirectoryRole"
accessPackageResource = @{
id = $roleResource.Id
resourceType = "Built-in"
originId = $Role.RoleTemplateId
originSystem = "DirectoryRole"
}
}
accessPackageResourceScope = @{
originId = $Role.RoleTemplateId
originSystem = "DirectoryRole"
}
}
}
try {
Write-Host "โจ Adding role scope to access package..."
$result = New-MgBetaEntitlementManagementAccessPackageResourceRoleScope @params -ErrorAction Stop
Write-Host "โ
Successfully added role scope for $($Role.DisplayName) to access package." -ForegroundColor Green
return $result
}
catch {
Write-Host "โ Failed to add role scope for $($Role.DisplayName) to access package: $_" -ForegroundColor Red
Write-Host "Request details:" -ForegroundColor Yellow
$params | ConvertTo-Json -Depth 10 | Write-Host
}
}
This function performs these mystical steps:
- Checks if the role resource exists in the catalog
- If it doesnโt exist, adds the role to the catalog
- Creates a role scope in the Access Package, linking it to the Entra ID role
This ensures that when someone is granted the Access Package, they receive the correct Entra ID role permissions.
๐ Conjuring the Catalog
Before we can summon the roles, we need a magical repository โ a catalog. Our Get-OrCreateCatalog
function creates this mystical container if it doesnโt already exist.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Get-OrCreateCatalog {
param (
[string]$CatalogName
)
Write-Host "๐ Searching for catalog: $CatalogName..."
$catalog = Get-MgEntitlementManagementCatalog -Filter "displayName eq '$CatalogName'" -All
if ($null -eq $catalog) {
Write-Host "โ ๏ธ Catalog '$CatalogName' not found. Creating new catalog..." -ForegroundColor Yellow
$newCatalog = @{
displayName = $CatalogName
description = "Catalog for Entra Admin roles"
isExternallyVisible = $false
}
$catalog = New-MgEntitlementManagementCatalog -BodyParameter $newCatalog
Write-Host "โ
Catalog '$CatalogName' created successfully." -ForegroundColor Green
}
return $catalog
}
This function either retrieves an existing catalog or creates a new one, ensuring we have a place to store our Access Packages.
โจ Adding Roles to the Catalog: A Bit of Hocus Pocus
The Add-EntraRoleToCatalog
function adds our Entra ID roles to the catalog, ensuring each role is ready for use in our access packages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function Add-EntraRoleToCatalog {
param (
[Parameter(Mandatory=$true)]
[string]$CatalogId,
[Parameter(Mandatory=$true)]
[object]$Role
)
Write-Host "๐ Checking if role $($Role.DisplayName) exists in the catalog..."
$existingResource = Get-MgEntitlementManagementCatalogResource -AccessPackageCatalogId $CatalogId -Filter "originId eq '$($Role.RoleTemplateId)' and originSystem eq 'DirectoryRole'"
if ($existingResource) {
Write-Host "โ ๏ธ Role $($Role.DisplayName) already exists in the catalog. Skipping." -ForegroundColor Yellow
return
}
$params = @{
catalogId = $CatalogId
requestType = "AdminAdd"
accessPackageResource = @{
displayName = $Role.DisplayName
description = $Role.Description
resourceType = "Built-in"
originId = $Role.RoleTemplateId
originSystem = "DirectoryRole"
}
justification = "Adding Directory Role to Catalog"
}
try {
Write-Host "โจ Adding role $($Role.DisplayName) to the catalog..."
$null = New-MgBetaEntitlementManagementAccessPackageResourceRequest -BodyParameter $params
Write-Host "โ
Successfully added $($Role.DisplayName) to the catalog." -ForegroundColor Green
}
catch {
Write-Host "โ Failed to add $($Role.DisplayName) to the catalog: $_" -ForegroundColor Red
}
}
This function checks if the role already exists in the catalog and adds it if it doesnโt. This step is crucial, as roles must be in the catalog before we can include them in Access Packages.
๐ Enchanting the Access Packages with Policies
Our policy creation functions, Get-ApprovalSettings
and Get-AccessReviewSettings
, have been significantly improved to provide a more user-friendly experience:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
function Get-ApprovalSettings {
param (
[Parameter(Mandatory = $true)]
[array]$EntraIDUsers
)
$approvalMode = Read-Host "๐ฎ Do you want a 1 or 2 step verification process? (Enter '1' or '2')"
$firstStageApprovers = @()
$secondStageApprovers = @()
if ($approvalMode -eq '1' -or $approvalMode -eq '2') {
$approverType = Read-Host "๐ฎ Choose the approver type for the first stage:
1. Manager
2. Specific User(s)
Enter the number of your choice (1 or 2)"
switch ($approverType) {
"1" {
# Manager
$firstStageApprovers += @{
"@odata.type" = "#microsoft.graph.requestorManager"
managerLevel = 1
}
# Select multiple backup approvers
$backupApprovers = $EntraIDUsers | Out-ConsoleGridView -Title "Select backup user(s) for first stage" -OutputMode Multiple
foreach ($approver in $backupApprovers) {
$firstStageApprovers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $approver.Id
isBackup = $true
}
Write-Host "Added $($approver.DisplayName) as a backup approver."
}
}
"2" {
# Specific User(s)
# Select multiple primary approvers
$primaryApprovers = $EntraIDUsers | Out-ConsoleGridView -Title "Select primary approver(s) for first stage" -OutputMode Multiple
foreach ($approver in $primaryApprovers) {
$firstStageApprovers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $approver.Id
}
Write-Host "Added $($approver.DisplayName) as a primary approver."
}
# Select multiple backup approvers
$backupApprovers = $EntraIDUsers | Out-ConsoleGridView -Title "Select backup user(s) for first stage" -OutputMode Multiple
foreach ($approver in $backupApprovers) {
$firstStageApprovers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $approver.Id
isBackup = $true
}
Write-Host "Added $($approver.DisplayName) as a backup approver."
}
}
default {
throw "Invalid approver type selected. Exiting script."
}
}
}
if ($approvalMode -eq '2') {
# Second stage
$secondStageApproverType = Read-Host "๐ฎ Choose the approver type for the second stage:
1. Manager
2. Specific User(s)
Enter the number of your choice (1 or 2)"
switch ($secondStageApproverType) {
"1" {
# Manager
$secondStageApprovers += @{
"@odata.type" = "#microsoft.graph.requestorManager"
managerLevel = 1
}
}
"2" {
# Specific User(s)
# Select multiple primary approvers for second stage
$primaryApprovers = $EntraIDUsers | Out-ConsoleGridView -Title "Select primary approver(s) for second stage" -OutputMode Multiple
foreach ($approver in $primaryApprovers) {
$secondStageApprovers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $approver.Id
}
Write-Host "Added $($approver.DisplayName) as a primary approver for second stage."
}
}
default {
throw "Invalid approver type selected for second stage. Exiting script."
}
}
# Select multiple backup approvers for second stage
$backupApprovers = $EntraIDUsers | Out-ConsoleGridView -Title "Select backup user(s) for second stage" -OutputMode Multiple
foreach ($approver in $backupApprovers) {
$secondStageApprovers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $approver.Id
isBackup = $true
}
Write-Host "Added $($approver.DisplayName) as a backup approver for second stage."
}
}
elseif ($approvalMode -ne '1') {
throw "Invalid verification process selected. Exiting script."
}
return @{
ApprovalMode = $approvalMode
FirstStageApprovers = $firstStageApprovers
SecondStageApprovers = $secondStageApprovers
}
}
function Get-AccessReviewSettings {
param (
[Parameter(Mandatory = $true)]
[array]$EntraIDUsers
)
$enableAccessReview = Read-Host "๐ฎ Do you want to enable access reviews? (Enter 'Yes' or 'No')"
if ($enableAccessReview -eq 'Yes') {
$reviewerType = Read-Host "๐ฎ Should the manager be the reviewer? (Enter 'Yes' or 'No')"
$backupReviewers = $EntraIDUsers | Out-ConsoleGridView -Title "Select backup reviewer(s) for access review" -OutputMode Multiple
$reviewers = @()
foreach ($reviewer in $backupReviewers) {
$reviewers += @{
"@odata.type" = "#microsoft.graph.singleUser"
userId = $reviewer.Id
isBackup = $true
}
Write-Host "Added $($reviewer.DisplayName) as a backup reviewer for access review."
}
return @{
isEnabled = $true
recurrenceType = "quarterly"
reviewerType = if ($reviewerType -eq 'Yes') { "Manager" } else { "Self" }
startDateTime = (Get-Date).AddDays(1).ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
durationInDays = 25
reviewers = $reviewers
isAccessRecommendationEnabled = $true
isApprovalJustificationRequired = $true
accessReviewTimeoutBehavior = "keepAccess"
}
}
return @{ isEnabled = $false }
}
These functions allow you to interactively:
- Choose between a one or two-step approval process
- Select managers or specific users as approvers
- Set up backup approvers for each stage
- Configure access review settings
By separating these settings, we make our script more modular and easier to maintain.
Finally, we use the Add-PolicyToAccessPackage
function to apply these settings to our Access Package:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
function Add-PolicyToAccessPackage {
param (
[Parameter(Mandatory = $true)]
[string]$AccessPackageId,
[Parameter(Mandatory = $true)]
[string]$RoleName,
[Parameter(Mandatory = $true)]
[string]$GroupId,
[Parameter(Mandatory = $true)]
[hashtable]$ApprovalSettings,
[Parameter(Mandatory = $true)]
[hashtable]$AccessReviewSettings
)
Write-Host "๐ Processing policy for access package..." -ForegroundColor Yellow
$policyDisplayName = "Policy for assigning $RoleName"
# Check if policy already exists
$existingPolicies = Get-MgBetaEntitlementManagementAccessPackageAssignmentPolicy -All
$existingPolicy = $existingPolicies | Where-Object { $_.AccessPackageId -eq $AccessPackageId -and $_.DisplayName -eq $policyDisplayName }
$policyParams = @{
accessPackageId = $AccessPackageId
displayName = $policyDisplayName
description = "Policy for requesting the following EntraID role: $RoleName"
canExtend = $false
durationInDays = 365
requestorSettings = @{
scopeType = "SpecificDirectorySubjects"
acceptRequests = $true
allowedRequestors = @(
@{
"@odata.type" = "#microsoft.graph.groupMembers"
groupId = $GroupId
}
)
}
requestApprovalSettings = @{
isApprovalRequired = $true
isApprovalRequiredForExtension = $false
isRequestorJustificationRequired = $true
approvalMode = "Serial"
approvalStages = @(
@{
approvalStageTimeOutInDays = 14
isApproverJustificationRequired = $true
isEscalationEnabled = $false
primaryApprovers = $ApprovalSettings.FirstStageApprovers
}
)
}
accessReviewSettings = $AccessReviewSettings
questions = @(
@{
isRequired = $true
text = @{
defaultText = "Why do you require this role?"
localizedTexts = @()
}
"@odata.type" = "#microsoft.graph.accessPackageTextInputQuestion"
isSingleLineQuestion = $false
}
)
}
if ($ApprovalSettings.ApprovalMode -eq '2') {
$policyParams.requestApprovalSettings.approvalStages += @{
approvalStageTimeOutInDays = 14
isApproverJustificationRequired = $true
isEscalationEnabled = $false
primaryApprovers = $ApprovalSettings.SecondStageApprovers
}
}
try {
if ($existingPolicy) {
Write-Host "โ ๏ธ Policy already exists. Updating existing policy." -ForegroundColor Yellow
$result = Set-MgBetaEntitlementManagementAccessPackageAssignmentPolicy -AccessPackageAssignmentPolicyId $existingPolicy.Id -BodyParameter $policyParams
Write-Host "โ
Successfully updated policy for $RoleName." -ForegroundColor Green
}
else {
Write-Host "โจ Creating new policy..." -ForegroundColor Yellow
$result = New-MgBetaEntitlementManagementAccessPackageAssignmentPolicy -BodyParameter $policyParams
Write-Host "โ
Successfully created new policy for $RoleName." -ForegroundColor Green
}
return $result
}
catch {
Write-Host "โ Failed to create or update policy: $_" -ForegroundColor Red
Write-Host "Policy details:" -ForegroundColor Yellow
$policyParams | ConvertTo-Json -Depth 10 | Write-Host
}
}
This function creates or updates the assignment policy for the Access Package, incorporating our approval and review settings.
๐ญ The Grand Finale: Bringing It All Together
Our main script now excludes the Global Administrator role and uses our new approval settings function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Main script section
Write-Host "๐ฎ Starting the magical process..."
# ... [Authentication and module import] ...
$defaultCatalogName = "Entra ID Admin Roles"
$catalogName = Read-Host "๐ฎ Enter the name of the catalog to create or update access packages for: [$defaultCatalogName]"
if ([string]::IsNullOrEmpty($catalogName)) {
$catalogName = $defaultCatalogName
}
$catalog = Get-OrCreateCatalog -CatalogName $catalogName
# Get all Directory roles, excluding Global Administrator
Write-Host "๐ Gathering all Directory roles (excluding Global Administrator)..."
$directoryRoles = Get-MgDirectoryRole -All | Where-Object {
$null -ne $_.RoleTemplateId -and
$_.DisplayName -ne "Global Administrator" -and
$_.DisplayName -ne "Company Administrator"
}
Write-Host "Found $($directoryRoles.Count) eligible directory roles."
# Get all enabled Entra ID users
Write-Host "๐ฅ Fetching all enabled Entra ID users..."
$EntraIDUsers = Get-MgUser -Filter "AccountEnabled eq true" -All | Select-Object DisplayName, Id
# Select the cloud admin group
$cloudAdminGroup = Select-Group
Write-Host "โ
Selected Cloud Admin Group: $($cloudAdminGroup.DisplayName)"
# Get approval settings once
$approvalSettings = Get-ApprovalSettings -EntraIDUsers $EntraIDUsers
# Get access review settings once
$accessReviewSettings = Get-AccessReviewSettings -EntraIDUsers $EntraIDUsers
# Add each role to the catalog and create access packages
foreach ($role in $directoryRoles) {
$roleName = $role.DisplayName
Write-Host "๐ฎ Processing access package for role: $roleName"
try {
Write-Host "โจ Adding role $($role.DisplayName) to catalog..."
Add-EntraRoleToCatalog -CatalogId $catalog.Id -Role $role
$accessPackage = Get-OrCreateAccessPackage -RoleName $roleName -CatalogId $catalog.Id -RoleId $role.RoleTemplateId
# Add role scope to the access package
Add-RoleScopeToAccessPackage -AccessPackageId $accessPackage.Id -Role $role -CatalogId $catalog.Id
# Add policy to the access package
Add-PolicyToAccessPackage -AccessPackageId $accessPackage.Id -RoleName $roleName -GroupId $cloudAdminGroup.Id -ApprovalSettings $approvalSettings -AccessReviewSettings $accessReviewSettings
}
catch {
Write-Host "โ Failed to process access package for '$roleName': $_" -ForegroundColor Red
}
}
Write-Host "๐ Process completed. Access packages have been created or updated for all eligible Directory roles in the '$catalogName' catalog."
# Disconnect from Microsoft Graph
Write-Host "๐ Disconnecting from Microsoft Graph..."
Disconnect-MgGraph
This script:
- Authenticates to Microsoft Graph
- Creates or retrieves the catalog
- Fetches all Entra ID roles (excluding Global Administrator)
- Selects the Cloud Admin group
- Gets approval and access review settings
- For each role, creates an Access Package, adds the role scope, and applies the policy
By the end of this magical performance, youโll have a fully automated system for managing access to your Entra ID admin roles!
๐ Conclusion: The Magic is in Your Hands
And there you have it, dear wizards! Our spellbook of PowerShell scripts to automate Access Package creation is complete. With this magic, you can ensure that access to your Entra ID admin roles (except Global Administrator) is consistently managed, properly approved, and regularly reviewed.
Remember, with great power comes great responsibility. Use these scripts wisely, always test in a non-production environment first, and may your access governance be ever strong and your security posture unbreakable!
๐ Further Learning: To deepen your understanding of Access Packages and Identity Governance, check out the official Microsoft documentation.
Until next time, keep conjuring those magical scripts! โ๐งโโ๏ธ
You can find the full script on GitHub: Create-Access-Package.ps1