Public/Get-AzRetirementRecommendation.ps1

function Get-AzRetirementRecommendation {
<#
.SYNOPSIS
Gets Azure service retirement recommendations for HighAvailability category and ServiceUpgradeAndRetirement subcategory
.DESCRIPTION
By default, uses the Az.Advisor PowerShell module to retrieve recommendations. This provides
complete parity with Azure Advisor data. Optionally, use -UseAPI to query the REST API directly
(requires Connect-AzRetirementMonitor first).

The Az.Advisor module method requires:
- Az.Advisor module installed
- Active Azure PowerShell session (Connect-AzAccount)

The API method requires:
- Connect-AzRetirementMonitor called first
- Valid access token
.PARAMETER SubscriptionId
One or more subscription IDs to query. Defaults to all subscriptions.
.PARAMETER UseAPI
Use the Azure REST API instead of Az.Advisor PowerShell module. Requires Connect-AzRetirementMonitor first.
.EXAMPLE
Get-AzRetirementRecommendation
Gets all retirement recommendations using Az.Advisor module (default)
.EXAMPLE
Get-AzRetirementRecommendation -SubscriptionId "12345678-1234-1234-1234-123456789012"
Gets recommendations for a specific subscription using Az.Advisor module
.EXAMPLE
Get-AzRetirementRecommendation -UseAPI
Gets recommendations using the REST API method
#>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        [string[]]$SubscriptionId,

        [Parameter()]
        [switch]$UseAPI
    )

    begin {
        $allRecommendations = [System.Collections.Generic.List[object]]::new()

        if ($UseAPI) {
            # API mode - requires authentication via Connect-AzRetirementMonitor
            if (-not $script:AccessToken) {
                throw "Not authenticated. Run Connect-AzRetirementMonitor -UsingAPI first."
            }

            if (-not (Test-AzRetirementMonitorToken)) {
                throw "Access token has expired. Run Connect-AzRetirementMonitor -UsingAPI again."
            }

            $headers = @{
                Authorization  = "Bearer $script:AccessToken"
                "Content-Type" = "application/json"
            }
        }
        else {
            # PowerShell module mode (default) - requires Az.Advisor and active session
            if (-not (Test-AzAdvisorSession)) {
                throw "Az.Advisor module not available or not connected. Run Connect-AzAccount first or use -UseAPI with Connect-AzRetirementMonitor."
            }
        }
    }

    process {
        if ($UseAPI) {
            # API-based retrieval (original implementation)
            if (-not $SubscriptionId) {
                $subsUri = "https://management.azure.com/subscriptions?api-version=2020-01-01"
                $subs = Invoke-AzPagedRequest -Uri $subsUri -Headers $headers
                $SubscriptionId = $subs.subscriptionId
            }

            foreach ($subId in $SubscriptionId) {
                Write-Verbose "Querying subscription via API: $subId"

                $uri = "https://management.azure.com/subscriptions/$subId/providers/Microsoft.Advisor/recommendations?api-version=$script:ApiVersion"

                # Filter for HighAvailability category and ServiceUpgradeAndRetirement subcategory only
                $filter = "Category eq 'HighAvailability' and SubCategory eq 'ServiceUpgradeAndRetirement'"
                $uri += "&`$filter=$filter"

                try {
                    $recommendations = Invoke-AzPagedRequest `
                        -Uri $uri `
                        -Headers $headers

                    foreach ($rec in $recommendations) {
                        $isRetirement =
                            $rec.properties.shortDescription.problem -match
                            'retire|deprecat|end of life|eol|sunset'

                        # Extract ResourceType from ResourceId
                        $resourceId = $rec.properties.resourceMetadata.resourceId
                        $resourceType = if ($resourceId) {
                            # Extract provider/type from resourceId
                            # Example: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{name}
                            if ($resourceId -match '/providers/([^/]+/[^/]+)(?:/|$)') {
                                $matches[1]
                            } else {
                                "N/A"
                            }
                        } else {
                            "N/A"
                        }

                        # Extract Resource Group from ResourceId
                        $resourceGroup = if ($resourceId) {
                            # Extract resource group name from resourceId
                            # Example: /subscriptions/{sub}/resourceGroups/{rg}/providers/...
                            if ($resourceId -match '/resourceGroups/([^/]+)') {
                                $matches[1]
                            } else {
                                "N/A"
                            }
                        } else {
                            "N/A"
                        }

                        # Build Azure Resource portal link
                        $resourceLink = if ($resourceId) {
                            "https://portal.azure.com/#resource$resourceId"
                        } else {
                            $null
                        }

                        $allRecommendations.Add([PSCustomObject]@{
                            SubscriptionId   = $subId
                            ResourceId       = $resourceId
                            ResourceName     = ($resourceId -split "/")[-1]
                            ResourceType     = $resourceType
                            ResourceGroup    = $resourceGroup
                            Category         = $rec.properties.category
                            Impact           = $rec.properties.impact
                            Problem          = $rec.properties.shortDescription.problem
                            Solution         = $rec.properties.shortDescription.solution
                            Description      = $rec.properties.extendedProperties.displayName
                            LastUpdated      = $rec.properties.lastUpdated
                            IsRetirement     = $isRetirement
                            RecommendationId = $rec.name
                            LearnMoreLink    = $rec.properties.learnMoreLink
                            ResourceLink     = $resourceLink
                        })
                    }
                }
                catch {
                    Write-Warning "Failed to query subscription $($subId): $_"
                }
            }
        }
        else {
            # PowerShell module-based retrieval (new default)
            try {
                # Get recommendations and filter by Category first (more efficient)
                $filter = "Category eq 'HighAvailability'"

                # Common filter for ServiceUpgradeAndRetirement subcategory
                $subcategoryFilter = {
                    # Parse extended properties to check subcategory
                    if ($_.ExtendedProperty) {
                        $extProps = $_.ExtendedProperty | ConvertFrom-Json
                        $extProps.recommendationSubCategory -eq 'ServiceUpgradeAndRetirement'
                    }
                    else {
                        $false
                    }
                }
                
                $recommendations = if ($SubscriptionId) {
                    # Query specific subscriptions
                    # Store the current context to restore later
                    $originalContext = Get-AzContext
                    
                    foreach ($subId in $SubscriptionId) {
                        Write-Verbose "Querying subscription via Az.Advisor: $subId"
                        
                        # Set context to the specific subscription
                        try {
                            $context = Set-AzContext -SubscriptionId $subId -ErrorAction Stop
                            
                            # Verify that the context was actually set to the intended subscription
                            if (-not $context -or -not $context.Subscription -or $context.Subscription.Id -ne $subId) {
                                Write-Warning "Azure context for subscription $($subId) could not be verified. Skipping recommendation query for this subscription."
                                continue
                            }
                            
                        }
                        catch {
                            Write-Warning "Failed to set Azure context for subscription $($subId): $_"
                            continue
                        }

                        # Query Advisor recommendations for the current subscription
                        try {
                            Get-AzAdvisorRecommendation -Filter $filter | Where-Object $subcategoryFilter
                        }
                        catch {
                            Write-Warning "Failed to query Advisor recommendations for subscription $($subId): $_"
                        }
                    }
                    
                    # Restore the original context
                    if ($originalContext) {
                        try {
                            $null = Set-AzContext -Context $originalContext -ErrorAction Stop
                        }
                        catch {
                            Write-Warning "Failed to restore original Azure context: $_"
                        }
                    }
                }
                else {
                    # Query all subscriptions
                    Write-Verbose "Querying all subscriptions via Az.Advisor"
                    Get-AzAdvisorRecommendation -Filter $filter | Where-Object $subcategoryFilter
                }

                foreach ($rec in $recommendations) {
                    # Parse extended properties for retirement information
                    $extProps = $null
                    $retirementFeatureName = $null
                    $retirementDate = $null

                    if ($rec.ExtendedProperty) {
                        # Reuse a previously-parsed ExtendedProperty if available to avoid redundant JSON parsing
                        if ($rec.PSObject.Properties.Name -contains 'ExtendedPropertyObject') {
                            $extProps = $rec.ExtendedPropertyObject
                        }
                        else {
                            try {
                                if ($rec.ExtendedProperty -is [string]) {
                                    # ExtendedProperty is JSON text; parse it once
                                    $extProps = $rec.ExtendedProperty | ConvertFrom-Json
                                }
                                elseif ($rec.ExtendedProperty -is [hashtable] -or $rec.ExtendedProperty -is [pscustomobject]) {
                                    # ExtendedProperty is already an object; no need to parse
                                    $extProps = $rec.ExtendedProperty
                                }

                                if ($extProps) {
                                    # Cache the parsed object on the recommendation to prevent re-parsing
                                    $rec | Add-Member -NotePropertyName ExtendedPropertyObject -NotePropertyValue $extProps -Force
                                }
                            }
                            catch {
                                Write-Verbose "Failed to parse ExtendedProperty: $_"
                                $extProps = $null
                            }
                        }

                        if ($extProps) {
                            $retirementFeatureName = $extProps.retirementFeatureName
                            $retirementDate = $extProps.retirementDate
                        }
                    }

                    # Check if this is a retirement recommendation
                    # Look in both the text and the extended properties
                    $isRetirement = $false
                    if ($rec.ShortDescriptionProblem -match 'retire|deprecat|end of life|eol|sunset|migration') {
                        $isRetirement = $true
                    }
                    elseif ($retirementFeatureName -or $retirementDate) {
                        $isRetirement = $true
                    }

                    # Extract ResourceId from ResourceMetadataResourceId property
                    $resourceId = $rec.ResourceMetadataResourceId
                    
                    $resourceType = if ($resourceId) {
                        if ($resourceId -match '/providers/([^/]+/[^/]+)(?:/|$)') {
                            $matches[1]
                        } else {
                            "N/A"
                        }
                    } else {
                        "N/A"
                    }

                    $resourceGroup = if ($resourceId) {
                        if ($resourceId -match '/resourceGroups/([^/]+)') {
                            $matches[1]
                        } else {
                            "N/A"
                        }
                    } else {
                        "N/A"
                    }

                    $subscriptionId = if ($resourceId) {
                        if ($resourceId -match '/subscriptions/([^/]+)') {
                            $matches[1]
                        } else {
                            "N/A"
                        }
                    } else {
                        "N/A"
                    }

                    # Build Azure Resource portal link
                    $resourceLink = if ($resourceId) {
                        "https://portal.azure.com/#resource$resourceId"
                    } else {
                        $null
                    }

                    # Build description from extended properties
                    # Prefer retirementFeatureName, fall back to displayName
                    $description = if ($retirementFeatureName) {
                        if ($retirementDate) {
                            "$retirementFeatureName (Retirement Date: $retirementDate)"
                        }
                        else {
                            $retirementFeatureName
                        }
                    }
                    elseif ($extProps -and $extProps.displayName) {
                        $extProps.displayName
                    }
                    else {
                        $null
                    }

                    $allRecommendations.Add([PSCustomObject]@{
                        SubscriptionId   = $subscriptionId
                        ResourceId       = $resourceId
                        ResourceName     = if ($resourceId) { ($resourceId -split "/")[-1] } else { "N/A" }
                        ResourceType     = $resourceType
                        ResourceGroup    = $resourceGroup
                        Category         = $rec.Category
                        Impact           = $rec.Impact
                        Problem          = $rec.ShortDescriptionProblem
                        Solution         = $rec.ShortDescriptionSolution
                        Description      = $description
                        LastUpdated      = $rec.LastUpdated
                        IsRetirement     = $isRetirement
                        RecommendationId = $rec.Name
                        LearnMoreLink    = if ($rec.LearnMoreLink) { $rec.LearnMoreLink } else { $null }
                        ResourceLink     = $resourceLink
                    })
                }
            }
            catch {
                Write-Error "Failed to retrieve recommendations via Az.Advisor: $_"
            }
        }
    }

    end {
        return $allRecommendations.ToArray()
    }
}