Currently, automatically assigning users to access packages is not a feature available. It is on the Microsoft product group’s agenda, but not on public roadmap yet.
So I built a little PowerShell workaround, which works fine.
The way I have done it is that a script is run on a scheduled basis, finding all request policies named “Automatic group assignment”, auto-requesting the access package for the user.
So, start by creating the following access package request policy:





Ok, so now you have an access package with a policy named “Automatic group assignment”, that allows an Azure AD group’s members to request the access package without any approval. What we need to do now is to have a script that does this for request for the user.
In order to run the script, we need an app registration and a user.
Create a user, with no Azure AD role (user is enough, no need for Global Admin or anything), and the ability to sign in without MFA. Assign the user the the role of “Access package manager” on each Entitlement Management catalog where you need this feature:

Create a new app registration, and grant the following permissions:

On the authentication page, enable “Treat application as public client”. This is to enable ROPC:

Now you are ready for the script. The first 10 lines is configuration you need to modify, while the rest can remain as is:
# Client ID and secret for the app registration
$clientid = "281d6e42-99c9-44a7-a24e-c80971210fff"
$clientsecret = read-host -Prompt "Client secret"
# Username and password for a user with access package manager role
$username = "Entitlement.Management.Automation@tenantname.onmicrosoft.com"
$password = read-host -Prompt "Password"
# Any domain registered in the tenant, such as tenantname.onmicrosoft.com
$tenant = "tenantname.onmicrosoft.com"
#
# Should not need to be touched anything below this line:
#
$VerbosePreference = "Continue"
$body = "client_id=$clientid&username=$username&password=$password&grant_type=password&scope=user.read.all%20group.read.all%20EntitlementManagement.ReadWrite.All"
$token = Invoke-RestMethod "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token" -Body $body -Method Post
$restParams = @{Headers = @{Authorization = "$($token.token_type) $($token.access_token)"}}
# Endpoints
$graphBase = "https://graph.microsoft.com/beta"
$endpoints = @{
accessPackageCatalogs = "{0}/identityGovernance/entitlementManagement/accessPackageCatalogs" -f $graphBase
accessPackages = "{0}/identityGovernance/entitlementManagement/accessPackages" -f $graphBase
accessPackageAssignments = "{0}/identityGovernance/entitlementManagement/accessPackageAssignments" -f $graphBase
users = "{0}/users" -f $graphBase
groups = "{0}/groups" -f $graphBase
accessPackageAssignmentPolicies = "{0}/identityGovernance/entitlementManagement/accessPackageAssignmentPolicies" -f $graphBase
me = "{0}/me" -f $graphBase
directoryObjects = "{0}/directoryObjects" -f $graphBase
accessPackageAssignmentRequests = "{0}/identityGovernance/entitlementManagement/accessPackageAssignmentRequests" -f $graphBase
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
#>
function Get-GraphRequestRecursive
{
[CmdletBinding()]
[Alias()]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipeline=$true,
Position=0)]
[string] $url
)
Write-Debug "Fetching url $url"
$Result = Invoke-RestMethod $url @restParams -Verbose:$false
if($Result.value) {
$result.value
}
# Calls itself when there is a nextlink
if($Result.'@odata.nextLink') {
Get-GraphRequestRecursive $Result.'@odata.nextLink'
}
}
#
# Get all access packages with all assignment policies
#
Write-Verbose "Getting all access packages" -Verbose
$AccessPackages = New-Object System.Collections.ArrayList
Get-GraphRequestRecursive "$($endpoints.accessPackages)?`$expand=accessPackageAssignmentPolicies" | Foreach {
$AccessPackages.Add($_) | Out-Null
}
#
# Get all access package assignment policies
#
$AccessPackageAssignmentPolicies = $AccessPackages |
ForEach-Object {$_.accessPackageAssignmentPolicies} |
Where-Object displayName -eq "Automatic group assignment" |
ForEach-Object {'{0}/{1}?$expand=accessPackage' -f $endpoints.accessPackageAssignmentPolicies, $_.id} |
ForEach-Object {Invoke-RestMethod $_ @restParams}
#
# Get all assigned users to the access packages relevant for the access package assignment policies
#
Write-Verbose "Getting all users assigned to access packages having an automatic group assignment access package policy..."
$Assignments = $AccessPackageAssignmentPolicies |
Where-Object displayName -eq "Automatic group assignment" | # Only query policies called "Automatic group assignment"
Sort-Object -Unique -Property accessPackageId | # Can only request assignments on a per access package level, so let's only query each access package once
ForEach-Object {
# Build query url and run recursive get
$url = '{0}?$filter=accessPackage/id eq ''{1}''&$expand=target' -f $endpoints.accessPackageAssignments, $_.accessPackageId
Get-GraphRequestRecursive -url $url
}
$AssignmentsMap = @{}
$Assignments |
Where-Object assignmentState -in "Delivered" |
ForEach-Object {
if(!$AssignmentsMap.ContainsKey($_.assignmentPolicyId)) {
$AssignmentsMap[$_.assignmentPolicyId] = New-Object System.Collections.ArrayList
}
$AssignmentsMap[$_.assignmentPolicyId].Add($_) | Out-Null
}
#
# Processing all assignments by getting group members and combining with current assignments
#
Write-Verbose "Processing all assignments by getting group members and combining with current assignments..."
$ProcessedAccessPackageAssignmentPolicies = $AccessPackageAssignmentPolicies |
Where-Object displayName -eq "Automatic group assignment" |
ForEach-Object {
Write-verbose " - Processing access package assignment policy $($_.id) for access package $($_.accessPackage.displayName)"
$obj = @{
CurrentAssignments = $AssignmentsMap[$_.id]
Id = $_.id
GroupObjectId = $_.requestorSettings.allowedRequestors.id
AccessPackageId = $_.accessPackageId
AccessPackageName = $_.accessPackage.displayName
GroupMembers = @()
ShouldNotBeMembers = @()
MissingMembers = @()
}
# Get all group members
Write-verbose " - Getting members for group $($obj.GroupObjectId)..."
$groupUrl = "{0}/{1}/members" -f $endpoints.groups, $obj.GroupObjectId
$obj.GroupMembers = Get-GraphRequestRecursive $groupUrl
# Calculate which members should be removed or added
$obj.MissingMembers = $obj.GroupMembers | Where-Object {$_.id -notin $obj.CurrentAssignments.target.objectid}
$obj.ShouldNotBeMembers = $obj.CurrentAssignments | Where-Object {$_.target.objectid -notin $obj.GroupMembers.id}
[PSCustomObject] $obj
}
#
# Processing access package assignment policies with users that should not be members
#
Write-Verbose "Processing access package assignment policies with users that should not be members"
$ProcessedAccessPackageAssignmentPolicies |
Where-Object ShouldNotBeMembers |
ForEach-Object {
Write-Verbose " - Processing access package policy $($_.id) for access package $($_.AccessPackageName)"
$AccessPackageName = $_.AccessPackageName
$_.ShouldNotBeMembers | ForEach-Object {
Write-Verbose " - Removing user $($_.id) from access package policy $($_.AssignmentPolicyId) for access package $($AccessPackageName)"
$body = @{
requestType = "AdminRemove"
accessPackageAssignment = @{
id = $_.id
assignmentPolicyId = $_.assignmentPolicyId
accessPackageId = $_.AccessPackageId
}
} | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri $endpoints.accessPackageAssignmentRequests -Body $body -ContentType "application/json" @restParams -Verbose:$false | Out-Null
}
}
#
# Processing access package assignment policies with missing members
#
Write-Verbose "Processing access package assignment policies with missing members"
$ProcessedAccessPackageAssignmentPolicies |
Where-Object MissingMembers |
ForEach-Object {
Write-Verbose " - Processing access package policy $($_.id)"
$AccessPackagePolicyId = $_.Id
$AccessPackageId = $_.AccessPackageId
$AccessPackageName = $_.AccessPackageName
$_.MissingMembers | ForEach-Object {
Write-Verbose " - Adding user $($_.id) to access package policy $($AccessPackagePolicyId) for access package $($AccessPackageName)"
$body = @{
requestType = "AdminAdd"
accessPackageAssignment = @{
targetId = $_.id
assignmentPolicyId = $AccessPackagePolicyId
accessPackageId = $AccessPackageId
}
} | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri $endpoints.accessPackageAssignmentRequests -Body $body -ContentType "application/json" @restParams -Verbose:$false | Out-Null
}
}
My suggestion is that you run this script on your Azure AD Connect server, if you have one. If you are among the very few cloud only organizations, it should be possible to run the script as an Azure Function with minor modifications.
For running as a scheduled task, you should modify the first lines as follows, running the ConvertFrom-SecureString stuff once as the user you are running the scheduled task as, pasting them on the line below:
# Client ID and secret for the app registration
$clientid = "281d6e42-99c9-44a7-a24e-c80971210ccc"
# RUN ONCE as the user you are running this script as: read-host -Prompt "Client secret" -AsSecureString | convertfrom-securestring | set-clipboard
$clientsecret = ConvertTo-SecureString '01000000d08c9ddf0115d1118c7a00c04fc297eb0100000017cf620bf110d741a7bd87dcf631179700000000020000000000106600000001000020000000dab44a28aacc8746f735bdba64ddc34c3043476512e579ae4e991e94d766ed96000000000e80000000020000200000007630a3d537526cf2c09bbffd95c009a5db890deab4ea18ac8b54ad50f555379d100000004bbb5857f2e526dd6f4e2e023ae5cc3140000000ac4915b2811f2fbbcedbf8648b73d8c915c153d45fd0e0913f1a695eac833bdffb709388fee11cd90742ccd6dfd9cf014b58026be7d708deaae5ce7a9f9bf488'
# Username and password for a user with access package manager role
$username = "Entitlement.Management.Automation@olavthon.onmicrosoft.com"
# RUN ONCE as the user you are running this script as: read-host -Prompt "Password" -AsSecureString | convertfrom-securestring | set-clipboard
$password = ConvertTo-SecureString "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000017cf620bf110d741a7bd87dcf63117970000000002000000000010660000000100002000000079588afe9bedef7f0ab7bce07c583325dffd7b03a2cf822297c4e4d5d26429ae000000000e8000000002000020000000950670c33dba8d6c1f57a20ddb7f5cf5f1a2ecbbe0228a942454facdcb12c71d100000005c13653550cf6780b1951efba7cd5bc8400000009fbf7e197a279cb8021d93cce48cfee68cf7efb2d67989f2d69d64884332b2241a701f5aac5e0e67d714e2721685d490481a578f872e2c56a257d8c33f97b2ed"
# Any domain registered in the tenant, such as x.onmicrosoft.com
$tenant = "tenantname.onmicrosoft.com"
Good luck 🙂
One thought on “Birth right permissions using Azure AD Entitlement Management access packages”