Showing posts with label Privileged Identity Management. Show all posts
Showing posts with label Privileged Identity Management. Show all posts

Saturday, June 13, 2026

Custom Extensions for Microsoft Entra Privileged Identity Management (PIM)

 You can now bring your own business logic into the Microsoft Entra Privileged Identity Management workflow with Custom Extensions. Validate ticket numbers against your ticketing system, verify required training completion, collect and validate additional business context, or integrate with your own internal approval and compliance processes before granting Just-In-Time access.

Whether you're enforcing security requirements, meeting compliance obligations, or integrating with existing business systems, Custom Extensions provide a flexible and powerful way to tailor PIM workflows to your organization's unique needs. The possibilities are endless. See https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/privileged-identity-management-custom-extensions 



Wednesday, September 30, 2020

Azure AD PowerShell for Azure Resources in PIM

Have you wondered how to user Azure AD PowerShell for Azure Resources in PIM. This is a little tricky since the id's in OData do not support slashes but the id's for Azure resources contains slashes. Hence the id for these resources are mapped to a GUID in Graph.
To query the azure resources in Graph, you will need to pass an ExternalId filter and the rest should be straight forward.
Below is an example of adding a user as Eligible Owner on a subscription.

The pre-requisite is that you have already installed Azure AD Preview PowerShell by following these steps https://docs.microsoft.com/en-us/powershell/azure/active-directory/install-adv2?view=azureadps-2.0

Connect-AzureAD
$resource = Get-AzureADMSPrivilegedResource -Provider azureResources -Filter "ExternalId eq '/subscriptions/38ab2ccc-3747-4567-b36b-9478f5602f0d'"
$roleDefinition = Get-AzureADMSPrivilegedRoleDefinition -ProviderId azureResources -ResourceId $resource.Id -Filter "DisplayName eq 'Owner'"
$subject = Get-AzureADUser -Filter "userPrincipalName eq 'upn'"

$schedule = New-Object Microsoft.Open.MSGraph.Model.AzureADMSPrivilegedSchedule
$schedule.Type = "Once"
$schedule.StartDateTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
$schedule.EndDateTime = (Get-Date).AddDays(30).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")

Open-AzureADMSPrivilegedRoleAssignmentRequest -ProviderId azureResources -Schedule $schedule -ResourceId $resource.Id -RoleDefinitionId $roleDefinition.Id -SubjectId $subject.ObjectId -AssignmentState "Eligible" -Type "AdminAdd" -Reason "Test"


Friday, August 2, 2019

Handle Conditional Access challenge for Privileged Identity Management on Microsoft Graph

Privileged Identity Management (PIM) for Azure resources api’s are available on Microsoft Graph (MSGraph) so that developers can automate the PIM operations like activation, assignment, etc. To learn more, see http://www.anujchaudhary.com/2018/02/powershell-sample-for-privileged.html

Some organizations enable conditional policies like Multi factor authentication (MFA) for accessing any Azure resources. When users go to PIM through Azure Portal, they are prompted for MFA while logging into the Azure Portal. When they access the PIM UI, everything works since they have already performed MFA.

However, if the users are accessing PIM api’s for Azure resources through MSGraph, they might not be prompted for MFA on login since no conditional access policy might be enabled for MSGraph. When a PIM api is called, it fails with 400 Bad Request invalid_grant error since a conditional access policy is not met for Azure resources.
Example:
HTTP/1.1 400 Bad Request
{
  "error": {
    "code": "invalid_grant",
    "message": "{\"Name\":\"MsalUiRequiredException\",\"Message\":\"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '797f4846-ba00-4fd7-ba43-dac1f8f63013'.\\r\\nTrace ID: 7bdbe148-89e6-4493-a150-93dac7a06c00\\r\\nCorrelation ID: ff221ee5-ebb9-42d0-8f70-dbffde1b2104\\r\\nTimestamp: 2020-09-16 01:16:03Z\",\"Claims\":\"{\\\"access_token\\\":{\\\"capolids\\\":{\\\"essential\\\":true,\\\"values\\\":[\\\"051744ca-6abe-4095-b526-14a7f4033309\\\"]}}}\"}",
    "innerError": {
      "date": "2020-09-16T01:16:03",
      "request-id": "82cd21d1-6413-4512-a2d1-fa1d0a3c5826",
      "client-request-id": "82cd21d1-6413-4512-a2d1-fa1d0a3c5826"
    }
  }
}

To handle this, the user needs to catch the error, get the claims challenge and send in a login request with claims challenge as an extra query string parameter.


Below is a PowerShell sample which showcases on how to handle the conditional access challenge when calling PIM api's on MSGraph. Just save this as a .ps1 file and run it with PowerShell.

Sceenshot










Source code

#Acquire AAD token
function AcquireToken($claims){
    $clientID = "dabc52c4-106b-4179-9df2-2f791f44ba14"
    $redirectUri = "https://pimmsgraph"
 
    $authority = "https://login.microsoftonline.com/common"
    if($claims -ne $null)
    {
        $authResult = Get-MSALToken -Scopes @("https://graph.microsoft.com/.default") -ClientId $ClientID -RedirectUri $redirectUri -Authority $authority -Interactive -ExtraQueryParameters @{claims=$claims}
        Set-Variable -Name mfaDone -Value $true -Scope Global
    }
    else
    {
        $authResult = Get-MSALToken -Scopes @("https://graph.microsoft.com/.default") -ClientId $ClientID -RedirectUri $redirectUri -Authority $authority -Interactive
    }
    if($authResult -ne $null)
    {
        Write-Host "User logged in successfully ..." -ForegroundColor Green
    }
    Set-Variable -Name headerParams -Value @{'Authorization'="$($authResult.AccessTokenType) $($authResult.AccessToken)"} -Scope Global
    Set-Variable -Name assigneeId -Value $authResult.UserInfo.UniqueId -Scope Global

#List resources
function ListResources(){
    $url = $serviceRoot + "resources?`$filter=(type+eq+'subscription')" 
     Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $resources = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($resource in $resources.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        ResourceId =  $resource.id
        ResourceName =  $resource.displayName
        ResourceType =  $resource.type
    }
    $obj = $obj + $item
}
 
return $obj
}

#Disaplay resources
function DisplayResources(){
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap Id,ResourceName,ResourceType
}
 
############################################################################################################################################################################
 
$global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/"
$global:MSGraphRoot = "https://graph.microsoft.com/v1.0/"
$global:headerParams = ""
$global:assigneeId = ""
$global:mfaDone = $false;
 
# Install msal.ps
if(!(Get-Module | Where-Object {$_.Name -eq 'PowerShellGet' -and $_.Version -ge '2.2.4.1'})) { Install-Module PowerShellGet -Force }
if(!(Get-Package msal.ps)) { Install-Package msal.ps }

$Authed = AcquireToken $global:clientID $global:redirectUri $global:resourceAppIdURI $global:authority $false
if ($Authed -eq $false)
{
    return
}

try
{
    DisplayResources
}
catch
{
    $stream = $_.Exception.Response.GetResponseStream()
    $stream.Position = 0;
    $streamReader = New-Object System.IO.StreamReader($stream)
    $err = $streamReader.ReadToEnd()
    $streamReader.Close()
    $stream.Close()
 
    if($err.Contains("invalid_grant"))
    {
        $errorObject = ConvertFrom-Json $err
        $message = ConvertFrom-Json $errorObject.error.message
        $claims = $message.claims
        Write-Host "Prompting the user again since since a conditional access policy is enabled..." -ForegroundColor Green
        AcquireToken $claims
        DisplayResources
    }
    else
    {
        Write-Host $err -ForegroundColor Red
    }
}

 
Write-Host ""

Thursday, June 7, 2018

PowerShell sample for Privileged Identity Management (PIM) for Azure AD Roles

PIM for Azure AD Roles provides Just in Time (JIT) capability for Azure AD Roles. See more at https://docs.microsoft.com/en-us/azure/active-directory/active-directory-privileged-identity-management-getting-started 
How cool would it be if I can use the MSGraph PIM api’s to build custom applications. For example, you have multiple roles where you want to activate every day. It would be time consuming to activate them one by one. Instead, you can build a custom app using PowerShell or UI so that you can activate to all of these roles in one shot.
In this blog, I will share a sample to list all your eligible roles and activate or deactivate them. You will also be able to assign someone to a role.
I will share the full source code so you can customize it to suit your needs. Just save this as a .ps1 file and run it with PowerShell.
Note: If you tenant has migrated to newer version of PIM, see http://www.anujchaudhary.com/2018/02/powershell-sample-for-privileged.html

Screenshot
Setup
Source code

#Loads Active Directory Authentication Library
function Load-ActiveDirectoryAuthenticationLibrary(){
    $moduleDirPath = [Environment]::GetFolderPath("MyDocuments") + "\WindowsPowerShell\Modules"
    $modulePath = $moduleDirPath + "\AADGraph"

    if(-not (Test-Path ($modulePath+"\Nugets"))) {New-Item -Path ($modulePath+"\Nugets") -ItemType "Directory" | out-null}
    $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)

    if($adalPackageDirectories.Length -eq 0){
        Write-Host "Active Directory Authentication Library Nuget doesn't exist. Downloading now ..." -ForegroundColor Yellow
        if(-not(Test-Path ($modulePath + "\Nugets\nuget.exe")))
        {
            Write-Host "nuget.exe not found. Downloading from http://www.nuget.org/nuget.exe ..." -ForegroundColor Yellow
            $wc = New-Object System.Net.WebClient
            $wc.DownloadFile("http://www.nuget.org/nuget.exe",$modulePath + "\Nugets\nuget.exe");
        }
        $nugetDownloadExpression = $modulePath + "\Nugets\nuget.exe install Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.14.201151115 -OutputDirectory " + $modulePath + "\Nugets | out-null"
        Invoke-Expression $nugetDownloadExpression
    }

    $adalPackageDirectories = (Get-ChildItem -Path ($modulePath+"\Nugets") -Filter "Microsoft.IdentityModel.Clients.ActiveDirectory*" -Directory)
    $ADAL_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)
    $ADAL_WindowsForms_Assembly = (Get-ChildItem "Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll" -Path $adalPackageDirectories[$adalPackageDirectories.length-1].FullName -Recurse)
    if($ADAL_Assembly.Length -gt 0 -and $ADAL_WindowsForms_Assembly.Length -gt 0){
        Write-Host "Loading ADAL Assemblies ..." -ForegroundColor Green
        [System.Reflection.Assembly]::LoadFrom($ADAL_Assembly[0].FullName) | out-null
        [System.Reflection.Assembly]::LoadFrom($ADAL_WindowsForms_Assembly.FullName) | out-null
        return $true
    }
    else{
        Write-Host "Fixing Active Directory Authentication Library package directories ..." -ForegroundColor Yellow
        $adalPackageDirectories | Remove-Item -Recurse -Force | Out-Null
        Write-Host "Not able to load ADAL assembly. Delete the Nugets folder under" $modulePath ", restart PowerShell session and try again ..."
        return $false
    }
}
#Acquire AAD token
function AcquireToken($mfa){
    $clientID = "c7c64917-42bd-4a36-8ed6-af40122626eb"
    $redirectUri = "https://pimmsgraph"
    $authority = "https://login.microsoftonline.com/common"
    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority,$false
    if($mfa)
    {
        $authResult = $authContext.AcquireToken("https://graph.microsoft.com",$ClientID,$redirectUri,[Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto, [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::AnyUser, "amr_values=mfa")
        Set-Variable -Name mfaDone -Value $true -Scope Global
    }
    else
    {
        $authResult = $authContext.AcquireToken("https://graph.microsoft.com",$ClientID,$redirectUri,[Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always)
    }
    if($authResult -ne $null)
    {
        Write-Host "User logged in successfully ..." -ForegroundColor Green
    }
    Set-Variable -Name headerParams -Value @{'Authorization'="$($authResult.AccessTokenType) $($authResult.AccessToken)"} -Scope Global
    Set-Variable -Name assigneeId -Value $authResult.UserInfo.UniqueId -Scope Global
}
#Gets my jit assignments
function MyJitAssignments(){
    $url = $serviceRoot + "privilegedRoleAssignments/my?`$expand=roleInfo&`$filter=isElevated+eq+false" 

    Write-Host $url
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $assignments = ConvertFrom-Json $response.Content
    Write-Host ""
    Write-Host "Role assignments..." -ForegroundColor Green
    $i = 0
    $obj = @()
    foreach ($assignment in $assignments.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        RoleAssignmentId =  $assignment.id
        RoleId =  $assignment.roleInfo.id
        RoleName =  $assignment.roleInfo.name
        UserId = $assignment.userid
    }
    $obj = $obj + $item
    }
    return $obj
}
#Gets my active assignments
function MyActivatedAssignments(){
    $url = $serviceRoot + "privilegedRoleAssignments/my?`$expand=roleInfo&`$filter=isElevated+eq+true+and+expirationDateTime+ne+null" 

    Write-Host $url
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $assignments = ConvertFrom-Json $response.Content
    Write-Host ""
    Write-Host "Role assignments..." -ForegroundColor Green
    $i = 0
    $obj = @()
    foreach ($assignment in $assignments.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        RoleAssignmentId =  $assignment.id
        RoleId =  $assignment.roleInfo.id
        RoleName =  $assignment.roleInfo.name
        UserId = $assignment.userid
        ExpirationDateTime = $assignment.expirationDateTime
    }
    $obj = $obj + $item
    }
    return $obj
}

#List roles
function ListRoles(){
    $url = $serviceRoot + "privilegedRoles?&`$orderby=name"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roles = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($role in $roles.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        RoleId =  $role.id
        RoleName =  $role.name
    }
    $obj = $obj + $item
    }
    return $obj
}

#List Assignment
function ListAssignmentsWithFilter($roleId){
    $url = $serviceRoot + "privilegedRoleAssignments?`$expand=roleInfo&`$filter=roleId+eq+'" + $roleId + "'"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roleAssignments = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($roleAssignment in $roleAssignments.value)
        {
        $item = New-Object psobject -Property @{
        Id = ++$i
        RoleAssignmentId =  $roleAssignment.id
        RoleId = $roleAssignment.roleInfo.id
        RoleName = $roleAssignment.roleInfo.name
        IsElevated = $roleAssignment.isElevated
        ExpirationDateTime = $roleAssignment.expirationDateTime
        UserId = $roleAssignment.userId
    }
    $obj = $obj + $item
}
return $obj
}

#List Users
function ListUsers($user_search){
    $url = $MSGraphRoot + "users?`$filter=startswith(displayName,'" + $user_search + "')"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $users = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($user in $users.value)
    {
        $item = New-Object psobject -Property @{
        Id = ++$i
        UserId =  $user.id
        UserName =  $user.DisplayName
    }
    $obj = $obj + $item
    }

    return $obj
}

#Activates the user
function Activate($isRecursive = $false){
    if($isRecursive -eq $false)
    {
        $assignments = MyJitAssignments
        $assignments | Format-Table -AutoSize -Wrap Id,RoleName
        $choice = Read-Host "Enter Id to activate"
        $hours = Read-Host "Enter Activation duration in hours"
        $reason = Read-Host "Enter Reason"
    }

    $roleId = $assignments[$choice-1].RoleId
    $url = $serviceRoot + "privilegedRoles('" + $roleId + "')/selfActivate"
    $postParams = '{"duration":"' + $hours + '","reason":"' + $reason + '"}'
    write-Host $postParams

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
        Write-Host "Role activated successfully ..." -ForegroundColor Green
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
        if($mfaDone -eq $false -and $err.Contains("MfaRule"))
        {
            Write-Host "Prompting the user for mfa ..." -ForegroundColor Green
            AcquireToken true
            Activate $true
        }
        else
        {
            Write-Host $err -ForegroundColor Red
        }
    }
}
#Deactivates the user
function Deactivate($isRecursive = $false){
    if($isRecursive -eq $false)
    {
        $assignments = MyActivatedAssignments
        $assignments | Format-Table -AutoSize -Wrap Id,RoleName,ExpirationDateTime
        $choice = Read-Host "Enter Id to deactivate"
    }

    $roleId = $assignments[$choice-1].RoleId
    $url = $serviceRoot + "privilegedRoles('" + $roleId + "')/selfDeactivate"
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json"
    Write-Host "Role deactivated successfully ..." -ForegroundColor Green
}
#List RoleAssignment
function ListAssignment(){
    #List and Pick a role
    $roles = ListRoles
    $roles | Format-Table -AutoSize -Wrap Id, RoleName, RoleId
    $role_choice = Read-Host "Pick a role Id"
    $roleId = $roles[$role_choice-1].RoleId
    write-Host $roleId

    #List Member
    $roleAssignments = ListAssignmentsWithFilter $roleId
    $roleAssignments | Format-Table -AutoSize -Wrap Id, RoleName, UserId, IsElevated, ExpirationDateTime
}

#Assign a user to Eligible
function AssignmentEligible() {
    #List and Pick a role
    $roles = ListRoles
    $roles | Format-Table -AutoSize -Wrap Id, RoleName
    $role_choice = Read-Host "Pick a role Id"
    $roleId = $roles[$role_choice-1].RoleId
    write-Host $roleId

    #Search user by Name, and pick a user
    $user_search = Read-Host "user Name start with..."
    $users = ListUsers($user_search)
    $users | Format-Table -AutoSize -Wrap Id, UserName, UserId
    $user_choice = Read-Host "Pick a user Id"
    $userId = $users[$user_choice-1].UserId


    $url = $serviceRoot + "privilegedRoleAssignments"
    $postParams = '{"roleId":"' + $roleId + '","userId":"' + $userId + '"}'
    write-Host $postParams
        
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
    Write-Host "Assignment added successfully ..." -ForegroundColor Green
}


#Show menu
function ShowMenu(){
    Write-Host ""
    Write-Host "Azure AD JIT - PowerShell Menu v1.0"
    Write-Host "  1. List your eligible role assignments"
    Write-Host "  2. Activate an eligible role"
    Write-Host "  3. Deactivate an active role"
    Write-Host "  4. List Assignment against a role"
    Write-Host "  5. Assign a user to a role"
    Write-Host "  6. Exit"
}
############################################################################################################################################################################
$global:serviceRoot = "https://graph.microsoft.com/beta/"
$global:MSGraphRoot = "https://graph.microsoft.com/v1.0/"
$global:headerParams = ""
$global:assigneeId = ""
$global:mfaDone = $false;
Load-ActiveDirectoryAuthenticationLibrary
AcquireToken
do
{
    ShowMenu
    #Write-Host "Enter your selection"
    $input = Read-Host "Enter your selection"
    switch ($input)
    {
        '1'
        {
            $assignments = MyJitAssignments
            $assignments | Format-Table -AutoSize -Wrap Id,RoleName
        }
        '2'
        {
            Activate
        }
        '3'
        {
            Deactivate
        }
        '4'
        {
            ListAssignment
        }
        '5'
        {
            AssignmentEligible
        }
        '6'
        {
            return
        }
    }
}
until ($input -eq '6')
Write-Host ""

Wednesday, April 11, 2018

Securing Azure resources with Privileged Identity Management

With Azure Active Directory Privileged Identity Management (PIM), you can now manage, control, and monitor access to Azure Resources within your organization. To learn more, see https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/azure-pim-resource-rbac
It is important to have a Just In Time (JIT) capability to JIT into an Azure subscription for few hours to investigate any issues. However, once a bad guy has access to the subscription, he can get a lot of information out (like secrets, etc.) and do bad things even after the access is lost.

With PIM, you can assign JIT access not only at the subscription level but also at a specific Resource group or Resource level to prevent any information disclosure. Here is an example of how you can manage your subscription securely with PIM.
Create a Resource group for all your secrets. Let’s call it SecretsResourceGroup. This resource group can have the following resources:
  • Azure Storage
  • SQL Azure
  • Cosmos DB
  • Etc.
Create a Resource group for all your deployments. Let’s call it DeploymentsResourceGroup. This resource can have the following resources:
  • Cloud Service
  • Service Fabric
  • App Service
  • Virtual Machines
  • Etc.
You will notice that almost anyone who needs access to the subscription actually needs access to a resource within DeploymentsResourceGroup. Typical scenarios include:
  • Investigating live site issue with a specific deployment
  • Restarting a VM
  • Installing a software update
  • Etc.
If they need access to multiple resources, you can assign them Eligible assignment on the Resource group. If they need access to a specific resource, you can assign them Eligible assignment on the specific resource. Now whenever, they need access, they can JIT into the specific resource at that time. You can also configure approval so that they can only JIT upon approval.
Thus, due to the granularity that PIM supports, you can now prevent people from accessing your secrets but still allow them to JIT into the specific resources where they need to investigate any issues.

Wednesday, February 7, 2018

PowerShell sample for Privileged Identity Management (PIM)

PIM for Azure Resources provides Just in Time (JIT) and Temporary access capabilities for Azure AD roles and Azure Resource roles. See more at https://docs.microsoft.com/en-us/azure/active-directory/privileged-identity-management/pim-configure
How cool would it be if I can use the MSGraph PIM api’s to build custom applications. For example, your IT Org has N different resource groups where you want to activate every day. It would be time consuming to activate them one by one. Instead, you can build a custom app using PowerShell or UI so that you can activate to all of these resource groups in one shot.
In this blog, I will share a sample to list all your eligible roles and activate or deactivate them. You will also be able to assign someone to a role.
I will share the full source code so you can customize it to suit your needs. Just save this as a .ps1 file and run it with PowerShell.
Screenshot
















Setup
  • Create a native AAD application. See https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-integrating-applications
  • Grant it the following permissions to the application.
    • Read and write privileged access to Azure AD - You will need it if you are going to use the app for PIM for Azure AD Roles
    • Read and write privileged access to Azure resources - You will need it if you are going to use the app for PIM for Azure Resources
    • Read directory data - You will need it if you are going to read users, etc. from directory like the assignment example in the below source code
    • Note than these permissions require Admin consent so you will have to contact the tenant admin to grant these permissions. See https://docs.microsoft.com/en-us/azure/active-directory/application-dev-registration-config-grant-permissions-how-to 
  • In the below code, update $clientID with your application id and $redirectUri with the redirect uri of the application.
  • For Azure resources, set $global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/"
  • For Azure AD roles, set $global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/aadRoles/"
  • For Azure AD groups, set $global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/aadGroups/"
Source code


#Acquire AAD token

function AcquireToken($clientID, $redirectUri, $scopes, $authority, $mfa)
{
    if($mfa)
    {
        $authResult = Get-MSALToken -Scopes $scopes -ClientId $clientID -RedirectUri $redirectUri -Authority $authority -Interactive -ExtraQueryParameters @{claims='{"access_token" : {"amr": { "values": ["mfa"] }}}'}
        Set-Variable -Name mfaDone -Value $true -Scope Global
    }
    else
    {
        $authResult = Get-MSALToken -Scopes $scopes -ClientId $clientID -RedirectUri $redirectUri -Authority $authority -Interactive
    }
    

    if($authResult -ne $null)
    {
        Write-Host "User logged in successfully ..." -ForegroundColor Green
    }
    Set-Variable -Name headerParams -Value @{'Authorization'="$($authResult.AccessTokenType) $($authResult.AccessToken)"} -Scope Global
    Set-Variable -Name assigneeId -Value $authResult.UserInfo.UniqueId -Scope Global
}
 
#Gets my jit assignments
function MyJitAssignments(){
    $urlme = $global:MSGraphRoot + "me/"
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $urlme -Method Get
    $me = ConvertFrom-Json $response.Content
    $subjectId = $me.id
    Write-Host $subjectId

    $url = $serviceRoot + "roleAssignments?`$expand=linkedEligibleRoleAssignment,subject,roleDefinition(`$expand=resource)&`$filter=(assignmentState+eq+'Eligible')+and+(subjectId+eq+'" + $subjectId + "')" 

    Write-Host $url
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $assignments = ConvertFrom-Json $response.Content
    Write-Host ""
    Write-Host "Role assignments..." -ForegroundColor Green
    $i = 0
    $obj = @()
    foreach ($assignment in $assignments.value)
    {
        $item = New-Object psobject -Property @{
        id = ++$i
        IdGuid =  $assignment.id
        ResourceId =  $assignment.roleDefinition.resource.id
        OriginalId =  $assignment.roleDefinition.resource.externalId
        ResourceName =  $assignment.roleDefinition.resource.displayName
        ResourceType =  $assignment.roleDefinition.resource.type
        RoleId = $assignment.roleDefinition.id
        RoleName = $assignment.roleDefinition.displayName
        ExpirationDate = $assignment.endDateTime
        SubjectId = $assignment.subject.id
    }
    $obj = $obj + $item
    }
 
    return $obj
}
 

#List resources
function ListResources(){
    $url = $serviceRoot + "resources?`$filter=(type+eq+'subscription')" 
     Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $resources = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($resource in $resources.value)
    {
        $item = New-Object psobject -Property @{
        id = ++$i
        ResourceId =  $resource.id
        ResourceName =  $resource.DisplayName
        Type =  $resource.type
        ExternalId =  $resource.externalId
    }
    $obj = $obj + $item
}
 
return $obj
}

#List roles
function ListRoles($resourceId){
    $url = $serviceRoot + "resources/" + $resourceId + "/roleDefinitions?&`$orderby=displayName"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roles = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($role in $roles.value)
    {
        $item = New-Object psobject -Property @{
        id = ++$i
        RoleDefinitionId =  $role.id
        RoleName =  $role.DisplayName
    }
    $obj = $obj + $item
    }
 
    return $obj
}


#List roles
function ListRoleSettings($resourceId){
    $url = $serviceRoot + "resources/" + $resourceId + "/roleSettings?&`$expand=resource,roleDefinition&`$orderby=lastUpdatedDateTime+desc"
    Write-Host $url

    
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roleSettings = ConvertFrom-Json -InputObject $response.Content
        
    $i = 0
    $obj = @()
    foreach ($roleSetting in $roleSettings.value)
    {
        # userMemberSettings
        $UMSExp = ""
        $UMSMFA = ""
        $UMSJus = ""
        $UMSActDay = ""
        $UMSApprov = ""
        $UMSTicket = ""

        foreach ($UMS in $roleSetting.userMemberSettings)
        {

            switch ($UMS.ruleIdentifier) {
               "MfaRule" 
                        {
                            $UMSMFA = $UMS.setting
                            break
                        }
               "ExpirationRule" 
                        {
                            $UMSExp = $UMS.setting
                            break
                        }
                "JustificationRule"
                        {
                            $UMSJus = $UMS.setting
                            break
                        }
                "ActivationDayRule"
                        {
                            $UMSActDay = $UMS.setting
                            break
                        }
                "ApprovalRule"
                        {
                            $UMSApprov = $UMS.setting
                            break
                        }
                "TicketingRule"
                        {
                            $UMSTicket = $UMS.setting
                            break
                        }
            }
        }
        
        # AdminEligibleSettings
        $AESExp = ""
        $AESMFA = ""
        $AESJus = ""
        $AESActDay = ""
        $AESApprov = ""
        $AESTicket = ""

        foreach ($AES in $roleSetting.adminEligibleSettings)
        {

            switch ($AES.ruleIdentifier) {
               "MfaRule" 
                        {
                            $AESMFA = $AES.setting
                            break
                        }
               "ExpirationRule" 
                        {
                            $AESExp = $AES.setting
                            break
                        }
                "JustificationRule"
                        {
                            $AESJus = $AES.setting
                            break
                        }
                "ActivationDayRule"
                        {
                            $AESActDay = $AES.setting
                            break
                        }
                "ApprovalRule"
                        {
                            $AESApprov = $AES.setting
                            break
                        }
                "TicketingRule"
                        {
                            $AESTicket = $AES.setting
                            break
                        }
            }
        }

        # AdminMemberSettings
        $AMSExp = ""
        $AMSMFA = ""
        $AMSJus = ""
        $AMSActDay = ""
        $AMSApprov = ""
        $AMSTicket = ""

        foreach ($AMS in $roleSetting.adminMemberSettings)
        {

            switch ($AMS.ruleIdentifier) {
               "MfaRule" 
                        {
                            $AMSMFA = $AMS.setting
                            break
                        }
               "ExpirationRule" 
                        {
                            $AMSExp = $AMS.setting
                            break
                        }
                "JustificationRule"
                        {
                            $AMSJus = $AMS.setting
                            break
                        }
                "ActivationDayRule"
                        {
                            $AMSActDay = $AMS.setting
                            break
                        }
                "ApprovalRule"
                        {
                            $AMSApprov = $AMS.setting
                            break
                        }
                "TicketingRule"
                        {
                            $AMSTicket = $AMS.setting
                            break
                        }
            }
        }

        $item = New-Object psobject -Property @{
            id = ++$i
            RoleSettingId = $roleSetting.id
            ResourceId = $roleSetting.resourceId
            ResourceName = $roleSetting.resource.displayName
            RoleDefinitionId = $roleSetting.roleDefinitionId
            RoleName = $roleSetting.roleDefinition.displayName
            AdminMemberSettings = $roleSetting.adminMemberSettings
            AdminEligibleSettings = $roleSetting.adminEligibleSettings
            UserEligibleSettings = $roleSetting.userEligibleSettings
            UserMemberSettings = $roleSetting.userMemberSettings
               
            UserMemberSettingsMfaRule = $UMSMFA
            UserMemberSettingsExpirationRule = $UMSExp
            UserMemberSettingsJustificationRule = $UMSJus
            UserMemberSettingsActivationDayRule = $UMSActDay
            UserMemberSettingsApprovalRule = $UMSApprov
            UserMemberSettingsTicketingRule = $UMSTicket

            AdminEligibleSettingsMfaRule = $AESMFA
            AdminEligibleSettingsExpirationRule = $AESExp
            AdminEligibleSettingsJustificationRule = $AESJus
            AdminEligibleSettingsActivationDayRule = $AESActDay
            AdminEligibleSettingsApprovalRule = $AESApprov
            AdminEligibleSettingsTicketingRule = $AESTicket

            AdminMemberSettingsMfaRule = $AMSMFA
            AdminMemberSettingsExpirationRule = $AMSExp
            AdminMemberSettingsJustificationRule = $AMSJus
            AdminMemberSettingsActivationDayRule = $AMSActDay
            AdminMemberSettingsApprovalRule = $AMSApprov
            AdminMemberSettingsTicketingRule = $AMSTicket
        }

         
        
        $obj = $obj + $item
    }
 
    return $obj
}

#List Assignment
function ListAssignmentsWithFilter($resourceId, $roleDefinitionId){
    $url = $serviceRoot + "resources/" + $resourceId + "`/roleAssignments?`$expand=subject,roleDefinition(`$expand=resource)&`$filter=(roleDefinition/id+eq+'" + $roleDefinitionId + "')"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roleAssignments = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($roleAssignment in $roleAssignments.value)
        {
        $item = New-Object psobject -Property @{
        id = ++$i
        RoleAssignmentId =  $roleAssignment.id
        ResourceId =  $roleAssignment.roleDefinition.resource.id
        OriginalId =  $roleAssignment.roleDefinition.resource.externalId
        ResourceName =  $roleAssignment.roleDefinition.resource.displayName
        ResourceType =  $roleAssignment.roleDefinition.resource.type
        RoleId = $roleAssignment.roleDefinition.id
        RoleName = $roleAssignment.roleDefinition.displayName
        ExpirationDate = $roleAssignment.endDateTime
        SubjectId = $roleAssignment.subject.id
        UserName = $roleAssignment.subject.displayName
        AssignmentState = $roleAssignment.AssignmentState
    }
    $obj = $obj + $item
}
 
return $obj
}


#List Assignment
function ListExpiringEligibleAssignmentsWithFilter($resourceId){
    $url = $serviceRoot + "resources/" + $resourceId + "`/roleAssignments?`$expand=subject,roleDefinition(`$expand=resource)&`$filter=(assignmentState+eq+'Eligible')"
   
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $roleAssignments = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
$expiration = (Get-Date).ToUniversalTime().AddDays(14)
    foreach ($roleAssignment in $roleAssignments.value)
    {
        if(($roleAssignment.endDateTime -ne $null) -and ([DateTime]$roleAssignment.endDateTime -lt $expiration))
        {
            $item = New-Object psobject -Property @{
                id = ++$i
                RoleAssignmentId =  $roleAssignment.id
                ResourceId =  $roleAssignment.roleDefinition.resource.id
                OriginalId =  $roleAssignment.roleDefinition.resource.externalId
                ResourceName =  $roleAssignment.roleDefinition.resource.displayName
                ResourceType =  $roleAssignment.roleDefinition.resource.type
                RoleId = $roleAssignment.roleDefinition.id
                RoleName = $roleAssignment.roleDefinition.displayName
                ExpirationDate = $roleAssignment.endDateTime
                SubjectId = $roleAssignment.subject.id
                UserName = $roleAssignment.subject.displayName
                AssignmentState = $roleAssignment.AssignmentState
            }
            $obj = $obj + $item
        }
        
    }
 
    return $obj
}

#List Users
function ListUsers($user_search){
    $url = $MSGraphRoot + "users?`$filter=startswith(displayName,'" + $user_search + "')"
    Write-Host $url

    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Get
    $users = ConvertFrom-Json $response.Content
    $i = 0
    $obj = @()
    foreach ($user in $users.value)
    {
        $item = New-Object psobject -Property @{
        id = ++$i
        UserId =  $user.id
        UserName =  $user.DisplayName
    }
    $obj = $obj + $item
    }

    return $obj
}

#Activates the user
function Activate($isRecursive = $false){
    if($isRecursive -eq $false)
    {
        $assignments = MyJitAssignments
        $assignments | Format-Table -AutoSize -Wrap id,RoleName,ResourceName,ResourceType,ExpirationDate
        $choice = Read-Host "Enter Id to activate"
        [int]$hours = Read-Host "Enter Activation duration in hours"
        $reason = Read-Host "Enter Reason"
    }
 
    $id = $assignments[$choice-1].IdGuid
    $resourceId = $assignments[$choice-1].ResourceId
    $roleDefinitionId = $assignments[$choice-1].RoleId
    $subjectId = $assignments[$choice-1].SubjectId
    $url = $serviceRoot + "roleAssignmentRequests"
    $postParams = '{"id":"00000000-0000-0000-0000-000000000000","assignmentState":"Active","type":"UserAdd","reason":"' + $reason + '","roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '","schedule":{"duration":"PT' + $hours + 'H","startDateTime":"' + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + '","type":"Once"},"linkedEligibleRoleAssignmentId":"' + $id + '"}'
    write-Host $postParams

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
        Write-Host "Activation request queued successfully ..." -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
 
        if($mfaDone -eq $false -and $err.Contains("MfaRule"))
        {
            Write-Host "Prompting the user for mfa ..." -ForegroundColor Green
            AcquireToken $global:clientID $global:redirectUri $global:resourceAppIdURI $global:authority $true
            Activate $true
        }
        else
        {
            Write-Host $err -ForegroundColor Red
        }
    }
}
 

#Extend the user role assignment
function ExtendRoleAssignment($roleAssignment, $hours){

    $url = $serviceRoot + "roleAssignmentRequests"
    $postParams = '{"id":"00000000-0000-0000-0000-000000000000","assignmentState":"Eligible","type":"AdminExtend","reason":"bulk extend","roleDefinitionId":"' + $roleAssignment.RoleId + '","resourceId":"' + $roleAssignment.ResourceId + '","subjectId":"' + $roleAssignment.SubjectId + '","schedule":{"duration":"PT' + $hours + 'H","startDateTime":"' + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + '","type":"Once"}}'
    write-Host $postParams

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
        Write-Host "Extend Successfully ..." -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
    }
}

#Delete the user role assignment
function DeleteRoleAssignment($roleAssignment){

    $resourceId = $roleAssignments[$ra_choice-1].ResourceId
    $roleDefinitionId = $roleAssignments[$ra_choice-1].RoleId
    $subjectId = $roleAssignments[$ra_choice-1].SubjectId
    $assignmentState = $roleAssignments[$ra_choice-1].AssignmentState

    # Delete the chosen member
    $url = $serviceRoot + "roleAssignmentRequests"
    $postParams = '{"assignmentState":"' + $assignmentState + '","type":"AdminRemove","reason":"Assign","roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '"}'
    
    write-Host $postParams
    write-Host $url
    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
        Write-Host "Assignment has been deleted" -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
        
        Write-Host $err -ForegroundColor Red
        
    }
}


#Deactivates the user
function Deactivate($isRecursive = $false){
    if($isRecursive -eq $false)
    {
        $assignments = MyJitAssignments
        $assignments | Format-Table -AutoSize -Wrap id,RoleName,ResourceName,ResourceType,ExpirationDate
        $choice = Read-Host "Enter Id to deactivate"
    }
 
    $id = $assignments[$choice-1].IdGuid
    $resourceId = $assignments[$choice-1].ResourceId
    $roleDefinitionId = $assignments[$choice-1].RoleId
    $subjectId = $assignments[$choice-1].SubjectId
    $url = $serviceRoot + "roleAssignmentRequests"
    $postParams = '{"assignmentState":"Active","type":"UserRemove","roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '"}'
    $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
    Write-Host "Role deactivated successfully ..." -ForegroundColor Green
    $recursive = $false
}

#Patch RoleSetting
function PatchRoleSetting($patchParams, $roleSettingId){

    $url = $serviceRoot + "roleSettings/" + $roleSettingId
    Write-Host $url
    Write-Host $patchParams

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Patch -ContentType "application/json" -Body $patchParams
        Write-Host "Update RoleSetting successfully ..." -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
    }
}
 
#List RoleAssignment
function ListAssignment(){
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick an resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List and Pick a role
    $roles = ListRoles($resourceId)
    $roles | Format-Table -AutoSize -Wrap id, RoleName, RoleDefinitionId
    $role_choice = Read-Host "Pick a role Id"
    $roleDefinitionId = $roles[$role_choice-1].RoleDefinitionId
    write-Host $roleDefinitionId

    #List Member
    $roleAssignments = ListAssignmentsWithFilter $resourceId $roleDefinitionId
    $roleAssignments | Format-Table -AutoSize -Wrap id, ResourceName, ResourceType, RoleName, UserName, AssignmentState, ExpirationDate
}


#Delete RoleAssignment
function DelAssignment(){
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick an resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List and Pick a role
    $roles = ListRoles($resourceId)
    $roles | Format-Table -AutoSize -Wrap id, RoleName, RoleDefinitionId
    $role_choice = Read-Host "Pick a role Id"
    $roleDefinitionId = $roles[$role_choice-1].RoleDefinitionId
    write-Host $roleDefinitionId

    #List Member
    $roleAssignments = ListAssignmentsWithFilter $resourceId $roleDefinitionId
    $roleAssignments | Format-Table -AutoSize -Wrap id, ResourceName, ResourceType, RoleName, UserName, AssignmentState, ExpirationDate
    
    $ra_choice = Read-Host "Pick a roleAssignment you want to delete, Pick 0 for Del All active, and Pick -1 to exit"

    if ($ra_choice -eq -1)
    {
        return
    }

    if (($roleAssignments -eq $null) -or ($roleAssignments[$ra_choice-1] -eq $null))
    {
        Write-Host "Number out-of range"
        return
    }

    if ($ra_choice -gt 0)
    {
        DeleteRoleAssignment $roleAssignments[$ra_choice-1]

    } elseif  ($ra_choice -eq 0)
    {
        foreach ($ra in $roleAssignments)
        {
            if ($ra.AssignmentState -eq "Active") 
            {
                DeleteRoleAssignment $ra
            }            
        }
    }

}


#List ExpiringRoleAssignment
function ListExpiringEligibleAssignments(){
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick a resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List Expiring Member of the target resource
    $roleAssignments = ListExpiringEligibleAssignmentsWithFilter $resourceId
    $roleAssignments | Format-Table -AutoSize -Wrap id, ResourceName, ResourceType, RoleName, UserName, AssignmentState, ExpirationDate
    

    if ($roleAssignments -eq $null)
    {
        Write-Host "No Eligible memberships are expiring" -ForegroundColor Green
    } else
    {
        $ra_choice = Read-Host "Pick a roleAssignment you want to extend, Pick 0 for ExtendAll, and Pick -1 to exit"

        if ($ra_choice -eq -1)
        {
            return
        }

        $days = Read-Host "Pick number of days, you want to extends"
        $hours = [int]$days * 24
        Write-Host $hours

        if ($ra_choice -gt 0)
        {
            ExtendRoleAssignment $roleAssignments[$ra_choice-1] $hours

        } elseif  ($ra_choice -eq 0)
        {
            foreach ($ra in $roleAssignments)
            {
                ExtendRoleAssignment $ra $hours                
            }
        }
    }
}


#Assign a user to Eligible
function AssignmentEligible() {
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick an resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List and Pick a role
    $roles = ListRoles($resourceId)
    $roles | Format-Table -AutoSize -Wrap id, RoleName, RoleDefinitionId
    $role_choice = Read-Host "Pick a role Id"
    $roleDefinitionId = $roles[$role_choice-1].RoleDefinitionId
    write-Host $roleDefinitionId

    #Search user by Name, and pick a user
    $user_search = Read-Host "user Name start with..."
    $users = ListUsers($user_search)
    $users | Format-Table -AutoSize -Wrap id, UserName, UserId
    $user_choice = Read-Host "Pick a user Id"
    
    if (($users -eq $null) -or ($users[$user_choice-1] -eq $null))
    {
        Write-Host "Number out-of range"
        return
    }

    $subjectId = $users[$user_choice-1].UserId

    $url = $serviceRoot + "roleAssignmentRequests"
    # Update end time
    $ts = New-TimeSpan -Days 30
    $postParams = '{"assignmentState":"Eligible","type":"AdminAdd","reason":"Assign","roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '","schedule":{"startDateTime":"' + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + '","endDateTime":"' + ((Get-Date) + $ts).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + '","type":"Once"}}'
    write-Host $postParams

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
        Write-Host "Assignment request queued successfully ..." -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
 
        if($mfaDone -eq $false -and $err.Contains("MfaRule"))
        {
            Write-Host "Prompting the user for mfa ..." -ForegroundColor Green
            AcquireToken $global:clientID $global:redirectUri $global:resourceAppIdURI $global:authority $true
            Activate $true
        }
        else
        {
            Write-Host $err -ForegroundColor Red
        }
    }
}


#Cancel Request
    function CancelRequest() {
    $requestId = Read-Host "RequestId"
    $url = $serviceRoot + "roleAssignmentRequests/" + $requestId + "/cancel" 
    write-Host $url

    try
    {
        $response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Method Post -ContentType "application/json"
        Write-Host "Cancel request queued successfully ..." -ForegroundColor Green
        $recursive = $false
    }
    catch
    {
        $stream = $_.Exception.Response.GetResponseStream()
        $stream.Position = 0;
        $streamReader = New-Object System.IO.StreamReader($stream)
        $err = $streamReader.ReadToEnd()
        $streamReader.Close()
        $stream.Close()
        Write-host $err 
    }
}


#List RoleSetting
function ListRoleSetting(){
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick an resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List RoleSettings and Pick a role/multiple role to update
    $roleSettings = ListRoleSettings($resourceId)
    $roleSettings | Format-List id, RoleName, RoleDefinitionId, ResourceName, ResourceId, AdminMemberSettings, AdminEligibleSettings, UserMemberSettings, UserEligibleSettings,
    AdminMemberSettingsMfaRule, AdminMemberSettingsExpirationRule, AdminMemberSettingsJustificationRule, AdminMemberSettingsActivationDayRule, AdminMemberSettingsApprovalRule, AdminMemberSettingsTicketingRule,
    AdminEligibleSettingsMfaRule, AdminEligibleSettingsExpirationRule, AdminEligibleSettingsJustificationRule, AdminEligibleSettingsActivationDayRule, AdminEligibleSettingsApprovalRule, AdminEligibleSettingsTicketingRule,
    UserMemberSettingsMfaRule, UserMemberSettingsExpirationRule, UserMemberSettingsJustificationRule, UserMemberSettingsActivationDayRule, UserMemberSettingsApprovalRule, UserMemberSettingsTicketingRule,
    UserEligibleSettingsMfaRule, UserEligibleSettingsExpirationRule, UserEligibleSettingsJustificationRule, UserEligibleSettingsActivationDayRule, UserEligibleSettingsApprovalRule, UserEligibleSettingsTicketingRule
    
}


#Update RoleSetting
function UpdateRoleSetting(){
    #List and Pick resource
    $resources = ListResources
    $resources | Format-Table -AutoSize -Wrap id, ResourceName, Type, ExternalId
    $res_choice = Read-Host "Pick an resource Id for assigment"
    $resourceId = $resources[$res_choice-1].ResourceId

    #List RoleSettings and Pick a role/multiple role to update
    $roleSettings = ListRoleSettings($resourceId)
    $roleSettings | Format-List id, RoleName, RoleDefinitionId, ResourceName, ResourceId, AdminMemberSettings, AdminEligibleSettings, UserMemberSettings, UserEligibleSettings,
    AdminMemberSettingsMfaRule, AdminMemberSettingsExpirationRule, AdminMemberSettingsJustificationRule, AdminMemberSettingsActivationDayRule, AdminMemberSettingsApprovalRule, AdminMemberSettingsTicketingRule,
    AdminEligibleSettingsMfaRule, AdminEligibleSettingsExpirationRule, AdminEligibleSettingsJustificationRule, AdminEligibleSettingsActivationDayRule, AdminEligibleSettingsApprovalRule, AdminEligibleSettingsTicketingRule,
    UserMemberSettingsMfaRule, UserMemberSettingsExpirationRule, UserMemberSettingsJustificationRule, UserMemberSettingsActivationDayRule, UserMemberSettingsApprovalRule, UserMemberSettingsTicketingRule,
    UserEligibleSettingsMfaRule, UserEligibleSettingsExpirationRule, UserEligibleSettingsJustificationRule, UserEligibleSettingsActivationDayRule, UserEligibleSettingsApprovalRule, UserEligibleSettingsTicketingRule
    
    $roleSettings | Format-Table -AutoSize -Wrap id, RoleName, ResourceName, RoleDefinitionId, ResourceId
    

    $roleSetting_choice = Read-Host "Pick a role Id"
    $roleSettingId = $roleSettings[$roleSetting_choice-1].RoleSettingId
    write-Host $roleSettingId
    

    # Activation Setting: "MfaRule", "ExpirationRule", "JustificationRule", "ActivationDayRule", "ApprovalRule", "TicketingRule"
    
    $Activation = Read-Host "Update Activation Setting or not (Y or N)"
    if ($Activation -like 'Y') 
    {
        $ActivationMfaRule = Read-Host "Activation MFA enabled (Y or N)"
        $ActivationExpirationRule = Read-Host "Activation Grant period in mins (ie. 240 is 4 hr)"
        $ActivationJustificationRule = Read-Host "Activation Justification enabled (Y or N)"
    
        $ActivationGP = [timespan]::fromminutes($ActivationExpirationRule)
        #$UserMemberSetting = '{@{ruleIdentifier=ExpirationRule; setting={"maximumGrantPeriod":"'+$ActivationGP+'","maximumGrantPeriodInMinutes":'+$ActivationExpirationRule+',"permanentAssignment":false}}'
        $UserMemberSetting =',"userMemberSettings": [{"ruleIdentifier":"ExpirationRule","setting": "{\"permanentAssignment\":false,\"maximumGrantPeriodInMinutes\":'+$ActivationExpirationRule+'}"}'
    
 
        if ($ActivationMfaRule -like 'Y') 
        {
            $UserMemberSetting = $UserMemberSetting + ',{"ruleIdentifier":"MfaRule","setting":"{\"mfaRequired\":true}"}'
        } elseif ($ActivationMfaRule -like 'N') 
        {
            $UserMemberSetting = $UserMemberSetting + ',{"ruleIdentifier":"MfaRule","setting":"{\"mfaRequired\":false}"}'
        }

        if ($ActivationJustificationRule -like 'Y') 
        {
            $UserMemberSetting = $UserMemberSetting + ',{"ruleIdentifier":"JustificationRule","setting":"{\"required\":true}"}]'
        } elseif ($ActivationJustificationRule -like 'N') 
        {
            $UserMemberSetting = $UserMemberSetting + ',{"ruleIdentifier":"JustificationRule","setting":"{\"required\":false}"}]'
        }

        Write-Host $UserMemberSetting
    }

    $AdminE = Read-Host "Update Admin Eligible Setting or not (Y or N)"
    if ($AdminE -like 'Y') 
    {
        $AdminEMfaRule = Read-Host "Admin Eligible MFA enabled (Y or N)"
        $AdminEExpirationRule = Read-Host "Admin Eligible Grant period in mins (ie. 240 is 4 hr)"
        $AdminEJustificationRule = Read-Host "Admin Eligible Justification enabled (Y or N)"
    
        $AdminEGP = [timespan]::fromminutes($AdminEExpirationRule)
        $AdminESetting =',"adminEligibleSettings": [{"ruleIdentifier":"ExpirationRule","setting": "{\"permanentAssignment\":false,\"maximumGrantPeriodInMinutes\":'+$AdminEExpirationRule+'}"}'
    
 
        if ($AdminEMfaRule -like 'Y') 
        {
            $AdminESetting = $AdminESetting + ',{"ruleIdentifier":"MfaRule","setting":"{\"mfaRequired\":true}"}'
        } elseif ($ActivationMfaRule -like 'N') 
        {
            $AdminESetting = $AdminESetting + ',{"ruleIdentifier":"MfaRule","setting":"{\"mfaRequired\":false}"}'
        }

        if ($AdminEJustificationRule -like 'Y') 
        {
            $AdminESetting = $AdminESetting + ',{"ruleIdentifier":"JustificationRule","setting":"{\"required\":true}"}]'
        } elseif ($ActivationJustificationRule -like 'N') 
        {
            $AdminESetting = $AdminESetting + ',{"ruleIdentifier":"JustificationRule","setting":"{\"required\":false}"}]'
        }

        Write-Host $AdminESetting
    }

    

    if (($AdminE -like 'Y') -or ($Activation -like 'Y'))
    {
        #foreach
        $SettingId = '{"id":"'+$roleSettingId+'"'
        Write-Host $SettingId
        $RoleSet = $SettingId+$UserMemberSetting+$AdminESetting+'}'
        Write-Host $RoleSet
        PatchRoleSetting $RoleSet $roleSettingId
    }
}


#Show menu
function ShowMenu(){
    Write-Host ""
    Write-Host "--------------------------------------- "
    Write-Host "Azure RBAC JIT - PowerShell Menu        "
    Write-Host "--------------------------------------- "
    Write-Host "  1.  EndUser List:             List your eligible role assignments"
    Write-Host "  2.  EndUser Activate:         Activate an eligible role"
    Write-Host "  3.  EndUser Deactivate:       Deactivate an active role"
    Write-Host "  4.  Admin List:               List Assignment against a resource"
    Write-Host "  5.  Admin Assign:             Assign a user to a role"
    Write-Host "  6.  Admin Delete:             Delete Assignment against a resource+role+user"
    Write-Host "  7.  Admin Extend:             List Expiring Eligible Assignment against a resource and option to extend"
    Write-Host "  8.  Admin/EndUser Cancel:     Cancel a request"
    Write-Host "  9.  Admin Query RoleSetting:  List roleSetting against a resource"
    Write-Host "  10. Admin Update RoleSetting: Choose RoleSetting to apply to a single or multiple roles"
    Write-Host "  11. Exit"
    Write-Host ""
}
 
############################################################################################################################################################################
 
$global:serviceRoot = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/"
$global:MSGraphRoot = "https://graph.microsoft.com/v1.0/"
$global:headerParams = ""
$global:assigneeId = ""
$global:mfaDone = $false;
$global:expiration = '2019-07-01T00:00:00Z'
$global:authority = "https://login.microsoftonline.com/common"
$global:scopes = @("https://graph.microsoft.com/.default");
    

$clientID = "dabc52c4-106b-4179-9df2-2f791f44ba14"
$redirectUri = "https://pimmsgraph"
    

# Install msal.ps
if(!(Get-Module | Where-Object {$_.Name -eq 'PowerShellGet' -and $_.Version -ge '2.2.4.1'})) { Install-Module PowerShellGet -Force }
if(!(Get-Package msal.ps)) { Install-Package msal.ps }

$Authed = AcquireToken $global:clientID $global:redirectUri $global:scopes $global:authority $false
if ($Authed -eq $false)
{
    return
}



do
{
    ShowMenu
    #Write-Host "Enter your selection"
    $input = Read-Host "Enter your selection"
    switch ($input)
    {
        '1'
        {
            $assignments = MyJitAssignments
            $assignments | Format-Table -AutoSize -Wrap id,RoleName,ResourceName,ResourceType,ExpirationDate
        }
        '2'
        {
            Activate
        }
        '3'
        {
            Deactivate
        }
        '4'
        {
            ListAssignment
        }
        '5'
        {
            AssignmentEligible
        }
        '6'
        {
            DelAssignment
        }
        '7'
        {
            ListExpiringEligibleAssignments
        }
        '8'
        {
            CancelRequest
        }
        '9'
        {
            ListRoleSetting
        }
        '10'
        {
            UpdateRoleSetting
        }
        '11'
        {
            return
        }

    }
}
until ($input -eq '11')
 
Write-Host ""