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”