all-azure-access is the package and scanner command identity for Azure and EntraID IAM Scanner, a console-runnable Azure and Microsoft Entra access discovery scanner. It is designed to inventory broad access across Azure control plane, Azure data plane role assignments, Entra directory roles, group-derived access, service principal ownership, Key Vault access policies, and optional/best-effort access surfaces such as PIM, billing, reservations, and classic subscription administrators.
The scanner is intentionally resilient: each access area is collected independently, failures are recorded in the output, and the run continues wherever possible.
- The scanner performs read-only discovery through Azure CLI, Microsoft Graph, and Azure Resource Manager APIs.
- The scanner uses your existing local
az loginsession. It does not ask for or store passwords, client secrets, certificates, refresh tokens, or private keys. - Real scan outputs can contain sensitive access data, including privileged users, groups, service principals, ownership relationships, subscriptions, resources, and role assignments.
- Do not commit real scan outputs to a public repository.
- Real scan outputs are written outside the repository by default under the OS temp folder, for example
%TEMP%\azure-scanners\all-azure-access\<timestamp>\on Windows. - Bearer tokens and other secret-shaped values are never written to the logs. A support bundle contains no access or role-assignment rows unless you explicitly pass
-IncludeData. See Diagnostics And Support.
This v1 package is Windows-first. It is tested on both Windows PowerShell 5.1 and PowerShell 7. Cross-platform Linux/macOS support is not the current compatibility target.
Clone the repository and install the Node dependency used for workbook generation:
git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm installSign in with Azure CLI, then run the scanner:
az login
powershell -ExecutionPolicy Bypass -File .\run.ps1 -ContinueOnErrorA practical bounded validation run:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnErrorTarget one or more subscriptions:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -SubscriptionIds <subscription-id-1>,<subscription-id-2> -ContinueOnErrorChoose a custom output root:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -OutputRoot D:\AzureScannerOutput -LimitResources 25 -SkipReservations -ContinueOnErrorYou can also set a default output root for your shell session:
$env:AZURE_IAM_SCANNER_OUTPUT_ROOT = 'D:\AzureScannerOutput'
powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnErrorCollects Entra directory role assignments through Microsoft Graph role management APIs.
Examples of roles represented in this surface:
- Global Administrator
- Privileged Role Administrator
- User Administrator
- Application Administrator
- Groups Administrator
- Security Administrator
- Exchange Administrator
- Teams Administrator
- SharePoint Administrator
- Intune Administrator
- Compliance and security portal roles exposed through Entra role APIs
Captured details include role name, principal id/type, assignment state, directory scope, app scope when present, assignment id, and source API metadata.
Collects Azure Resource Manager role assignments across visible Azure subscriptions and scopes.
Supported scope levels include:
- tenant root scope, best effort
- management groups, best effort
- subscriptions
- resource groups
- resources
- selected child-resource scopes such as storage containers and file shares
Examples of Azure RBAC roles represented in this surface:
- Owner
- Contributor
- Reader
- User Access Administrator
- Network Contributor
- Virtual Machine Contributor
- Key Vault Contributor
- Storage Account Contributor
- custom Azure roles
The scanner preserves scope, scope type, role definition id, assignment id, assignment timestamps, conditions, condition version, inherited flag when returned, subscription name/id, resource group, resource type, and resource name.
Azure data-plane roles are usually assigned through Azure RBAC but grant access to data inside services rather than only management of the ARM resource shell.
The scanner classifies role definitions and observed assignments as control-plane, data-plane, or mixed where possible.
Examples:
- Storage Blob Data Reader
- Storage Blob Data Contributor
- Storage Blob Data Owner
- Storage File Data SMB Share Reader
- Storage File Data SMB Share Contributor
- Key Vault Secrets User
- Key Vault Crypto Officer
- Event Hubs Data Receiver
- Event Hubs Data Sender
- Service Bus Data Sender
- Service Bus Data Receiver
The scanner attempts child-scope discovery for common Storage RBAC data scopes, including:
- blob containers
- file shares
When those scopes are visible and supported by the Azure CLI in the current environment, role assignments are queried at the child scope and included in the normalized output.
For assignments granted to Entra groups, the scanner attempts to expand transitive group membership through Microsoft Graph.
It records:
- the assigned group
- effective members resolved through transitive membership
- access path as
GroupTransitive - group chain text where available
- source group id/name
This lets the report answer both questions:
- Who was directly assigned?
- Who effectively receives access through group membership?
Every assignment is multiplied by every member, so one very large group can dominate the output. Use these flags to bound or skip expansion:
-NoGroupExpansion
-MaxGroupDepth <1-10>
-MaxGroupMembers <int>-MaxGroupDepth defaults to 10, meaning no limit, which Graph answers in a single transitiveMembers call per group. A lower value walks direct members level by level instead. -MaxGroupMembers stops expanding a group after the given number of members and records the truncation in errorsWarnings.csv.
For service principals observed in access assignments, the scanner attempts to retrieve service principal owners through Microsoft Graph.
Owner rows are emitted as effective ownership access because owners may be able to control credentials, federated credentials, or operational identity configuration depending on tenant policy.
Output includes:
- service principal id/name
- owner principal id/type/name
- owner UPN when available
- access path
Owner - assignment type
Owner
The scanner handles Key Vault legacy access policies separately from Azure RBAC.
This matters because older Key Vaults may still grant data-plane permissions using access policies rather than Azure RBAC roles.
Captured details include:
- vault scope
- policy object id
- serialized key/secret/certificate/storage permissions
- subscription/resource group/vault metadata
The scanner attempts to collect eligible assignments where permitted.
Best-effort collectors include:
- Entra directory role eligibility through Graph
- Azure role eligibility through ARM authorization APIs
PIM is not treated as a separate permission model. Rows are marked with assignment states such as Eligible so the workbook can distinguish active access from eligible/JIT access.
Billing and reservation APIs often require separate permissions. These collectors are best-effort and record status rather than blocking the run.
Best-effort surfaces include:
- billing accounts
- billing scopes
- reservation orders
- reservation scopes
- role assignments at billing/reservation scopes where the Azure CLI/API supports them
For console stability, reservation collection can be skipped:
-SkipReservationsThe scanner attempts to include classic administrator style assignments where the Azure CLI returns them.
Examples:
- Account Administrator
- Service Administrator
- Co-Administrator
Classic admin rows are labeled with the ClassicAzureAdmin access model.
The scanner is built around independent collectors. If one collector fails, the failure is recorded and the scan continues.
Collector statuses include:
SucceededSkippedFailedUnauthorizedThrottledPartiallyCollected
Exit codes:
0: completed successfully2: completed with warnings, partial failures, or unauthorized optional collectors1: startup-level failure, such as missing Azure CLI, no login, or output folder failure
Errors and warnings are written to:
output\collectorStatus.csv
output\errorsWarnings.csv
output\coverageSummary.json
errors\<collector>.json
logs\events.jsonl
logs\errorSummary.json
logs\all-azure-access.log
collectorStatus.csv, errorsWarnings.csv, coverageSummary.json, and counts.json are written even when the run is interrupted with Ctrl+C or fails partway through. The event log is flushed as it is written, so it stays readable even if the process is killed outright.
Repeated failures are collapsed: a failure affecting 50,000 scopes produces five examples plus a count in logs\errorSummary.json, not 50,000 log lines.
Each run writes beneath:
%TEMP%\azure-scanners\all-azure-access\<timestamp>\
The run folder contains:
output\
logs\
errors\
cache\
raw\
reports\
support-bundle-<timestamp>.zip
support-bundle-<timestamp>.zip is present when the run was started with -SupportBundle, or whenever the run did not succeed.
output\allAzureAccess.csv
output\effectiveAccess.csv
output\directAssignments.csv
output\groupDerivedAccess.csv
output\nestedGroupAccess.csv
output\servicePrincipalOwners.csv
output\principalResolution.csv
output\groupExpansion.csv
output\roleDefinitions.csv
output\scopeInventory.csv
output\collectorStatus.csv
output\coverageSummary.json
output\counts.json
output\errorsWarnings.csv
output\results.json
output\results.zip
reports\all-azure-access-report-<timestamp>.xlsx
reports\all-azure-access-summary-<timestamp>.json
output\allAzureAccess.json is written only when -EmitJson is supplied. It contains the same data as allAzureAccess.csv, and serialising it is slow and memory-hungry in large tenants.
Diagnostic files are written under logs\:
logs\events.jsonl
logs\errorSummary.json
logs\apiTiming.csv
logs\apiSummary.json
logs\environment.json
logs\parameters.json
logs\all-azure-access.log
The generated workbook is designed for review and pivot-style discovery.
Sheets include:
- Executive Summary
- Collector Status
- Coverage
- All Access Raw
- Effective Access
- Privileged Access
- Data Plane Access
- Service Principal Owners
- Group Access
- Entra Roles
- Azure RBAC
- Management Groups
- Subscriptions
- Resource Groups
- Resources
- Storage Child Scopes
- Key Vault Access
- PIM
- Billing Reservations
- Classic Admins
- Role Definitions
- Principal Directory
- Errors Warnings
- Source Metadata
- Pivots
The workbook includes precomputed summary sections such as:
- Access by Surface
- Access by Role
- Access by Principal Type
- Access by Principal
- Access by Subscription
- Access by Management Group
- Access by Scope Type
- Privileged Roles by Principal
- Data Plane Roles by Resource Type
- Group-Derived Access by Group
- PIM Eligible vs Active
- Failures by Collector
- Resources Discovered by Type
The primary normalized output uses a wide schema so different access models can be compared in one workbook.
Important columns include:
surface
accessModel
collector
assignmentState
assignmentType
principalId
principalType
principalDisplayName
principalUserPrincipalName
principalAppId
effectivePrincipalId
effectivePrincipalType
effectivePrincipalName
effectivePrincipalUserPrincipalName
accessPath
groupChain
sourceGroupId
sourceGroupName
roleName
roleDefinitionId
roleCategory
roleIsPrivileged
roleHasDataActions
scope
scopeType
scopeDisplayName
tenantId
managementGroupId
managementGroupName
subscriptionId
subscriptionName
resourceGroup
resourceType
resourceName
childResourceType
childResourceName
assignmentId
assignmentCreatedOn
assignmentUpdatedOn
condition
conditionVersion
isInherited
sourceApi
collectionStatus
errorCode
errorMessage
For collectors that have both, -Include* forces the collector on, -Skip* forces it off, and -Include* wins if both are supplied. Every collector listed below is on by default.
-SubscriptionIds <string[]>Limit scan to specific subscriptions.
-ManagementGroupIds <string[]>Limit management group RBAC collection to specific management groups.
-ResourceTypes <string[]>Limit resource inventory to specific ARM resource types.
-LimitResources <int>Limit resources per subscription during resource-level discovery. Useful for validation runs.
-SkipResourceLevelSkip resource-level role assignment checks.
-SkipChildDataScopesSkip storage child-scope discovery such as blob containers and file shares.
-SkipPimSkip PIM eligibility collectors.
-SkipBillingSkip billing collectors.
-SkipReservationsSkip reservation collectors. Recommended for faster console runs unless reservation access is explicitly needed.
-SkipClassicAdminsSkip classic subscription administrator checks.
-SkipKeyVaultAccessPoliciesSkip legacy Key Vault access policy collection.
-NoGroupExpansionDo not expand group assignments into effective transitive user/service principal rows.
-FullRoleDefinitionScanCollect role definitions from every visible subscription and management group. Without this flag, the scanner collects role definitions from a responsive subscription and infers metadata from observed assignments to keep console runs fast.
-IncludeOtherTenantsAlso scan subscriptions homed in a tenant other than the signed-in one. These are skipped by default because token acquisition fails for them (AADSTS50020); skipped subscriptions are listed in errorsWarnings.csv.
-DeepScopeScanQuery every resource group, resource, and storage child scope individually instead of relying on the subscription-wide enumeration that already returns assignments at all nested scopes. This is exhaustive but issues one Azure CLI call per scope, so it is very slow in large tenants.
-Parallel <int>Number of Azure CLI processes to run concurrently for per-subscription work (subscription RBAC, scope inventory, Key Vault policies, classic admins, storage child scopes). Defaults to 1. Values of 4 to 8 are a good starting point; going wider risks ARM throttling, which costs more time than it saves.
-MaxGroupDepth <1-10>How many levels of nested group membership to expand. The default 10 means "no limit" and is answered by a single Graph transitiveMembers call per group. Any lower value walks direct members level by level, which is slower but bounds the expansion.
-MaxGroupMembers <int>Stop expanding a group after this many members (0 = unlimited). Because every assignment is multiplied by every member, one very large group can dominate the entire output. Truncated groups are recorded in errorsWarnings.csv.
-EmitJsonAlso write allAzureAccess.json. Off by default because serialising every row is slow and memory-hungry in large tenants; the CSVs contain the same data.
-NoHttpDisable direct HTTPS calls and route every Graph/ARM request through az rest instead. Much slower; use only if the in-process HTTP path is blocked by your environment.
-ContinueOnErrorContinue on collector failures. This is the expected operating mode for broad tenant discovery.
-FailFastStop on the first non-optional collector failure.
-OutputRoot <path>Write run outputs beneath a custom root. If omitted, the scanner uses AZURE_IAM_SCANNER_OUTPUT_ROOT when set, otherwise the OS temp folder plus azure-scanners.
-Quiet
-NoColor
-NoProgressSuppress the progress display, colour, or the progress bar. Colour and progress are disabled automatically when output is redirected or when CI, TF_BUILD, or GITHUB_ACTIONS is set, and NO_COLOR is honoured. The machine-readable RUN_DIR=, ALL_ACCESS_CSV=, FINAL_STATUS= lines are never coloured or suppressed.
The completion estimate is weighted by measured collector cost rather than by collector count, and each run writes timings to <OutputRoot>\.calibration\all-azure-access.json. From the second run against the same environment onward, the percentage and ETA are based on your own measured timings.
-LogLevel Error|Warn|Info|Debug|TraceDefaults to Info. Debug records every Azure CLI and HTTP call with its duration, exit code, and response size.
-RedactLogsReplace user principal names, GUIDs, and resource names in the logs with stable pseudonyms (user-xxxxxxxx@redacted, id-xxxxxxxx, name-xxxxxxxx). Identical values always map to identical pseudonyms, so redacted logs remain correlatable. The reversal map is written to logs\redaction-map.LOCAL-ONLY.json and is never included in a support bundle.
Bearer tokens, JWTs, and secret-shaped values are always stripped, regardless of this switch.
-SupportBundle
-IncludeDataProduce support-bundle-<runid>.zip in the run directory. A bundle is created automatically whenever the run does not succeed. It contains the event log, error summary, API timings, environment details, the exact parameters used, collector status, and row counts. It contains no access or role-assignment rows unless -IncludeData is specified.
-NoTranscript
-MaxLogMegabytes <int>Skip the PowerShell transcript, or change the event-log size cap (default 50 MB).
Every run writes the following under logs\:
| File | Contents |
|---|---|
events.jsonl |
One JSON object per line. Repeated failures are collapsed to five examples plus a count, so a failure affecting 50,000 scopes produces a handful of lines rather than 50,000. |
errorSummary.json |
Every distinct failure class with counts, first/last occurrence, and examples. |
apiTiming.csv |
Per-call timing for every Azure CLI and HTTP request. |
apiSummary.json |
Call counts and durations per API, plus the 50 slowest calls. |
environment.json |
OS, PowerShell, Azure CLI (and extensions), and Node versions. |
parameters.json |
The exact parameters the run was started with. |
collectorStatus.csv, errorsWarnings.csv, coverageSummary.json, and counts.json are written even if the run is interrupted with Ctrl+C or fails partway through.
To report a problem, re-run with -LogLevel Debug -RedactLogs -SupportBundle and send the resulting support-bundle-<runid>.zip.
Fast validation:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -LimitResources 25 -SkipReservations -ContinueOnErrorSingle subscription validation:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -SubscriptionIds <subscription-id> -LimitResources 10 -SkipReservations -ContinueOnErrorBroader tenant run:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -SkipReservations -ContinueOnErrorDeep run with full role definition coverage:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -FullRoleDefinitionScan -ContinueOnErrorLarge tenant:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -Parallel 8 -MaxGroupMembers 5000 -SkipReservations -SkipBilling -ContinueOnErrorReproducing a problem for support:
powershell -ExecutionPolicy Bypass -File .\run.ps1 -LogLevel Debug -RedactLogs -SupportBundle -ContinueOnError- Windows PowerShell 5.1 or PowerShell 7; Windows is the supported v1 operating system
- Git, to clone the repository
- Azure CLI available as
az - Valid
az login - Sufficient Azure RBAC permissions to list assignments and resources
- Microsoft Graph permissions available through the signed-in Azure CLI session for Entra role, principal, group, and owner lookups
- Node.js 18+ for XLSX workbook generation through
report.mjs - Local Node dependency install with
npm install, which installsexceljsfrompackage.json
Clone the repository and run the built-in package validation from the repository root:
git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm run validateThe validation script parses every PowerShell file, verifies that no tokens, user principal names, GUIDs, or resource names can leak into a shareable log file, installs npm dependencies when needed, regenerates the dummy sample workbook, and verifies the sample summary contains rows. It needs no Azure connection.
A dummy-only sample workbook is provided at:
samples\all-azure-access-sample-ALL-dummy.xlsx
The sample workbook is generated only from synthetic inputs under:
samples\dummy-input\
The sample uses fake IDs, fake subscriptions, and example.invalid identities. It does not contain real tenant data.
To regenerate the sample workbook from the dummy inputs:
git clone https://github.com/zmustafa/AzureEntraIDIAMScanner.git
cd AzureEntraIDIAMScanner
npm install
node .\report.mjs `
--run-dir .\samples\dummy-input `
--access-csv .\samples\dummy-input\allAzureAccess.csv `
--effective-csv .\samples\dummy-input\effectiveAccess.csv `
--direct-csv .\samples\dummy-input\directAssignments.csv `
--group-csv .\samples\dummy-input\groupDerivedAccess.csv `
--service-principal-owners-csv .\samples\dummy-input\servicePrincipalOwners.csv `
--collector-status-csv .\samples\dummy-input\collectorStatus.csv `
--scope-inventory-csv .\samples\dummy-input\scopeInventory.csv `
--role-definitions-csv .\samples\dummy-input\roleDefinitions.csv `
--principal-resolution-csv .\samples\dummy-input\principalResolution.csv `
--errors-csv .\samples\dummy-input\errorsWarnings.csv `
--coverage-json .\samples\dummy-input\coverageSummary.json `
--output .\samples\all-azure-access-sample-ALL-dummy.xlsx `
--summary-json .\samples\all-azure-access-sample-ALL-dummy-summary.json- Workload-specific access models such as Exchange RBAC, SharePoint item permissions, Teams membership, Intune RBAC, Azure DevOps permissions, SQL database permissions, and AKS Kubernetes RBAC are not fully implemented in this scanner. They require separate workload APIs and data models.
- Some billing, reservation, PIM, and management group APIs may return no data or unauthorized responses depending on the signed-in account.
- Large tenants take time. Use
-Parallel, and narrow the run with-LimitResources,-ResourceTypes,-SkipResourceLevel, or-SkipChildDataScopes.-DeepScopeScanis exhaustive but issues one Azure CLI call per scope and is very slow. - By default, resource groups and resources are not queried individually, because the subscription-wide enumeration already returns assignments at every nested scope.
-DeepScopeScanrestores per-scope queries if you need to verify this. - Effective access is expanded for groups where Graph transitive membership is available. If Graph denies membership reads, direct assignment rows still remain in the report and the collector status records the issue.
- Excel caps a worksheet at 1,048,576 rows.
report.mjskeeps the first--max-sheet-rowsrows per sheet (250,000 by default) and records any truncation in the report summary; the CSV outputs are always complete. - Subscriptions homed in another tenant are skipped by default, because a token cannot be acquired for them. Use
-IncludeOtherTenantsto attempt them anyway. - v1 is Windows-first. Cross-platform PowerShell support may require additional testing and small process-launch changes.