PowerShell tooling for subscription-scoped Azure Cost Management alerts. The
module creates, inventories, updates, and removes monthly budgets and
Cost Management InsightAlert anomaly alerts. It also includes bounded
deployment and cleanup scripts for multiple subscriptions.
The ready-built module is in SubscriptionCostAlert; building is not required
to use it. From the repository root, authenticate to Azure and import the
module manifest:
az login
Import-Module ./SubscriptionCostAlert/SubscriptionCostAlert.psd1 -Force
Get-Command -Module SubscriptionCostAlertRun az bicep install before using New-SubscriptionCostAlert or the bulk
deployment script. Build only when you modify files under src/; by default,
the build writes the refreshed module to the root SubscriptionCostAlert
folder:
./Build-SubscriptionCostAlertModule.ps1Before creating or changing an alert, inventory the configured alerts visible to the current Azure CLI tenant:
Get-SubscriptionCostAlertPreview a budget update before making any Azure changes:
Set-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Budget `
-BudgetName 'engineering-monthly-budget' `
-BudgetAmount 2000 `
-ForecastedPercent 80 `
-DisableActual `
-WhatIfRemove -WhatIf only after reviewing the preview. The module uses the current
Azure CLI login and requires access to Cost Management resources in each target
subscription.
The module exports four commands:
New-SubscriptionCostAlertcreates one Budget or Anomaly alert.Get-SubscriptionCostAlertinventories Cost Management alert rules.Set-SubscriptionCostAlertupdates recipients and existing budget settings.Remove-SubscriptionCostAlertremoves one named Budget or Anomaly alert.
Create or preview a monthly budget. -BudgetAmount and -EmailRecipient are
required for Budget alerts. ActualPercent and ForecastedPercent accept
multiple values, up to five of each:
New-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Budget `
-BudgetName 'engineering-monthly-budget' `
-BudgetAmount 1000 `
-EmailRecipient 'finops@contoso.com', 'owner@contoso.com' `
-ActualPercent 80, 120 `
-ForecastedPercent 90, 110 `
-WhatIfCreate or preview an anomaly alert. -NotificationEmail is optional and
defaults to the first recipient:
New-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Anomaly `
-EmailRecipient 'finops@contoso.com', 'owner@contoso.com' `
-NotificationEmail 'owner@contoso.com' `
-WhatIfInventory the current Azure CLI tenant, a specific subscription, missing standard coverage, or every visible tenant:
Get-SubscriptionCostAlert
Get-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000'
Get-SubscriptionCostAlert -Missing
Get-SubscriptionCostAlert -Missing -AllTenantsThe command is read-only. It returns normalized PowerShell objects and retains endpoint errors so inaccessible subscriptions are not reported as missing.
With the default -AlertType All, a recipient replacement applies to both the
standard anomaly action and the budget named after the subscription:
Set-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-EmailRecipient 'finops@contoso.com', 'owner@contoso.com' `
-WhatIfUse a hashtable to add or remove recipients incrementally:
Set-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Budget `
-BudgetName 'engineering-monthly-budget' `
-EmailRecipient @{ Add = 'new-owner@contoso.com'; Remove = 'former-owner@contoso.com' } `
-WhatIfFor -AlertType Budget, -BudgetName selects an existing Cost Management
budget; it does not rename it. Supply one or more budget settings to update
only those values while preserving unrequested budget properties. The percent
parameters accept arrays. A multi-value request reconciles that notification
family to the supplied thresholds, while a single-value request preserves the
existing single-threshold update behavior:
Set-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Budget `
-BudgetName 'engineering-monthly-budget' `
-BudgetAmount 2000 `
-ActualPercent 90, 110 `
-ForecastedPercent 80, 100 `
-WhatIfUse -DisableActual or -DisableForecasted to set that notification's
enabled value to false without deleting it. When a supplied Actual or
Forecasted percentage has no matching notification, the command creates one
using the existing budget notification recipients. Omitted settings remain
unchanged.
Removal is high impact. Preview the targeted resource first:
Remove-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Budget `
-BudgetName 'engineering-monthly-budget' `
-WhatIf
Remove-SubscriptionCostAlert `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-AlertType Anomaly `
-ActionName 'SubscriptionAnomalyAlert' `
-WhatIfBudget removal requires -BudgetName; anomaly removal requires -ActionName.
The budget workflow treats a not-found budget as an idempotent completed state.
Deploy-AllSubscriptionCostAlerts.ps1 discovers Enabled subscriptions in the
current Azure CLI account and deploys the standard anomaly and budget alerts to
each one. It returns structured results and continues after an individual rule
failure:
./Deploy-AllSubscriptionCostAlerts.ps1 `
-EmailRecipient 'finops@contoso.com', 'owner@contoso.com' `
-BudgetAmount 1000 `
-WhatIfFor subscription-specific configuration, use the mutually exclusive -CsvPath
mode. The CSV must contain EmailRecipient, AlertType, SubscriptionId,
SubscriptionName, and BudgetAmount. An optional AlertName column selects
the Azure budget name; when blank, the module uses the subscription-derived
default. Budget rows must contain at least one
Actual threshold column (BudgetPercent or BudgetPercent1) and at least one
Forecasted threshold column (ForecastedPercent or ForecastedPercent1). Add
BudgetPercent2 through BudgetPercent5 and
ForecastedPercent2 through ForecastedPercent5 for additional thresholds.
The unsuffixed columns and the 1 columns are aliases for the first threshold
and must not contain conflicting values. Azure Cost budgets support up to five
Actual and five Forecasted notifications per budget. Separate multiple
recipients in one EmailRecipient cell with semicolons. SubscriptionName is
optional report-only metadata; SubscriptionId identifies the Azure target.
Budget rows require positive threshold values, while Anomaly rows must leave
BudgetAmount and all threshold columns blank.
EmailRecipient,AlertType,SubscriptionId,SubscriptionName,AlertName,BudgetAmount,BudgetPercent,ForecastedPercent,BudgetPercent2,ForecastedPercent2
finops@contoso.com;owner@contoso.com,Budget,00000000-0000-0000-0000-000000000000,Engineering,engineering-monthly-budget,1000,80,90,120,110
finops@contoso.com,Anomaly,11111111-1111-1111-1111-111111111111,Shared Services,,,,,,Preview every requested CSV deployment before applying it:
./Deploy-AllSubscriptionCostAlerts.ps1 `
-CsvPath ./subscription-cost-alerts.csv `
-WhatIfRemove-AllSubscriptionCostAlerts.ps1 removes the standard budget named after
each subscription and the SubscriptionAnomalyAlert action. Supply either a
direct ID array or a tenant-aware CSV; the parameter sets are mutually
exclusive. The script previews every target, requests one high-impact
confirmation for a live cleanup, continues after independent failures, and
supports -WhatIf.
For direct IDs, Azure CLI refreshes account discovery to resolve each subscription name and tenant before removal:
./Remove-AllSubscriptionCostAlerts.ps1 `
-SubscriptionId `
'00000000-0000-0000-0000-000000000000',
'11111111-1111-1111-1111-111111111111' `
-WhatIfFor explicitly tenant-scoped removal, the CSV must begin with the columns
SubscriptionName,SubscriptionId,TenantID. SubscriptionName is the budget
resource name to remove, and the script verifies that each ID belongs to its
declared tenant after authentication:
SubscriptionName,SubscriptionId,TenantID
Engineering,00000000-0000-0000-0000-000000000000,aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
Shared Services,11111111-1111-1111-1111-111111111111,bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb./Remove-AllSubscriptionCostAlerts.ps1 `
-CsvPath ./subscription-cost-alert-removals.csv `
-WhatIfGet-BillingSubscriptions.ps1 lists current subscriptions billed to a billing
account. With no parameters, it discovers one accessible billing account; if
multiple accounts are available, it lists their IDs and requires an explicit
selection. It also accepts either the billing-account name or its full ARM
resource ID, follows all API result pages, and does not change the active Azure
CLI subscription:
./Get-BillingSubscriptions.ps1Select a specific billing account when more than one is accessible:
./Get-BillingSubscriptions.ps1 `
-BillingAccountId '00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000_2019-05-31'Build the package after source changes, then run the mock-only Pester tests:
./Build-SubscriptionCostAlertModule.ps1
Invoke-Pester ./testsThe build parses source fragments, builds the generated module, validates the manifest, imports the package, and confirms the four exported functions. The tests do not make live Azure calls.
Before targeting a subscription, list the subscriptions available to your authenticated PowerShell or Azure CLI session. In PowerShell, use the Az module to return the subscription name and identifier as objects:
Get-AzSubscription |
Select-Object Name, IdUse Azure CLI directly to display the same values as a table:
az account subscription list --query '[].{DisplayName:displayName,SubscriptionId:subscriptionId}' -o table- PowerShell 7 or Windows PowerShell 5.1.
- Azure CLI with Bicep installed (
az bicep install). - An authenticated Azure CLI session (
az login). - Read access to enumerate inventory targets.
- Permission to create, update, or remove Cost Management budgets and scheduled actions in each target subscription.
The module restores Azure CLI subscription context for create and removal
workflows where it changes the target subscription. Use -WhatIf for all
write-capable commands before a live run.