Wednesday, June 24, 2026

Automate Azure PIM Role Activation for Entra ID + Azure Resources with PowerShell

Automate Azure PIM Role Activation for Entra ID + Azure Resources with PowerShell

If you're working in a Zero Trust security environment, your Azure roles are likely managed through Privileged Identity Management (PIM) — meaning they expire and need to be manually re-activated every few hours. Clicking through the portal every time is tedious.

This PowerShell script automates activation for both Entra ID directory roles (via Microsoft Graph) and Azure resource roles (via ARM REST API) in a single run — with automatic detection of the maximum allowed duration from your PIM policy.


What This Script Does

  • Auto-installs all required PowerShell modules if not present
  • Activates all eligible Entra ID (Microsoft Graph) PIM roles — e.g. Global Reader, Security Administrator
  • Activates all eligible Azure Resource PIM roles — e.g. Contributor, Owner on subscriptions
  • Auto-detects the maximum allowed activation duration from your PIM policy per role
  • Displays a clean summary table of all eligible roles before activation

Prerequisites

  • PowerShell 5.1+ or PowerShell 7+
  • Internet access (modules are auto-installed if missing)
  • An account with eligible PIM role assignments in Entra ID or Azure subscriptions

The script will auto-install these modules if not found:

ModulePurpose
Az.AccountsAzure authentication & context
Az.ResourcesAzure AD user lookup
Microsoft.Graph.AuthenticationGraph API authentication
Microsoft.Graph.Identity.GovernancePIM role management
Microsoft.Graph.Identity.SignInsSign-in & identity operations

The Script

# ============================================================
#  Azure PIM Role Activator - Entra ID + Azure Resources
# ============================================================

# ── Install & Import Modules ─────────────────────────────────────────────────
foreach ($module in @("Az.Accounts", "Az.Resources")) {
    if (-not (Get-Module -ListAvailable -Name $module)) {
        Write-Host "Installing $module..." -ForegroundColor Yellow
        Install-Module $module -Scope CurrentUser -Force -AllowClobber
    }
    Import-Module $module -ErrorAction Stop
}

$targetVersion = (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication | Sort-Object Version -Descending | Select-Object -First 1).Version
if (-not $targetVersion) {
    Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force -AllowClobber
    $targetVersion = (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication | Sort-Object Version -Descending | Select-Object -First 1).Version
}
foreach ($module in @("Microsoft.Graph.Authentication", "Microsoft.Graph.Identity.Governance", "Microsoft.Graph.Identity.SignIns")) {
    if (-not (Get-Module -ListAvailable -Name $module | Where-Object { $_.Version -eq $targetVersion })) {
        Write-Host "Installing $module $targetVersion..." -ForegroundColor Yellow
        Install-Module $module -RequiredVersion $targetVersion -Scope CurrentUser -Force -AllowClobber
    }
    Import-Module $module -RequiredVersion $targetVersion -ErrorAction Stop
}

# ============================================================
#  PART 1 — Entra ID (Microsoft Graph) PIM Roles
# ============================================================
Write-Host "`n===== ENTRA ID ROLES =====" -ForegroundColor Cyan

Disconnect-MgGraph -ErrorAction SilentlyContinue
Connect-MgGraph -Scopes "User.Read", "RoleManagement.ReadWrite.Directory", "RoleAssignmentSchedule.ReadWrite.Directory" -NoWelcome

$user = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me" -Method GET
if (-not $user) { Write-Error "Failed to retrieve user."; exit }
Write-Host "Logged in as: $($user.userPrincipalName)" -ForegroundColor Green

$eligibleRoles = Get-MgRoleManagementDirectoryRoleEligibilitySchedule -Filter "principalId eq '$($user.id)'" -ExpandProperty RoleDefinition
if (-not $eligibleRoles) { Write-Warning "No eligible Entra ID PIM roles found." }

foreach ($role in $eligibleRoles) {
    try {
        $rawDuration = ((Get-MgPolicyRoleManagementPolicyAssignment `
            -Filter "scopeId eq '/' and scopeType eq 'DirectoryRole' and roleDefinitionId eq '$($role.RoleDefinitionId)'" `
            -ExpandProperty "Policy(`$expand=Rules)").Policy.Rules | Where-Object { $_.Id -eq "Expiration_EndUser_Assignment" }).AdditionalProperties["maximumDuration"]
        $hours = if ($rawDuration -match "PT(\d+)H") { [int]$Matches[1] } elseif ($rawDuration -match "P(\d+)D") { [int]$Matches[1]*24 } else { 8 }
        New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest -BodyParameter @{
            Action           = "selfActivate"
            PrincipalId      = $user.id
            RoleDefinitionId = $role.RoleDefinitionId
            DirectoryScopeId = $role.DirectoryScopeId
            Justification    = "M365 Team"
            ScheduleInfo     = @{ StartDateTime = (Get-Date).ToUniversalTime(); Expiration = @{ Type = "AfterDuration"; Duration = "PT${hours}H" } }
        } | Out-Null
        Write-Host "  ✔ Activated : $($role.RoleDefinition.DisplayName) for $hours hr(s)" -ForegroundColor Green
    }
    catch { Write-Host "  ✘ Failed    : $($role.RoleDefinition.DisplayName) — $($_.Exception.Message)" -ForegroundColor Red }
}

# ============================================================
#  PART 2 — Azure Resources (ARM) PIM Roles
# ============================================================
Write-Host "`n===== AZURE RESOURCE ROLES =====" -ForegroundColor Cyan

Connect-AzAccount
$tokenObj = Get-AzAccessToken -ResourceUrl "https://management.azure.com"
$token    = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
                [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($tokenObj.Token))
$headers  = @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" }
$userId   = (Get-AzADUser -SignedIn).Id

Write-Host "Subscription : $((Get-AzContext).Subscription.Name)" -ForegroundColor Green
Write-Host "User ID      : $userId" -ForegroundColor Green

$roles = (Invoke-RestMethod `
    -Uri "https://management.azure.com/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01&`$filter=asTarget()" `
    -Headers $headers).value

if (-not $roles) { Write-Warning "No eligible Azure resource roles found." } else {
    $roles | ForEach-Object {
        [PSCustomObject]@{
            Role     = $_.properties.expandedProperties.roleDefinition.displayName
            Resource = $_.properties.expandedProperties.scope.displayName
            Type     = $_.properties.expandedProperties.scope.type
            Status   = $_.properties.status
        }
    } | Format-Table -AutoSize
}

Write-Host "`n===== ACTIVATING ALL ROLES =====" -ForegroundColor Yellow

foreach ($role in $roles) {

    $roleName  = $role.properties.expandedProperties.roleDefinition.displayName
    $scopePath = $role.properties.scope
    $roleDefId = $role.properties.roleDefinitionId

    # ── Auto-detect max allowed hours from PIM policy assignment ─────────────
    try {
        $policyAssignments = (Invoke-RestMethod `
            -Uri "https://management.azure.com$scopePath/providers/Microsoft.Authorization/roleManagementPolicyAssignments?api-version=2020-10-01&`$filter=roleDefinitionId eq '$roleDefId'" `
            -Headers $headers).value

        $policyId = $policyAssignments[0].properties.policyId

        $rules = (Invoke-RestMethod `
            -Uri "https://management.azure.com$policyId`?api-version=2020-10-01" `
            -Headers $headers).properties.effectiveRules

        $expiryRule  = $rules | Where-Object { $_.id -eq "Expiration_EndUser_Assignment" }
        $rawDuration = $expiryRule.maximumDuration

        $hours = if ($rawDuration -match "PT(\d+)H")     { [int]$Matches[1] }
                 elseif ($rawDuration -match "P(\d+)D")  { [int]$Matches[1] * 24 }
                 else                                     { 1 }

        Write-Host "  Policy max duration for '$roleName': $rawDuration ($hours hr)" -ForegroundColor DarkGray
    }
    catch {
        $hours = 1
        Write-Host "  Could not read policy for '$roleName', defaulting to 1hr" -ForegroundColor DarkYellow
    }

    # ── Activate the role ─────────────────────────────────────────────────────
    try {
        $body = @{ properties = @{
            principalId      = $userId
            roleDefinitionId = $roleDefId
            requestType      = "SelfActivate"
            linkedRoleEligibilityScheduleId = $role.properties.roleEligibilityScheduleId
            justification    = "Self activation"
            scheduleInfo     = @{ expiration = @{ type = "AfterDuration"; duration = "PT${hours}H" } }
        }} | ConvertTo-Json -Depth 10

        Invoke-RestMethod `
            -Uri "https://management.azure.com$scopePath/providers/Microsoft.Authorization/roleAssignmentScheduleRequests/$([guid]::NewGuid())?api-version=2020-10-01" `
            -Headers $headers -Method Put -Body $body | Out-Null

        Write-Host "  ✔ Activated : $roleName → $($role.properties.expandedProperties.scope.displayName) for $hours hour(s)" -ForegroundColor Green
    }
    catch {
        $err = ($_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue).error.message
        Write-Host "  ✘ Failed    : $roleName — $err" -ForegroundColor Red
    }
}

Write-Host "`nDone!" -ForegroundColor Cyan

How It Works

Module Auto-Install — The script checks for all required Az and Microsoft Graph modules and installs any missing ones before proceeding. It also pins Graph modules to the same version to avoid compatibility conflicts.

Part 1 — Entra ID Roles (Graph API) — Connects via Connect-MgGraph with the required scopes, fetches all eligible directory role assignments for the signed-in user, reads the max allowed duration from each role's PIM policy, and submits a selfActivate request via New-MgRoleManagementDirectoryRoleAssignmentScheduleRequest.

Part 2 — Azure Resource Roles (ARM REST API) — Connects via Connect-AzAccount, retrieves a bearer token (handling the SecureString conversion in newer Az module versions), queries the tenant-level PIM endpoint for eligible resource role assignments, auto-detects max duration from roleManagementPolicyAssignments, and submits activation requests via the ARM REST API.

No comments:

Post a Comment

Featured Post

Automate Azure PIM Role Activation for Entra ID + Azure Resources with PowerShell

Automate Azure PIM Role Activation for Entra ID + Azure Resources with PowerShell If you're working in a Zero Trust security environmen...

Popular posts