public/Get-AdmxPolicySetting.ps1

function Get-AdmxPolicySetting {
    <#
    .SYNOPSIS
        Retrieves Group Policy setting details from ADMX/ADML files matching a registry key path and value name.
 
    .DESCRIPTION
        Parses ADMX files to find policy settings matching a given registry key and value name,
        returning registry type, element type, policy metadata, and display strings resolved from
        the corresponding ADML file.
 
        Supports three parameter sets:
        - Path : Provide a path to an ADMX file or directory containing ADMX files.
        - XmlFile : Provide file paths to ADMX and optionally ADML files as strings.
        - XmlContent : Provide raw XML content strings for ADMX and optionally ADML.
 
        Use -All to return every policy in the file(s) without filtering.
        Otherwise, both -RegistryKey and -ValueName are required.
 
        ADML auto-detection order (Path and XmlFile sets):
        1. Subfolder matching the current UI culture (e.g., en-GB) in the ADMX directory.
        2. Fallback to en-US subfolder.
        3. Any available language subfolder found.
 
    .PARAMETER AdmxPath
        Path to a single ADMX file or a directory containing ADMX files.
 
    .PARAMETER Recurse
        When AdmxPath is a directory, recurse into subdirectories to find ADMX files.
 
    .PARAMETER AdmxFilePath
        File path to a single ADMX file (XmlFile parameter set).
 
    .PARAMETER AdmlFilePath
        File path to a single ADML file (XmlFile parameter set). Optional; if omitted,
        auto-detection is attempted relative to the ADMX file location.
 
    .PARAMETER AdmxContent
        Raw XML string content of the ADMX file (XmlContent parameter set).
 
    .PARAMETER AdmlContent
        Raw XML string content of the ADML file (XmlContent parameter set). Optional.
 
    .PARAMETER AdmxFileName
        Optional filename hint for the ADMX source when using the XmlContent parameter set
        (e.g. 'ControlPanel.admx'). Populates the AdmxFile and SourceFile output fields.
 
    .PARAMETER AdmlFileName
        Optional filename hint for the ADML source when using the XmlContent parameter set
        (e.g. 'ControlPanel.adml'). Populates the AdmlFile and SourceAdml output fields.
 
    .PARAMETER All
        Return all policy settings from the ADMX file(s) without filtering by registry key or value name.
        When specified, -RegistryKey and -ValueName are not required.
 
    .PARAMETER RegistryKey
        The registry key path to match (e.g., 'SOFTWARE\Policies\Microsoft\Edge').
        HKLM/HKCU prefixes are stripped automatically.
 
    .PARAMETER ValueName
        The registry value name to match.
 
    .EXAMPLE
        Get-AdmxPolicySetting -AdmxPath 'C:\Windows\PolicyDefinitions' `
            -RegistryKey 'SOFTWARE\Policies\Microsoft\Edge' `
            -ValueName 'HomepageIsNewTabPage' -Recurse
 
    .EXAMPLE
        Get-AdmxPolicySetting -AdmxFilePath 'C:\PolicyDefs\msedge.admx' `
            -AdmlFilePath 'C:\PolicyDefs\en-US\msedge.adml' `
            -RegistryKey 'SOFTWARE\Policies\Microsoft\Edge' `
            -ValueName 'HomepageIsNewTabPage'
 
    .EXAMPLE
        $admxXml = Get-Content 'C:\PolicyDefs\msedge.admx' -Raw
        $admlXml = Get-Content 'C:\PolicyDefs\en-US\msedge.adml' -Raw
        Get-AdmxPolicySetting -AdmxContent $admxXml -AdmlContent $admlXml `
            -RegistryKey 'SOFTWARE\Policies\Microsoft\Edge' `
            -ValueName 'HomepageIsNewTabPage'
 
    .NOTES
        Function : Get-AdmxPolicySetting
        Author : John Billekens
    Copyright : (c) John Billekens Consultancy & AppVentiX
        Version : 2026.0307.1000
    #>

    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        # --- Path parameter set ---
        [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
        [ValidateScript({ Test-Path $_ })]
        [string]$AdmxPath,

        [Parameter(ParameterSetName = 'Path')]
        [switch]$Recurse,

        # --- XmlFile parameter set ---
        [Parameter(Mandatory = $true, ParameterSetName = 'XmlFile')]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]$AdmxFilePath,

        [Parameter(ParameterSetName = 'XmlFile')]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]$AdmlFilePath,

        # --- XmlContent parameter set ---
        [Parameter(Mandatory = $true, ParameterSetName = 'XmlContent')]
        [ValidateNotNullOrEmpty()]
        [string]$AdmxContent,

        [Parameter(ParameterSetName = 'XmlContent')]
        [ValidateNotNullOrEmpty()]
        [string]$AdmlContent,

        [Parameter(ParameterSetName = 'XmlContent')]
        [string]$AdmxFileName,

        [Parameter(ParameterSetName = 'XmlContent')]
        [string]$AdmlFileName,

        # --- Common parameters ---
        [Parameter()]
        [switch]$All,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$RegistryKey,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ValueName
    )

    #region Internal helpers

    function ConvertTo-NormalizedKey {
        [CmdletBinding()]
        param([string]$Key)
        $normalized = $Key -replace '^(HKEY_LOCAL_MACHINE|HKLM|HKEY_CURRENT_USER|HKCU)\\', ''
        if ($normalized -ne $Key) {
            Write-Verbose "Stripped registry hive prefix: '$($Key)' -> '$($normalized)'"
        }
        return $normalized
    }

    function Get-AdmlStringTable {
        <#
        .SYNOPSIS
            Parses an ADML XML document and returns a hashtable of string ID to display value.
        #>

        [CmdletBinding()]
        param([xml]$AdmlXml)

        $table = @{}
        $ns = New-Object System.Xml.XmlNamespaceManager($AdmlXml.NameTable)

        $rootNamespaceUri = $AdmlXml.DocumentElement.NamespaceURI
        $strings = if (-not [string]::IsNullOrEmpty($rootNamespaceUri)) {
            $ns.AddNamespace('ad', $rootNamespaceUri)
            $AdmlXml.SelectNodes('//ad:stringTable/ad:string', $ns)
        } else {
            $AdmlXml.SelectNodes('//stringTable/string')
        }
        foreach ($s in $strings) {
            $table[$s.GetAttribute('id')] = $s.InnerText
        }
        Write-Verbose "ADML string table loaded: $($table.Count) entries"
        return $table
    }

    function Resolve-AdmlString {
        <#
        .SYNOPSIS
            Resolves a $(string.ID) reference against a string table hashtable.
        #>

        [CmdletBinding()]
        param(
            [string]$Reference,
            [hashtable]$StringTable
        )
        if ($Reference -match '^\$\(string\.(.+)\)$') {
            $id = $Matches[1]
            if ($StringTable.ContainsKey($id)) {
                return $StringTable[$id]
            }
            Write-Verbose "ADML string reference not resolved: '$($id)' (string table has $($StringTable.Count) entries)"
        }
        return $Reference
    }

    function Find-AdmlFile {
        <#
        .SYNOPSIS
            Attempts to locate an ADML file adjacent to the given ADMX file path.
            Detection order: same-name ADML in UI culture subfolder, en-US, any available language.
        #>

        [CmdletBinding()]
        param([string]$AdmxFileFullPath)

        $admxDir = Split-Path -Parent $AdmxFileFullPath
        $admlName = [System.IO.Path]::GetFileNameWithoutExtension($AdmxFileFullPath) + '.adml'

        $culturesToTry = [System.Collections.Generic.List[string]]::new()

        $currentCulture = (Get-Culture).Name
        if (-not [string]::IsNullOrEmpty($currentCulture)) {
            $culturesToTry.Add($currentCulture)
        }
        if (-not $culturesToTry.Contains('en-US')) {
            $culturesToTry.Add('en-US')
        }

        foreach ($culture in $culturesToTry) {
            $candidate = Join-Path $admxDir "$($culture)\$($admlName)"
            Write-Verbose "ADML probe [$($culture)]: $($candidate)"
            if (Test-Path $candidate -PathType Leaf) {
                Write-Verbose "Resolved ADML via culture '$($culture)': $($candidate)"
                return $candidate
            }
        }

        # Fallback: any language subfolder
        Write-Verbose "ADML not found via culture probes, scanning subdirectories of '$($admxDir)' for '$($admlName)'"
        $anyAdml = Get-ChildItem -Path $admxDir -Filter $admlName -Recurse -File -ErrorAction SilentlyContinue |
            Select-Object -First 1
        if ($null -ne $anyAdml) {
            Write-Verbose "Resolved ADML via fallback scan: $($anyAdml.FullName)"
            return $anyAdml.FullName
        }

        Write-Verbose "ADML not found for '$($admlName)' in any subfolder of '$($admxDir)'"
        return $null
    }

    function Get-CrossNamespaceStringTables {
        <#
        .SYNOPSIS
            Reads the policyNamespaces/using declarations from an ADMX document, locates each
            referenced ADMX file, loads its ADML string table, and returns a hashtable keyed
            by namespace prefix mapping to that prefix's string table hashtable.
 
            Search order per referenced namespace:
            1. Same directory as the source ADMX file.
            2. Recursive scan of that same directory.
            3. Any additional search root provided (e.g. the -AdmxPath directory).
        #>

        [CmdletBinding()]
        param(
            [xml]$AdmxXml,
            [string]$SourceAdmxDir,
            [string]$AdditionalSearchRoot
        )

        $result = @{}

        $rootNamespaceUri = $AdmxXml.DocumentElement.NamespaceURI
        $nsManager = New-Object System.Xml.XmlNamespaceManager($AdmxXml.NameTable)
        if (-not [string]::IsNullOrEmpty($rootNamespaceUri)) {
            $nsManager.AddNamespace('ad', $rootNamespaceUri)
            $usingNodes = $AdmxXml.SelectNodes('//ad:policyNamespaces/ad:using', $nsManager)
        } else {
            $usingNodes = $AdmxXml.SelectNodes('//policyNamespaces/using')
        }

        if ($null -eq $usingNodes -or $usingNodes.Count -eq 0) {
            Write-Verbose "No cross-namespace 'using' declarations found in ADMX"
            return $result
        }

        Write-Verbose "Found $($usingNodes.Count) cross-namespace 'using' declaration(s)"

        foreach ($usingNode in $usingNodes) {
            $prefix = $usingNode.GetAttribute('prefix')
            $namespace = $usingNode.GetAttribute('namespace')

            if ([string]::IsNullOrEmpty($prefix) -or [string]::IsNullOrEmpty($namespace)) {
                continue
            }

            Write-Verbose "Resolving namespace prefix '$($prefix)' (namespace: $($namespace))"

            # Derive candidate ADMX filename from the last segment of the namespace
            # e.g. Microsoft.Policies.Windows -> Windows.admx
            $namespaceParts = $namespace -split '\.'
            $candidateAdmxName = $namespaceParts[-1] + '.admx'

            # Build ordered list of search paths
            $searchPaths = [System.Collections.Generic.List[string]]::new()
            $searchPaths.Add($SourceAdmxDir)
            if (-not [string]::IsNullOrEmpty($AdditionalSearchRoot) -and
                $AdditionalSearchRoot -ne $SourceAdmxDir) {
                $searchPaths.Add($AdditionalSearchRoot)
            }

            $resolvedAdmxPath = $null

            foreach ($searchPath in $searchPaths) {
                if ([string]::IsNullOrEmpty($searchPath) -or -not (Test-Path $searchPath -ErrorAction SilentlyContinue)) {
                    continue
                }

                # 1. Direct child
                $directCandidate = Join-Path $searchPath $candidateAdmxName
                if (Test-Path $directCandidate -PathType Leaf) {
                    Write-Verbose "Found namespace ADMX (direct): $($directCandidate)"
                    $resolvedAdmxPath = $directCandidate
                    break
                }

                # 2. Recursive scan
                $recursiveMatch = Get-ChildItem -Path $searchPath -Filter $candidateAdmxName `
                    -Recurse -File -ErrorAction SilentlyContinue |
                    Select-Object -First 1
                if ($null -ne $recursiveMatch) {
                    Write-Verbose "Found namespace ADMX (recursive): $($recursiveMatch.FullName)"
                    $resolvedAdmxPath = $recursiveMatch.FullName
                    break
                }
            }

            if ($null -eq $resolvedAdmxPath) {
                Write-Warning "Cannot resolve ADMX for namespace prefix '$($prefix)' (expected '$($candidateAdmxName)'). Cross-namespace references using this prefix will be empty."
                continue
            }

            # Load the ADML for this namespace ADMX
            $resolvedAdmlPath = Find-AdmlFile -AdmxFileFullPath $resolvedAdmxPath
            if ($null -eq $resolvedAdmlPath) {
                Write-Warning "Found ADMX for prefix '$($prefix)' but no ADML. Cross-namespace display strings for this prefix will be empty."
                continue
            }

            try {
                [xml]$nsAdmlXml = Get-Content -LiteralPath $resolvedAdmlPath -Raw -ErrorAction Stop
                $nsStringTable = Get-AdmlStringTable -AdmlXml $nsAdmlXml
                $result[$prefix] = $nsStringTable
                Write-Verbose "Loaded string table for prefix '$($prefix)': $($nsStringTable.Count) entries from '$($resolvedAdmlPath)'"
            } catch {
                Write-Warning "Failed to load ADML '$($resolvedAdmlPath)' for prefix '$($prefix)': $($_.Exception.Message)"
            }
        }

        return $result
    }

    function Resolve-CrossNamespaceRef {
        <#
        .SYNOPSIS
            Resolves a cross-namespace string or category reference against a hashtable of
            per-prefix string tables.
 
            Handles two reference formats:
            - $(using:prefix.StringId) - used for displayName/explainText/supportedOn
            - prefix:CategoryName - used for parentCategory ref attribute values
        #>

        [CmdletBinding()]
        param(
            [string]$Reference,
            [hashtable]$NamespaceStringTables
        )

        if ([string]::IsNullOrEmpty($Reference) -or $NamespaceStringTables.Count -eq 0) {
            return $Reference
        }

        # Format 1: $(using:prefix.StringId)
        if ($Reference -match '^\$\(using:([^.]+)\.(.+)\)$') {
            $prefix = $Matches[1]
            $stringId = $Matches[2]
            if ($NamespaceStringTables.ContainsKey($prefix)) {
                $table = $NamespaceStringTables[$prefix]
                if ($table.ContainsKey($stringId)) {
                    Write-Verbose "Resolved cross-namespace ref '$(using:$($prefix).$($stringId))' -> '$($table[$stringId])'"
                    return $table[$stringId]
                }
                Write-Verbose "Cross-namespace string '$($stringId)' not found in prefix '$($prefix)' table ($($table.Count) entries)"
            } else {
                Write-Verbose "Cross-namespace prefix '$($prefix)' not loaded"
            }
            return $null
        }

        # Format 2: prefix:CategoryName (parentCategory ref)
        if ($Reference -match '^([^:$(]+):(.+)$') {
            $prefix = $Matches[1]
            $categoryId = $Matches[2]
            if ($NamespaceStringTables.ContainsKey($prefix)) {
                $table = $NamespaceStringTables[$prefix]
                # Category display names are stored with a conventional string ID pattern
                # Try common patterns: CategoryName, Cat_CategoryName
                foreach ($candidateId in @($categoryId, "Cat_$($categoryId)", "Category_$($categoryId)")) {
                    if ($table.ContainsKey($candidateId)) {
                        Write-Verbose "Resolved cross-namespace category '$($Reference)' via id '$($candidateId)' -> '$($table[$candidateId])'"
                        return $table[$candidateId]
                    }
                }
                Write-Verbose "Cross-namespace category '$($categoryId)' not found in prefix '$($prefix)' string table - returning raw ref"
            } else {
                Write-Verbose "Cross-namespace prefix '$($prefix)' not loaded for category ref '$($Reference)'"
            }
        }

        return $Reference
    }

    function Get-RegistryTypeFromElement {
        <#
        .SYNOPSIS
            Determines the registry type string from an ADMX XML element node.
        #>

        [CmdletBinding()]
        param(
            [System.Xml.XmlElement]$Element,
            [System.Xml.XmlNamespaceManager]$Ns,
            [bool]$UseNamespace = $true
        )

        $storeAsText = $Element.GetAttribute('storeAsText') -eq 'true'

        switch ($Element.LocalName) {
            'decimal'     {
                if ($storeAsText) {
                    Write-Verbose "Element type 'decimal' with storeAsText=true -> REG_SZ"
                    return 'REG_SZ'
                } else {
                    Write-Verbose "Element type 'decimal' -> REG_DWORD"
                    return 'REG_DWORD'
                }
            }
            'longDecimal' {
                Write-Verbose "Element type 'longDecimal' -> REG_QWORD"
                return 'REG_QWORD'
            }
            'text'        {
                Write-Verbose "Element type 'text' -> REG_SZ"
                return 'REG_SZ'
            }
            'multiText'   {
                Write-Verbose "Element type 'multiText' -> REG_MULTI_SZ"
                return 'REG_MULTI_SZ'
            }
            'boolean'     {
                Write-Verbose "Element type 'boolean' -> REG_DWORD"
                return 'REG_DWORD'
            }
            'list'        {
                Write-Verbose "Element type 'list' -> REG_SZ (multiple values)"
                return 'REG_SZ'
            }
            'enum'        {
                $firstItem = if ($UseNamespace) {
                    $Element.SelectSingleNode('ad:item/ad:value/*', $Ns)
                } else {
                    $Element.SelectSingleNode('item/value/*')
                }
                if ($null -ne $firstItem) {
                    switch ($firstItem.LocalName) {
                        'decimal' {
                            Write-Verbose "Element type 'enum' (first item value: decimal) -> REG_DWORD"
                            return 'REG_DWORD'
                        }
                        'string'  {
                            Write-Verbose "Element type 'enum' (first item value: string) -> REG_SZ"
                            return 'REG_SZ'
                        }
                        default   {
                            Write-Verbose "Element type 'enum' (first item value: $($firstItem.LocalName), unknown) -> REG_DWORD"
                            return 'REG_DWORD'
                        }
                    }
                }
                Write-Verbose "Element type 'enum' (no item value nodes found) -> REG_DWORD"
                return 'REG_DWORD'
            }
            default       {
                Write-Verbose "Element type '$($Element.LocalName)' not recognised -> UNKNOWN"
                return 'UNKNOWN'
            }
        }
    }

    function Get-ElementConstraints {
        <#
        .SYNOPSIS
            Returns a hashtable of constraints defined on a policy element (min, max, maxLength, etc.).
        #>

        [CmdletBinding()]
        param([System.Xml.XmlElement]$Element)

        $constraints = @{}

        $minValue = $Element.GetAttribute('minValue')
        $maxValue = $Element.GetAttribute('maxValue')
        $maxLength = $Element.GetAttribute('maxLength')
        $required = $Element.GetAttribute('required')

        if (-not [string]::IsNullOrEmpty($minValue)) { $constraints['MinValue'] = $minValue }
        if (-not [string]::IsNullOrEmpty($maxValue)) { $constraints['MaxValue'] = $maxValue }
        if (-not [string]::IsNullOrEmpty($maxLength)) { $constraints['MaxLength'] = $maxLength }
        if (-not [string]::IsNullOrEmpty($required)) { $constraints['Required'] = $required }

        return $constraints
    }

    function Invoke-AdmxSearch {
        <#
        .SYNOPSIS
            Performs the actual policy search against a parsed ADMX XML document.
        #>

        [CmdletBinding()]
        param(
            [xml]$AdmxXml,
            [hashtable]$StringTable,
            [hashtable]$NamespaceStringTables,
            [string]$DetectedHive,
            [bool]$ReturnAll = $false,
            [string]$NormalizedKey,
            [string]$SearchValueName,
            [string]$SourceFile,
            [string]$SourceAdml
        )

        $ns = New-Object System.Xml.XmlNamespaceManager($AdmxXml.NameTable)

        # Auto-detect namespace from the document root rather than assuming it.
        # Some ADMX files omit the namespace entirely; others use the standard URI.
        $rootNamespaceUri = $AdmxXml.DocumentElement.NamespaceURI
        $knownUri = 'http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions'
        $useNamespace = $false

        if (-not [string]::IsNullOrEmpty($rootNamespaceUri)) {
            $useNamespace = $true
            if ($rootNamespaceUri -ne $knownUri) {
                Write-Verbose "Document namespace '$($rootNamespaceUri)' differs from expected - using document namespace"
            } else {
                Write-Verbose "Document namespace matches expected URI"
            }
            $ns.AddNamespace('ad', $rootNamespaceUri)
        } else {
            Write-Verbose "Document has no namespace declaration - using namespace-agnostic XPath"
        }

        $policies = if ($useNamespace) {
            $AdmxXml.SelectNodes('//ad:policy', $ns)
        } else {
            $AdmxXml.SelectNodes('//policy')
        }

        if ($ReturnAll) {
            Write-Verbose "Returning all $($policies.Count) policies from '$($SourceFile)'"
        } else {
            Write-Verbose "Searching $($policies.Count) policies in '$($SourceFile)' for key '$($NormalizedKey)' valueName '$($SearchValueName)'"
        }

        foreach ($policy in $policies) {
            $policyKey = ConvertTo-NormalizedKey -Key $policy.GetAttribute('key')
            $policyValueName = $policy.GetAttribute('valueName')

            $isDirectMatch = ($policyKey -ieq $NormalizedKey) -and ($policyValueName -ieq $SearchValueName)
            $hasElementMatch = $false

            # Resolve enabledValue/disabledValue nodes
            $enabledValueNode = if ($useNamespace) {
                $policy.SelectSingleNode('ad:enabledValue', $ns)
            } else {
                $policy.SelectSingleNode('enabledValue')
            }
            $disabledValueNode = if ($useNamespace) {
                $policy.SelectSingleNode('ad:disabledValue', $ns)
            } else {
                $policy.SelectSingleNode('disabledValue')
            }

            $enabledValueResolved = if ($null -ne $enabledValueNode) {
                $child = $enabledValueNode.ChildNodes |
                    Where-Object { $_.NodeType -ne [System.Xml.XmlNodeType]::Whitespace } |
                    Select-Object -First 1
                if ($null -ne $child) {
                    if ($child.LocalName -eq 'decimal') { [uint64]$child.GetAttribute('value') }
                    elseif ($child.LocalName -eq 'string') { $child.InnerText }
                    else { $null }
                }
            }

            $disabledValueResolved = if ($null -ne $disabledValueNode) {
                $child = $disabledValueNode.ChildNodes |
                    Where-Object { $_.NodeType -ne [System.Xml.XmlNodeType]::Whitespace } |
                    Select-Object -First 1
                if ($null -ne $child) {
                    if ($child.LocalName -eq 'decimal') { [uint64]$child.GetAttribute('value') }
                    elseif ($child.LocalName -eq 'string') { $child.InnerText }
                    else { $null }
                }
            }

            $policyRegistryType = if ($null -ne $enabledValueNode) {
                $child = $enabledValueNode.ChildNodes |
                    Where-Object { $_.NodeType -ne [System.Xml.XmlNodeType]::Whitespace } |
                    Select-Object -First 1
                switch ($child.LocalName) {
                    'decimal' { 'REG_DWORD' }
                    'string'  { 'REG_SZ' }
                    default   { 'REG_DWORD' }
                }
            } else { 'REG_DWORD' }

            # Build Elements array from <elements> child nodes
            $elementNodes = if ($useNamespace) {
                $policy.SelectNodes('ad:elements/*', $ns)
            } else {
                $policy.SelectNodes('elements/*')
            }

            $elementsArray = [System.Collections.Generic.List[PSCustomObject]]::new()
            $matchedElement = $null

            foreach ($element in $elementNodes) {
                $elemKey = $element.GetAttribute('key')
                $elemKey = if ([string]::IsNullOrEmpty($elemKey)) { $policyKey } else { ConvertTo-NormalizedKey -Key $elemKey }
                $elemValueName = $element.GetAttribute('valueName')
                $elemId = $element.GetAttribute('id')
                $elemType = $element.LocalName

                $regType = Get-RegistryTypeFromElement -Element $element -Ns $ns -UseNamespace $useNamespace
                $elemConstraints = Get-ElementConstraints -Element $element

                $elemEnumItems = $null
                $elemTrueValue = $null
                $elemFalseValue = $null

                if ($elemType -eq 'enum') {
                    $enumItemNodes = if ($useNamespace) {
                        $element.SelectNodes('ad:item', $ns)
                    } else {
                        $element.SelectNodes('item')
                    }
                    $enumItems = foreach ($item in $enumItemNodes) {
                        $itemDisplay = Resolve-AdmlString -Reference $item.GetAttribute('displayName') -StringTable $StringTable
                        $valueNode = if ($useNamespace) {
                            $item.SelectSingleNode('ad:value/*', $ns)
                        } else {
                            $item.SelectSingleNode('value/*')
                        }
                        $itemValue = if ($null -eq $valueNode) { $null }
                                     elseif ($valueNode.LocalName -eq 'decimal') { $valueNode.GetAttribute('value') }
                                     elseif ($valueNode.LocalName -eq 'longDecimal') { $valueNode.GetAttribute('value') }
                                     elseif ($valueNode.LocalName -eq 'string') { $valueNode.InnerText }
                                     elseif ($valueNode.LocalName -eq 'delete') { $null }
                                     else { $valueNode.InnerText }
                        [PSCustomObject]@{
                            DisplayName   = $itemDisplay
                            RegistryValue = $itemValue
                            ValueType     = if ($null -ne $valueNode) { $valueNode.LocalName } else { $null }
                        }
                    }
                    $firstValueType = (@($enumItems) | Select-Object -First 1).ValueType
                    $regType = switch ($firstValueType) {
                        'decimal'     { 'REG_DWORD' }
                        'longDecimal' { 'REG_QWORD' }
                        'string'      { 'REG_SZ' }
                        default       { 'REG_DWORD' }
                    }
                    $elemEnumItems = @($enumItems)
                } elseif ($elemType -eq 'boolean') {
                    # Extract explicit trueValue/falseValue child nodes if present
                    $trueValueNode = if ($useNamespace) {
                        $element.SelectSingleNode('ad:trueValue/*', $ns)
                    } else {
                        $element.SelectSingleNode('trueValue/*')
                    }
                    $falseValueNode = if ($useNamespace) {
                        $element.SelectSingleNode('ad:falseValue/*', $ns)
                    } else {
                        $element.SelectSingleNode('falseValue/*')
                    }
                    if ($null -ne $trueValueNode) {
                        $elemTrueValue = if ($trueValueNode.LocalName -eq 'decimal') { [uint64]$trueValueNode.GetAttribute('value') }
                                         elseif ($trueValueNode.LocalName -eq 'string') { $trueValueNode.InnerText }
                                         else { $null }
                    }
                    if ($null -ne $falseValueNode) {
                        $elemFalseValue = if ($falseValueNode.LocalName -eq 'decimal') { [uint64]$falseValueNode.GetAttribute('value') }
                                          elseif ($falseValueNode.LocalName -eq 'string') { $falseValueNode.InnerText }
                                          else { $null }
                    }
                }

                $elementObj = [PSCustomObject]@{
                    ElementId       = $elemId
                    ElementType     = $elemType
                    ConfiguredState = 'Unconfigured'
                    RegistryKey     = $elemKey
                    ValueName       = $elemValueName
                    RegistryType    = $regType
                    Constraints     = if ($elemConstraints.Count -gt 0) { $elemConstraints } else { $null }
                    EnumItems       = $elemEnumItems
                    TrueValue       = $elemTrueValue
                    FalseValue      = $elemFalseValue
                }

                $elementsArray.Add($elementObj)

                if (-not $ReturnAll -and -not $hasElementMatch) {
                    if (($elemKey -ieq $NormalizedKey) -and ($elemValueName -ieq $SearchValueName)) {
                        $hasElementMatch = $true
                        $matchedElement = $elementObj
                        Write-Verbose "Element-level match: '$($policy.GetAttribute('name'))' element '$elemType' id='$elemId' valueName='$elemValueName' in '$SourceFile'"
                    }
                }
            }

            $shouldEmit = $ReturnAll -or $isDirectMatch -or $hasElementMatch
            if (-not $shouldEmit) { continue }

            # Resolve policy-level display strings
            $displayNameRef = $policy.GetAttribute('displayName')
            $explainRef     = $policy.GetAttribute('explainText')
            $displayName    = Resolve-AdmlString -Reference $displayNameRef -StringTable $StringTable
            $explainText    = Resolve-AdmlString -Reference $explainRef -StringTable $StringTable

            $categoryNode = if ($useNamespace) {
                $policy.SelectSingleNode('ad:parentCategory', $ns)
            } else {
                $policy.SelectSingleNode('parentCategory')
            }
            $categoryRef = if ($null -ne $categoryNode) { $categoryNode.GetAttribute('ref') } else { '' }

            $supportedNode = if ($useNamespace) {
                $policy.SelectSingleNode('ad:supportedOn', $ns)
            } else {
                $policy.SelectSingleNode('supportedOn')
            }
            $supportedRef = if ($null -ne $supportedNode) { $supportedNode.GetAttribute('ref') } else { '' }

            $categoryName = if ($categoryRef -match '^[^:$(]+:.+$') {
                Resolve-CrossNamespaceRef -Reference $categoryRef -NamespaceStringTables $NamespaceStringTables
            } else {
                Resolve-AdmlString -Reference $categoryRef -StringTable $StringTable
            }

            $supportedOn = $null
            if (-not [string]::IsNullOrEmpty($supportedRef)) {
                if ($supportedRef -match '^\$\(using:' -or $supportedRef -match '^[^:$(]+:.+$') {
                    $resolved = Resolve-CrossNamespaceRef -Reference $supportedRef -NamespaceStringTables $NamespaceStringTables
                    $supportedOn = if (-not [string]::IsNullOrEmpty($resolved)) { $resolved } else { $supportedRef }
                } else {
                    $supportedOn = Resolve-AdmlString -Reference $supportedRef -StringTable $StringTable
                }
            }

            $policyClass = $policy.GetAttribute('class')
            $resolvedClass = if ($policyClass -ieq 'Both' -and -not [string]::IsNullOrEmpty($DetectedHive)) {
                $DetectedHive
            } else {
                $policyClass
            }

            Write-Verbose "Emitting result: PolicyName='$($policy.GetAttribute('name'))' Elements=$($elementsArray.Count)"
            [PSCustomObject]@{
                PolicyName      = $policy.GetAttribute('name')
                DisplayName     = $displayName
                ExplainText     = $explainText
                Class           = $resolvedClass
                Category        = $categoryName
                SupportedOn     = $supportedOn
                ConfiguredState = 'Unconfigured'
                RegistryKey     = $policyKey
                ValueName       = $policyValueName
                RegistryType    = $policyRegistryType
                EnabledValue    = $enabledValueResolved
                DisabledValue   = $disabledValueResolved
                Elements        = if ($elementsArray.Count -gt 0) { $elementsArray.ToArray() } else { @() }
                MatchedElement  = $matchedElement
                AdmxFile        = $(try { [System.IO.Path]::GetFileName($SourceFile) } catch { $null })
                AdmlFile        = $(try { [System.IO.Path]::GetFileName($SourceAdml) } catch { $null })
                SourceFile      = $SourceFile
                SourceAdml      = $SourceAdml
            }
        }
    }

    #endregion Internal helpers

    #region Main logic

    # Infer return-all mode: explicit -All switch, or neither -RegistryKey nor -ValueName provided
    $returnAll = $returnAll -or ([string]::IsNullOrEmpty($RegistryKey) -and [string]::IsNullOrEmpty($ValueName))

    # When not in return-all mode both parameters are required
    if (-not $returnAll) {
        if ([string]::IsNullOrEmpty($RegistryKey) -or [string]::IsNullOrEmpty($ValueName)) {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.ArgumentException]::new('Specify both -RegistryKey and -ValueName, or omit both (or use -All) to return all policies.'),
                    'MissingSearchParameters',
                    [System.Management.Automation.ErrorCategory]::InvalidArgument,
                    $null
                )
            )
        }
    }

    $normalizedKey = if (-not [string]::IsNullOrEmpty($RegistryKey)) {
        ConvertTo-NormalizedKey -Key $RegistryKey
    } else {
        $null
    }

    # Detect the hive prefix from the original RegistryKey for resolving class="Both" policies
    $detectedHive = $null
    if (-not [string]::IsNullOrEmpty($RegistryKey)) {
        if ($RegistryKey -match '^(HKEY_LOCAL_MACHINE|HKLM)\') {
            $detectedHive = 'Machine'
        } elseif ($RegistryKey -match '^(HKEY_CURRENT_USER|HKCU)\') {
            $detectedHive = 'User'
        }
    }

    if ($returnAll) {
        Write-Verbose "Returning all policies (ParameterSet: $($PSCmdlet.ParameterSetName))"
    } else {
        Write-Verbose "Searching for: Key='$($normalizedKey)' ValueName='$($ValueName)' (ParameterSet: $($PSCmdlet.ParameterSetName))"
    }

    switch ($PSCmdlet.ParameterSetName) {

        'XmlContent' {
            Write-Verbose "Parameter set: XmlContent"

            try {
                if ($base64DecodedAdmx = Test-Base64String -InputString $AdmxContent -PassThru -AsString) {
                    [xml]$admxXml = $base64DecodedAdmx
                } else {
                    [xml]$admxXml = $AdmxContent
                }
                Write-Verbose "AdmxContent parsed successfully"
            } catch {
                $PSCmdlet.ThrowTerminatingError(
                    [System.Management.Automation.ErrorRecord]::new(
                        [System.ArgumentException]::new("Failed to parse AdmxContent as XML: $($_.Exception.Message)"),
                        'InvalidAdmxXml',
                        [System.Management.Automation.ErrorCategory]::InvalidData,
                        $AdmxContent
                    )
                )
            }

            $stringTable = @{}
            if ($PSBoundParameters.ContainsKey('AdmlContent')) {
                try {
                    if ($base64DecodedAdml = Test-Base64String -InputString $AdmlContent -PassThru -AsString) {
                        [xml]$admlXml = $base64DecodedAdml
                    } else {
                        [xml]$admlXml = $AdmlContent
                    }
                    $stringTable = Get-AdmlStringTable -AdmlXml $admlXml
                    Write-Verbose "AdmlContent parsed successfully"
                } catch {
                    Write-Warning "Failed to parse AdmlContent as XML: $($_.Exception.Message). Display names will not be resolved."
                }
            } else {
                Write-Warning "No AdmlContent provided. Display names will not be resolved."
            }

            Invoke-AdmxSearch -AdmxXml $admxXml -StringTable $stringTable `
                -NamespaceStringTables @{} `
                -DetectedHive $detectedHive `
                -ReturnAll $returnAll `
                -NormalizedKey $normalizedKey -SearchValueName $ValueName `
                -SourceFile $AdmxFileName `
                -SourceAdml $AdmlFileName
        }

        'XmlFile' {
            Write-Verbose "Parameter set: XmlFile - $($AdmxFilePath)"

            try {
                [xml]$admxXml = Get-Content -LiteralPath $AdmxFilePath -Raw -ErrorAction Stop
                Write-Verbose "ADMX file loaded: $($AdmxFilePath)"
            } catch {
                $PSCmdlet.ThrowTerminatingError(
                    [System.Management.Automation.ErrorRecord]::new(
                        [System.IO.IOException]::new("Failed to read ADMX file '$($AdmxFilePath)': $($_.Exception.Message)"),
                        'AdmxReadError',
                        [System.Management.Automation.ErrorCategory]::ReadError,
                        $AdmxFilePath
                    )
                )
            }

            $stringTable = @{}
            $resolvedAdml = if ($PSBoundParameters.ContainsKey('AdmlFilePath')) {
                Write-Verbose "Using explicitly provided ADML: $($AdmlFilePath)"
                $AdmlFilePath
            } else {
                Write-Verbose "AdmlFilePath not provided, attempting auto-detection"
                Find-AdmlFile -AdmxFileFullPath $AdmxFilePath
            }

            if ($null -ne $resolvedAdml) {
                try {
                    [xml]$admlXml = Get-Content -LiteralPath $resolvedAdml -Raw -ErrorAction Stop
                    $stringTable = Get-AdmlStringTable -AdmlXml $admlXml
                    Write-Verbose "Loaded ADML: $($resolvedAdml)"
                } catch {
                    Write-Warning "Failed to read ADML '$($resolvedAdml)': $($_.Exception.Message). Display names will not be resolved."
                }
            } else {
                Write-Warning "No ADML file found for '$($AdmxFilePath)'. Display names will not be resolved."
            }

            Invoke-AdmxSearch -AdmxXml $admxXml -StringTable $stringTable `
                -NamespaceStringTables (Get-CrossNamespaceStringTables -AdmxXml $admxXml `
                    -SourceAdmxDir (Split-Path -Parent $AdmxFilePath) `
                    -AdditionalSearchRoot (Split-Path -Parent $AdmxFilePath)) `
                -DetectedHive $detectedHive `
                -ReturnAll $returnAll `
                -NormalizedKey $normalizedKey -SearchValueName $ValueName `
                -SourceFile $AdmxFilePath `
                -SourceAdml $(if ($null -ne $resolvedAdml) { $resolvedAdml } else { $null })
        }

        'Path' {
            Write-Verbose "Parameter set: Path - $($AdmxPath)"

            $admxFiles = if ((Get-Item -LiteralPath $AdmxPath).PSIsContainer) {
                $getSplat = @{
                    Path    = $AdmxPath
                    Filter  = '*.admx'
                    Recurse = $Recurse.IsPresent
                    File    = $true
                }
                Get-ChildItem @getSplat
            } else {
                Write-Verbose "AdmxPath is a single file: $($AdmxPath)"
                Get-Item -LiteralPath $AdmxPath
            }

            if (-not $admxFiles) {
                Write-Warning "No ADMX files found at '$($AdmxPath)'."
                return
            }

            Write-Verbose "Found $(@($admxFiles).Count) ADMX file(s) to process"

            foreach ($admxFile in $admxFiles) {
                Write-Verbose "Processing ADMX: $($admxFile.FullName)"

                try {
                    [xml]$admxXml = Get-Content -LiteralPath $admxFile.FullName -Raw -ErrorAction Stop
                    Write-Verbose "ADMX loaded successfully: $($admxFile.Name)"
                } catch {
                    Write-Warning "Failed to read '$($admxFile.Name)': $($_.Exception.Message). Skipping."
                    continue
                }

                $stringTable = @{}
                $resolvedAdml = Find-AdmlFile -AdmxFileFullPath $admxFile.FullName

                if ($null -ne $resolvedAdml) {
                    try {
                        [xml]$admlXml = Get-Content -LiteralPath $resolvedAdml -Raw -ErrorAction Stop
                        $stringTable = Get-AdmlStringTable -AdmlXml $admlXml
                        Write-Verbose "Loaded ADML: $($resolvedAdml)"
                    } catch {
                        Write-Warning "Failed to read ADML '$($resolvedAdml)': $($_.Exception.Message). Display names will not be resolved for $($admxFile.Name)."
                    }
                } else {
                    Write-Warning "No ADML found for '$($admxFile.Name)'. Display names will not be resolved."
                }

                $sourceAdmxDir = Split-Path -Parent $admxFile.FullName
                $additionalRoot = if ((Get-Item -LiteralPath $AdmxPath).PSIsContainer) { $AdmxPath } else { $sourceAdmxDir }
                $nsStringTables = Get-CrossNamespaceStringTables -AdmxXml $admxXml `
                    -SourceAdmxDir $sourceAdmxDir -AdditionalSearchRoot $additionalRoot

                Invoke-AdmxSearch -AdmxXml $admxXml -StringTable $stringTable `
                    -NamespaceStringTables $nsStringTables `
                    -DetectedHive $detectedHive `
                    -ReturnAll $returnAll `
                    -NormalizedKey $normalizedKey -SearchValueName $ValueName `
                    -SourceFile $admxFile.FullName `
                    -SourceAdml $(if ($null -ne $resolvedAdml) { $resolvedAdml } else { $null })
            }
        }
    }

    #endregion Main logic
}

# SIG # Begin signature block
# MIImdwYJKoZIhvcNAQcCoIImaDCCJmQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDYqfxiJcUPKon3
# yP2fnsynNrP2YubSKpAPOi6Ac5iMUaCCIAowggYUMIID/KADAgECAhB6I67aU2mW
# D5HIPlz0x+M/MA0GCSqGSIb3DQEBDAUAMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQK
# Ew9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUg
# U3RhbXBpbmcgUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5
# WjBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSwwKgYD
# VQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNjCCAaIwDQYJ
# KoZIhvcNAQEBBQADggGPADCCAYoCggGBAM2Y2ENBq26CK+z2M34mNOSJjNPvIhKA
# VD7vJq+MDoGD46IiM+b83+3ecLvBhStSVjeYXIjfa3ajoW3cS3ElcJzkyZlBnwDE
# JuHlzpbN4kMH2qRBVrjrGJgSlzzUqcGQBaCxpectRGhhnOSwcjPMI3G0hedv2eNm
# GiUbD12OeORN0ADzdpsQ4dDi6M4YhoGE9cbY11XxM2AVZn0GiOUC9+XE0wI7CQKf
# OUfigLDn7i/WeyxZ43XLj5GVo7LDBExSLnh+va8WxTlA+uBvq1KO8RSHUQLgzb1g
# bL9Ihgzxmkdp2ZWNuLc+XyEmJNbD2OIIq/fWlwBp6KNL19zpHsODLIsgZ+WZ1AzC
# s1HEK6VWrxmnKyJJg2Lv23DlEdZlQSGdF+z+Gyn9/CRezKe7WNyxRf4e4bwUtrYE
# 2F5Q+05yDD68clwnweckKtxRaF0VzN/w76kOLIaFVhf5sMM/caEZLtOYqYadtn03
# 4ykSFaZuIBU9uCSrKRKTPJhWvXk4CllgrwIDAQABo4IBXDCCAVgwHwYDVR0jBBgw
# FoAU9ndq3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFF9Y7UwxeqJhQo1SgLqz
# YZcZojKbMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMBEGA1UdIAQKMAgwBgYEVR0gADBMBgNVHR8ERTBDMEGg
# P6A9hjtodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3Rh
# bXBpbmdSb290UjQ2LmNybDB8BggrBgEFBQcBAQRwMG4wRwYIKwYBBQUHMAKGO2h0
# dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1YmxpY1RpbWVTdGFtcGluZ1Jv
# b3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAN
# BgkqhkiG9w0BAQwFAAOCAgEAEtd7IK0ONVgMnoEdJVj9TC1ndK/HYiYh9lVUacah
# RoZ2W2hfiEOyQExnHk1jkvpIJzAMxmEc6ZvIyHI5UkPCbXKspioYMdbOnBWQUn73
# 3qMooBfIghpR/klUqNxx6/fDXqY0hSU1OSkkSivt51UlmJElUICZYBodzD3M/SFj
# eCP59anwxs6hwj1mfvzG+b1coYGnqsSz2wSKr+nDO+Db8qNcTbJZRAiSazr7KyUJ
# Go1c+MScGfG5QHV+bps8BX5Oyv9Ct36Y4Il6ajTqV2ifikkVtB3RNBUgwu/mSiSU
# ice/Jp/q8BMk/gN8+0rNIE+QqU63JoVMCMPY2752LmESsRVVoypJVt8/N3qQ1c6F
# ibbcRabo3azZkcIdWGVSAdoLgAIxEKBeNh9AQO1gQrnh1TA8ldXuJzPSuALOz1Uj
# b0PCyNVkWk7hkhVHfcvBfI8NtgWQupiaAeNHe0pWSGH2opXZYKYG4Lbukg7HpNi/
# KqJhue2Keak6qH9A8CeEOB7Eob0Zf+fU+CCQaL0cJqlmnx9HCDxF+3BLbUufrV64
# EbTI40zqegPZdA+sXCmbcZy6okx/SjwsusWRItFA3DE8MORZeFb6BmzBtqKJ7l93
# 9bbKBy2jvxcJI98Va95Q5JnlKor3m0E7xpMeYRriWklUPsetMSf2NvUQa/E5vVye
# fQIwggZFMIIELaADAgECAhAIMk+dt9qRb2Pk8qM8Xl1RMA0GCSqGSIb3DQEBCwUA
# MFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMu
# QS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAyMSBDQTAeFw0yNDA0
# MDQxNDA0MjRaFw0yNzA0MDQxNDA0MjNaMGsxCzAJBgNVBAYTAk5MMRIwEAYDVQQH
# DAlTY2hpam5kZWwxIzAhBgNVBAoMGkpvaG4gQmlsbGVrZW5zIENvbnN1bHRhbmN5
# MSMwIQYDVQQDDBpKb2huIEJpbGxla2VucyBDb25zdWx0YW5jeTCCAaIwDQYJKoZI
# hvcNAQEBBQADggGPADCCAYoCggGBAMslntDbSQwHZXwFhmibivbnd0Qfn6sqe/6f
# os3pKzKxEsR907RkDMet2x6RRg3eJkiIr3TFPwqBooyXXgK3zxxpyhGOcuIqyM9J
# 28DVf4kUyZHsjGO/8HFjrr3K1hABNUszP0o7H3o6J31eqV1UmCXYhQlNoW9FOmRC
# 1amlquBmh7w4EKYEytqdmdOBavAD5Xq4vLPxNP6kyA+B2YTtk/xM27TghtbwFGKn
# u9Vwnm7dFcpLxans4ONt2OxDQOMA5NwgcUv/YTpjhq9qoz6ivG55NRJGNvUXsM3w
# 2o7dR6Xh4MuEGrTSrOWGg2A5EcLH1XqQtkF5cZnAPM8W/9HUp8ggornWnFVQ9/6M
# ga+ermy5wy5XrmQpN+x3u6tit7xlHk1Hc+4XY4a4ie3BPXG2PhJhmZAn4ebNSBwN
# Hh8z7WTT9X9OFERepGSytZVeEP7hgyptSLcuhpwWeR4QdBb7dV++4p3PsAUQVHFp
# wkSbrRTv4EiJ0Lcz9P1HPGFoHiFAQQIDAQABo4IBeDCCAXQwDAYDVR0TAQH/BAIw
# ADA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY2NzY2EyMDIxLmNybC5jZXJ0dW0u
# cGwvY2NzY2EyMDIxLmNybDBzBggrBgEFBQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0
# dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0dW0uY29tMDUGCCsGAQUFBzAChilodHRw
# Oi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNlcjAfBgNVHSMEGDAW
# gBTddF1MANt7n6B0yrFu9zzAMsBwzTAdBgNVHQ4EFgQUO6KtBpOBgmrlANVAnyiQ
# C6W6lJwwSwYDVR0gBEQwQjAIBgZngQwBBAEwNgYLKoRoAYb2dwIFAQQwJzAlBggr
# BgEFBQcCARYZaHR0cHM6Ly93d3cuY2VydHVtLnBsL0NQUzATBgNVHSUEDDAKBggr
# BgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAEQsN8wg
# PMdWVkwHPPTN+jKpdns5AKVFjcn00psf2NGVVgWWNQBIQc9lEuTBWb54IK6Ga3hx
# QRZfnPNo5HGl73YLmFgdFQrFzZ1lnaMdIcyh8LTWv6+XNWfoyCM9wCp4zMIDPOs8
# LKSMQqA/wRgqiACWnOS4a6fyd5GUIAm4CuaptpFYr90l4Dn/wAdXOdY32UhgzmSu
# xpUbhD8gVJUaBNVmQaRqeU8y49MxiVrUKJXde1BCrtR9awXbqembc7Nqvmi60tYK
# lD27hlpKtj6eGPjkht0hHEsgzU0Fxw7ZJghYG2wXfpF2ziN893ak9Mi/1dmCNmor
# GOnybKYfT6ff6YTCDDNkod4egcMZdOSv+/Qv+HAeIgEvrxE9QsGlzTwbRtbm6gwY
# YcVBs/SsVUdBn/TSB35MMxRhHE5iC3aUTkDbceo/XP3uFhVL4g2JZHpFfCSu2TQr
# rzRn2sn07jfMvzeHArCOJgBW1gPqR3WrJ4hUxL06Rbg1gs9tU5HGGz9KNQMfQFQ7
# 0Wz7UIhezGcFcRfkIfSkMmQYYpsc7rfzj+z0ThfDVzzJr2dMOFsMlfj1T6l22GBq
# 9XQx0A4lcc5Fl9pRxbOuHHWFqIBD/BCEhwniOCySzqENd2N+oz8znKooSISStnkN
# aYXt6xblJF2dx9Dn89FK7d1IquNxOwt0tI5dMIIGYjCCBMqgAwIBAgIRAKQpO24e
# 3denNAiHrXpOtyQwDQYJKoZIhvcNAQEMBQAwVTELMAkGA1UEBhMCR0IxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGlt
# ZSBTdGFtcGluZyBDQSBSMzYwHhcNMjUwMzI3MDAwMDAwWhcNMzYwMzIxMjM1OTU5
# WjByMQswCQYDVQQGEwJHQjEXMBUGA1UECBMOV2VzdCBZb3Jrc2hpcmUxGDAWBgNV
# BAoTD1NlY3RpZ28gTGltaXRlZDEwMC4GA1UEAxMnU2VjdGlnbyBQdWJsaWMgVGlt
# ZSBTdGFtcGluZyBTaWduZXIgUjM2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEA04SV9G6kU3jyPRBLeBIHPNyUgVNnYayfsGOyYEXrn3+SkDYTLs1crcw/
# ol2swE1TzB2aR/5JIjKNf75QBha2Ddj+4NEPKDxHEd4dEn7RTWMcTIfm492TW22I
# 8LfH+A7Ehz0/safc6BbsNBzjHTt7FngNfhfJoYOrkugSaT8F0IzUh6VUwoHdYDpi
# ln9dh0n0m545d5A5tJD92iFAIbKHQWGbCQNYplqpAFasHBn77OqW37P9BhOASdmj
# p3IijYiFdcA0WQIe60vzvrk0HG+iVcwVZjz+t5OcXGTcxqOAzk1frDNZ1aw8nFhG
# EvG0ktJQknnJZE3D40GofV7O8WzgaAnZmoUn4PCpvH36vD4XaAF2CjiPsJWiY/j2
# xLsJuqx3JtuI4akH0MmGzlBUylhXvdNVXcjAuIEcEQKtOBR9lU4wXQpISrbOT8ux
# +96GzBq8TdbhoFcmYaOBZKlwPP7pOp5Mzx/UMhyBA93PQhiCdPfIVOCINsUY4U23
# p4KJ3F1HqP3H6Slw3lHACnLilGETXRg5X/Fp8G8qlG5Y+M49ZEGUp2bneRLZoyHT
# yynHvFISpefhBCV0KdRZHPcuSL5OAGWnBjAlRtHvsMBrI3AAA0Tu1oGvPa/4yeei
# Ayu+9y3SLC98gDVbySnXnkujjhIh+oaatsk/oyf5R2vcxHahajMCAwEAAaOCAY4w
# ggGKMB8GA1UdIwQYMBaAFF9Y7UwxeqJhQo1SgLqzYZcZojKbMB0GA1UdDgQWBBSI
# YYyhKjdkgShgoZsx0Iz9LALOTzAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIw
# ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDBKBgNVHSAEQzBBMDUGDCsGAQQBsjEB
# AgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzAIBgZn
# gQwBBAIwSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2NybC5zZWN0aWdvLmNvbS9T
# ZWN0aWdvUHVibGljVGltZVN0YW1waW5nQ0FSMzYuY3JsMHoGCCsGAQUFBwEBBG4w
# bDBFBggrBgEFBQcwAoY5aHR0cDovL2NydC5zZWN0aWdvLmNvbS9TZWN0aWdvUHVi
# bGljVGltZVN0YW1waW5nQ0FSMzYuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
# cC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAYEAAoE+pIZyUSH5ZakuPVKK
# 4eWbzEsTRJOEjbIu6r7vmzXXLpJx4FyGmcqnFZoa1dzx3JrUCrdG5b//LfAxOGy9
# Ph9JtrYChJaVHrusDh9NgYwiGDOhyyJ2zRy3+kdqhwtUlLCdNjFjakTSE+hkC9F5
# ty1uxOoQ2ZkfI5WM4WXA3ZHcNHB4V42zi7Jk3ktEnkSdViVxM6rduXW0jmmiu71Z
# pBFZDh7Kdens+PQXPgMqvzodgQJEkxaION5XRCoBxAwWwiMm2thPDuZTzWp/gUFz
# i7izCmEt4pE3Kf0MOt3ccgwn4Kl2FIcQaV55nkjv1gODcHcD9+ZVjYZoyKTVWb4V
# qMQy/j8Q3aaYd/jOQ66Fhk3NWbg2tYl5jhQCuIsE55Vg4N0DUbEWvXJxtxQQaVR5
# xzhEI+BjJKzh3TQ026JxHhr2fuJ0mV68AluFr9qshgwS5SpN5FFtaSEnAwqZv3IS
# +mlG50rK7W3qXbWwi4hmpylUfygtYLEdLQukNEX1jiOKMIIGgjCCBGqgAwIBAgIQ
# NsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYD
# VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBS
# U0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEwMzIyMDAwMDAwWhcNMzgw
# MTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1p
# dGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3Qg
# UjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiJ3YuUVnnR3d6Lkm
# gZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizICFNeINCSJS+lV1ipnW5ihkQy
# C0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ2aHHsPHggGsCi7uE0awqKggE
# /LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2GAXsePHi+/JUNAax3kpqstbl3
# vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WABKf+Gnoi4cmisS7oSimgHUI0
# Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/FDDrOJ3rWqauUP8hsokDoI7D/
# yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop06m7NUNHdlTDEMovXAIDGAvYy
# nPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE9vtiTMzz/o2dYfdP0KWZwZIX
# bYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj3NgevsAsvxsAnI8Oa5s2oy25
# qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoqzEIbW3q0b2iPuWLA911cRxgY
# 5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7jk27MAe145GWxK4O3m3gEFEI
# kv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYwggESMB8GA1UdIwQYMBaAFFN5
# v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2d2rdP/0BE/8WoWyCAi/QCj0U
# JTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zATBgNVHSUEDDAKBggr
# BgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0
# cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25B
# dXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDov
# L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEADr5lQe1oRLjl
# ocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARaAdAxUeiX1ktLJ3+lgxtoLQhn
# 5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsDLRhsyeIiJsms9yAWnvdYOdEM
# q1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxVf7nkNtUjPfcxuFtrQdRMRi/f
# InV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJyz9gvEOcF1VWXG8OMeM7Vy7B
# s6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGinCwKPyFO7ApcmVJOtlw9FVJxw
# /mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp4pmAWjR+Pb/SIduPnmFzbSN/
# G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/UYOy8k60mKcmaAZsEVkhOFuoj
# 4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SBIkiRzfPfS9T+JesylbHa1LtR
# V9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14suVFBmbzrt5V5cQPnwtd3UOTpS
# 9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HHFHeRPRYrMBKjkb8/IN7Po0d0
# hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8wgga5MIIEoaADAgECAhEAmaOACiZVO2Wr
# 3G6EprPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoT
# GVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0
# aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0
# d29yayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkG
# A1UEBhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIG
# A1UEAxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTm
# flN4mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbv
# IKck6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27a8RdrT1PH9ud0IF+njvM
# k2xqbNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzu
# C8KNqfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD
# 2bSlbfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QO
# ZgYqVWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkc
# SkAExzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDY
# O962WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq
# 1UiYPIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBS
# ND36E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JS
# qPECAwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA
# 23ufoHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79
# MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAn
# MCWgI6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUF
# BwEBBGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNv
# bTAyBggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNh
# Mi5jZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93
# d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q
# 7EP89DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARMgwRzLRUStJl490L94C9L
# GF3vjzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLU
# UAsO0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXd
# d3x2mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rN
# LqyA3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMc
# ZNhpn8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vU
# BfSouCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTp
# V2Na3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGg
# K2W1eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2yw
# zOr+tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602
# NCmvO1nm+/80nLy5r0AZvCQxaQ4xggXDMIIFvwIBATBqMFYxCzAJBgNVBAYTAlBM
# MSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0Nl
# cnR1bSBDb2RlIFNpZ25pbmcgMjAyMSBDQQIQCDJPnbfakW9j5PKjPF5dUTANBglg
# hkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3
# DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV
# MC8GCSqGSIb3DQEJBDEiBCAyXPPkRQ1/qWHsSmrc4nAlHM53Zh5wORiWlmjcgBNq
# fzANBgkqhkiG9w0BAQEFAASCAYASNXHg4FSbysak7iJBtsN2zK1BaOseEGy2OKU1
# i8m+BMOC2xdTMN73pAlgmdkqXGY9Ci27k71hOJf8/7Q7fP4Bi33M0uEFJiAaHGys
# qrjlMk7amT/qfKVMS6nEWDujC7CLAvUpg27IUYzkZlVIdTjdrXSNZoPtIQFUAC35
# rDi2bNIxUhZMHthYQc4ts5+6nSt+kjmrEJU070aNJ9JUxTy7KKFsV53KVycmC9k2
# nBoLCivvGt2y1JClbrVfwtA9rjF7vYgdyUWBtiTQjF7+F9RWsILh0yzCz0aJmvKq
# hmtxP6MuvWsX+DxpCNYahtnzNCavA1qOrnbE6g86Fw3GUwlUfRWvpc+HWgMmKk8e
# ak9g0zcO1RUckSrwr17liWNc+++8X7ziahNDV+0WAztb3EetG2+3zTPAJ4mIhIyM
# z9hwzPex4Z8Q6qGcDICABnDg0GV6BYvw1XBi6bSQrFX1D0V3pVsd0aTes9JlF4qU
# XeNZkJpS069DLObb8G07ASq/JDqhggMjMIIDHwYJKoZIhvcNAQkGMYIDEDCCAwwC
# AQEwajBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMSww
# KgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIENBIFIzNgIRAKQp
# O24e3denNAiHrXpOtyQwDQYJYIZIAWUDBAICBQCgeTAYBgkqhkiG9w0BCQMxCwYJ
# KoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNjA2MTYxODMxNTdaMD8GCSqGSIb3
# DQEJBDEyBDBPitiA5REgi3Qazwf7R54Li9l3w+VIr5ijqyw5Sd6zbc/pMKlEGzOp
# KZ+o8E6Bad4wDQYJKoZIhvcNAQEBBQAEggIAoHSjFFITh2g3SrIfLOwN+DVtnK+3
# m+478f6EHTiX/Gi8MVCj/VKEyQsOwWeNNlAVgzDThpex66C6rvN1TAs8XyLJpeR3
# zD0TGyWlLdJ4LkmqmideBCdlwAbty8KIBSCROYKELP3wB4BfE4/h4AcSjqhoTKE+
# xQCtqIQ06bV9fyHj6ukHYSVvrPhBEEpEuJuj532IG/G1WceKlcVUtz5kyORRfysY
# hlOEVO7UcEulfdJ4a33Xx/pjwIVBD9i7bodBvlPA+1hnzQ9iV1z1U4g+35DMvM7S
# NmNzkZp9W6euocZVf2rCz2S+Y9VyNkjJR78oW7nKOrNfDH3tbyGOz+BN1OIe9TEZ
# TuiXyPbN8qxrTspcRDitauJ4YXnsGnd2dWesIsLtsKmt05RzZU7j/EB5mU5LMooM
# /1vfuEXouGUECk6kZ4PGdn4OCPsDIwLgZWZ/f8Svf8pp9rhfZo6YGPD9z6X+fTNu
# iR/3NnAVFMKdpde7E1lBRuBzw9OlQsons1bqw222mNiGAsYOo2r9CTPck4mtC7bN
# YkgRSrk+Sq83+ayF7T6oc4hrLSB/wwn0Q7hL7gieHian6srpuC5Cz590LZtgdfEa
# 64vkzDXm9GvI77dDKkeYvDhdlRHSFJeQedAJtcIWMk8Vxwv3ZPa1T7E8pflQ1Cvd
# Rmy26jov4XOT/A8=
# SIG # End signature block