A PowerShell script to retrieve VM vCPU and App Service Plan quota consumption across all subscriptions in an Azure tenant, with an interactive HTML report.
This script iterates through every subscription in your Azure tenant and retrieves the current VM quota usage and App Service Plan worker SKU quotas for one or more regions (default: UK South). It generates a comprehensive HTML report showing tenant-wide aggregated usage with expandable subscription-level details, and can optionally export the raw data to CSV/JSON.
- ✅ Automatic Authentication - Reuses an existing Az PowerShell session, or prompts for device code authentication
- ✅ Parameterised - Region(s), output path, usage threshold and subscription scope are all command-line parameters; no need to edit the script
- ✅ Multi-Region - Scan one or many regions in a single run; results are aggregated per SKU and per region
- ✅ Parallel Scanning - Optional
-ThrottleLimitscans many subscriptions concurrently for large tenants, with deterministic results - ✅ Tenant-Wide Scanning - Iterates through all accessible subscriptions (or a supplied subset)
- ✅ VM vCPU Quotas - Filters to compute-related quotas (vCPUs, Virtual Machines, Availability Sets, etc.)
- ✅ App Service Plan Quotas - Retrieves worker SKU quotas (F1, B1, P1v4, EP3, WS1, etc.) via the Microsoft.Quota resource provider
- ✅ HTML Report - Generates an interactive HTML report with:
- Tenant name and ID display
- Tabbed interface switching between VM and App Service views
- Aggregated SKU usage across all subscriptions, broken down by region
- Helpful empty-state messages when no data is returned (e.g. Microsoft.Quota provider not registered)
- Visual progress bars with colour-coded usage levels
- Expandable dropdowns showing per-subscription breakdown
- Independent search/filter for each section
- ✅ CSV / JSON Export - Optional
-ExportCsv/-ExportJsonfor automation and trend tracking - ✅ CI-Friendly -
-FailOnThresholdreturns a non-zero exit code when any SKU is over the threshold;-PassThruemits objects to the pipeline - ✅ High Usage Alerts - Highlights quotas at or above a configurable threshold (default 80%) in both console and report
- ✅ Safe HTML - All dynamic values (subscription names, SKU names, tenant name) are HTML-encoded to prevent markup/script injection into the report
- ✅ Graceful Degradation - App Service quotas are skipped for subscriptions where the Microsoft.Quota provider is not registered, without blocking execution
- ✅ Error Handling - Comprehensive try-catch blocks with informative error messages
- ✅ Tested - Pester unit tests and PSScriptAnalyzer linting run in CI on every push and pull request
- PowerShell Core 7+ (recommended) or PowerShell 5.1+
- Azure PowerShell Modules:
Az.AccountsAz.Compute
Install-Module -Name Az.Accounts -Scope CurrentUser -Repository PSGallery -Force
Install-Module -Name Az.Compute -Scope CurrentUser -Repository PSGallery -ForceApp Service Plan quotas use the Microsoft.Quota resource provider. This provider must be registered on each subscription you want App Service data for. If it is not registered, VM quotas will still be collected and the App Service section will simply be empty for those subscriptions.
To register (requires Contributor or Owner on the subscription):
Register-AzResourceProvider -ProviderNamespace Microsoft.QuotaOr via Azure CLI:
az provider register --namespace Microsoft.QuotaRegistration takes a few minutes to propagate. The script itself only requires Reader access and does not attempt registration.
# Scan all subscriptions for UK South, write QuotaReport.html
./quotafinder.ps1
# Or with PowerShell Core explicitly
pwsh -File ./quotafinder.ps1| Parameter | Description | Default |
|---|---|---|
-Location |
One or more Azure regions to query | uksouth |
-OutputPath |
Path of the HTML report to write | QuotaReport.html (current dir) |
-Threshold |
Usage % (1-100) treated as high/critical | 80 |
-SubscriptionId |
Restrict the scan to specific subscription IDs | all accessible |
-ThrottleLimit |
Number of subscriptions to scan in parallel (1 = sequential) | 1 |
-ExportCsv |
Also export raw per-subscription results to CSV | off |
-ExportJson |
Also export the aggregated summaries to JSON | off |
-PassThru |
Emit the VM and App Service summaries to the pipeline | off |
-FailOnThreshold |
Return a non-zero exit code if any SKU is over the threshold | off |
# Multiple regions, custom threshold, plus CSV export
./quotafinder.ps1 -Location uksouth,ukwest -Threshold 90 -ExportCsv
# Scope to two subscriptions and write to a specific path
./quotafinder.ps1 -SubscriptionId 1111-..., 2222-... -OutputPath ./reports/quota.html
# Large tenant: scan 8 subscriptions at a time (much faster)
./quotafinder.ps1 -ThrottleLimit 8
# CI gate: fail the pipeline if anything is at 85%+
./quotafinder.ps1 -Threshold 85 -FailOnThreshold
# Capture summaries as objects for further processing
$summary = ./quotafinder.ps1 -PassThru
$summary.Vm | Where-Object UsagePercentage -ge 80Performance: by default subscriptions are scanned sequentially (with a progress bar). On large tenants,
-ThrottleLimitscans several at once — each in its own isolated Azure context, so results are identical to a sequential run. Requires PowerShell 7+.
The script will:
- Check for an existing Az PowerShell session, otherwise prompt for device code authentication
- Retrieve all subscriptions in your tenant (or those passed via
-SubscriptionId) - Query VM quotas for the requested region(s) across all subscriptions
- Query App Service Plan quotas via the Microsoft.Quota RP (where registered)
- Display results in the console
- Generate the HTML report (and any requested CSV/JSON exports)
All configuration is via the parameters above — no need to edit the script. For example, to change the target region(s):
./quotafinder.ps1 -Location westeurope,northeuropeNote: the generated report and exports embed tenant and subscription IDs (and names). Treat them as you would any inventory of your environment and avoid committing them to public repositories.
SubscriptionName SubscriptionId Location ResourceType CurrentUsage Limit UsagePercentage
---------------- -------------- -------- ------------ ------------ ----- ---------------
Production-Sub-001 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx uksouth Total Regional vCPUs 442 475 93.05
Production-Sub-002 yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy uksouth Standard FSv2 Family vCPUs 512 520 98.46
Quotas with usage over 80%:
...
The HTML report (QuotaReport.html) includes:
| Section | Description |
|---|---|
| Header | Tenant name, tenant ID, region(s), report generation timestamp |
| VM Quota Summary | All VM SKU families aggregated across subscriptions, per region |
| App Service Plan Quota Summary | All App Service worker SKU quotas aggregated across subscriptions, per region |
| Progress Bars | Visual usage indicators (green < 50%, yellow 50-80%, red ≥ threshold) |
| Expandable Rows | Click any row to see which subscriptions contribute to that SKU's usage |
| Search Boxes | Independent search/filter for each section |
| Field | Description |
|---|---|
| Region | The Azure region the quota applies to |
| SKU Name | VM family quota type (e.g., "Standard DSv5 Family vCPUs") |
| Total Used | Sum of current usage across all subscriptions |
| Total Limit | Sum of quota limits across all subscriptions |
| Usage % | Percentage of total quota consumed |
| Subscriptions | Number of subscriptions with this quota allocated |
The script collects two categories of quotas:
Filtered to include only compute-related quotas:
- Total Regional vCPUs
- Total Regional Low-priority vCPUs
- Virtual Machines
- Availability Sets
- Dedicated vCPUs
- All VM Family vCPUs (DSv5, FSv2, NCASv3_T4, etc.)
All App Service worker SKU types, including:
- Free / Shared tier (F1, D1)
- Basic tier (B1, B2, B3)
- Standard tier (S1, S2, S3)
- Premium v3/v4 tier (P1mv3, P2mv3, P0v4, P1v4, etc.)
- Isolated tier (I1, I2, I3, I1v2, etc.)
- Elastic Premium (EP1, EP2, EP3)
- Workflow Standard (WS1, WS2, WS3)
The account running this script needs:
- Reader role on all subscriptions to be queried
- Or specifically:
Microsoft.Compute/locations/usages/readfor VM quotasMicrosoft.Quota/usages/readandMicrosoft.Quota/quotas/readfor App Service quotas
| Issue | Solution |
|---|---|
| "No subscriptions found" | Ensure you're authenticated and have access to subscriptions |
| "Failed to import Az modules" | Run the Install-Module commands above |
| Device code not appearing | Check the terminal output for the authentication URL and code |
| Some subscriptions skipped | Check you have Reader access to those subscriptions |
| Report shows 0 for some SKUs | Those SKU families have no quota allocated in the region |
| App Service section is empty | The Microsoft.Quota provider is not registered on the subscriptions — see prerequisites |
| "Selected subscription is in 'Disabled' state" | Expected warning for disabled subscriptions — they are skipped automatically |
Pass the -Threshold parameter (no need to edit the script):
./quotafinder.ps1 -Threshold 90Pass multiple regions to -Location:
./quotafinder.ps1 -Location uksouth,ukwest,westeuropeUnit tests (Pester) and linting (PSScriptAnalyzer) run automatically in CI. To run them locally:
Install-Module Pester, PSScriptAnalyzer -Scope CurrentUser -Force
# Lint
Invoke-ScriptAnalyzer -Path . -Recurse -Settings ./PSScriptAnalyzerSettings.psd1
# Test
Invoke-Pester -Path ./testsThe pure helper functions are unit-tested; the script's main entry point is guarded so dot-sourcing it for tests does not require the Az modules or attempt any Azure calls.
Copyright (c) Microsoft Corporation. Licensed under the MIT License.