Modules/Private/Extraction/Get-AZTIAPIResources.ps1

<#
.Synopsis
Parameter-translation shim: maps the v1 ARM REST contract onto src/collect.
 
.DESCRIPTION
This function used to BE the inventory engine's ARM REST layer -- it acquired its own token,
built seven URLs by hand and drove them per subscription, duplicating what
Get-ScoutApiResources (src/collect/Get-ScoutApiResources.ps1) was already written to do. That
is what AB#5648 retired. It now issues no HTTP call of its own.
 
The five datasets are the ones Resource Graph does not index, because they are not resources:
Microsoft.ResourceHealth/events, Microsoft.ManagedIdentity/userAssignedIdentities,
Microsoft.Advisor/advisorScore, Microsoft.Consumption/reservationRecommendations, and the
Policy assignment-summary / definition / set-definition triple.
 
The v1 contract is preserved exactly at this boundary:
  - one result element per subscription, in subscription order;
  - each element is a HASHTABLE, because Get-AZSCCollectedValue's IDictionary branch is what
    Start-AZSCExtractionOrchestration has always exercised for these results;
  - the v1 key names, which are NOT the src/collect property names and are load-bearing --
    Start-AZSCExtractionOrchestration reads 'ReservationRecomen', 'PolicyAssign', 'PolicyDef'
    and 'PolicySetDef' by string;
  - an unknown -AzureEnvironment writes the v1 message and returns nothing;
  - a token acquisition failure returns nothing rather than throwing;
  - a single endpoint failing degrades that ONE field for that ONE subscription to a falsy
    value and the run continues.
 
One deliberate behaviour change, called out rather than hidden: v1 wrapped all three Policy
calls in a SINGLE try/catch, so a failure on the first (the policyStates summarize, which is
the one most likely to be denied) discarded the two definition lists that were never even
attempted. src/collect issues them independently, so a partial Policy result is now possible
where v1 produced none. That is the same defect class as AB#5636 -- one optional dataset
failing must not destroy its neighbours.
 
.Link
https://github.com/thisismydemo/azure-scout/Modules/Private/Extraction/Get-AZTIAPIResources.ps1
 
.COMPONENT
This PowerShell Module is part of Azure Scout (AZSC).
 
.NOTES
Tracks ADO AB#5648 (Epic AB#5638). Original v1 implementation: Claudio Merola, 15th Oct 2024.
#>

function Get-AZSCAPIResources {
    Param($Subscriptions, $AzureEnvironment, $SkipPolicy)

    Write-Debug ((Get-Date -Format 'yyyy-MM-dd_HH_mm_ss') + ' - ' + 'Starting API Inventory (src/collect, AB#5648)')

    # Preserved verbatim from the v1 contract: an environment with no known ARM management
    # host is reported to the operator and yields no API data, rather than failing the run.
    $KnownEnvironments = @('AzureCloud', 'AzureUSGovernment', 'AzureChinaCloud')
    if ($AzureEnvironment -notin $KnownEnvironments) {
        Write-Host ('Invalid Azure Environment for API Rest Inventory: ' + $AzureEnvironment) -ForegroundColor Red
        return
    }

    # v1 iterated $Subscriptions with foreach, so a $null or empty list produced no rows and no
    # error. Get-ScoutApiResources declares -Subscriptions mandatory, which would prompt.
    if (-not $Subscriptions) { return }

    $Skip = [bool]$SkipPolicy

    $Collected = Get-ScoutApiResources -Subscriptions @($Subscriptions) `
        -AzureEnvironment $AzureEnvironment -SkipPolicy:$Skip

    # Back to the v1 hashtable shape and the v1 key names.
    #
    # The `if (...) { ... } else { $null }` on the four resource fields is NOT decoration and
    # must not be simplified to a direct assignment. v1 wrote exactly that construct, and an
    # `if` statement used as a value flows through the pipeline, which UNROLLS a
    # single-element array to a scalar. v1's Policy fields were plain assignments and did not
    # unroll. Get-ScoutApiResources now returns the wire shape unchanged for every field, so
    # this is where the v1 shapes are reproduced -- four unrolled, three not.
    $APIResults = foreach ($Entry in @($Collected)) {

        # Plain assignments, deliberately, for the same reason the four fields below use an
        # `if` EXPRESSION: an assignment preserves an array, an if-expression unrolls a
        # single-element one. v1 assigned the Policy fields and unrolled the other four, and
        # the difference is visible in $ReturnData.PolicyDef.
        if ($Skip) {
            # v1 initialised these to '' and left them that way when -SkipPolicy was set.
            $PolicyAssign = ''
            $PolicyDef = ''
            $PolicySetDef = ''
        }
        else {
            $PolicyAssign = $Entry.PolicyAssignments
            $PolicyDef = $Entry.PolicyDefinitions
            $PolicySetDef = $Entry.PolicySetDefinitions
        }

        @{
            'Subscription'       = $Entry.Subscription
            'ResourceHealth'     = if ($Entry.ResourceHealth) { $Entry.ResourceHealth } else { $null }
            'ManagedIdentities'  = if ($Entry.ManagedIdentities) { $Entry.ManagedIdentities } else { $null }
            'AdvisorScore'       = if ($Entry.AdvisorScore) { $Entry.AdvisorScore } else { $null }
            'ReservationRecomen' = if ($Entry.ReservationRecommendations) { $Entry.ReservationRecommendations } else { $null }
            'PolicyAssign'       = $PolicyAssign
            'PolicyDef'          = $PolicyDef
            'PolicySetDef'       = $PolicySetDef
        }
    }

    return $APIResults
}