src/collect/ConvertTo-ScoutManagementGroupHierarchy.ps1

#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

<#
.SYNOPSIS
    Flatten expanded Azure management-group trees for declarative collection.
 
.DESCRIPTION
    Converts one or more expanded Get-AzManagementGroup results into a deterministic pre-order
    row set. Each row carries the hierarchy values the inventory collector previously derived
    recursively inside its Processing branch. Subscription children are counted but are not
    emitted as management-group rows.
 
.PARAMETER Root
    Expanded management-group root objects. Each object may contain management-group and
    subscription entries in Children.
 
.OUTPUTS
    One PSCustomObject per management group, in root-before-descendants order.
 
.NOTES
    Collect-phase prerequisite for AB#5933 (Epic AB#5917).
#>

function ConvertTo-ScoutManagementGroupHierarchy {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [AllowEmptyCollection()]
        [object[]]$Root
    )

    begin {
        function Get-NodeValue {
            param(
                [AllowNull()] [object]$InputObject,
                [Parameter(Mandatory)] [string]$Name
            )

            if ($null -eq $InputObject -or
                $InputObject.PSObject.Properties.Name -notcontains $Name) {
                return $null
            }
            return $InputObject.$Name
        }

        function Expand-Node {
            param(
                [AllowNull()] [object]$Node,
                [int]$Depth = 0,
                [string]$ParentPath = ''
            )

            if ($null -eq $Node) { return }

            $Name = [string](Get-NodeValue -InputObject $Node -Name 'Name')
            $DisplayName = [string](Get-NodeValue -InputObject $Node -Name 'DisplayName')
            if ([string]::IsNullOrWhiteSpace($DisplayName)) { $DisplayName = $Name }

            $NodePath = if ([string]::IsNullOrWhiteSpace($ParentPath)) {
                $DisplayName
            }
            else {
                "$ParentPath / $DisplayName"
            }

            $Children = @(
                if ($Node.PSObject.Properties.Name -contains 'Children' -and
                    $null -ne $Node.Children) {
                    @($Node.Children)
                }
            )
            $DirectGroups = @(
                $Children | Where-Object {
                    $null -ne $_ -and
                    $_.PSObject.Properties.Name -contains 'Type' -and
                    [string]$_.Type -like '*managementGroups'
                }
            )
            $DirectSubscriptions = @(
                $Children | Where-Object {
                    $null -ne $_ -and
                    $_.PSObject.Properties.Name -contains 'Type' -and
                    [string]$_.Type -like '*subscriptions'
                }
            )
            $DirectSubscriptionNames = @(
                $DirectSubscriptions | ForEach-Object {
                    [string](Get-NodeValue -InputObject $_ -Name 'DisplayName')
                } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
            ) -join ', '

            $ParentName = [string](Get-NodeValue -InputObject $Node -Name 'ParentName')
            $ParentDisplayName = [string](Get-NodeValue -InputObject $Node -Name 'ParentDisplayName')
            if (-not [string]::IsNullOrWhiteSpace($ParentName) -and
                [string]::IsNullOrWhiteSpace($ParentDisplayName)) {
                $ParentDisplayName = $ParentName
            }

            [PSCustomObject][ordered]@{
                'Management Group ID'       = $Name
                'Display Name'              = (' ' * $Depth) + $DisplayName
                'Display Name (Raw)'        = $DisplayName
                'Full Path'                 = $NodePath
                'Hierarchy Depth'           = $Depth
                'Parent Management Group'   = if ([string]::IsNullOrWhiteSpace($ParentName)) {
                    'Tenant Root'
                } else {
                    $ParentDisplayName
                }
                'Tenant ID'                 = Get-NodeValue -InputObject $Node -Name 'TenantId'
                'Direct Child MGs'          = $DirectGroups.Count
                'Direct Subscriptions'      = $DirectSubscriptions.Count
                'Direct Subscription Names' = $DirectSubscriptionNames
            }

            foreach ($Child in $DirectGroups) {
                Expand-Node -Node $Child -Depth ($Depth + 1) -ParentPath $NodePath
            }
        }
    }

    process {
        foreach ($RootNode in @($Root)) {
            Expand-Node -Node $RootNode
        }
    }
}