Automate Azure PIM Role Activation with PowerShell
Tired of manually clicking Activate in the Azure portal every time your PIM role expires?
This script automatically activates all eligible Azure resource roles — and even detects the maximum allowed duration from your PIM policy, so you never hit the "duration exceeds maximum" error again.
Prerequisites
- Az PowerShell module installed (
Install-Module Az -Scope CurrentUser -Force) - Eligible Azure RBAC roles assigned via PIM
Az.AccountsandAz.Resourcesmodules
The Script
# ============================================================
# Azure PIM Resource Role Activator - Full Script
# ============================================================
# ── Auto-install required 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
}
# Or if you want to install the full Az bundle (all Az modules) instead:
# if (-not (Get-Module -ListAvailable -Name Az.Accounts)) {
# Write-Host "Installing Az module..." -ForegroundColor Yellow
# Install-Module Az -Scope CurrentUser -Force -AllowClobber
# }
# Import-Module Az.Accounts, Az.Resources -ErrorAction Stop
Connect-AzAccount
# Get token (handle SecureString in newer Az versions)
$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" }
$subId = (Get-AzContext).Subscription.Id
$userId = (Get-AzADUser -SignedIn).Id
Write-Host "Subscription : $subId" -ForegroundColor Cyan
Write-Host "User ID : $userId" -ForegroundColor Cyan
# ── Get eligible roles (tenant-level works, subscription-level doesn't) ──────
$roles = (Invoke-RestMethod `
-Uri "https://management.azure.com/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01&`$filter=asTarget()" `
-Headers $headers).value
$roles | ForEach-Object {
[PSCustomObject]@{
Role = $_.properties.expandedProperties.roleDefinition.displayName
Resource = $_.properties.expandedProperties.scope.displayName
ResourceType = $_.properties.expandedProperties.scope.type
Scope = $_.properties.scope
StartTime = $_.properties.startDateTime
EndTime = $_.properties.endDateTime
Status = $_.properties.status
}
} | Format-Table -AutoSize
Write-Host "`nFound $($roles.Count) eligible role(s)" -ForegroundColor Yellow
$roles | ForEach-Object {
Write-Host " • $($_.properties.expandedProperties.roleDefinition.displayName) → $($_.properties.expandedProperties.scope.displayName)"
}
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
1. Authentication — Connects to Azure and retrieves a bearer token, handling the SecureString conversion introduced in newer Az module versions.
2. Eligible Role Discovery — Queries the tenant-level PIM endpoint to fetch all eligible role assignments for the signed-in user.
3. Auto-Duration Detection — For each role, it reads the roleManagementPolicyAssignment to find the Expiration_EndUser_Assignment rule and extracts the maximumDuration (e.g. PT4H). This prevents the "duration exceeds maximum" API error.
4. Role Activation — Submits a SelfActivate request for each eligible role using the exact maximum duration allowed by policy.
No comments:
Post a Comment