Public/Deploy/Core/budgets/New-CmAzCoreBillingRule.ps1

function New-CmAzCoreBillingRule {

    <#
        .Synopsis
          Deploys a set of budgets at the subscription level.
 
        .Description
         Completes the following:
            * Deploys a set of budgets at the subscription level.
            * Applies filtering via tags (cm-charge: accountnumber) to budgets for resource targeting.
            * Sets the action group to be notified once the threshold for a budget is met.
 
        .Parameter SettingsFile
         File path for the settings file to be converted into a settings object.
 
        .Parameter SettingsObject
         Object containing the configuration values required to run this cmdlet.
 
        .Component
         Core
 
        .Example
         New-CmAzCoreBillingRule -SettingsFile "c:/directory/settingsFile.yml"
 
        .Example
         New-CmAzCoreBillingRule -SettingsObject $settings
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")]
    param(
        [parameter(Mandatory = $true, ParameterSetName = "Settings File")]
        [String]$SettingsFile,
        [parameter(Mandatory = $true, ParameterSetName = "Settings Object")]
        [Object]$SettingsObject
    )

    $ErrorActionPreference = "Stop"

    try {

        Write-CommandStatus -CommandName $MyInvocation.MyCommand.Name

        $SettingsObject = Get-Settings -SettingsFile $SettingsFile -SettingsObject $SettingsObject -CmdletName (Get-CurrentCmdletName -ScriptRoot $PSCommandPath)

        if ($PSCmdlet.ShouldProcess((Get-CmAzSubscriptionName), "Deploying billing rules")) {

            # Below meathod is used because Get-AzConsumptionBudgets is broken and returns null
            Write-Verbose "Getting all budgets..."
            $context = Get-AzContext
            $restCall = Invoke-AzRestMethod -Path "/subscriptions/$($context.Subscription.Id)/providers/Microsoft.Consumption/budgets?api-version=2019-10-01" -Method "GET"
            $existingBudgets = @()
            $existingBudgets += ($restCall.Content | ConvertFrom-Json).value

            foreach($budget in $SettingsObject.budgets) {

                Write-Verbose "Generating budget name..."
                $budget.name = Get-CmAzResourceName -Resource "Budget" -Architecture "Core" -Location $SettingsObject.location -Name $budget.name

                Write-Verbose "Validating budget: $($budget.name)..."
                $currentMonth = (Get-Date -Day 1).date

                if (!($budget.startDate -Is [DateTime]) -or $budget.startDate -lt $currentMonth) {

                    if ($existingBudgets -and $existingBudgets.name.contains($budget.name)) {

                        Write-Verbose "Existing budget: $($budget.name). Start date will be set as per existing configuration..."
                        $existingBudgetConfig = $existingBudgets | Where-Object {$_.name -eq $budget.name }
                        $budget.startDate = $existingBudgetConfig.properties.timePeriod.startDate
                    }
                    else {
                        $budget.startDate = $currentMonth
                    }
                }

                if (!($budget.endDate -Is [DateTime]) -or $budget.endDate -le $currentMonth) {
                    $budget.endDate = $currentMonth.AddYears(1)
                }

                Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "actiongroup" -ResourceServiceContainer $budget -IsDependency

                $actionGroupResourceId = (Get-CmAzService -Service $budget.service.dependencies.actiongroup -ThrowIfUnavailable -ThrowIfMultiple).resourceId

                # Workaround as ARM templates don't support additional copy objects nested within a copied resource.
                # Required for multiple notifications over multiple budgets.

                Write-Verbose "Creating notifications for budget..."
                $notifications = @{ }

                for ($i = 0; $i -lt $budget.thresholds.count; $i++) {

                    $notifications["Notification$i"] = @{
                        enabled = $true;
                        operator = "GreaterThanOrEqualTo";
                        threshold = $budget.thresholds[$i];
                        contactGroups = @($actionGroupResourceId);
                        contactRoles = @(
                            "Owner",
                            "Contributor",
                            "Reader"
                        )
                    }
                }

                $budget.notifications = $notifications
            }

            Write-Verbose "Deploying budgets..."

            $deploymentName = Get-CmAzResourceName -Resource "Deployment" -Architecture "Core" -Location $SettingsObject.location -Name "New-CmAzCoreBillingRule"

            New-AzDeployment `
                -Name $deploymentName `
                -Location $SettingsObject.location `
                -TemplateFile "$PSScriptRoot\New-CmAzCoreBillingRule.json" `
                -TemplateParameterObject @{
                    Budgets = $SettingsObject.budgets
                }

            Write-CommandStatus -CommandName $MyInvocation.MyCommand.Name -Start $false
        }
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($PSItem);
    }
}