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 |