invoke-EntraIDPimElevations.ps1

<#PSScriptInfo
.VERSION 1.3
.GUID 78e7abb6-aeae-4486-81e8-9b61e8b2e3e3
.AUTHOR JankeSkanke
.COMPANYNAME CloudWay
.COPYRIGHT 2024 CloudWay
.TAGS EntraID, PIM, Privileged Identity Management
.LICENSEURI https://github.com/JankeSkanke/EntraIDPIMElevation/blob/main/LICENSE
.PROJECTURI https://github.com/JankeSkanke/EntraIDPIMElevation
.ICONURI
.EXTERNALMODULEDEPENDENCIES Microsoft.Graph.Users, Microsoft.Graph.Identity.Governance
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
Script now supports the following features:
    Added consent switch and cleaned up code
    Added ForceRefresh switch and user context to PowerShell Windows Title
.PRIVATEDATA
#>


<#
.SYNOPSIS
    Name: invoke-EntraIDPimElevations.ps1
    Script to activate Eligible roles in Entra ID PIM for a user
.DESCRIPTION
    Script to activate Eligible roles in Entra ID PIM for a user
.PARAMETER RolesToActivate
    Comma separated list of roles to activate, Example -RolesToActivate "Global Reader, Intune Administrator"
.PARAMETER Justification
    Justification for activating the roles
.PARAMETER ActivationDuration
    Duration in hours for the activation, default is 8 hours
.PARAMETER GetAvailableRoles
    Switch to get all available eligible roles for the user
.PARAMETER Consent
    Switch to request consent on all required scopes
.PARAMETER ForceRefresh
    Switch to force refresh of your logon context (tokens)
.EXAMPLE
  invoke-EntraIDPimElevations.ps1 -RolesToActivate "Global Reader, Intune Administrator" -Justification "Testing" -ActivationDuration 4
  invoke-EntraIDPimElevations.ps1 -GetAvailableRoles -ForceRefresh
  invoke-EntraIDPimElevations.ps1 -Consent
 
.NOTES
    Version: 1.3
    Author: Jan Ketil Skanke (@JankeSkanke)
    Creation Date: 2023-12-13
    Author: Jan Ketil Skanke
    Contact: @JankeSkanke
    Created: 2023-12-13
    Version history:
    1.1.0 - (2023-12-13) Script Created
    1.2.0 - (2023-12-15) Added consent switch and cleaned up code
    1.3.0 . (2024-01-26) Added ForceRefresh switch and added context to Windows Title
#>

#Requires -Module Microsoft.Graph.Identity.Governance
#Requires -Module Microsoft.Graph.Users

param (
    [Parameter(Mandatory=$true, ParameterSetName='ActivateRoles', HelpMessage="Specifies the roles to activate.")]
    [ValidateNotNullOrEmpty()]
    [string]$RolesToActivate,
    [Parameter(Mandatory=$true, ParameterSetName='ActivateRoles', HelpMessage="Specifies justification for activation.")]
    [ValidateNotNullOrEmpty()]
    [ValidateLength(6, 200)]
    [string]$Justification,
    [Parameter(Mandatory=$false, ParameterSetName='ActivateRoles', HelpMessage="Specifies the duration of the activation.")]
    [int]$ActivationDuration = 8,
    [Parameter(Mandatory=$false, ParameterSetName='GetRoles', HelpMessage="Get all available eligible roles for the user.")]
    [switch]$GetAvailableRoles,
    [Parameter(Mandatory=$false, ParameterSetName='GetRoles', HelpMessage="Get all available eligible roles for the user.")]
    [Parameter(Mandatory=$false, ParameterSetName='ActivateRoles', HelpMessage="Get all available eligible roles for the user.")]
    [switch]$ForceRefresh,
    [Parameter(Mandatory=$true, ParameterSetName='Consent', HelpMessage="Request consent on scopes.")]
    [switch]$Consent
)
# Clear out any existing tokens
if ($ForceRefresh) {
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
}

# Check if consent is requested and request it if true
if ($Consent) {
    try {
        Connect-MgGraph -Scopes "RoleEligibilitySchedule.Read.Directory, RoleAssignmentSchedule.ReadWrite.Directory, User.Read.All" -NoWelcome -ErrorAction Stop
        Write-Host "Consent OK, exiting script" -ForegroundColor Green
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    exit      
    }
    catch {
        Write-Host "Consent not granted, admin request might be needed" -ForegroundColor Yellow
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
        exit
    }
}

# Connect to Graph
try {
    Connect-MgGraph -NoWelcome -ErrorAction Stop 
    # Get auth context
    $MyContext = Get-MgContext
    $Me = (Get-MgUser -UserId $MyContext.Account).Id
    if ($MyContext.Scopes -notcontains "RoleEligibilitySchedule.Read.Directory" -or $MyContext.Scopes -notcontains "RoleAssignmentSchedule.ReadWrite.Directory" -or $MyContext.Scopes -notcontains "User.Read.All" ) {
        Write-Warning "Missing consent, run script with -Consent switch to request consent"
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
        exit
    }
    $host.ui.RawUI.WindowTitle = $($MyContext.Account)
    Write-Output "Connected as $($MyContext.Account)"     
}
catch {
    Write-Warning "Failed to connect to Graph: $($_.Exception.Message), exiting script"
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    exit
}
# Try to get all eligible roles for user for processing
try {
    $myRoles = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -ExpandProperty RoleDefinition -All -Filter "principalId eq '$Me'" -ErrorAction Stop
}
catch {
    Write-Warning "Failed to get eligible roles: $($_.Exception.Message), exiting script"
    Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
    exit
}

switch ($PSCmdlet.ParameterSetName) {
    "GetRoles"{
        if ($($myroles.count) -eq 0) {
            Write-Warning "No eligible roles found, exiting script"
            break
        }
        Write-Output "You have $($myroles.count) eligible roles and they are:"
        Write-Output $($myRoles.RoleDefinition.DisplayName | Sort-Object | Format-Table)
        break
    }
    "ActivateRoles"{
        if ($($myroles.count) -eq 0) {
            Write-Warning "No eligible roles found, exiting script"
            break
        }
        $MyRolesNames = $myRoles.RoleDefinition.DisplayName
        $Roles = $RolesToActivate.Split(",") | ForEach-Object { $_.Trim() }
        foreach ($role in $Roles) {
            if ($role -notin $MyRolesNames) {
                Write-Warning "Role $role is not eligible for you to activate, exiting script"
                exit
            }
        }
        Write-Host "You are about to activate the following roles:" -ForegroundColor Green
        Write-Output $Roles | Sort-Object | Format-Table
        Write-Output "This will be active for $($ActivationDuration) hours"
        Write-Host "Are you sure you want to continue? (Y/N)" -ForegroundColor Yellow
        $answer = Read-Host
        if ($answer -ne "Y") {
            Write-Output "Exiting script"
            break
        }
        Write-Output "Activating roles: $RolesToActivate"
        Write-Output "Reason: $Justification"
        
        # Activate the roles
        foreach ($role in $Roles) {
            $myRole = $myroles | Where-Object {$_.RoleDefinition.DisplayName -eq $role}
            #Write-Output $myRole
            $params = @{
                Action = "selfActivate"
                PrincipalId = $myRole.PrincipalId
                RoleDefinitionId = $myRole.RoleDefinitionId
                DirectoryScopeId = $myRole.DirectoryScopeId
                Justification = "Enable $role role with reason $Justification"
                ScheduleInfo = @{
                    StartDateTime = Get-Date
                    Expiration = @{
                        Type = "AfterDuration"
                        Duration = "PT$($ActivationDuration)H"
                    }
                }
            }
            try {
                $Activation = New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter $params -ErrorAction Stop
                Write-Output "Activated role $($role) for $($ActivationDuration) hours from UTC $($Activation.ScheduleInfo.StartDateTime) and will expire at UTC $($Activation.ScheduleInfo.StartDateTime.AddHours($ActivationDuration))" 
            }
            catch {
                <#Do this if a terminating exception happens#>
                Write-Warning "Error activating role $($role): $($_.Exception.Message)"
                
            }
        }
    }
    Default {exit}
}
#Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null