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
Confirm you have Global Admin or Tenant Owner permissions in your Azure tenant.
Have the client ID of the service principal (app registration) you created in the previous step.
Have the list of subscription IDs you want to grant access to.
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]
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]
Update the
$Assignmentsblock 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