Framework/Core/PIM/PIMScript.ps1


#Acquire Access token
class PIM: CommandBase {
    hidden $APIroot = [string]::Empty
    hidden $headerParams = "";
    hidden $UserId = "";
    hidden  $AccessToken = "";
    hidden $AccountId = "" ;
    hidden $abortflow = 0;

    PIM([string] $subscriptionId, [InvocationInfo] $invocationContext)
    : Base([string] $subscriptionId, [InvocationInfo] $invocationContext) {
        $this.DoNotOpenOutputFolder = $true;
        $this.AccessToken = "";
        $this.AccountId = "";
        $this.APIroot = "https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess/azureResources/";
    }
  
    #Acquire Access token
    AcquireToken() {
        # Using helper method to get current context and access token
        $ResourceAppIdURI = [WebRequestHelper]::GetServiceManagementUrl()
        $this.AccessToken = [Helpers]::GetAccessToken($ResourceAppIdURI);
        $this.AccountId = [Helpers]::GetCurrentSessionUser()
        $this.UserId = (Get-AzADUser -UserPrincipalName  $this.AccountId).Id
        $this.headerParams = @{'Authorization' = "Bearer $($this.AccessToken)" }
    
    }

    #Gets the jit assignments for logged-in user
    hidden [PSObject] MyJitAssignments() {
        $this.AcquireToken();    
        $urlme = $this.APIroot + "/roleAssignments?`$expand=linkedEligibleRoleAssignment,subject,roleDefinition(`$expand=resource)&`$filter=(subject/id%20eq%20%27$($this.UserId)%27)+and+(assignmentState%20eq%20%27Eligible%27)"
        $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $urlme -Method Get
        $assignments = ConvertFrom-Json $response.Content
        $assignments = $assignments.value 
        $assignments = $assignments | Sort-Object  roleDefinition.resource.type , roleDefinition.resource.displayName
        $obj = @()        
        if (($assignments | Measure-Object).Count -gt 0) {
            $i = 0
            foreach ($assignment in $assignments) {
                $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
    }

    # This function resolves the resource that matches to parameters passed in command
    hidden [PIMResource] PIMResourceResolver($SubscriptionId, $ResourceGroupName, $ResourceName) {
        $rtype = 'subscription'
        $selectedResourceName = $SubscriptionId.Trim()
    
        if (-not([string]::IsNullOrEmpty($resourcegroupName))) {
            $selectedResourceName = $resourcegroupName;
            $rtype = 'resourcegroup'
        }
        if (-not([string]::IsNullOrEmpty($resourceName))) {
            $selectedResourceName = $resourceName;
            $rtype = 'resource'
        }
        $item = New-Object psobject -Property @{
            ResourceType = $rtype
            ResourceName = $selectedResourceName
        }
        $resources = $this.ListResources($item.ResourceType);
        if($item.ResourceType -eq 'resource')
        {
            $resolvedResource = $resources | Where-Object { $_.ResourceName -eq $item.ResourceName}
            #If context has access over resourcegroups or resources with same name, get a match based on Subscription and rg passed in param
            if (($resolvedResource | Measure-Object).Count -gt 1) {       
           
                $resolvedResource = $resolvedResource | Where-Object { $_.ExternalId -match $SubscriptionId }
                if (-not([string]::IsNullOrEmpty($ResourceGroupName))) {
                    $resolvedResource = $resolvedResource | Where-Object { $_.ExternalId -match $ResourceGroupName }
                }
        
            }
        }
        else
        {
            $resolvedResource = [PIMResource]::new()
            if($item.ResourceType -eq 'subscription')
            {
                $resolvedResource.ExternalId = "/subscriptions/$($item.ResourceName)"
            }
            elseif($item.ResourceType -eq 'resourcegroup')
            {
                $resolvedResource.ExternalId = "/subscriptions/$($SubscriptionId.Trim())/resourceGroups/$($item.ResourceName)"
            }
            $temp = $resources | Where-Object { $_.ExternalId -eq $resolvedResource.ExternalId}
            if(($temp| Measure-Object).Count -gt 0)
            {
                $resolvedResource = $temp
            }
        }
        return $resolvedResource    
    }
    #List all the resources accessible to context.
    hidden [System.Collections.Generic.List[PIMResource]] ListResources($type) {
        $this.AcquireToken();
        $resources = $null
        #this seperation is required due to nature of API, it operates in paging/batching manner when we query for all types
        if ($type -eq 'resource') {
            $Resourceurl = $this.APIroot + "/resources?`$select=id,displayName,type&`$filter=(type%20ne%20%27resourcegroup%27%20and%20type%20ne%20%27subscription%27%20and%20type%20ne%20%27managementgroup%27%20and%20type%20ne%20%27resourcegroup%27%20and%20type%20ne%20%27subscription%27%20and%20type%20ne%20%27managementgroup%27)" 
            try {
                $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $Resourceurl -Method Get
                $values = ConvertFrom-Json $response.Content
                $resources = $values.value
                $hasOdata = $values | Get-Member -Name '@odata.nextLink'
                while ($null -ne $hasOdata -and -not([string]::IsNullOrEmpty(($values).'@odata.nextLink'))) {
                    $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $(($values).'@odata.nextLink') -Method Get
                    $values = ConvertFrom-Json $response.Content
                    $resources += $values.value
                    $hasOdata = $values | Get-Member -Name '@odata.nextLink'
                
                }
            
            
            }
            catch {
                if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                {
                    $this.PublishCustomMessage($_.ErrorDetails.Message,[MessageType]::Error)
                }
                else
                {
                    $this.PublishCustomMessage($_.Exception, [MessageType]::Error)
                }
            }
        }
        else {
            #TODO - why is management group also used in the else part filter?
            if ($type -eq 'subscription')
            {
                $Resourceurl = $this.APIroot + "/resources?`$select=id,displayName,type&`$filter=(type%20eq%20%27subscription%27or%20type%20eq%20%27managementgroup%27)&`$orderby=type"
            }
            else
            {
                $Resourceurl = $this.APIroot + "/resources?`$select=id,displayName,type&`$filter=(type%20eq%20%27resourcegroup%27or%20type%20eq%20%27managementgroup%27)&`$orderby=type"
            }
            $response = $null
            try
            {
                $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $Resourceurl -Method Get
                $values = ConvertFrom-Json $response.Content
                $resources = $values.value
                $hasOdata = $values | Get-Member -Name '@odata.nextLink'
                while ($null -ne $hasOdata -and -not([string]::IsNullOrEmpty(($values).'@odata.nextLink')))
                {
                    $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $(($values).'@odata.nextLink') -Method Get
                    $values = ConvertFrom-Json $response.Content
                    $resources += $values.value
                    $hasOdata = $values | Get-Member -Name '@odata.nextLink'
                }
            }
            catch
            {
                if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                {
                    $this.PublishCustomMessage($_.ErrorDetails.Message,[MessageType]::Error)
                }
                else
                {
                    $this.PublishCustomMessage($_.Exception, [MessageType]::Error)
                }
                return $null;
            }
        }
        $i = 0
        $obj = New-Object "System.Collections.Generic.List[PIMResource]"
        foreach ($resource in $resources) {
            $item = New-Object PIMResource
            $item.Id = ++$i
            $item.ResourceId = $resource.id
            $item.ResourceName = $resource.DisplayName
            $item.Type = $resource.type
            $item.ExternalId = $resource.externalId
            $obj.Add($item);
        }
        return $obj
    }



    #List roles
    hidden [PSObject] ListRoles($resourceId) {
        $this.AcquireToken();
        $url = $this.APIroot + "resources/" + $resourceId + "/roleDefinitions?`$select=id,displayName,type,templateId,resourceId,externalId,subjectCount,eligibleAssignmentCount,activeAssignmentCount&`$orderby=activeAssignmentCount%20desc"
        $response = Invoke-WebRequest -UseBasicParsing -Headers $this.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
                SubjectCount     = $role.SubjectCount
            }
            $obj = $obj + $item
        }

        return $obj 
    }

    #List Assignment
    hidden [PSObject] ListAssignmentsWithFilter($resourceId, $IsPermanent) {
        $this.AcquireToken()
        $url = $this.APIroot + "resources/" + $resourceId + "`/roleAssignments?`$expand=subject,roleDefinition(`$expand=resource)"
        #Write-Host $url

        $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $url -Method Get
        $roleAssignments = ConvertFrom-Json $response.Content
        $i = 0
        $obj = @()
        $assignments = @();
        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
                IsPermanent      = $roleAssignment.IsPermanent
                RoleName         = $roleAssignment.roleDefinition.displayName
                ExpirationDate   = $roleAssignment.endDateTime
                SubjectId        = $roleAssignment.subject.id
                SubjectType      = $roleAssignment.subject.type
                UserName         = $roleAssignment.subject.displayName
                AssignmentState  = $roleAssignment.AssignmentState
                MemberType       = $roleAssignment.memberType
                PrincipalName    = $roleAssignment.subject.principalName
            }
            $obj = $obj + $item
        }
        if (($obj | Measure-Object).Count -gt 0) {
            if ($IsPermanent) {
                $assignments = $obj | Where-Object { $_.IsPermanent -eq $true }
                
            }
            else {
                $assignments = $obj | Where-Object { $_.IsPermanent -eq $false }
                
            }
        }
        
        return $assignments
    }

    #Activates the user
    hidden Activate($SubscriptionId, $ResourceGroupName, $ResourceName, $roleName, $Justification, $Duration) {
        $this.AcquireToken();
        $assignments = $this.MyJitAssignments()
        $resource = $this.PIMResourceResolver($SubscriptionId, $ResourceGroupName, $ResourceName)

        if (($assignments | Measure-Object).Count -gt 0 -and (-not [string]::IsNullOrEmpty($resource.ExternalId))) {
            $matchingAssignment = $assignments | Where-Object { $_.OriginalId -eq $resource.ExternalId -and $_.RoleName -eq $roleName }
            if (($matchingAssignment | Measure-Object).Count -gt 0) {
                $this.PublishCustomMessage("Requesting activation of your [$($matchingAssignment.RoleName)] role on [$($matchingAssignment.ResourceName)]... ", [MessageType]::Info);
                $resourceId = $matchingAssignment.ResourceId
                $roleDefinitionId = $matchingAssignment.RoleId
                $subjectId = $matchingAssignment.SubjectId
                $RequestActivationurl = $this.APIroot + "roleAssignmentRequests "
                $postParams = '{"roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '","assignmentState":"Active","type":"UserAdd","reason":"' + $Justification + '","schedule":{"type":"Once","startDateTime":"' + (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + '","duration":"PT' + $Duration + 'H"},"linkedEligibleRoleAssignmentId":"' + $matchingAssignment.IdGuid + '"}'
       
    
                try {
                    $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $RequestActivationurl -Method Post -ContentType "application/json" -Body $postParams
                    if ($response.StatusCode -eq 201) {
                        $this.PublishCustomMessage("Activation queued successfully. The role(s) should get activated in a few minutes.", [MessageType]::Update);
                    }
                 
                }
                catch {
                    if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                    {
                        $this.PublishCustomMessage($_.ErrorDetails.Message,[MessageType]::Error)
                    }
                    else
                    {
                        $this.PublishCustomMessage($_.Exception, [MessageType]::Error)
                    }
                }
            }
            else {
                $this.PublishCustomMessage("No matching eligible role found for the current context", [MessageType]::Warning)
            }
        }    
        else {
            $this.PublishCustomMessage("No eligible role found for the current context", [MessageType]::Warning)
        }
    }

    #Deactivates the user
    hidden Deactivate($SubscriptionId, $ResourceGroupName, $ResourceName, $roleName) {
        $this.AcquireToken();
        $assignments = $this.MyJitAssignments() | Where-Object { -not [string]::IsNullorEmpty($_.ExpirationDate) }
        $resource = $this.PIMResourceResolver($SubscriptionId, $ResourceGroupName, $ResourceName)

        if (($assignments | Measure-Object).Count -gt 0 -and (-not [string]::IsNullOrEmpty($resource.ExternalId))) {
            $matchingAssignment = $assignments | Where-Object { $_.OriginalId -eq $resource.ExternalId -and $_.RoleName -eq $roleName }
            if (($matchingAssignment | Measure-Object).Count -gt 0)
            {     
                $this.PublishCustomMessage("Requesting deactivation of your [$($matchingAssignment.RoleName)] role on [$($matchingAssignment.ResourceName)]... ", [MessageType]::Info);
                $id = $matchingAssignment.IdGuid
                $resourceId = $matchingAssignment.ResourceId
                $roleDefinitionId = $matchingAssignment.RoleId
                $subjectId = $matchingAssignment.SubjectId
                $deactivaturl = $this.APIroot + "/roleAssignmentRequests "
                $postParams = '{"roleDefinitionId":"' + $roleDefinitionId + '","resourceId":"' + $resourceId + '","subjectId":"' + $subjectId + '","assignmentState":"Active","type":"UserRemove","linkedEligibleRoleAssignmentId":"' + $id + '"}'
                try {
                    $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $deactivaturl -Method Post -ContentType "application/json" -Body $postParams
                    if ($response.StatusCode -eq '201') {
                        $this.PublishCustomMessage("Deactivation queued successfully. The role(s) should get deactivated in a few minutes.", [MessageType]::Update);
                    }
                }
                catch {
                    if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                    {
                        $this.PublishCustomMessage($_.ErrorDetails.Message,[MessageType]::Error)
                    }
                    else
                    {
                        $this.PublishCustomMessage($_.Exception, [MessageType]::Error)
                    }
                }
            }
            else
            {
                $this.PublishCustomMessage("No active assignments found for the current context.", [MessageType]::Warning);
            }
        }
        else {
            $this.PublishCustomMessage("No active assignments found for the current context.", [MessageType]::Warning);
        }
    

    }

    #List RoleAssignment
    hidden ListAssignment($SubscriptionId, $ResourceGroupName, $ResourceName, $RoleNames, $CheckPermanent) {
        $this.AcquireToken();
        $criticalRoles = @();
        $criticalRoles += $this.ConvertToStringArray($RoleNames)
        $resources = $this.PIMResourceResolver($SubscriptionId, $ResourceGroupName, $ResourceName)
        $permanentAssignment = $null
        if (($resources | Measure-Object).Count -gt 0 -and (-not [string]::IsNullOrEmpty($resources.ResourceId))) {       
            $roleAssignments = $this.ListAssignmentsWithFilter($resources.ResourceId, $CheckPermanent)
            if(-not [String]::IsNullOrEmpty($RoleNames))
            {
                $roleAssignments = $roleAssignments | Where-Object { $_.RoleName -in $criticalRoles -and $_.MemberType -ne 'Inherited' }
            }
            else
            {
                $roleAssignments = $roleAssignments | Where-Object { $_.MemberType -ne 'Inherited' }
            }
            if (($roleAssignments | Measure-Object).Count -gt 0) {
                $roleAssignments = $roleAssignments | Sort-Object -Property RoleName, Name 
                $this.PublishCustomMessage("")
                $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default)
                $this.PublishCustomMessage($($roleAssignments | Format-Table -Property @{Label = "Role"; Expression = { $_.RoleName } }, PrincipalName, AssignmentState, @{Label = "Type"; Expression = { $_.SubjectType } } | Out-String), [MessageType]::Default)
            }
            else {
                if ($CheckPermanent) {
                    $this.PublishCustomMessage("No permanent assignments found for this combination.", [MessageType]::Warning);
                }
                else {
                    $this.PublishCustomMessage("No PIM eligible assignments found for this combination.", [MessageType]::Warning);
                }    
            }
        }
        else {
            $this.PublishCustomMessage("No active assignments found for the current logged in context.", [MessageType]::Warning )
        }
        
    }

    #Assign a user to Eligible Role
    hidden AssignPIMRole($subscriptionId, $resourcegroupName, $resourceName, $roleName, $PrincipalName, $duration) {
        $this.AcquireToken();
        $resolvedResource = $this.PIMResourceResolver($subscriptionId, $resourcegroupName, $resourceName)
        if (($resolvedResource | Measure-Object).Count -gt 0 -and (-not [string]::IsNullOrEmpty($resolvedResource.ResourceId))) {
            $resourceId = $resolvedResource.ResourceId
            $roles = $this.ListRoles($resourceId)
            $roleDefinitionId = ($roles | Where-Object { $_.RoleName -eq $RoleName }).RoleDefinitionId
            $users = $null
            $subjectId = "";
            try {
                $users = Get-AzADUser -UserPrincipalName $PrincipalName
            }
            catch {
                $this.PublishCustomMessage("Unable to fetch details of the principal name provided.", [MessageType]::Warning)
                return;
            }
            if (($users | Measure-Object).Count -gt 0) {
                $subjectId = $users.Id
            }
            else {
                $this.PublishCustomMessage("Unable to fetch details of the principal name provided.", [MessageType]::Error)
                return;
            }            
            $url = $this.APIroot + "/roleAssignmentRequests"
            # Update end time
            if (-not($duration)) {
                $duration = 15
            }
            $ts = New-TimeSpan -Days $duration
            $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"}}'
    
            try {
                $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $url -Method Post -ContentType "application/json" -Body $postParams
                if ($response.StatusCode -eq 201) {
                    $this.PublishCustomMessage("Assignment request for [$PrincipalName] for the [$RoleName] role on [$($resolvedResource.ResourceName)] queued successfully.", [MessageType]::Update);
                }  
                if ($response.StatusCode -eq 401) {
                    $this.PublishCustomMessage("You are not eligible to assign a role. If you have recently elevated/activated your permissions, please run Connect-AzAccount and re-run the script.", [MessageType]::Error);
                }          
            }
            catch {
                if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                {
                    $this.PublishCustomMessage($_.ErrorDetails.Message,[MessageType]::Error)
                }
                else
                {
                    $this.PublishCustomMessage($_.Exception, [MessageType]::Error)
                }         
            }
        }
        else {
            $this.PublishCustomMessage("No matching resource found for assignment.", [MessageType]::Warning)
        }
    }

    hidden ListMyEligibleRoles() {
        $assignments = $this.MyJitAssignments()
        if (($assignments | Measure-Object).Count -gt 0) {
            $this.PublishCustomMessage("Your eligible role assignments:", [MessageType]::Default)
            $this.PublishCustomMessage("");
            $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default)
            $this.PublishCustomMessage(($assignments | Format-Table -AutoSize Id, RoleName, ResourceName, ResourceType, ExpirationDate | Out-String), [MessageType]::Default)
            $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default)
            $this.PublishCustomMessage("");
        }
        else {
            $this.PublishCustomMessage("No eligible roles found for the current login.", [MessageType]::Warning);
        }
    }

    hidden TransitionFromPermanentRolesToPIM($SubscriptionId, $ResourceGroupName, $ResourceName, $RoleNames, $DurationInDays, $Force) {
       
        $resolvedResource = $this.PIMResourceResolver($subscriptionId, $resourcegroupName, $resourceName)
        if (($resolvedResource | Measure-Object).Count -gt 0 -and (-not [string]::IsNullOrEmpty($resolvedResource.ResourceId))) {    
            $resourceId = $resolvedResource.ResourceId
            $roles = $this.ListRoles($resourceId)
            $roles = ($roles | Where-Object { $_.RoleName -in $($RoleNames.split(",").Trim()) })
            # $roleDefinitionId = $role.RoleDefinitionId
            $CriticalRoles = $roles.RoleName #$ControlSettings.CriticalPIMRoles
            $this.PublishCustomMessage("Fetching permanent assignment for [$(($criticalRoles) -join ", ")] role on $($resolvedResource.Type) [$($resolvedResource.ResourceName)]...",[MessageType]::Info)
            $permanentRoles = $this.ListAssignmentsWithFilter($resourceId, $true)
            if (($permanentRoles | Measure-Object).Count -gt 0) {
                $permanentRolesForTransition = $permanentRoles | Where-Object { $_.SubjectType -eq 'User' -and $_.MemberType -ne 'Inherited' -and $_.RoleName -in $CriticalRoles }
                if (($permanentRolesForTransition | Measure-Object).Count -gt 0) {
                    $ToContinue = ''
                    if(!$Force)
                    {
                        $this.PublishCustomMessage($($permanentRolesForTransition | Format-Table -AutoSize -Wrap PrincipalName, ResourceName, ResourceType, RoleName | Out-String), [MessageType]::Default)
                        $this.PublishCustomMessage("");
                        Write-Host "The above role assignments will be moved from 'permanent' to 'PIM'. `nPlease confirm (Y/N): " -ForegroundColor Yellow -NoNewline
                        $ToContinue = Read-Host
                    }
                    if ($ToContinue -eq 'y' -or $Force) {               
                        $Assignmenturl = $this.APIroot + "/roleAssignmentRequests"
                        $roles = $this.ListRoles($resourceId)  
                        $ts = $DurationInDays;
                        $totalPermanentAssignments = ($permanentRolesForTransition | Measure-Object).Count
                        $this.PublishCustomMessage("Initiating PIM assignment for [$totalPermanentAssignments] permanent assignments..."); #TODO: Check the color
                        $i = 1
                        $permanentRolesForTransition | ForEach-Object {
                            $roleName = $_.RoleName
                            $roleDefinitionId = ($roles | Where-Object { $_.RoleName -eq $roleName }).RoleDefinitionId 
                            $subjectId = $_.SubjectId
                            $PrincipalName = $_.PrincipalName
                            #$Scope= $_.OriginalId
                            $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).AddDays($ts).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")) + '","type":"Once"}}'
                            try {
                                
                                $this.PublishCustomMessage([Constants]::SingleDashLine)
                                # $this.PublishCustomMessage("Requesting PIM assignment for [$($_.RoleName)' role for $($_.PrincipalName) on $($_.ResourceType) '$($resolvedResource.ResourceName)'...");
                                $response = Invoke-WebRequest -UseBasicParsing -Headers $this.headerParams -Uri $Assignmenturl -Method Post -ContentType "application/json" -Body $postParams
                                if ($response.StatusCode -eq 201) {
                                    $this.PublishCustomMessage("[$i`/$totalPermanentAssignments] Successfully requested PIM assignment for [$PrincipalName]", [MessageType]::Update);
                                }
                                $this.PublishCustomMessage([Constants]::SingleDashLine)
                          
                            }
                            catch {
                                if([Helpers]::CheckMember($_,"ErrorDetails.Message"))
                                {
                                    $code = $_.ErrorDetails.Message | ConvertFrom-Json
                                    if ($code.error.code -eq "RoleAssignmentExists") {
                                        $this.PublishCustomMessage("[$i`/$totalPermanentAssignments] PIM Assignment for [$PrincipalName] already exists.", [MessageType]::Update)
                                    }
                                    else {
                                        $this.PublishCustomMessage("$($code.error)", [MessageType]::Error)
                                    }
                                }
                                else
                                {
                                    $this.PublishCustomMessage("$($_.Exception)", [MessageType]::Error)
                                }                                                             
                            }         
                            $i++;
                        }#foreach
                    }
                    else {
                        return;
                    }
                }
                else {
                    $this.PublishCustomMessage("No permanent assignments eligible for PIM assignment found.", [MessageType]::Warning);       
                }
            }
            else {
                $this.PublishCustomMessage("No permanent assignments found for this resource.", [MessageType]::Warning);       
            }
        }
        else
        {
            $this.PublishCustomMessage("No matching resource found for the current context.", [MessageType]::Warning)
        }
    }

    hidden RemovePermanentAssignments($SubscriptionId, $ResourceGroupName, $ResourceName, $RoleNames, $RemoveAssignmentFor, $Force) {
        $this.AcquireToken();
        $resolvedResource = $this.PIMResourceResolver($subscriptionId, $resourcegroupName, $resourceName)
        if(-not [String]::IsNullOrEmpty($resolvedResource.ResourceId))
        {
            $resourceId = ($resolvedResource).ResourceId 
            $users = @();
            $CriticalRoles = $RoleNames.split(",").Trim()
            $this.PublishCustomMessage("Note: This command will *not* remove your permanent assignment if one exists.", [MessageType]::Warning)
            $this.PublishCustomMessage("Fetching permanent assignment for [$(($criticalRoles) -join ", ")] role on $($resolvedResource.Type) [$($resolvedResource.ResourceName)]...", [MessageType]::Info)
            $permanentRoles = $this.ListAssignmentsWithFilter($resourceId, $true)
            $eligibleAssignments = $this.ListAssignmentsWithFilter($resourceId, $false)
            $eligibleAssignments = $eligibleAssignments | Where-Object { $_.SubjectType -eq 'User' -and $_.MemberType -ne 'Inherited' -and $_.RoleName -in $CriticalRoles }
            if (($permanentRoles | Measure-Object).Count -gt 0) {
                $permanentRolesForTransition = $permanentRoles | Where-Object { $_.SubjectType -eq 'User' -and $_.MemberType -ne 'Inherited' -and $_.RoleName -in $CriticalRoles }
                $successfullyassignedRoles = @();
                $currentContext = [Helpers]::GetCurrentRmContext();
                $permanentRolesForTransition = $permanentRolesForTransition | Where-Object { $_.PrincipalName -ne $currentContext.Account.Id }
                if ($RemoveAssignmentFor -ne "AllExceptMe") {
                    $eligibleAssignments | ForEach-Object {
                        $allUser = $_;
                        $permanentRolesForTransition | ForEach-Object {
                    
                            if ($_.SubjectId -eq $allUser.SubjectId -and $_.RoleName -eq $allUser.RoleName) {
                                $successfullyassignedRoles += $_
                            } 
                        }
                    }             
                    $users = $successfullyassignedRoles            
                }
                else {
                    $users = $permanentRolesForTransition
                }
            }
        
            if (($users | Measure-Object).Count -gt 0) {
                $userResp = ''
                $totalRemovableAssignments = ($users | Measure-Object).Count
                if(!$Force)
                {
                    $this.PublishCustomMessage($($users | Format-Table -Property PrincipalName, RoleName, OriginalId | Out-String), [MessageType]::Default)
                    Write-Host "The above role assignments will be moved from 'permanent' to 'PIM'. `nPlease confirm (Y/N): " -ForegroundColor Yellow -NoNewline
                    $userResp = Read-Host
                } 
                if ($userResp -eq 'y' -or $Force) {
                    $i = 0
                    $this.PublishCustomMessage("Initiating removal of [$totalRemovableAssignments] permanent assignments...")
                    foreach ($user in $users) {
                        $i++;
                        $this.PublishCustomMessage([Constants]::SingleDashLine);
                        Remove-AzRoleAssignment -SignInName $user.PrincipalName -RoleDefinitionName $user.RoleName -Scope $user.OriginalId
                        $this.PublishCustomMessage("[$i`/$totalRemovableAssignments]Successfully removed permanent assignment", [MessageType]::Update )                
                        $this.PublishCustomMessage([Constants]::SingleDashLine);

                    }
                }
            }
            else {
                $this.PublishCustomMessage("No permanent assignments found for the scope.", [MessageType]::Warning)
            }
        }
        else
        {
            $this.PublishCustomMessage("No matching resource found for the current context.", [MessageType]::Warning)
        }
    }
}

class PIMResource {
    [int] $Id
    [string] $ResourceId #Id refered by PIM API to uniquely identify a resource
    [string] $ResourceName 
    [string] $Type 
    [string] $ExternalId #ARM resourceId
}