Activate and deactivate your own Azure PIM assignments from the command line.
Covers Microsoft Entra ID directory roles and PIM for Groups. Both are "selfActivate" operations, so azpim only ever acts as the signed-in user on that user's own eligible assignments — there is no app-only mode.
Download an archive for your platform from the
releases page and put the
azpim binary somewhere on your PATH:
tar xzf azpim_Darwin_arm64.tar.gz
install azpim /usr/local/bin/
checksums.txt in each release covers the archives, if you want to verify one.
Or build it yourself with Go 1.27 or later:
go install github.com/winebarrel/azpim/cmd/azpim@latest
Either way, azpim --version reports what you have.
Usage: azpim <command> [flags]
Commands:
role list List the directory roles you are eligible for.
role active List the directory roles currently assigned to you.
role requests List your recent activation requests and their approval status.
role activate <role> Activate a directory role you are eligible for.
role deactivate <role> Deactivate a directory role you have active.
group list List the group memberships you are eligible for.
group active List the group memberships currently assigned to you.
group requests List your recent activation requests and their approval status.
group activate <group> Activate a group membership you are eligible for.
group deactivate <group> Deactivate a group membership you have active.
Flags:
--tenant-id=STRING Tenant to sign in to ($AZPIM_TENANT_ID).
--client-id=STRING Application to sign in through ($AZPIM_CLIENT_ID).
--scope=SCOPE,... Delegated scopes to request instead of the ones the
command would ask for ($AZPIM_SCOPE).
$ azpim role list
ROLE SCOPE MEMBER TYPE EXPIRES
Global Reader tenant Direct permanent
Helpdesk Administrator tenant Direct permanent
User Administrator tenant Direct permanent
$ azpim role activate "global reader" --duration 2h --justification "INC-1234"
requested Global Reader for PT2H (Provisioned)
$ azpim role deactivate "global reader"
deactivated Global Reader (Revoked)
Roles and groups are named by their display name in full, case-insensitively. The match is exact, because one role's name can be contained in another's, and a name matching more than one assignment is an error listing the candidates rather than a guess, since activating the wrong role is not a harmless mistake.
--duration takes a Go duration (8h, 1h30m) or ISO 8601 (PT8H, P1D).
It defaults to 8 hours and must stay within the role's PIM policy.
Being eligible for a group as both member and owner is normal, so
group activate accepts --access member|owner to say which one is meant.
Listings go to stdout and can be piped; everything else goes to stderr.
azpim signs in with the authorization code flow and PKCE, redirecting to a
loopback address. It opens your browser, and the token is cached under
~/.cache/azpim (mode 0600) and renewed with its refresh token, so signing in
is a once-in-a-while thing.
The device code flow is deliberately not used: tenants commonly block it with a Conditional Access authentication-flow policy, which no client-side change can work around. The browser flow also runs in your normal browser session, so device-compliance conditions are satisfied the same way they are for the portal.
A role can sit behind a Conditional Access authentication context, which
requires you to authenticate again — usually with MFA — at the moment you
activate rather than when you signed in. PIM refuses such an activation with
RoleAssignmentRequestAcrsValidationFailed and a claims challenge naming the
context it wants.
azpim answers that challenge: it signs you in again carrying the claims, then sends the same request once more. Expect a browser to open mid-command the first time, and not again until that token expires — a token issued for a challenge is cached apart from the ordinary one. The claims are never sent unprompted, because which contexts exist and which roles require them is a per-tenant setting.
--client-id defaults to Microsoft Graph PowerShell
(14d82eec-204b-4c2f-b7e8-296a70dab67e), the first-party public client that
Connect-MgGraph uses. PowerShell itself is not needed or invoked.
The Azure CLI application cannot be used. The delegated Graph scopes Microsoft
grants it are fixed and contain nothing from PIM, so a token borrowed from
az account get-access-token is rejected by every endpoint azpim calls, no
matter how you sign in.
Sign-ins are recorded against whichever application is used, and only scopes the
tenant has already consented to can ever be issued. If your organization would
rather you not sign in through Graph PowerShell, register a public client of
your own with the scopes below and pass --client-id.
Each command asks for only the scopes its own area needs, so a tenant that has consented to one area keeps working there even if the other is unavailable. Access tokens are cached per scope set for the same reason.
Signing in is not per area, though. The refresh token is kept per application, which is what it is issued as, so the first sign-in covers both areas: asking for the other one redeems that same refresh token for its scopes without opening a browser. An area the tenant has not consented to is refused at that point and falls back to a sign-in, which is where the refusal can be seen.
Directory roles:
RoleEligibilitySchedule.Read.DirectoryRoleAssignmentSchedule.ReadWrite.Directory
Groups:
PrivilegedEligibilitySchedule.Read.AzureADGroupPrivilegedAssignmentSchedule.ReadWrite.AzureADGroupGroup.Read.All(only to turn group ids into names)
All of these require admin consent. Note that PrivilegedAccess.*.AzureAD and
PrivilegedAccess.*.AzureADGroup are different scopes despite the names: consent
to the former grants nothing over groups.
If a command fails with PermissionScopeNotGranted, the error names the scopes
the tenant is missing. Use --scope to request a different set — for example
when the tenant has consented to the broader RoleManagement.ReadWrite.Directory
or Directory.Read.All instead of the narrower scopes above, which is common in
tenants that reached their consent through Graph PowerShell rather than through
an application registered for azpim.
A role whose policy requires approval answers with PendingApproval rather than
refusing. azpim says so explicitly, because an accepted request is not the same
as access. Follow it with azpim role requests.