internal/functions/Save-AzOpsManagementGroupChild.ps1

function Save-AzOpsManagementGroupChild {

    <#
        .SYNOPSIS
            Recursively build/change Management Group hierarchy in file system from provided scope.
        .DESCRIPTION
            Recursively build/change Management Group hierarchy in file system from provided scope.
        .PARAMETER Scope
            Scope to discover - assumes [AzOpsScope] object
        .PARAMETER StatePath
            The root path to where the entire state is being built in.
        .PARAMETER Confirm
            If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
        .PARAMETER WhatIf
            If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
        .EXAMPLE
            > Save-AzOpsManagementGroupChild -Scope (New-AzOpsScope -scope /providers/Microsoft.Management/managementGroups/contoso)
            Discover Management Group hierarchy from scope
        .INPUTS
            AzOpsScope
        .OUTPUTS
            Management Group hierarchy in file system
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType()]
    param (
        [Parameter(Mandatory = $true)]
        $Scope,

        [string]
        $StatePath = (Get-PSFConfigValue -FullName 'AzOps.Core.State')
    )

    process {
        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Starting'
        Invoke-PSFProtectedCommand -ActionString 'Save-AzOpsManagementGroupChild.Creating.Scope' -Target $Scope -ScriptBlock {
            $scopeObject = New-AzOpsScope -Scope $Scope -StatePath $StatePath -ErrorAction SilentlyContinue -Confirm:$false
        } -EnableException $true -PSCmdlet $PSCmdlet
        if (-not $scopeObject) { return } # In case -WhatIf is used

        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Processing' -LogStringValues $scopeObject.Scope

        # Construct all file paths for scope
        $scopeStatepath = $scopeObject.StatePath
        $statepathFileName = [IO.Path]::GetFileName($scopeStatepath)
        $statepathDirectory = [IO.Path]::GetDirectoryName($scopeStatepath)
        $statepathScopeDirectory = [IO.Directory]::GetParent($statepathDirectory).ToString()
        $statepathScopeDirectoryParent = [IO.Directory]::GetParent($statepathScopeDirectory).ToString()

        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Data.StatePath' -LogStringValues $scopeStatepath
        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Data.FileName' -LogStringValues $statepathFileName
        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Data.Directory' -LogStringValues $statepathDirectory
        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Data.ScopeDirectory' -LogStringValues $statepathScopeDirectory
        Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Data.ScopeDirectoryParent' -LogStringValues $statepathScopeDirectoryParent

        # If file is found anywhere in "AzOps.Core.State", ensure that it is at the right scope or else it doesn't matter
        if (Get-ChildItem -Path $Statepath -File -Recurse -Force | Where-Object Name -eq $statepathFileName) {
            # If the file is found in AzOps State
            $exisitingScopePath = (Get-ChildItem -Path $Statepath -File -Recurse -Force | Where-Object Name -eq $statepathFileName).Directory

            # Looking at parent of parent if AutoGeneratedTemplateFolderPath is sub-directory, looking for parent (scope folder) of parent (actual parent in Azure)
            if ( ((Get-PSFConfigValue -FullName 'AzOps.Core.AutoGeneratedTemplateFolderPath') -notin '.') -and
                $exisitingScopePath.Parent.Parent.FullName -ne $statepathScopeDirectoryParent) {
                if ($exisitingScopePath.Parent.FullName -ne $statepathScopeDirectoryParent) {
                    Write-AzOpsMessage -LogLevel Important -LogString 'Save-AzOpsManagementGroupChild.Moving.Source' -LogStringValues $exisitingScopePath
                    Move-Item -Path $exisitingScopePath.Parent -Destination $statepathScopeDirectoryParent -Force
                    Write-AzOpsMessage -LogLevel Important -LogString 'Save-AzOpsManagementGroupChild.Moving.Destination' -LogStringValues $statepathScopeDirectoryParent
                }
            }

            # Files might be at the right scope but not in right AutoGeneratedTemplateFolderPath e.g. when AutoGeneratedTemplateFolderPath is changed.
            if (-not (Test-Path $statepathDirectory)) {
                New-Item -Path $statepathDirectory -ItemType Directory -Force | out-null
            }
            # For all the files in AutoGeneratedTemplateFolderPath directory, only moving files that are auto generated
            Get-ChildItem -Path (Get-ChildItem -Path $Statepath -File -Recurse -Force | Where-Object Name -eq $statepathFileName).Directory -File -Filter 'Microsoft.*' | Move-Item -Destination $statepathDirectory -Force

        }
        # Create empty object for any discovered subscriptions below
        $subscriptions = @()
        # Based on $scopeObject perform unique logic
        switch ($scopeObject.Type) {
            managementGroups {
                ConvertTo-AzOpsState -Resource ($script:AzOpsAzManagementGroup | Where-Object { $_.Name -eq $scopeObject.ManagementGroup }) -ExportPath $scopeObject.StatePath -StatePath $StatePath
                foreach ($child in $script:AzOpsAzManagementGroup.Where{ $_.Name -eq $scopeObject.ManagementGroup }.Children) {
                    if ($child.Type -eq '/subscriptions') {
                        if ($script:AzOpsSubscriptions.Id -contains $child.Id) {
                            # Subscription discovered at ManagementGroup scope, collect subscription information based on $child object and store information in $subscriptions for later
                            $subscriptions += Save-AzOpsManagementGroupChild -Scope $child.Id -StatePath $StatePath
                        }
                        else {
                            Write-AzOpsMessage -LogLevel Debug -LogString 'Save-AzOpsManagementGroupChild.Subscription.NotFound' -LogStringValues $child.Name
                        }
                    }
                    else {
                        Save-AzOpsManagementGroupChild -Scope $child.Id -StatePath $StatePath
                    }
                }
            }
            subscriptions {
                if (($script:AzOpsSubscriptions.Id -contains $scopeObject.Scope) -and ($script:AzOpsAzManagementGroup.Children | Where-Object Name -eq $scopeObject.Name)) {
                    # Subscription matching conditions found, construct subscription and object statepath into $return and output information to caller
                    $return = [PSCustomObject]@{
                        Subscription = ($script:AzOpsAzManagementGroup.Children | Where-Object Name -eq $scopeObject.Name)
                        Path = $scopeObject.StatePath
                    }
                    return $return
                }
            }
        }
        # If $subscriptions exists process all subscriptions with parallel to increase performance during folder/file creation
        if ($subscriptions) {
            # Prepare Input Data for parallel processing
            $runspaceData = @{
                AzOpsPath                       = "$($script:ModuleRoot)\AzOps.psd1"
                StatePath                       = $StatePath
                runspace_AzOpsAzManagementGroup = $script:AzOpsAzManagementGroup
                runspace_AzOpsSubscriptions     = $script:AzOpsSubscriptions
                runspace_AzOpsPartialRoot       = $script:AzOpsPartialRoot
                runspace_AzOpsResourceProvider  = $script:AzOpsResourceProvider
            }
            $subscriptions | Foreach-Object -ThrottleLimit (Get-PSFConfigValue -FullName 'AzOps.Core.ThrottleLimit') -Parallel {
                $subscription = $_
                $runspaceData = $using:runspaceData

                Import-Module "$([PSFramework.PSFCore.PSFCoreHost]::ModuleRoot)/PSFramework.psd1"
                $azOps = Import-Module $runspaceData.AzOpsPath -Force -PassThru

                & $azOps {
                    $script:AzOpsAzManagementGroup = $runspaceData.runspace_AzOpsAzManagementGroup
                    $script:AzOpsSubscriptions = $runspaceData.runspace_AzOpsSubscriptions
                    $script:AzOpsPartialRoot = $runspaceData.runspace_AzOpsPartialRoot
                    $script:AzOpsResourceProvider = $runspaceData.runspace_AzOpsResourceProvider
                }

                & $azOps {
                    ConvertTo-AzOpsState -Resource $subscription.Subscription -ExportPath $subscription.Path -StatePath $runspaceData.StatePath
                }
            }
            Clear-PSFMessage
        }
    }
}