Src/LabResource.ps1
function Test-LabResource { <# .SYNOPSIS Tests whether a lab's resources are present. #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## PowerShell DSC configuration document (.psd1) containing lab metadata. [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Lab resource Id to test. [Parameter(ValueFromPipelineByPropertyName)] [System.String] $ResourceId, ## Lab resource path [Parameter(ValueFromPipelineByPropertyName)] [System.String] $ResourcePath ) begin { if (-not $ResourcePath) { $hostDefaults = GetConfigurationData -Configuration Host; $ResourcePath = $hostDefaults.ResourcePath; } } process { if ($resourceId) { $resources = ResolveLabResource -ConfigurationData $ConfigurationData -ResourceId $ResourceId } else { $resources = $ConfigurationData.NonNodeData.($labDefaults.ModuleName).Resource } foreach ($resource in $resources) { $fileName = $resource.Id; if ($resource.Filename) { $fileName = $resource.Filename; } $testResourceDownloadParams = @{ DestinationPath = Join-Path -Path $ResourcePath -ChildPath $fileName;; Uri = $resource.Uri; } if ($resource.Checksum) { $testResourceDownloadParams['Checksum'] = $resource.Checksum } if (-not (TestResourceDownload @testResourceDownloadParams)) { return $false; } } return $true; } #end process } #end Test-LabResource function TestLabLocalResource { <# .SYNOPSIS Test whether a lab resource is available locally #> [CmdletBinding()] [OutputType([System.Boolean])] param ( ## PowerShell DSC configuration document (.psd1) containing lab metadata. [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Lab resource Id to test. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $ResourceId, ## Node's target resource folder [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $LocalResourcePath ) process { $resource = ResolveLabResource -ConfigurationData $ConfigurationData -ResourceId $ResourceId; if (($resource.Expand) -and ($resource.Expand -eq $true)) { ## Check the ResourceId folder is present $resourcePath = Join-Path -Path $LocalResourcePath -ChildPath $resourceId; $resourceExtension = [System.IO.Path]::GetExtension($resource.Filename); switch ($resourceExtension) { '.iso' { $isPresent = Test-Path -Path $resourcePath -PathType Container; } '.zip' { $isPresent = Test-Path -Path $resourcePath -PathType Container; } default { throw ($localized.ExpandNotSupportedError -f $resourceExtension); } } } else { $resourcePath = Join-Path -Path $LocalResourcePath -ChildPath $resource.Filename; $isPresent = Test-Path -Path $resourcePath -PathType Leaf; } if ($isPresent) { WriteVerbose -Message ($localized.ResourceFound -f $resourcePath); return $true; } else { WriteVerbose -Message ($localized.ResourceNotFound -f $resourcePath); return $false; } } #end process } #end function TestLabResourceLocal function Invoke-LabResourceDownload { <# .SYNOPSIS Starts a download of all required lab resources. .DESCRIPTION When a lab configuration is started, Lability will attempt to download all the required media and resources. In some scenarios you many need to download lab resources in advance, e.g. where internet access is not readily available or permitted. The `Invoke-LabResourceDownload` cmdlet can be used to manually download all required resources or specific media/resources as needed. .PARAMETER ConfigurationData Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. .PARAMETER All Specifies all media, custom and DSC resources should be downloaded. .PARAMETER MediaId Specifies the specific media IDs to download. .PARAMETER ResourceId Specifies the specific custom resource IDs to download. .PARAMETER Media Specifies all media IDs should be downloaded. .PARAMETER Resources Specifies all custom resource IDs should be downloaded. .PARAMETER DSCResources Specifies all DSC resources should be downloaded. .PARAMETER Force Forces a download of all resources, overwriting any existing resources. .PARAMETER DestinationPath Specifies the target destination path of downloaded custom resources (not media or DSC resources). .EXAMPLE Invoke-LabResourceDownload -ConfigurationData ~\Documents\MyLab.psd1 -All Downloads all required lab media, any custom resources and DSC resources defined in the 'MyLab.psd1' configuration. .EXAMPLE Invoke-LabResourceDownload -ConfigurationData ~\Documents\MyLab.psd1 -MediaId 'WIN10_x64_Enterprise_EN_Eval' Downloads only the 'WIN10_x64_Enterprise_EN_Eval' media. .EXAMPLE Invoke-LabResourceDownload -ConfigurationData ~\Documents\MyLab.psd1 -ResourceId 'MyCustomResource' Downloads only the 'MyCustomResource' resource defined in the 'MyLab.psd1' configuration. .EXAMPLE Invoke-LabResourceDownload -ConfigurationData ~\Documents\MyLab.psd1 -Media Downloads only the media defined in the 'MyLab.psd1' configuration. .EXAMPLE Invoke-LabResourceDownload -ConfigurationData ~\Documents\MyLab.psd1 -Resources -DSCResources Downloads only the custom file resources and DSC resources defined in the 'MyLab.psd1' configuration. #> [CmdletBinding(DefaultParameterSetName = 'All')] param ( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData = @{ }, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'All')] [System.Management.Automation.SwitchParameter] $All, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'MediaId')] [System.String[]] $MediaId, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ResourceId')] [System.String[]] $ResourceId, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Media')] [System.Management.Automation.SwitchParameter] $Media, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Resources')] [System.Management.Automation.SwitchParameter] $Resources, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'DSCResources')] [System.Management.Automation.SwitchParameter] $DSCResources, [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'Resources')] [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = 'ResourceId')] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) begin { $hostDefaults = GetConfigurationData -Configuration Host; if (-not $DestinationPath) { $DestinationPath = $hostDefaults.ResourcePath; } } process { if ($PSCmdlet.ParameterSetName -in 'MediaId','Media','All') { if (-not $MediaId) { WriteVerbose ($Localized.DownloadingAllRequiredMedia); $uniqueMediaIds = @(); $ConfigurationData.AllNodes.Where({ $_.NodeName -ne '*' }) | ForEach-Object { $id = (ResolveLabVMProperties -NodeName $_.NodeName -ConfigurationData $ConfigurationData).Media; if ($uniqueMediaIds -notcontains $id) { $uniqueMediaIds += $id; } } $MediaId = $uniqueMediaIds; } if ($MediaId) { foreach ($id in $MediaId) { $labMedia = ResolveLabMedia -ConfigurationData $ConfigurationData -Id $id; InvokeLabMediaImageDownload -Media $labMedia -Force:$Force; WriteVerbose $Localized.DownloadingAllRequiredHotfixes; if ($labMedia.Hotfixes.Count -gt 0) { foreach ($hotfix in $labMedia.Hotfixes) { InvokeLabMediaHotfixDownload -Id $hotfix.Id -Uri $hotfix.Uri; } } else { WriteVerbose ($localized.NoHotfixesSpecified); } } } else { WriteVerbose ($localized.NoMediaDefined); } } #end if MediaId or MediaOnly if ($PSCmdlet.ParameterSetName -in 'ResourceId','Resources','All') { if (-not $ResourceId) { WriteVerbose ($Localized.DownloadingAllDefinedResources); $ResourceId = $ConfigurationData.NonNodeData.$($labDefaults.ModuleName).Resource.Id; } if (($ResourceId.Count -gt 0) -and (-not $MediaOnly)) { foreach ($id in $ResourceId) { $resource = ResolveLabResource -ConfigurationData $ConfigurationData -ResourceId $id; $fileName = $resource.Id; if ($resource.Filename) { $fileName = $resource.Filename; } $resourceDestinationPath = Join-Path -Path $DestinationPath -ChildPath $fileName; [ref] $null = InvokeResourceDownload -DestinationPath $resourceDestinationPath -Uri $resource.Uri -Checksum $resource.Checksum -Force:$Force; Write-Output (Get-Item -Path $resourceDestinationPath); } } else { WriteVerbose ($localized.NoResourcesDefined); } } #end if ResourceId or ResourceOnly if ($PSCmdlet.ParameterSetName -in 'DSCResources','All') { if ($ConfigurationData.NonNodeData.$($labDefaults.ModuleName).DSCResource) { $dscResourceDefinitions = $ConfigurationData.NonNodeData.$($labDefaults.ModuleName).DSCResource; if ($dscResources.Count -gt 0) { WriteVerbose ($Localized.DownloadingAllDSCResources); InvokeDscResourceDownload -DSCResource $dscResourceDefinitions -Force:$Force; } else { WriteVerbose ($localized.NoDSCResourcesDefined); } } } } #end process } #end function Invoke-LabResourceDownload function ResolveLabResource { <# .SYNOPSIS Resolves a lab resource by its ID #> param ( ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Lab resource ID [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $ResourceId ) process { $resource = $ConfigurationData.NonNodeData.($labDefaults.ModuleName).Resource | Where-Object Id -eq $ResourceId; if ($resource) { return $resource; } else { throw ($localized.CannotResolveResourceIdError -f $resourceId); } } } #end function ResolveLabResource function ExpandIsoResource { <# .SYNOPSIS Expands an ISO disk image resource #> param ( ## Source ISO file path [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Path, ## Destination folder path [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $DestinationPath ) process { WriteVerbose ($localized.MountingDiskImage -f $Path); $iso = Mount-DiskImage -ImagePath $Path -StorageType ISO -Access ReadOnly -PassThru -Verbose:$false; ## Refresh drives [ref] $null = Get-PSDrive; $isoDriveLetter = $iso | Get-Volume | Select-Object -ExpandProperty DriveLetter; $sourcePath = '{0}:\' -f $isoDriveLetter; WriteVerbose ($localized.ExpandingIsoResource -f $DestinationPath); #[ref] $null = New-Item -Path $DestinationPath -ItemType Directory -Force; CopyDirectory -SourcePath $sourcePath -DestinationPath $DestinationPath -Force -Verbose:$false; WriteVerbose ($localized.DismountingDiskImage -f $Path); Dismount-DiskImage -ImagePath $Path; } #end process } #end function ExpandIsoResource function ExpandLabResource { <# .SYNOPSIS Copies files, e.g. EXEs, ISOs and ZIP file resources into a lab VM's mounted VHDX differencing disk image. .NOTES VHDX should already be mounted and passed in via the $DestinationPath parameter Can expand ISO and ZIP files if the 'Expand' property is set to $true on the resource's properties. #> param ( ## Specifies a PowerShell DSC configuration document (.psd1) containing the lab configuration. [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData, ## Lab VM name [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Name, ## Destination mounted VHDX path to expand resources into [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $DestinationPath, ## Source resource path [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ResourcePath ) begin { if (-not $ResourcePath) { $hostDefaults = GetConfigurationData -Configuration Host; $ResourcePath = $hostDefaults.ResourcePath; } } process { ## Create the root container if (-not (Test-Path -Path $DestinationPath -PathType Container)) { [ref] $null = New-Item -Path $DestinationPath -ItemType Directory -Force; } $node = ResolveLabVMProperties -NodeName $Name -ConfigurationData $ConfigurationData -ErrorAction Stop; foreach ($resourceId in $node.Resource) { WriteVerbose ($localized.InjectingVMResource -f $resourceId); $resource = ResolveLabResource -ConfigurationData $ConfigurationData -ResourceId $resourceId; ## Default to resource.Id unless there is a filename property defined! $resourceItemPath = Join-Path -Path $ResourcePath -ChildPath $resource.Id; if ($resource.Filename) { $resourceItemPath = Join-Path -Path $ResourcePath -ChildPath $resource.Filename; } if (-not (Test-Path -Path $resourceItemPath)) { [ref] $null = Invoke-LabResourceDownload -ConfigurationData $ConfigurationData -ResourceId $resourceId; } $resourceItem = Get-Item -Path $resourceItemPath; $isCustomDestinationPath = $false; if ($resource.DestinationPath -and (-not [System.String]::IsNullOrEmpty($resource.DestinationPath))) { ## Use the explicit $Resource.DestinationPath\ResourceId path $destinationDrive = Split-Path -Path $DestinationPath -Qualifier; $destinationRootPath = Join-Path -Path $destinationDrive -ChildPath $resource.DestinationPath; $destinationResourcePath = Join-Path -Path $destinationRootPath -ChildPath $resourceId; $isCustomDestinationPath = $true; } else { ## Otherwise default to (Resources)\ResourceId $destinationRootPath = $DestinationPath; $destinationResourcePath = Join-Path -Path $DestinationPath -ChildPath $resourceId; } if (($resource.Expand) -and ($resource.Expand -eq $true)) { switch ($resourceItem.Extension) { '.iso' { if ($isCustomDestinationPath) { ## Use the custom DestinationPath ExpandIsoResource -Path $resourceItem.FullName -DestinationPath $destinationRootPath; } else { [ref] $null = New-Item -Path $destinationResourcePath -ItemType Directory -Force; ExpandIsoResource -Path $resourceItem.FullName -DestinationPath $destinationResourcePath; } } '.zip' { if ($isCustomDestinationPath) { ## Use the custom DestinationPath WriteVerbose -Message ($localized.ExpandingZipResource -f $resourceItem.FullName); [ref] $null = ExpandZipArchive -Path $resourceItem.FullName -DestinationPath $destinationRootPath -Verbose:$false; } else { [ref] $null = New-Item -Path $destinationResourcePath -ItemType Directory -Force; WriteVerbose -Message ($localized.ExpandingZipResource -f $resourceItem.FullName); [ref] $null = ExpandZipArchive -Path $resourceItem.FullName -DestinationPath $destinationResourcePath -Verbose:$false; } } Default { throw ($localized.ExpandNotSupportedError -f $resourceItem.Extension); } } #end switch } else { WriteVerbose ($localized.CopyingFileResource -f $destinationResourcePath); Copy-Item -Path $resourceItem.FullName -Destination $destinationRootPath -Force -Verbose:$false; } } #end foreach ResourceId } #end process } #end function ExpandLabResource # SIG # Begin signature block # MIIXtwYJKoZIhvcNAQcCoIIXqDCCF6QCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUDw5ZjUy9poxdZ2pbjfgx7YNJ # p2GgghLqMIID7jCCA1egAwIBAgIQfpPr+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 # NwIBFTAjBgkqhkiG9w0BCQQxFgQUZlv9satEflU6mpMTICpYXCI5c50wDQYJKoZI # hvcNAQEBBQAEggEAhW9DJa4BeRLpmlHPi+5dtH4ZLNXL30V8NZfiB+F7asrkLR/j # La7pwFWbOjXrNjDvWkua9+eAy5l11MJ66Jl6SGKLmf0uTj62EkZLpUz9tvQVd9MK # MFvfyl5D4m0JSvDNOfHbW32yCAOvOEw6++C+nk5kb0xDeltkPRIHmVHE9v3/ezKH # 9ycnBdbdTE+NUwh8j8VLObwVnPsKNl6aoJY1Kop4JgywjXcKUT8Bpn+D19TJqXmZ # UpI6r8q7q7GYtuWt3b6Z2QIjadETqEhJm0dtXJ8kU13jQQm7hVd9+SYWcaLkau9t # u4QkKA7/1qGdn2fvd8sfG9iS4IDGrM72fYuXQaGCAgswggIHBgkqhkiG9w0BCQYx # ggH4MIIB9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBD # b3Jwb3JhdGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2 # aWNlcyBDQSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNjA0MTMxMzMz # MjJaMCMGCSqGSIb3DQEJBDEWBBTuFcvY3mW971JK9wssm4Io4kE6GjANBgkqhkiG # 9w0BAQEFAASCAQAFV7IlICrN/M6RH/R5+trZbiEpkIdXB870eozuVfnLGSr7VCCV # N5gLYm/i6wJca+g1hB/KUxQQZjxEiTziQ0D97CtnRLhcJmpo5JZTAkigQK5Dfp7A # Ki8SykgalARJC0GrXhcPsdVoMwIf7WFLJxfH+ajecTUhdkqkcWiGqVugS5u4UmQr # 9V4ZfOLVOpd7hZt/h2x93DYuChWnzQ0HNSUTj6/UIXM3hjKErDHfxrbyDsEwvLed # 1Yx4inIQuuK+b7iZM6B/RVb1coM7K45zL5HaaZMZegVqbqKC+5ka7yiFJmzvwdHr # odvk165qjtwCRAC6k1OmEdR6tgqjTAnxfnPW # SIG # End signature block |