Src/LabNode.ps1
function TestLabNodeCertificate { <# .SYNOPSIS Tests whether the certificate is installed. #> param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $CertificatePath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('My','Root')] [System.String] $Store ) process { $CertificatePath = ResolvePathEx -Path $CertificatePath; if (-not (Test-Path -Path $CertificatePath)) { return $false; } $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromCertFile($CertificatePath); $localCertificate = Get-ChildItem -Path "Cert:\LocalMachine\$Store" | Where-Object { $_.Subject -eq $certificate.Subject } return ($null -ne $localCertificate); } #end process } #end function TestLabNodeCertificate function InstallLabNodeCertificates { <# .SYNOPSIS Installs lab node certificates .NOTES Enables easier unit testing! #> param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $RootCertificatePath, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $ClientCertificatePath, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) process { ## Import certificates $resolvedRootCertificatePath = ResolvePathEx -Path $RootCertificatePath; if ($Force -or (-not (TestLabNodeCertificate -CertificatePath $resolvedRootCertificatePath -Store 'Root'))) { WriteVerbose -Message ($localized.AddingCertificate -f 'Root', $resolvedRootCertificatePath); certutil.exe -addstore -f "Root" $resolvedRootCertificatePath | WriteVerbose; } ## Import the .PFX certificate with a blank password $resolvedClientCertificatePath = ResolvePathEx -Path $ClientCertificatePath; if ($Force -or (-not (TestLabNodeCertificate -CertificatePath $resolvedClientCertificatePath -Store 'My'))) { WriteVerbose -Message ($localized.AddingCertificate -f 'Client', $resolvedClientCertificatePath); "" | certutil.exe -f -importpfx $resolvedClientCertificatePath | WriteVerbose; } } #end process } #end function InstallLabNodeCertificates function Test-LabNodeConfiguration { <# .SYNOPSIS Test a node's configuration for manual deployment. .DESCRIPTION The Test-LabNodeConfiguration determines whether the local node has all the required defined prerequisites available locally. When invoked, defined custom resources, certificates and DSC resources are checked. WARNING: Only metadata defined in the Powershell DSC configuration document can be tested! .PARAMETER ConfigurationData Specifies a PowerShell DSC configuration data hashtable or a path to an existing PowerShell DSC .psd1 configuration document used to create the virtual machines. Each node defined in the AllNodes array is tested. .PARAMETER NodeName Specifies the node name in the PowerShell DSC configuration document to check. If not specified, the local hostname is used. .PARAMETER DestinationPath Specifies the local directory path that resources are expected to be located in. If not specified, it defaults to the default ResourceShareName in the root of the system drive, i.e. C:\Resources. .PARAMETER SkipDscCheck Specifies that checking of the local DSC resource availability is skipped. .PARAMETER SkipResourceCheck Specifies that checking of the local custom resource availability is skipped. .PARAMETER SkipCertificateCheck Specifies that checking of the local certificates is skipped. .LINK Invoke-LabNodeConfiguration #> [CmdletBinding(DefaultParameterSetName = 'All')] [OutputType([System.Boolean])] param ( ## Lab DSC configuration data [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $NodeName = ([System.Net.Dns]::GetHostName()), [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'SkipDscCheck')] [System.Management.Automation.SwitchParameter] $SkipDscCheck, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'SkipResourceCheck')] [System.Management.Automation.SwitchParameter] $SkipResourceCheck, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'SkipResourceCheck')] [System.Management.Automation.SwitchParameter] $SkipCertificateCheck ) process { $node = ResolveLabVMProperties -NodeName $NodeName -ConfigurationData $ConfigurationData -ErrorAction Stop; if ((-not $node) -or ($node.NodeName -eq '*') -or ([System.String]::IsNullOrEmpty($node.NodeName))) { throw ($localized.CannotLocateNodeError -f $NodeName); } if (-not $PSBoundParameters.ContainsKey('DestinationPath')) { $DestinationPath = '{0}\{1}' -f $env:SystemDrive, (GetConfigurationData -Configuration Host).ResourceShareName; } $inDesiredState = $true; if (-not $SkipCertificateCheck) { ## Test node certificates $clientCertificatePath = ResolvePathEx -Path $node.ClientCertificatePath; WriteVerbose -Message ($localized.TestingNodeCertificate -f $clientCertificatePath); if (-not (TestLabNodeCertificate -CertificatePath $clientCertificatePath -Store 'My')) { WriteWarning -Message ($localized.MissingRequiredCertWarning -f $clientCertificatePath); $inDesiredState = $false; } $rootCertificatePath = ResolvePathEx -Path $node.RootCertificatePath; WriteVerbose -Message ($localized.TestingNodeCertificate -f $rootCertificatePath); if (-not (TestLabNodeCertificate -CertificatePath $rootCertificatePath -Store 'Root')) { WriteWarning -Message ($localized.MissingRequiredCertWarning -f $rootCertificatePath); $inDesiredState = $false; } } #end if not skip certificates # Test DSC modules if (-not $SkipDscCheck -and $ConfigurationData.NonNodeData.($labDefaults.ModuleName).DSCResource) { foreach ($module in $ConfigurationData.NonNodeData.($labDefaults.ModuleName).DSCResource) { WriteVerbose -Message ($localized.TestingNodeDscModule -f $module.Name); if ((-not $module.MinimimVerions) -and (-not $module.RequiredVersion)) { $module['MinimumVersion'] = '0.0'; } if (-not (TestModule @module)) { WriteWarning -Message ($localized.MissingRequiredModuleWarning -f $module.Name); $inDesiredState = $false; } } #end foreach module } ### Test resource master availability if (-not $SkipResourceCheck -and $node.Resource) { foreach ($resourceId in $node.Resource) { ## Check resource is available locally WriteVerbose -Message ($localized.TestingNodeResource -f $resourceId); $testLabLocalResourceParams = @{ ConfigurationData = $ConfigurationData; ResourceId = $resourceId; LocalResourcePath = $DestinationPath; } $isAvailableLocally = TestLabLocalResource @testLabLocalResourceParams; if (-not $isAvailableLocally) { $resourceFilename = Join-Path -Path $DestinationPath -ChildPath $resourceId; WriteWarning -Message ($localized.MissingRequiredResourceWarning -f $resourceFilename); $inDesiredState = $false; } } #end foreach resource } return $inDesiredState; } #end process } #end function Test-LabNodeConfiguration function Invoke-LabNodeConfiguration { <# .SYNOPSIS Configures a node for manual lab deployment. .DESCRIPTION The Invoke-LabNodeConfiguration installs the client certificates, downloads all required DSC resources and checks whether all resources are present locally. This is convenient when using alternative hypervisors that cannot be auto-provisioned by Lability. Examples include virtual machines deployed on VMware Workstation or AmazonW Web Services. NOTE: The Invoke-LabConfiguration will not download custom resources but will test for their presence. WARNING: Only metadata defined in the Powershell DSC configuration document can be tested! .PARAMETER ConfigurationData Specifies a PowerShell DSC configuration data hashtable or a path to an existing PowerShell DSC .psd1 configuration document used to create the virtual machines. Each node defined in the AllNodes array is tested. .PARAMETER NodeName Specifies the node name in the PowerShell DSC configuration document to check. If not specified, the local hostname is used. .PARAMETER DestinationPath Specifies the local directory path that resources are expected to be located in. If not specified, it defaults to the default ResourceShareName in the root of the system drive, i.e. C:\Resources. .PARAMETER Force Specifies that DSC resources should be re-downloaded, overwriting existing versions. .LINK Test-LabNodeConfiguration #> [CmdletBinding()] param ( ## Lab DSC configuration data [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $NodeName = ([System.Net.Dns]::GetHostName()), ## Node's local target resource folder [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) process { $node = ResolveLabVMProperties -NodeName $NodeName -ConfigurationData $ConfigurationData -ErrorAction Stop; if ((-not $node) -or ($node.NodeName -eq '*') -or ([System.String]::IsNullOrEmpty($node.NodeName))) { throw ($localized.CannotLocateNodeError -f $NodeName); } if (-not $PSBoundParameters.ContainsKey('DestinationPath')) { $DestinationPath = '{0}\{1}' -f $env:SystemDrive, (GetConfigurationData -Configuration Host).ResourceShareName; } ## Install lab root CA and client certificate $installLabNodeCertificatesParams = @{ RootCertificatePath = $node.RootCertificatePath; ClientCertificatePath = $node.ClientCertificatePath; Force = $Force; } InstallLabNodeCertificates @installLabNodeCertificatesParams; # Test DSC modules if ($ConfigurationData.NonNodeData.($labDefaults.ModuleName).DSCResource) { foreach ($module in $ConfigurationData.NonNodeData.($labDefaults.ModuleName).DSCResource) { if ((-not $module.MinimimVerions) -and (-not $module.RequiredVersion)) { $module['MinimumVersion'] = '0.0'; } if (-not (TestModule @module) -or $Force) { InvokeDscResourceDownload -DSCResource $module -Force; } } #end foreach module } ## Call Test-LabNodeConfiguration to display any remaining warnings [ref] $null = Test-LabNodeConfiguration -ConfigurationData $ConfigurationData -DestinationPath $DestinationPath -NodeName $NodeName -SkipDscCheck; } #end process } #end function Invoke-LabNodeConfiguration function Get-LabNodeResourceList { <# .SYNOPSIS Generates a list of required resources for each node. .DESCRIPTION Outputs a hashtable of each selected node containing all required custom resources. This is handy to create a list of resources when manually configuring nodes is required, e.g. locally on VMware Workstation or in Microsoft Azure etc. #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $Name ) process { $requiredResources = @{ }; if (-not $PSBoundParameters.ContainsKey('Name')) { $Name = $ConfigurationData.AllNodes | Where-Object { $_.NodeName -ne '*' } | ForEach-Object { $_.NodeName } } foreach ($nodeName in $Name) { $node = ResolveLabVMProperties -NodeName $nodeName -ConfigurationData $ConfigurationData -NoEnumerateWildcardNode -ErrorAction Stop; if ([System.String]::IsNullOrEmpty($node.NodeName)) { throw ($localized.CannotLocateNodeError -f $nodeName); } $requiredResources[$node.NodeName] = @(); foreach ($resourceId in $node.Resource) { $resource = [PSCustomObject] (ResolveLabResource -ConfigurationData $ConfigurationData -ResourceId $resourceId); if ($resource) { if (-not $resource.DestinationPath) { $resourcePath = '\Resources\{0}' -f $resource.Filename; } if (($resource.Expand -eq $true) -and ($resource.DestinationPath)) { # if expand and destinationpath = extract into destinationpath $resourcePath = $resource.DestinationPath; } elseif ($resource.Expand -eq $true) { # elseif expand = extract into resources\resourceid $resourcePath = '\Resources\{0}' -f $resource.Id; } elseif ($resource.DestinationPath) { # elseif not expand and destinationpath = download/copy into destinationpath $resourcePath = '{0}\{1}' -f $resource.DestinationPath, $resource.Filename; } else { # else download/copy into resource\resourcefilename $resourcePath = '\Resources\{0}' -f $resource.Filename; } [ref] $null = Add-Member -InputObject $resource -MemberType NoteProperty -Name ResourcePath -Value $resourcePath; $requiredResources[$node.NodeName] += $resource; } else { WriteWarning -Message ($localized.ResourceNotFound -f $resourceId); } } #end foreach resource id } #end foreach node Write-Output -InputObject $requiredResources; } #end process } #end function Get-LabNodeResource function Show-LabNodeResourceList { <# .SYNOPSIS Generates a display-friendly list of required custom resources for each node. .DESCRIPTION Outputs string of each selected node containing all required custom resources. This is handy to create a list of resources when manually configuring nodes is required, e.g. locally on VMware Workstation or in Microsoft Azure etc. #> [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String[]] $Name ) process { $requiredResources = Get-LabNodeResourceList @PSBoundParameters; $stringBuilder = New-Object -TypeName System.Text.StringBuilder; $now = Get-Date; [ref] $null = $stringBuilder.AppendFormat('Resource checklist generated {0} {1}.', $now.ToShortDateString(), $now.ToShortTimeString()); [ref] $null = $stringBuilder.AppendLine().AppendLine(); foreach ($node in $requiredResources.Keys) { [ref] $null = $stringBuilder.AppendFormat('{0}', $node.ToUpper()).AppendLine(); [ref] $null = $stringBuilder.AppendFormat('{0}', ('=' * $node.Length)).AppendLine(); $requiredResources[$node] | Sort-Object ResourcePath | ForEach-Object { $resource = $PSItem; [ref] $null = $stringBuilder.AppendLine(); [ref] $null = $stringBuilder.AppendFormat(' {0}', $resource.Id).AppendLine(); [ref] $null = $stringBuilder.AppendFormat(' {0}', ('-' * $resource.Id.Length)).AppendLine(); [ref] $null = $stringBuilder.AppendFormat(' Download: {0}', $resource.Uri).AppendLine(); if ($resource.Expand -eq $true) { [ref] $null = $stringBuilder.AppendFormat(' Extract/expand into: {0}', $resource.ResourcePath).AppendLine(); } else { [ref] $null = $stringBuilder.AppendFormat(' Save as: {0}', $resource.ResourcePath).AppendLine(); } if ($resource.Checksum) { [ref] $null = $stringBuilder.AppendFormat(' MD5 Checksum: {0}', $resource.Checksum).AppendLine(); } } [ref] $null = $stringBuilder.AppendLine(); } Write-Output $stringBuilder.ToString(); } #end process } #end function Show-LabNodeResourceList # SIG # Begin signature block # MIIXtwYJKoZIhvcNAQcCoIIXqDCCF6QCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU19QPe4hsF9+/dGmi7nqxseLO # FY6gghLqMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B # AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG # A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh # d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg # Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV # UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu # dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q # WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC # i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4 # ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3 # +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI # fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd # BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG # CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB # Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro # YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV # HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y # MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf # plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y # 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq # IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3 # DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh # dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD # QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE # BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT # eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow # mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0 # jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu # ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh # d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz # C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB # o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO # BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw # Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90 # cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx # oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy # bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV # HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa # 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH # bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73 # BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR # EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW # yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu # e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw # ggUZMIIEAaADAgECAhADViTO4HBjoJNSwH9//cwJMA0GCSqGSIb3DQEBCwUAMHIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ # RCBDb2RlIFNpZ25pbmcgQ0EwHhcNMTUwNTE5MDAwMDAwWhcNMTcwODIzMTIwMDAw # WjBgMQswCQYDVQQGEwJHQjEPMA0GA1UEBxMGT3hmb3JkMR8wHQYDVQQKExZWaXJ0 # dWFsIEVuZ2luZSBMaW1pdGVkMR8wHQYDVQQDExZWaXJ0dWFsIEVuZ2luZSBMaW1p # dGVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqLQmabdimcQtYPTQ # 9RSjv3ThEmFTRJt/MzseYYtZpBTcR6BnSfj8RfkC4aGZvspFgH0cGP/SNJh1w67b # iX9oT5NFL9sUJHUsVdyPBA1LhpWcF09PP28mGGKO3oQHI4hTLD8etiIlF9qFantd # 1Pmo0jdqT4uErSmx0m4kYGUUTa5ZPAK0UZSuAiNX6iNIL+rj/BPbI3nuPJzzx438 # oHYkZGRtsx11+pLA6hIKyUzRuIDoI7JQ0nZ0MkCziVyc6xGfS54JVLaVCEteTKPz # Gc4yyvCqp6Tfe9gs8UuxJiEMdH5fvllTU4aoXbm+W8tonkE7i/19rv8S1A2VPiVV # xNLbpwIDAQABo4IBuzCCAbcwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1 # DlgwHQYDVR0OBBYEFP2RNOWYipdNCSRVb5jIcyRp9tUDMA4GA1UdDwEB/wQEAwIH # gDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYv # aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmww # QgYDVR0gBDswOTA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93 # d3cuZGlnaWNlcnQuY29tL0NQUzCBhAYIKwYBBQUHAQEEeDB2MCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYBBQUHMAKGQmh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURDb2RlU2ln # bmluZ0NBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCclXHR # DhDyJr81eiD0x+AL04ryDwdKT+PooKYgOxc7EhRn59ogxNO7jApQPSVo0I11Zfm6 # zQ6K6RPWhxDenflf2vMx7a0tIZlpHhq2F8praAMykK7THA9F3AUxIb/lWHGZCock # yD/GQvJek3LSC5NjkwQbnubWYF/XZTDzX/mJGU2DcG1OGameffR1V3xODHcUE/K3 # PWy1bzixwbQCQA96GKNCWow4/mEW31cupHHSo+XVxmjTAoC93yllE9f4Kdv6F29H # bRk0Go8Yn8WjWeLE/htxW/8ruIj0KnWkG+YwmZD+nTegYU6RvAV9HbJJYUEIfhVy # 3DeK5OlY9ima2sdtMIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkq # hkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j # MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBB # c3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAw # WjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL # ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy # ZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB # CgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6 # kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQj # ZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5w # MWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp # 6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH # 5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgw # BgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYI # KwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6 # Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmww # OqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ # RFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUH # AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYD # VR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuC # MS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2 # qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4Q # pO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEp # KBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/Dm # ZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9 # CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHv # MYIENzCCBDMCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 # IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNl # cnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQA1YkzuBwY6CTUsB/ # f/3MCTAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq # hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC # NwIBFTAjBgkqhkiG9w0BCQQxFgQU4Et39ERXVm/adDy71vdAXm15VA4wDQYJKoZI # hvcNAQEBBQAEggEAknrkD/teVl4p5w8q98RTAas8h8f8dJ0gO7Qlh8EtRooChjwE # sckXtj45EJ/7g+Cj9kDqKMAavu9gG/8fNuS711ioTnFUNSU3O7mMb8G1tFrG5Qpj # ffELt5BqB2GLe47eZW+RYMlnjV5sbuEWbf2fNcAgPs180XuLN0P6GneLKjIyfaha # iqZFPC3EDytnqKL3ftsBuYkNNvL3hk7QoIYX27uRUbSV9fSI8aMSBIN/OlOc3OiT # Rq9SvGGkMgKAYpiZYftNU1vjN2MTZVNuXskNumunFtM8CzOV+GiNCWC+F7smrLet # 6bvOV/N9UgX+h/JBtnO7ab3QHTAlTUWZiopQa6GCAgswggIHBgkqhkiG9w0BCQYx # ggH4MIIB9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBD # b3Jwb3JhdGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2 # aWNlcyBDQSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MTMxMzMz # MjJaMCMGCSqGSIb3DQEJBDEWBBTOMjz8LKxMl8CI3f8mMd9qHlRgYzANBgkqhkiG # 9w0BAQEFAASCAQAPotFoWZ/mGTv+44rtUvghN3fserTaVp6S0OHhCZFVL+y8CrDW # gmXozplWGkmEEFXNPHXP2P+nsPkW8rvE+NAcD92OOgKnsL4HUQdvoBTxIGA5byWg # StXtWsFQ3/f1Hdt8BETwP7RJbrPtZBVjBks9iusBEYENki+p8GVgVepnwVP357xW # DnOhJrPgyQSUR+h4GIRq8034aqO8cTBHDI8vcJdH/4cA/QLr7CL9cWbTiSqJBrG+ # Vc6C/9HqoipS0dRNFwIx9o/J/AncpptPHa8c8rgyeCyUBzffVrVG5gmDppT85aCb # Wv9m0rWgjQ/DtS+cCV1rXkd1/uZPBc5IQHzj # SIG # End signature block |