Back to Blog
7 min read

Implementing Azure Advisor Recommendations at Scale

Introduction

Azure Advisor is your personalized cloud consultant that analyzes your resource configuration and usage to provide recommendations across five categories: Cost, Security, Reliability, Operational Excellence, and Performance. This guide covers programmatic access to Advisor recommendations and strategies for implementing them at scale.

Accessing Advisor Recommendations

Azure CLI Queries

# List all recommendations
az advisor recommendation list --output table

# Filter by category
az advisor recommendation list --category Cost --output table

# Get recommendations for specific resource
az advisor recommendation list \
  --resource-group myResourceGroup \
  --category Performance

# Get high-impact recommendations
az advisor recommendation list \
  --query "[?impact=='High']" \
  --output table

PowerShell Access

# Get all recommendations
$recommendations = Get-AzAdvisorRecommendation

# Group by category
$recommendations | Group-Object Category | Select-Object Name, Count

# Filter cost recommendations with potential savings
$costRecs = Get-AzAdvisorRecommendation |
    Where-Object { $_.Category -eq 'Cost' } |
    Select-Object ResourceId, ShortDescription, ExtendedProperties

# Show potential savings
$costRecs | ForEach-Object {
    $savings = $_.ExtendedProperties['annualSavingsAmount']
    if ($savings) {
        [PSCustomObject]@{
            Resource = ($_.ResourceId -split '/')[-1]
            Description = $_.ShortDescription.Problem
            AnnualSavings = "$" + $savings
        }
    }
}

C# SDK Integration

using Azure.Identity;
using Azure.ResourceManager;
using Azure.ResourceManager.Advisor;

public class AdvisorService
{
    private readonly ArmClient _armClient;

    public AdvisorService()
    {
        _armClient = new ArmClient(new DefaultAzureCredential());
    }

    public async Task<IList<RecommendationSummary>> GetRecommendationsAsync(
        string? category = null)
    {
        var recommendations = new List<RecommendationSummary>();
        var subscription = await _armClient.GetDefaultSubscriptionAsync();

        await foreach (var rec in subscription.GetAdvisorRecommendationsAsync())
        {
            if (category == null ||
                rec.Data.Category.ToString().Equals(category, StringComparison.OrdinalIgnoreCase))
            {
                recommendations.Add(new RecommendationSummary
                {
                    Id = rec.Data.Id,
                    Category = rec.Data.Category.ToString(),
                    Impact = rec.Data.Impact.ToString(),
                    ResourceId = rec.Data.ResourceId,
                    Problem = rec.Data.ShortDescription?.Problem,
                    Solution = rec.Data.ShortDescription?.Solution,
                    PotentialBenefits = rec.Data.ExtendedProperties
                        ?.GetValueOrDefault("annualSavingsAmount")
                });
            }
        }

        return recommendations;
    }

    public async Task<Dictionary<string, int>> GetRecommendationCountsAsync()
    {
        var counts = new Dictionary<string, int>();
        var subscription = await _armClient.GetDefaultSubscriptionAsync();

        await foreach (var rec in subscription.GetAdvisorRecommendationsAsync())
        {
            var category = rec.Data.Category.ToString();
            counts[category] = counts.GetValueOrDefault(category) + 1;
        }

        return counts;
    }

    public async Task SuppressRecommendationAsync(
        string recommendationId,
        string suppressionName,
        TimeSpan? duration = null)
    {
        var subscription = await _armClient.GetDefaultSubscriptionAsync();

        // Create suppression
        // Note: Implementation depends on specific SDK version
    }
}

public record RecommendationSummary
{
    public string Id { get; init; }
    public string Category { get; init; }
    public string Impact { get; init; }
    public string ResourceId { get; init; }
    public string Problem { get; init; }
    public string Solution { get; init; }
    public string PotentialBenefits { get; init; }
}

Automated Recommendation Processing

Azure Function for Daily Processing

public class AdvisorProcessingFunction
{
    private readonly ILogger<AdvisorProcessingFunction> _logger;
    private readonly AdvisorService _advisorService;
    private readonly NotificationService _notificationService;

    [Function("ProcessAdvisorRecommendations")]
    public async Task Run([TimerTrigger("0 0 6 * * *")] TimerInfo timer)
    {
        _logger.LogInformation("Processing Advisor recommendations");

        var recommendations = await _advisorService.GetRecommendationsAsync();

        // Group by category
        var grouped = recommendations.GroupBy(r => r.Category);

        foreach (var group in grouped)
        {
            await ProcessCategoryAsync(group.Key, group.ToList());
        }

        // Generate daily report
        await GenerateDailyReportAsync(recommendations);
    }

    private async Task ProcessCategoryAsync(
        string category,
        List<RecommendationSummary> recommendations)
    {
        switch (category)
        {
            case "Cost":
                await ProcessCostRecommendationsAsync(recommendations);
                break;
            case "Security":
                await ProcessSecurityRecommendationsAsync(recommendations);
                break;
            case "HighAvailability":
                await ProcessReliabilityRecommendationsAsync(recommendations);
                break;
            default:
                _logger.LogInformation(
                    "{Count} {Category} recommendations to review",
                    recommendations.Count,
                    category);
                break;
        }
    }

    private async Task ProcessCostRecommendationsAsync(
        List<RecommendationSummary> recommendations)
    {
        var highImpact = recommendations
            .Where(r => r.Impact == "High")
            .ToList();

        if (highImpact.Any())
        {
            // Notify finance team
            await _notificationService.SendTeamsMessageAsync(
                "finance-channel",
                $"Found {highImpact.Count} high-impact cost recommendations",
                highImpact.Select(r => $"- {r.Problem}").ToList());
        }

        // Auto-implement safe recommendations
        foreach (var rec in recommendations.Where(r => IsSafeToAutoImplement(r)))
        {
            await AutoImplementRecommendationAsync(rec);
        }
    }

    private bool IsSafeToAutoImplement(RecommendationSummary recommendation)
    {
        // Define criteria for auto-implementation
        var safePatterns = new[]
        {
            "Delete unattached disk",
            "Delete unused public IP",
            "Delete empty resource group"
        };

        return safePatterns.Any(p =>
            recommendation.Problem?.Contains(p, StringComparison.OrdinalIgnoreCase) == true);
    }

    private async Task AutoImplementRecommendationAsync(RecommendationSummary rec)
    {
        _logger.LogInformation("Auto-implementing: {Problem}", rec.Problem);
        // Implementation logic based on recommendation type
    }
}

Resource Graph for Advisor Data

// Query Advisor recommendations via Resource Graph
AdvisorResources
| where type == 'microsoft.advisor/recommendations'
| extend
    category = tostring(properties.category),
    impact = tostring(properties.impact),
    problem = tostring(properties.shortDescription.problem),
    solution = tostring(properties.shortDescription.solution),
    resourceId = tostring(properties.resourceMetadata.resourceId)
| summarize count() by category, impact

// Find high-impact recommendations
AdvisorResources
| where type == 'microsoft.advisor/recommendations'
| where properties.impact == 'High'
| project
    category = properties.category,
    problem = properties.shortDescription.problem,
    resource = properties.resourceMetadata.resourceId
| order by category

// Calculate potential cost savings
AdvisorResources
| where type == 'microsoft.advisor/recommendations'
| where properties.category == 'Cost'
| extend savings = todouble(properties.extendedProperties.annualSavingsAmount)
| summarize TotalSavings = sum(savings)

Implementing Specific Recommendations

Right-Size Virtual Machines

public async Task RightSizeVirtualMachinesAsync()
{
    var armClient = new ArmClient(new DefaultAzureCredential());
    var subscription = await armClient.GetDefaultSubscriptionAsync();

    // Get right-sizing recommendations
    var recommendations = await subscription.GetAdvisorRecommendationsAsync()
        .Where(r => r.Data.ShortDescription?.Problem?.Contains("right-size") == true)
        .ToListAsync();

    foreach (var rec in recommendations)
    {
        var vmId = rec.Data.ResourceId;
        var recommendedSize = rec.Data.ExtendedProperties?
            .GetValueOrDefault("recommendedSku");

        if (recommendedSize != null)
        {
            _logger.LogInformation(
                "VM {VmId} recommended to resize to {Size}",
                vmId, recommendedSize);

            // Create work item for manual review
            await CreateResizeWorkItemAsync(vmId, recommendedSize);
        }
    }
}

Delete Unused Resources

public async Task CleanupUnusedResourcesAsync()
{
    var armClient = new ArmClient(new DefaultAzureCredential());
    var subscription = await armClient.GetDefaultSubscriptionAsync();

    // Get recommendations for unused resources
    var unusedResourceRecs = await subscription.GetAdvisorRecommendationsAsync()
        .Where(r => r.Data.Category == Category.Cost)
        .Where(r => IsUnusedResourceRecommendation(r))
        .ToListAsync();

    foreach (var rec in unusedResourceRecs)
    {
        var resourceId = rec.Data.ResourceId;
        var resource = armClient.GetGenericResource(new ResourceIdentifier(resourceId));

        // Check tags for auto-cleanup eligibility
        var resourceData = await resource.GetAsync();
        if (resourceData.Value.Data.Tags?.TryGetValue("AutoCleanup", out var value) == true &&
            value == "true")
        {
            _logger.LogInformation("Auto-deleting unused resource: {Id}", resourceId);
            await resource.DeleteAsync(Azure.WaitUntil.Started);
        }
        else
        {
            // Notify owner
            var owner = resourceData.Value.Data.Tags?.GetValueOrDefault("Owner");
            if (owner != null)
            {
                await NotifyOwnerAsync(owner, resourceId, rec.Data.ShortDescription?.Problem);
            }
        }
    }
}

private bool IsUnusedResourceRecommendation(AdvisorRecommendationResource rec)
{
    var problem = rec.Data.ShortDescription?.Problem?.ToLower() ?? "";
    return problem.Contains("unused") ||
           problem.Contains("unattached") ||
           problem.Contains("idle");
}

Advisor Score Tracking

public class AdvisorScoreService
{
    public async Task<AdvisorScoreReport> GetScoreReportAsync()
    {
        var armClient = new ArmClient(new DefaultAzureCredential());
        var subscription = await armClient.GetDefaultSubscriptionAsync();

        var scores = new Dictionary<string, double>();
        var recommendations = await subscription.GetAdvisorRecommendationsAsync()
            .ToListAsync();

        // Calculate category scores
        var categories = new[] { "Cost", "Security", "HighAvailability", "OperationalExcellence", "Performance" };

        foreach (var category in categories)
        {
            var categoryRecs = recommendations
                .Where(r => r.Data.Category.ToString() == category)
                .ToList();

            var implemented = categoryRecs.Count(r =>
                r.Data.SuppressionIds?.Any() == true);
            var total = categoryRecs.Count;

            scores[category] = total > 0
                ? (double)implemented / total * 100
                : 100;
        }

        return new AdvisorScoreReport
        {
            Date = DateTime.UtcNow,
            OverallScore = scores.Values.Average(),
            CategoryScores = scores,
            TotalRecommendations = recommendations.Count,
            HighImpactCount = recommendations.Count(r => r.Data.Impact.ToString() == "High")
        };
    }
}

public record AdvisorScoreReport
{
    public DateTime Date { get; init; }
    public double OverallScore { get; init; }
    public Dictionary<string, double> CategoryScores { get; init; }
    public int TotalRecommendations { get; init; }
    public int HighImpactCount { get; init; }
}

Weekly Advisor Report

public class WeeklyAdvisorReportService
{
    [Function("WeeklyAdvisorReport")]
    public async Task GenerateReport([TimerTrigger("0 0 9 * * MON")] TimerInfo timer)
    {
        var report = new StringBuilder();
        report.AppendLine("# Weekly Azure Advisor Report\n");

        var recommendations = await _advisorService.GetRecommendationsAsync();
        var scores = await _scoreService.GetScoreReportAsync();

        // Overall Score
        report.AppendLine($"## Advisor Score: {scores.OverallScore:0.0}/100\n");

        // Category Breakdown
        report.AppendLine("## Category Scores");
        foreach (var (category, score) in scores.CategoryScores)
        {
            var emoji = score >= 80 ? "OK" : score >= 60 ? "WARN" : "CRIT";
            report.AppendLine($"- {category}: {score:0.0} [{emoji}]");
        }

        // High Impact Items
        report.AppendLine("\n## High Impact Recommendations");
        var highImpact = recommendations.Where(r => r.Impact == "High").Take(10);
        foreach (var rec in highImpact)
        {
            report.AppendLine($"- [{rec.Category}] {rec.Problem}");
        }

        // Potential Savings
        var savings = recommendations
            .Where(r => r.PotentialBenefits != null)
            .Sum(r => double.TryParse(r.PotentialBenefits, out var v) ? v : 0);
        report.AppendLine($"\n## Potential Annual Savings: ${savings:N0}");

        await _emailService.SendAsync("ops-team@company.com", "Weekly Advisor Report", report.ToString());
    }
}

Conclusion

Azure Advisor provides actionable recommendations that can significantly improve your cloud infrastructure. By programmatically accessing these recommendations and implementing automation, you can maintain optimal configurations across large Azure estates. Regular review and action on Advisor recommendations should be part of every organization’s cloud operations practice.

References

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.