Assign permission at the subscription level

Prev Next

Use this script to assign the Reader role to your Turbo360 app registration at the subscription level. Run it once per service principal to grant Cost Analyzer access to one or more Azure subscriptions.

Before running the script

  1. Confirm you have Global Admin or Tenant Owner permissions in your Azure tenant.

  2. Have the client ID of the service principal (app registration) you created in the previous step.

  3. Have the list of subscription IDs you want to grant access to.

  4. Sign in to the Azure CLI and obtain an Azure Resource Manager-scoped token using the following command.

az login --use-device-code --tenant [Tenant ID]

  1. Sign in again to obtain a Microsoft Graph API-scoped token using the following command.

az login --use-device-code  --scope https://graph.microsoft.com//.default --tenant [Tenant ID]
  1. Update the $Assignments block in the script below with your client ID and subscription IDs, then run it.

Script

Update the $Assignments block with your client ID and subscription IDs before running. Add more entries to the array to cover additional service principal and subscription combinations.



Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

# --------------------------------------------

$RoleName = "Reader"

# Each entry: ClientId (App ID) + the subscriptions it should be granted access to
$Assignments = @(   
    @{
        ClientId        = "[Client-ID-1]"
        SubscriptionIds = @(
            "[Sub-ID-1]",
            "[Sub-ID-2]",
            "[Sub-ID-3]",
            "[Sub-ID-4]",
            "[Sub-ID-5]"
        )
    }
)

# ---------------------------------------------------------------------------
# Functions
# ---------------------------------------------------------------------------

function Assert-AzCliLoggedIn {
    $account = az account show 2>$null | ConvertFrom-Json
    if (-not $account) {
        throw "Not logged in to Azure CLI. Please run 'az login' before executing this script."
    }
    Write-Host "Logged in as: $($account.user.name)" -ForegroundColor Green
}

function Get-ServicePrincipalObjectId {
    param([string]$ClientId)

    $result = az ad sp show --id $ClientId --query id --output tsv 2>$null
    if (-not $result) {
        throw "Service Principal not found for ClientId: $ClientId"
    }
    return $result.Trim()
}

function Set-RoleAssignment {
    param(
        [string]$ObjectId,
        [string]$ClientId,
        [string]$SubscriptionId,
        [string]$RoleName
    )

    $scope = "/subscriptions/$SubscriptionId"

    # Check if the assignment already exists
    $existing = az role assignment list `
        --assignee $ObjectId `
        --role $RoleName `
        --scope $scope `
        --query "[0].id" `
        --output tsv 2>$null

    if ($existing) {
        return [PSCustomObject]@{
            ClientId       = $ClientId
            SubscriptionId = $SubscriptionId
            Status         = "SKIPPED (already exists)"
        }
    }

    az role assignment create `
        --assignee-object-id $ObjectId `
        --assignee-principal-type ServicePrincipal `
        --role $RoleName `
        --scope $scope `
        --output none 2>$null

    if ($LASTEXITCODE -ne 0) {
        return [PSCustomObject]@{
            ClientId       = $ClientId
            SubscriptionId = $SubscriptionId
            Status         = "FAILED"
        }
    }

    return [PSCustomObject]@{
        ClientId       = $ClientId
        SubscriptionId = $SubscriptionId
        Status         = "ASSIGNED"
    }
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

Assert-AzCliLoggedIn

$results = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($entry in $Assignments) {
    $clientId = $entry.ClientId
    Write-Host "`n--- Service Principal: $clientId ---" -ForegroundColor Cyan

    try {
        $objectId = Get-ServicePrincipalObjectId -ClientId $clientId
    } catch {
        Write-Warning $_.Exception.Message
        foreach ($subId in $entry.SubscriptionIds) {
            $results.Add([PSCustomObject]@{
                ClientId       = $clientId
                SubscriptionId = $subId
                Status         = "FAILED (SP not found)"
            })
        }
        continue
    }

    foreach ($subscriptionId in $entry.SubscriptionIds) {
        Write-Host "  Assigning '$RoleName' on $subscriptionId ..." -NoNewline
        $result = Set-RoleAssignment `
            -ObjectId $objectId `
            -ClientId $clientId `
            -SubscriptionId $subscriptionId `
            -RoleName $RoleName

        $color = switch ($result.Status) {
            { $_ -like "ASSIGNED*" }  { "Green"  }
            { $_ -like "SKIPPED*"  }  { "Yellow" }
            default                    { "Red"    }
        }
        Write-Host " $($result.Status)" -ForegroundColor $color
        $results.Add($result)
    }
}

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------

Write-Host "`n========== SUMMARY ==========" -ForegroundColor Cyan
$results | Format-Table -AutoSize

$assigned = @($results | Where-Object Status -eq "ASSIGNED").Count
$skipped  = @($results | Where-Object { $_.Status -like "SKIPPED*" }).Count
$failed   = @($results | Where-Object { $_.Status -like "FAILED*"  }).Count

Write-Host "Assigned : $assigned" -ForegroundColor Green
Write-Host "Skipped  : $skipped"  -ForegroundColor Yellow
Write-Host "Failed   : $failed"   -ForegroundColor Red