Classes/Public/DscConfigurationFileParser.ps1
<#
Copyright (c) 2018-2021 VMware, Inc. All rights reserved The BSD-2 license (the "License") set forth below applies to all parts of the Desired State Configuration Resources for VMware project. You may not use this file except in compliance with the License. BSD-2 License Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #> class DscConfigurationFileParser { <# .DESCRIPTION Parses the PowerShell script file containing the DSC Configurations by performing the following steps: 1. Extracts each DSC Configuration defined in the file and converts it into a DscConfigurationBlock object, which contains the following information: the name of the DSC Configuration, the configurationData defined as hashtable which is needed if the DSC Configuration retrieves data from it. Also the object contains the text information for the DSC Configuration: the start and end line of the DSC Configuration in the file and also the DSC Configuration as text. This whole information is later passed to the DscConfigurationCompiler which compiles it and produces the VmwDscConfiguration object. 2. Invokes all non DSC Configuration lines in the provided file and this ways makes them available in the current scope. This way for example if there is configurationData defined, the hashtable will be available in memory for the DscConfigurationCompiler to produce the VmwDscConfiguration object. 3. If the Parameters hashtable is passed, invokes all non DSC Configuration lines with the specified PowerShell script file parameters. #> [DscConfigurationBlock[]] ParseDscConfigurationFile([string] $dscConfigurationFilePath, [System.Collections.Hashtable] $parameters) { $dscConfigurationFileContent = Get-Content -Path $dscConfigurationFilePath $dscConfigurationFileContentRaw = Get-Content -Path $dscConfigurationFilePath -Raw $tokens = [System.Management.Automation.PSParser]::Tokenize($dscConfigurationFileContentRaw, [ref]$null) $dscConfigurations = $this.GetAllDscConfigurations($tokens) $scriptContent = [System.Text.StringBuilder]::new() $dscConfigurationsContent = [System.Text.StringBuilder]::new() $j = 0 for ($i = 0; $i -lt $dscConfigurationFileContent.Length; $i++) { if ($i -lt ($dscConfigurations[$j].Extent.StartLine - 1) -or $i -gt $dscConfigurations[$j].Extent.EndLine) { $scriptContent.AppendLine($dscConfigurationFileContent[$i]) | Out-Null } else { $dscConfigurationsContent.AppendLine($dscConfigurationFileContent[$i]) | Out-Null } if ($i + 1 -gt $dscConfigurations[$j].Extent.EndLine - 1) { $dscConfigurationsContentAsString = $dscConfigurationsContent.ToString() # Only the content of the DSC Configuration is needed so we trim the Configuration keyword and the name of the DSC Configuration. $dscConfigurationsContentAsString = $dscConfigurationsContentAsString.TrimStart('Configuration').TrimStart() $dscConfigurationsContentAsString = $dscConfigurationsContentAsString.TrimStart($dscConfigurations[$j].Name).TrimStart() $dscConfigurationsContentAsString = $dscConfigurationsContentAsString.TrimEnd() # After the text of the DSC Configuration is retrieved, we clear the content, so that we can start # with an empty string for the next DSC Configuration in the file. $dscConfigurations[$j].Extent.Text = $dscConfigurationsContentAsString if ($j -lt ($dscConfigurations.Length - 1)) { $dscConfigurationsContent = [System.Text.StringBuilder]::new() $j++ } } } # The ScriptBlock is invoked for all non DSC Configurations lines with the PowerShell script file parameters if specified. $scriptContentAsString = $scriptContent.ToString() $scriptBlock = [ScriptBlock]::Create($scriptContentAsString) if ($parameters.Count -gt 0) { $invokeParameters = $this.GetOrderedScriptParameters($tokens, $parameters) $scriptBlock.Invoke($invokeParameters) } else { $scriptBlock.Invoke() } <# In the file there should be only one configurationData hashtable for all defined DSC Configurations. If the user wants to use different configurationData hashtables, the DSC Configurations should be defined in separate files as there is no way to know which hashtable is for which DSC Configuration. So the assumption is that there should be only one hashtable defined with the AllNodes key in the file. #> $configurationData = Get-Variable | Where-Object -FilterScript { $null -ne $_.Value -and $_.Value.GetType() -eq [System.Collections.Hashtable] -and $_.Value.ContainsKey('AllNodes') } | Select-Object -First 1 if ($null -ne $configurationData -and $scriptContentAsString -Match $configurationData.Name) { foreach ($dscConfiguration in $dscConfigurations) { $dscConfiguration.ConfigurationData = $configurationData.Value } } return $dscConfigurations } <# .DESCRIPTION Extracts each DSC Configuration defined in the file and converts it into a DscConfigurationBlock object. The content of the file is passed as an array of PSTokens from which each DSC Configuration info is extracted. #> hidden [DscConfigurationBlock[]] GetAllDscConfigurations([System.Collections.ObjectModel.Collection[System.Management.Automation.PSToken]] $tokens) { $dscConfigurations = @() for ($i = 0; $i -lt $tokens.Count; $i++) { $token = $tokens[$i] if ($token.Type -eq 'Keyword' -and $token.Content -eq 'Configuration') { $dscConfiguration = [DscConfigurationBlock]::new() $dscConfiguration.Extent = [DscConfigurationBlockExtent]::new() $dscConfiguration.Extent.StartLine = $token.StartLine $j = $i + 1 $dscConfigurationBlockReached = $false $endOfDscConfigurationReached = $false $bracketsCounter = 0 while ($j -lt $tokens.Count -and !$endOfDscConfigurationReached) { $dscConfigurationToken = $tokens[$j] # The name of the DSC Configuration is in the token of type CommandArgument. if ($null -eq $dscConfiguration.Name -and $dscConfigurationToken.Type -eq 'CommandArgument') { $dscConfiguration.Name = $dscConfigurationToken.Content } if ($dscConfigurationToken.Type -eq 'GroupStart' -and $dscConfigurationToken.Content -eq '{') { if (!$dscConfigurationBlockReached) { $dscConfigurationBlockReached = $true } $bracketsCounter++ } if ($dscConfigurationToken.Type -eq 'GroupEnd' -and $dscConfigurationToken.Content -eq '}') { $bracketsCounter-- } # bracketsCounter equal to zero indicates that the closing bracket of the current DSC Configuration was reached. if ($dscConfigurationBlockReached -and $bracketsCounter -eq 0) { $dscConfiguration.Extent.EndLine = $dscConfigurationToken.EndLine $endOfDscConfigurationReached = $true } <# The first index should be updated at each step, so after the nested loop finishes and the DSC Configuration block is populated with the text of the DSC Configuration, the first loop continues from the end of the current DSC Configuration. #> $j++ $i = $j } $dscConfigurations += $dscConfiguration } } return $dscConfigurations } <# .DESCRIPTION Orders the specified script parameters in the correct order. 1. Extracts each script parameter in an array in the correct order when parsing the script file. 2. Checks all passed parameters and orders the values in the correct parameter order. 3. If a parameter is not passed, $null is added to the array for the specified parameter. #> hidden [array] GetOrderedScriptParameters( [System.Collections.ObjectModel.Collection[System.Management.Automation.PSToken]] $tokens, [System.Collections.Hashtable] $parameters) { $scriptParameters = @() $invokeParameters = @() $i = 0 $endOfParamsReached = $false while ($i -lt $tokens.Count -and !$endOfParamsReached) { $token = $tokens[$i] if ($token.Type -eq 'Keyword' -and $token.Content -eq 'Param') { $j = $i + 1 $paramsBlockReached = $false $bracketsCounter = 0 $attributeBracketsCounter = 0 while ($j -lt $tokens.Count -and !$endOfParamsReached) { $paramsToken = $tokens[$j] if ($paramsToken.Type -eq 'GroupStart' -and $paramsToken.Content -eq '(') { if (!$paramsBlockReached) { $paramsBlockReached = $true } $bracketsCounter++ } if ($paramsToken.Type -eq 'GroupEnd' -and $paramsToken.Content -eq ')') { $bracketsCounter-- } if ($paramsToken.Type -eq 'Operator' -and $paramsToken.Content -eq '[') { $attributeBracketsCounter++ } if ($paramsToken.Type -eq 'Operator' -and $paramsToken.Content -eq ']') { $attributeBracketsCounter-- } <# The additional check is needed to ignore the Parameter Attributes, for example: [Parameter(Mandatory = $true)] where true is also a variable. #> if ($paramsToken.Type -eq 'Variable' -and $attributeBracketsCounter -eq 0) { $scriptParameters += $paramsToken.Content } # bracketsCounter equal to zero indicates that the closing bracket of the Param was reached. if ($paramsBlockReached -and $bracketsCounter -eq 0) { $endOfParamsReached = $true } $j++ $i = $j } } $i++ } foreach ($scriptParameter in $scriptParameters) { $parameterName = $parameters.Keys | Where-Object -FilterScript { $_ -eq $scriptParameter } if ($null -ne $parameterName) { if ($parameters.$parameterName -is [array]) { <# Arrays should be passed with the below syntax, otherwise only the first element of the array is passed to the script. #> $invokeParameters += (, $parameters.$parameterName) } else { $invokeParameters += $parameters.$parameterName } } else { <# If not specified, the parameter shoule be added with $null value to keep the order of the parameters. #> $invokeParameters += $null } } return $invokeParameters } } # SIG # Begin signature block # MIIgFgYJKoZIhvcNAQcCoIIgBzCCIAMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCn7rp1VVymnTRP # JsLIR7druOaThTKQixOro80WrfuPKqCCD8swggTMMIIDtKADAgECAhBdqtQcwalQ # C13tonk09GI7MA0GCSqGSIb3DQEBCwUAMH8xCzAJBgNVBAYTAlVTMR0wGwYDVQQK # ExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3Qg # TmV0d29yazEwMC4GA1UEAxMnU3ltYW50ZWMgQ2xhc3MgMyBTSEEyNTYgQ29kZSBT # aWduaW5nIENBMB4XDTE4MDgxMzAwMDAwMFoXDTIxMDkxMTIzNTk1OVowZDELMAkG # A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0 # bzEVMBMGA1UECgwMVk13YXJlLCBJbmMuMRUwEwYDVQQDDAxWTXdhcmUsIEluYy4w # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuswYfqnKot0mNu9VhCCCR # vVcCrxoSdB6G30MlukAVxgQ8qTyJwr7IVBJXEKJYpzv63/iDYiNAY3MOW+Pb4qGI # bNpafqxc2WLW17vtQO3QZwscIVRapLV1xFpwuxJ4LYdsxHPZaGq9rOPBOKqTP7Jy # KQxE/1ysjzacA4NXHORf2iars70VpZRksBzkniDmurvwCkjtof+5krxXd9XSDEFZ # 9oxeUGUOBCvSLwOOuBkWPlvCnzEqMUeSoXJavl1QSJvUOOQeoKUHRycc54S6Lern # 2ddmdUDPwjD2cQ3PL8cgVqTsjRGDrCgOT7GwShW3EsRsOwc7o5nsiqg/x7ZmFpSJ # AgMBAAGjggFdMIIBWTAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDArBgNVHR8E # JDAiMCCgHqAchhpodHRwOi8vc3Yuc3ltY2IuY29tL3N2LmNybDBhBgNVHSAEWjBY # MFYGBmeBDAEEATBMMCMGCCsGAQUFBwIBFhdodHRwczovL2Quc3ltY2IuY29tL2Nw # czAlBggrBgEFBQcCAjAZDBdodHRwczovL2Quc3ltY2IuY29tL3JwYTATBgNVHSUE # DDAKBggrBgEFBQcDAzBXBggrBgEFBQcBAQRLMEkwHwYIKwYBBQUHMAGGE2h0dHA6 # Ly9zdi5zeW1jZC5jb20wJgYIKwYBBQUHMAKGGmh0dHA6Ly9zdi5zeW1jYi5jb20v # c3YuY3J0MB8GA1UdIwQYMBaAFJY7U/B5M5evfYPvLivMyreGHnJmMB0GA1UdDgQW # BBTVp9RQKpAUKYYLZ70Ta983qBUJ1TANBgkqhkiG9w0BAQsFAAOCAQEAlnsx3io+ # W/9i0QtDDhosvG+zTubTNCPtyYpv59Nhi81M0GbGOPNO3kVavCpBA11Enf0CZuEq # f/ctbzYlMRONwQtGZ0GexfD/RhaORSKib/ACt70siKYBHyTL1jmHfIfi2yajKkMx # UrPM9nHjKeagXTCGthD/kYW6o7YKKcD7kQUyBhofimeSgumQlm12KSmkW0cHwSSX # TUNWtshVz+74EcnZtGFI6bwYmhvnTp05hWJ8EU2Y1LdBwgTaRTxlSDP9JK+e63vm # SXElMqnn1DDXABT5RW8lNt6g9P09a2J8p63JGgwMBhmnatw7yrMm5EAo+K6gVliJ # LUMlTW3O09MbDTCCBVkwggRBoAMCAQICED141/l2SWCyYX308B7KhiowDQYJKoZI # hvcNAQELBQAwgcoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5j # LjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMp # IDIwMDYgVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFF # MEMGA1UEAxM8VmVyaVNpZ24gQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZp # Y2F0aW9uIEF1dGhvcml0eSAtIEc1MB4XDTEzMTIxMDAwMDAwMFoXDTIzMTIwOTIz # NTk1OVowfzELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0 # aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTAwLgYDVQQDEydT # eW1hbnRlYyBDbGFzcyAzIFNIQTI1NiBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqG # SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXgx4AFq8ssdIIxNdok1FgHnH24ke021hN # I2JqtL9aG1H3ow0Yd2i72DarLyFQ2p7z518nTgvCl8gJcJOp2lwNTqQNkaC07BTO # kXJULs6j20TpUhs/QTzKSuSqwOg5q1PMIdDMz3+b5sLMWGqCFe49Ns8cxZcHJI7x # e74xLT1u3LWZQp9LYZVfHHDuF33bi+VhiXjHaBuvEXgamK7EVUdT2bMy1qEORkDF # l5KK0VOnmVuFNVfT6pNiYSAKxzB3JBFNYoO2untogjHuZcrf+dWNsjXcjCtvanJc # YISc8gyUXsBWUgBIzNP4pX3eL9cT5DiohNVGuBOGwhud6lo43ZvbAgMBAAGjggGD # MIIBfzAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9zMi5zeW1j # Yi5jb20wEgYDVR0TAQH/BAgwBgEB/wIBADBsBgNVHSAEZTBjMGEGC2CGSAGG+EUB # BxcDMFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20vY3BzMCgG # CCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20vcnBhMDAGA1UdHwQp # MCcwJaAjoCGGH2h0dHA6Ly9zMS5zeW1jYi5jb20vcGNhMy1nNS5jcmwwHQYDVR0l # BBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIBBjApBgNVHREE # IjAgpB4wHDEaMBgGA1UEAxMRU3ltYW50ZWNQS0ktMS01NjcwHQYDVR0OBBYEFJY7 # U/B5M5evfYPvLivMyreGHnJmMB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ80M5+gKv # MzEzMA0GCSqGSIb3DQEBCwUAA4IBAQAThRoeaak396C9pK9+HWFT/p2MXgymdR54 # FyPd/ewaA1U5+3GVx2Vap44w0kRaYdtwb9ohBcIuc7pJ8dGT/l3JzV4D4ImeP3Qe # 1/c4i6nWz7s1LzNYqJJW0chNO4LmeYQW/CiwsUfzHaI+7ofZpn+kVqU/rYQuKd58 # vKiqoz0EAeq6k6IOUCIpF0yH5DoRX9akJYmbBWsvtMkBTCd7C6wZBSKgYBU/2sn7 # TUyP+3Jnd/0nlMe6NQ6ISf6N/SivShK9DbOXBd5EDBX6NisD3MFQAfGhEV0U5eK9 # J0tUviuEXg+mw3QFCu+Xw4kisR93873NQ9TxTKk/tYuEr2Ty0BQhMIIFmjCCA4Kg # AwIBAgIKYRmT5AAAAAAAHDANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNyb3NvZnQgQ29kZSBW # ZXJpZmljYXRpb24gUm9vdDAeFw0xMTAyMjIxOTI1MTdaFw0yMTAyMjIxOTM1MTda # MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNV # BAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZl # cmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMT # PFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBB # dXRob3JpdHkgLSBHNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8k # CAgpejWeYAyq50s7Ttx8vDxFHLsr4P4pAvlXCKNkhRUn9fGtyDGJXSLoKqqmQrOP # +LlVt7G3S7P+j34HV+zvQ9tmYhVhz2ANpNje+ODDYgg9VBPrScpZVIUm5SuPG5/r # 9aGRwjNJ2ENjalJL0o/ocFFN0Ylpe8dw9rPcEnTbe11LVtOWvxV3obD0oiXyrxyS # Zxjl9AYE75C55ADk3Tq1Gf8CuvQ87uCL6zeL7PTXrPL28D2v3XWRMxkdHEDLdCQZ # IZPZFP6sKlLHj9UESeSNY0eIPGmDy/5HvSt+T8WVrg6d1NFDwGdz4xQIfuU/n3O4 # MwrPXT80h5aK7lPoJRUCAwEAAaOByzCByDARBgNVHSAECjAIMAYGBFUdIAAwDwYD # VR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAYYwHQYDVR0OBBYEFH/TZafC3ey78DAJ # 80M5+gKvMzEzMB8GA1UdIwQYMBaAFGL7CiFbf0NuEdoJVFBr9dKWcfGeMFUGA1Ud # HwROMEwwSqBIoEaGRGh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By # b2R1Y3RzL01pY3Jvc29mdENvZGVWZXJpZlJvb3QuY3JsMA0GCSqGSIb3DQEBBQUA # A4ICAQCBKoIWjDRnK+UD6zR7jKKjUIr0VYbxHoyOrn3uAxnOcpUYSK1iEf0g/T9H # BgFa4uBvjBUsTjxqUGwLNqPPeg2cQrxc+BnVYONp5uIjQWeMaIN2K4+Toyq1f75Z # +6nJsiaPyqLzghuYPpGVJ5eGYe5bXQdrzYao4mWAqOIV4rK+IwVqugzzR5NNrKSM # B3k5wGESOgUNiaPsn1eJhPvsynxHZhSR2LYPGV3muEqsvEfIcUOW5jIgpdx3hv08 # 44tx23ubA/y3HTJk6xZSoEOj+i6tWZJOfMfyM0JIOFE6fDjHGyQiKEAeGkYfF9sY # 9/AnNWy4Y9nNuWRdK6Ve78YptPLH+CHMBLpX/QG2q8Zn+efTmX/09SL6cvX9/zoc # Qjqh+YAYpe6NHNRmnkUB/qru//sXjzD38c0pxZ3stdVJAD2FuMu7kzonaknAMK5m # yfcjKDJ2+aSDVshIzlqWqqDMDMR/tI6Xr23jVCfDn4bA1uRzCJcF29BUYl4DSMLV # n3+nZozQnbBP1NOYX0t6yX+yKVLQEoDHD1S2HmfNxqBsEQOE00h15yr+sDtuCjqm # a3aZBaPxd2hhMxRHBvxTf1K9khRcSiRqZ4yvjZCq0PZ5IRuTJnzDzh69iDiSrkXG # GWpJULMF+K5ZN4pqJQOUsVmBUOi6g4C3IzX0drlnHVkYrSCNlDGCD6Ewgg+dAgEB # MIGTMH8xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlv # bjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEwMC4GA1UEAxMnU3lt # YW50ZWMgQ2xhc3MgMyBTSEEyNTYgQ29kZSBTaWduaW5nIENBAhBdqtQcwalQC13t # onk09GI7MA0GCWCGSAFlAwQCAQUAoIGWMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3 # AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCoGCisGAQQBgjcCAQwx # HDAaoRiAFmh0dHA6Ly93d3cudm13YXJlLmNvbS8wLwYJKoZIhvcNAQkEMSIEIJqs # bMII43fUM6w95DuZ0bbvqa/n4ZgkeSMOt9kVzFTdMA0GCSqGSIb3DQEBAQUABIIB # AHXWNBHLm1khsofHZmj6uyYgh1toRUX3MlrP1uWEKGJ0X6hLc7LheSuAXdUpgBAx # ca1NqEfXQ2gtovVQQg1ZM5uQVuWGvcC8bTUgCfs3Zwxes9aQnN0NhNlNuusypL2F # FkhwWd/lb+YNUrNTOPw9CgqGpaiYDjR+xMHsBj745tDc1HlUlDwQi7qw/SN/mVgB # b9+fE8rXnIElknF52zS6jh5UJsgO3rBPtFFNu0EOzuzjtalqbIJJCQm3oCU8f/7o # LvkAHT2MZQ6zMBcRpROqrWP2zCXT6WRPgZRSshHdOpsh0d3IKxkIrp7WnGzb+r/W # tUdfIGOCHvpZgKO4+Huxygihgg1FMIINQQYKKwYBBAGCNwMDATGCDTEwgg0tBgkq # hkiG9w0BBwKggg0eMIINGgIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3DQEJ # EAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgCJeGJCe3 # x506yP/ExHHW/7ZDKbdkQg9ev5/kzI5nStUCEQCJrSZ3OtYJQeHbnerEHbXPGA8y # MDIxMDIyNDA5MDA1NVqgggo3MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw # 3TANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl # cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdp # Q2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAw # MDAwMFoXDTMxMDEwNjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp # Z2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCC # ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HX # OhFCvQp1dU2UtAxQtSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB # +in43Stwhd4CGPN4bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPh # Lxs6t2HWc+xObTOKfF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizV # I9r0TXhG4wODMSlKXAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKb # aYK02/xWVLwfoYervnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQEC # AwEAAaOCAbgwggG0MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1Ud # JQEB/wQMMAoGCCsGAQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYI # KwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAW # gBT0tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ2 # 3eNqerwwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29t # L3NoYTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0 # LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYB # BQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0 # cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRp # bWVzdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucg # Do5nRv1CclF0CiNHo6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1p # pA/2uHDPYuj1UUp4eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18j # UmmDgvoaU+2QzI2hF3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFc # z8QRcucbZEnYIpp1FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub # 9y4bTxMd90oNcX6Xt/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpb # h6aKLzCCBTEwggQZoAMCAQICEAqhJdbWMht+QeQF2jaXwhUwDQYJKoZIhvcNAQEL # BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ # RCBSb290IENBMB4XDTE2MDEwNzEyMDAwMFoXDTMxMDEwNzEyMDAwMFowcjELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRp # bWVzdGFtcGluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3Q # Mu5LzY9/3am6gpnFOVQoV7YjSsQOB0UzURB90Pl9TWh+57ag9I2ziOSXv2MhkJi/ # E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+X2U/4Jvr40ZHBhpVfgsnfsCi9aDg3iI/ # Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPuXciaC1TjqAlxa+DPIhAPdc9xck4Krd9A # Oly3UeGheRTGTSQjMF287DxgaqwvB8z98OpH2YhQXv1mblZhJymJhFHmgudGUP2U # Kiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQhBlyF/EXBu89zdZN7wZC/aJTKk+FHcQd # PK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4wggHKMB0GA1UdDgQWBBT0tuEgHf4prtLk # YaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzASBgNV # HRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF # BQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig # NoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v # dENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # QXNzdXJlZElEUm9vdENBLmNybDBQBgNVHSAESTBHMDgGCmCGSAGG/WwAAgQwKjAo # BggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzALBglghkgB # hv1sBwEwDQYJKoZIhvcNAQELBQADggEBAHGVEulRh1Zpze/d2nyqY3qzeM8GN0CE # 70uEv8rPAwL9xafDDiBCLK938ysfDCFaKrcFNB1qrpn4J6JmvwmqYN92pDqTD/iy # 0dh8GWLoXoIlHsS6HHssIeLWWywUNUMEaLLbdQLgcseY1jxk5R9IEBhfiThhTWJG # JIdjjJFSLK8pieV4H9YLFKWA1xJHcLN11ZOFk362kmf7U2GJqPVrlsD0WGkNfMgB # sbkodbeZY4UijGHKeZR+WfyMD+NvtQEmtmyl7odRIeRYYJu6DC0rbaLEfrvEJStH # Agh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR79VYzIi8iNrJLokqV2PWmjlIxggJNMIIC # SQIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEy # IEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDdMA0G # CWCGSAFlAwQCAQUAoIGYMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkq # hkiG9w0BCQUxDxcNMjEwMjI0MDkwMDU1WjArBgsqhkiG9w0BCRACDDEcMBowGDAW # BBTh14Ko4ZG+72vKFpG1qrSUpiSb8zAvBgkqhkiG9w0BCQQxIgQgqoALo+9l2YW7 # /eFbnxFQ9qyCP7AGTiQAFET3OrxBqpEwDQYJKoZIhvcNAQEBBQAEggEAc/gJdcK1 # zknqC6DFS0xEQBYBHT6qMmPnuvSGSw0kSasXM5U8XcGvZEK7f/kQ+sYP2dZDOlC0 # VSoY3ACh0RwZqRurKbqWjfoZzLRRSKCLSd5MQsRO/glXcEq4Lm/CMG0yzSsizv3p # jRyrC6S9NjwJOaabLP8y/S6zs4c5yMUSdZlp2oYsmQRRj+A1EOqpfP5zRkAcuJim # RCwM4wEPSsrblT8+IfjupZWtjImYtr/zPsCnJm8cvPDdg5HHErXy7dLGOx8uhNkl # nt5sfMjnjDBS3J9rSN5Tihe9l/QmfM3riDYfGbWXp0W3oi+/4BgGlDrd1+5oPQyx # DmmD8HU5OMRJMA== # SIG # End signature block |