Private/ConvertTo-AACStorageConfiguration.ps1

function ConvertTo-AACStorageConfiguration {
    <#
    .SYNOPSIS
        Reads a storage account's configuration - from a file, parameters, or
        both - into one case-insensitive hashtable with the Azure Verified
        Module (avm/res/storage/storage-account) parameter names, and refuses
        what isn't supported.
    .DESCRIPTION
        -Path is a .psd1 file, or a .json file: either the configuration
        itself, or an AVM / ARM parameters file ({ "parameters": { "name":
        { "value": ... } } }), whose values are unwrapped - so a team's
        existing AVM parameters can be used as they are. -Override (the
        command's own parameters) wins over the file.
 
        Every key, at every level, becomes case-insensitive. Keys AVM has that
        this command doesn't implement yet are refused by name (rather than
        quietly ignored, which would deploy something other than asked);
        enableTelemetry, AVM's own, is ignored.
    #>

    [CmdletBinding()]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    param(
        [string] $Path,

        [System.Collections.IDictionary] $Override = @{}
    )

    # Hashtables at every level, keys case-insensitive ([ordered] is).
    $normalize = {
        param($Value)
        if ($Value -is [System.Collections.IDictionary]) {
            $out = [ordered]@{}
            foreach ($key in $Value.Keys) { $out[[string]$key] = & $normalize $Value[$key] }
            return , $out
        }
        if ($Value -is [System.Collections.IList] -and $Value -isnot [string]) { return , @(foreach ($item in $Value) { & $normalize $item }) }
        if ($Value -is [psobject] -and $Value.PSObject.BaseObject -is [System.Management.Automation.PSCustomObject]) {
            $out = [ordered]@{}
            foreach ($property in $Value.PSObject.Properties) { $out[$property.Name] = & $normalize $property.Value }
            return , $out
        }
        $Value
    }

    $config = [ordered]@{}
    if ($Path) {
        if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "No configuration file at '$Path'." }
        $raw = switch -Regex ([System.IO.Path]::GetExtension($Path)) {
            '^\.psd1$' { Import-PowerShellDataFile -LiteralPath $Path }
            '^\.json[c]?$' { ConvertFrom-Json -InputObject ([System.IO.File]::ReadAllText($Path)) -AsHashtable -Depth 100 }
            default { throw "'$Path' isn't a .psd1 or .json configuration file." }
        }
        $raw = & $normalize $raw
        # An AVM / ARM parameters file: { parameters: { name: { value } } }.
        if ($raw.Contains('parameters') -and $raw['parameters'] -is [System.Collections.IDictionary] -and -not $raw.Contains('name')) {
            foreach ($key in $raw['parameters'].Keys) {
                $entry = $raw['parameters'][$key]
                # Not '= if (...)': that would unroll a one-item list.
                $config[$key] = $entry
                if ($entry -is [System.Collections.IDictionary] -and $entry.Contains('value')) { $config[$key] = $entry['value'] }
            }
        }
        else {
            foreach ($key in $raw.Keys) { if ($key -notin '$schema', 'contentVersion') { $config[$key] = $raw[$key] } }
        }
    }
    foreach ($key in $Override.Keys) { $config[[string]$key] = & $normalize $Override[$key] }
    $config.Remove('enableTelemetry')

    $supported = @(
        'name', 'location', 'kind', 'skuName', 'accessTier', 'tags'
        'allowBlobPublicAccess', 'allowSharedKeyAccess', 'allowCrossTenantReplication', 'defaultToOAuthAuthentication', 'minimumTlsVersion', 'supportsHttpsTrafficOnly'
        'publicNetworkAccess', 'networkAcls', 'requireInfrastructureEncryption', 'largeFileSharesState', 'enableHierarchicalNamespace', 'enableNfsV3', 'enableSftp', 'isLocalUserEnabled'
        'allowedCopyScope', 'dnsEndpointType'
        'blobServices', 'fileServices', 'queueServices', 'tableServices', 'managementPolicyRules', 'privateEndpoints', 'diagnosticSettings', 'roleAssignments', 'lock'
        # Not AVM's: the files to upload into containers (data plane).
        'blobs'
    )
    $unsupported = @($config.Keys | Where-Object { $_ -notin $supported })
    if ($unsupported.Count) {
        $problem = [System.InvalidOperationException]::new("These storage account settings aren't supported yet: $($unsupported -join ', '). Nothing was changed.")
        $problem.Data['AACHint'] = "Supported: $($supported -join ', '). Remove the others from the configuration, or set them another way after this deployment."
        throw $problem
    }
    if (-not $config['name']) { throw 'The storage account needs a name: -Name, or name in the configuration.' }
    $config
}