Private/ConvertTo-AACTerraformExport.ps1

function ConvertTo-AACTerraformExport {
    <#
    .SYNOPSIS
        Shapes what Export-AACTerraform exported - folder by folder - into
        one row per Azure resource (AAC.TerraformExportResource) and one
        object per folder (AAC.TerraformExport), with the totals.
    .DESCRIPTION
        -Folder takes, for each folder written: Path, Label, SubscriptionId,
        Subscription, Provider, Imports (from ConvertTo-AACTerraformFile),
        Skipped (the IDs no export could do), FallbackTried, Errors (the
        export's ErrorDetail list), Files, OperationId, Duration, Plan,
        Batches (how many exports it was split into), Duplicates (resources
        two batches both returned, kept once), Planned (the resources
        Resource Graph counted beforehand), Pending (still running: collect
        it later with -Resume) and Failure (why the folder has nothing).
 
        Rows:
          Exported a Terraform address for an Azure resource
          Skipped no Terraform support in the provider - with what to try
          Error an error the export reported for a resource
          Failed the whole export of a scope failed (provider not
                    registered, no resources, access denied...)
          Running the export was still running when the command stopped
                    waiting
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [object[]] $Folder
    )

    # The Azure type from a resource ID: Microsoft.Network/virtualNetworks/subnets.
    $typeOf = {
        param([string] $Id)
        $parts = @((($Id -split '\?')[0]).Trim('/') -split '/')
        $at = [Array]::FindLastIndex([string[]]$parts, [Predicate[string]] { param($p) $p -ieq 'providers' })
        if ($at -ge 0 -and $at + 2 -lt $parts.Count) {
            $types = for ($k = $at + 2; $k -lt $parts.Count; $k += 2) { $parts[$k] }
            return "$($parts[$at + 1])/$($types -join '/')"
        }
        if ($parts.Count -eq 4 -and $parts[2] -ieq 'resourceGroups') { return 'Microsoft.Resources/resourceGroups' }
        if ($parts.Count -eq 2 -and $parts[0] -ieq 'subscriptions') { return 'Microsoft.Resources/subscriptions' }
        ''
    }
    $groupOf = { param([string] $Id) $m = [regex]::Match($Id, '/resourceGroups/([^/?]+)', 'IgnoreCase'); if ($m.Success) { $m.Groups[1].Value } else { '' } }
    $nameOf = { param([string] $Id) @((($Id -split '\?')[0]).TrimEnd('/') -split '/')[-1] }

    $rows = [System.Collections.Generic.List[object]]::new()
    $folders = [System.Collections.Generic.List[object]]::new()
    foreach ($f in $Folder) {
        $mine = [System.Collections.Generic.List[object]]::new()
        $row = {
            param([string] $Status, [string] $Id, [string] $Address, [string] $Provider, [string] $File, [string] $Note)
            $mine.Add([pscustomobject][ordered]@{
                    PSTypeName    = 'AAC.TerraformExportResource'
                    Status        = $Status
                    Address       = $Address
                    TerraformType = $(if ($Address) { ($Address -split '\.')[0] } else { '' })
                    ResourceType  = $(if ($Id) { & $typeOf $Id } else { '' })
                    Name          = $(if ($Id) { & $nameOf $Id } else { '' })
                    ResourceGroup = $(if ($Id) { & $groupOf $Id } else { '' })
                    Subscription  = [string]$f.Subscription
                    Provider      = $Provider
                    File          = $File
                    Folder        = [string]$f.Path
                    Note          = $Note
                    ResourceId    = (($Id -split '\?')[0])
                })
        }
        if ($f.Pending) {
            & $row 'Running' ([string]$f.Label) '' ([string]$f.Provider) '' ([string]$f.Failure)
        }
        elseif ($f.Failure) {
            & $row 'Failed' ([string]$f.Label) '' ([string]$f.Provider) '' ([string]$f.Failure)
        }
        else {
            foreach ($import in @($f.Imports)) { & $row 'Exported' ([string]$import.Id) ([string]$import.Address) ([string]$import.Provider) ([string]$import.File) '' }
            $hint = if ($f.FallbackTried) { 'Not supported by the azapi provider either.' } elseif ($f.Provider -eq 'azurerm') { 'No azurerm support for this type - -FallbackToAzApi exports it with the azapi provider.' } else { 'The provider has no support for this type.' }
            foreach ($id in @($f.Skipped | Where-Object { $_ })) { & $row 'Skipped' ([string]$id) '' ([string]$f.Provider) '' $hint }
            foreach ($e in @($f.Errors | Where-Object { $_ })) {
                $message = if ($e -is [System.Collections.IDictionary]) { "$($e['code']): $($e['message'])".Trim(': ') } else { [string]$e }
                $target = if ($e -is [System.Collections.IDictionary]) { [string]$e['target'] } else { '' }
                & $row 'Error' $target '' ([string]$f.Provider) '' $message
            }
        }
        $rows.AddRange([object[]]$mine.ToArray())

        $count = { param([string] $Status) @($mine | Where-Object { $_.Status -eq $Status }).Count }
        $exported = & $count 'Exported'
        $folders.Add([pscustomobject][ordered]@{
                PSTypeName     = 'AAC.TerraformExport'
                Status         = $(if ($f.Pending) { 'Running' } elseif ($f.Failure) { 'Failed' } elseif ((& $count 'Skipped') -or (& $count 'Error')) { 'Partial' } else { 'Exported' })
                Path           = [string]$f.Path
                Scope          = [string]$f.Label
                Subscription   = [string]$f.Subscription
                SubscriptionId = [string]$f.SubscriptionId
                Provider       = [string]$f.Provider
                Exported       = $exported
                Skipped        = & $count 'Skipped'
                Errors         = & $count 'Error'
                Files          = @($f.Files)
                Plan           = $f.Plan
                Duration       = $f.Duration
                Planned        = $f.Planned
                Batches        = [int]$f.Batches
                Duplicates     = [int]$f.Duplicates
                OperationId    = [string]$f.OperationId
                Message        = [string]$f.Failure
                Resources      = $mine.ToArray()
            })
    }

    $all = $rows.ToArray()
    @{
        Rows    = $all
        Folders = $folders.ToArray()
        Stats   = [ordered]@{
            Exported = @($all | Where-Object { $_.Status -eq 'Exported' }).Count
            Skipped  = @($all | Where-Object { $_.Status -eq 'Skipped' }).Count
            Errors   = @($all | Where-Object { $_.Status -eq 'Error' }).Count
            Failed   = @($folders | Where-Object { $_.Status -eq 'Failed' }).Count
            Running  = @($folders | Where-Object { $_.Status -eq 'Running' }).Count
            Folders  = $folders.Count
            AzApi    = @($all | Where-Object { $_.Status -eq 'Exported' -and $_.Provider -eq 'azapi' }).Count
        }
    }
}