Src/LabMedia.ps1
function NewLabMedia { <# .SYNOPSIS Creates a new lab media object. .DESCRIPTION Permits validation of custom NonNodeData\Lability\Media entries. #> [CmdletBinding()] param ( [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Id = $(throw ($localized.MissingParameterError -f 'Id')), [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Filename = $(throw ($localized.MissingParameterError -f 'Filename')), [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Description = '', [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('x86','x64')] [System.String] $Architecture = $(throw ($localized.MissingParameterError -f 'Architecture')), [Parameter(ValueFromPipelineByPropertyName)] [System.String] $ImageName = '', [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('ISO','VHD')] [System.String] $MediaType = $(throw ($localized.MissingParameterError -f 'MediaType')), [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri = $(throw ($localized.MissingParameterError -f 'Uri')), [Parameter(ValueFromPipelineByPropertyName)] [System.String] $Checksum = '', [Parameter(ValueFromPipelineByPropertyName)] [System.String] $ProductKey = '', [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Windows','Linux')] [System.String] $OperatingSystem = 'Windows', [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Collections.Hashtable] $CustomData = @{}, [Parameter(ValueFromPipelineByPropertyName)] [AllowNull()] [System.Array] $Hotfixes ) begin { ## Confirm we have a valid Uri try { $resolvedUri = New-Object -TypeName 'System.Uri' -ArgumentList $Uri; if ($resolvedUri.Scheme -notin 'http','https','file') { throw ($localized.UnsupportedUriSchemeError -f $resolvedUri.Scheme); } } catch { throw $_; } } process { $labMedia = [PSCustomObject] @{ Id = $Id; Filename = $Filename; Description = $Description; Architecture = $Architecture; ImageName = $ImageName; MediaType = $MediaType; OperatingSystem = $OperatingSystem; Uri = [System.Uri] $Uri; Checksum = $Checksum; CustomData = $CustomData; Hotfixes = $Hotfixes; } ## Ensure any explicit product key overrides the CustomData value if ($ProductKey) { $CustomData['ProductKey'] = $ProductKey; } return $labMedia; } #end process } #end function NewLabMedia function ResolveLabMedia { <# .SYNOPSIS Resolves the specified media using the registered media and configuration data. .DESCRIPTION Resolves the specified lab media from the registered media, but permitting the defaults to be overridden by configuration data. This also permits specifying of media within Configuration Data and not having to be registered on the lab host. #> [CmdletBinding()] param ( ## Media ID [Parameter(Mandatory, ValueFromPipeline)] [System.String] $Id, ## Lab DSC configuration data [Parameter(ValueFromPipelineByPropertyName)] [System.Collections.Hashtable] [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformationAttribute()] $ConfigurationData ) process { ## Avoid any $media variable scoping issues $media = $null; ## If we have configuration data specific instance, return that if ($PSBoundParameters.ContainsKey('ConfigurationData')) { $customMedia = $ConfigurationData.NonNodeData.$($labDefaults.ModuleName).Media.Where({ $_.Id -eq $Id }); if ($customMedia) { $newLabMediaParams = @{}; foreach ($key in $customMedia.Keys) { $newLabMediaParams[$key] = $customMedia.$key; } $media = NewLabMedia @newLabMediaParams; } } ## If we have custom media, return that if (-not $media) { $media = Get-ConfigurationData -Configuration CustomMedia; $media = $media | Where-Object { $_.Id -eq $Id }; } ## If we still don't have a media image, return the built-in object if (-not $media) { $media = Get-LabMedia -Id $Id; } ## We don't have any defined, custom or built-in media if (-not $media) { throw ($localized.CannotLocateMediaError -f $Id); } return $media; } #end process } #end function ResolveLabMedia function Get-LabMedia { <# .SYNOPSIS Gets registered lab media. .DESCRIPTION The Get-LabMedia cmdlet retrieves all built-in and registered custom media. .PARAMETER Id Specifies the specific media Id to return. .PARAMETER CustomOnly Specifies that only registered custom media are returned. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] param ( ## Media ID [Parameter(ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id, ## Only return custom media [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $CustomOnly ) process { ## Retrieve built-in media if (-not $CustomOnly) { $defaultMedia = Get-ConfigurationData -Configuration Media; } ## Retrieve custom media $customMedia = @(Get-ConfigurationData -Configuration CustomMedia); if (-not $customMedia) { $customMedia = @(); } ## Are we looking for a specific media Id if ($Id) { ## Return the custom media definition first (if it exists) $media = $customMedia | Where-Object { $_.Id -eq $Id }; if ((-not $media) -and (-not $CustomOnly)) { ## We didn't find a custom media entry, return a default entry (if it exists) $media = $defaultMedia | Where-Object { $_.Id -eq $Id }; } } else { ## Return all custom media $media = $customMedia; if (-not $CustomOnly) { foreach ($mediaEntry in $defaultMedia) { ## Determine whether the media is present in the custom media, i.e. make sure ## we don't override a custom entry with the default one. $defaultMediaEntry = $customMedia | Where-Object { $_.Id -eq $mediaEntry.Id } ## If not, add it to the media array to return if (-not $defaultMediaEntry) { $media += $mediaEntry; } } #end foreach default media } #end if not custom only } foreach ($mediaObject in $media) { $mediaObject.PSObject.TypeNames.Insert(0, 'VirtualEngine.Lability.Media'); Write-Output -InputObject $mediaObject; } } #end process } #end function Get-LabMedia function Test-LabMedia { <# .SYNOPSIS Tests whether lab media has already been successfully downloaded. .DESCRIPTION The Test-LabMedia cmdlet will check whether the specified media Id has been downloaded and its checksum is correct. .PARAMETER Id Specifies the media Id to test. #> [CmdletBinding()] [OutputType([System.Boolean])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id ) process { $hostDefaults = Get-ConfigurationData -Configuration Host; $media = Get-LabMedia -Id $Id; if ($media) { if (-not $hostDefaults.DisableLocalFileCaching) { $testResourceDownloadParams = @{ DestinationPath = Join-Path -Path $hostDefaults.IsoPath -ChildPath $media.Filename; Uri = $media.Uri; Checksum = $media.Checksum; } return TestResourceDownload @testResourceDownloadParams; } else { ## Local file resource caching is disabled return $true; } } else { return $false; } } #end process } #end function Test-LabMedia function InvokeLabMediaImageDownload { <# .SYNOPSIS Downloads ISO/WIM/VHDX media resources. .DESCRIPTION Initiates a download of a media resource. If the resource has already been downloaded and the checksum is correct, it won't be re-downloaded. To force download of a ISO/VHDX use the -Force switch. .NOTES ISO media is downloaded to the default IsoPath location. VHD(X) files are downloaded directly into the ParentVhdPath location. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( ## Lab media object [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNull()] [System.Object] $Media, ## Force (re)download of the resource [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) process { $hostDefaults = Get-ConfigurationData -Configuration Host; $invokeResourceDownloadParams = @{ DestinationPath = Join-Path -Path $hostDefaults.IsoPath -ChildPath $media.Filename; Uri = $media.Uri; Checksum = $media.Checksum; } if ($media.MediaType -eq 'VHD') { $invokeResourceDownloadParams['DestinationPath'] = Join-Path -Path $hostDefaults.ParentVhdPath -ChildPath $media.Filename; } $mediaUri = New-Object -TypeName System.Uri -ArgumentList $Media.Uri; if ($mediaUri.Scheme -eq 'File') { ## Use a bigger buffer for local file copies.. $invokeResourceDownloadParams['BufferSize'] = 1MB; } if ($media.MediaType -eq 'VHD') { ## Always download VHDXs regardless of Uri type [ref] $null = InvokeResourceDownload @invokeResourceDownloadParams -Force:$Force; } elseif (($mediaUri.Scheme -eq 'File') -and ($media.MediaType -eq 'WIM') -and $hostDefaults.DisableLocalFileCaching) ## TODO: elseif (($mediaUri.Scheme -eq 'File') -and $hostDefaults.DisableLocalFileCaching) { ## NOTE: Only WIM media can currently be run from a file share (see https://github.com/VirtualEngine/Lab/issues/28) ## Caching is disabled and we have a file resource, so just return the source URI path WriteVerbose ($localized.MediaFileCachingDisabled -f $Media.Id); $invokeResourceDownloadParams['DestinationPath'] = $mediaUri.LocalPath; } else { ## Caching is enabled or it's a http/https source [ref] $null = InvokeResourceDownload @invokeResourceDownloadParams -Force:$Force; } return (Get-Item -Path $invokeResourceDownloadParams.DestinationPath); } #end process } #end InvokeLabMediaImageDownload function InvokeLabMediaHotfixDownload { <# .SYNOPSIS Downloads resources. .DESCRIPTION Initiates a download of a media resource. If the resource has already been downloaded and the checksum is correct, it won't be re-downloaded. To force download of a ISO/VHDX use the -Force switch. .NOTES ISO/WIM media is downloaded to the default IsoPath location. VHD(X) files are downloaded directly into the ParentVhdPath location. #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [System.String] $Id, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Checksum, [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) process { $hostDefaults = Get-ConfigurationData -Configuration Host; $destinationPath = Join-Path -Path $hostDefaults.HotfixPath -ChildPath $Id; $invokeResourceDownloadParams = @{ DestinationPath = $destinationPath; Uri = $Uri; } if ($Checksum) { [ref] $null = $invokeResourceDownloadParams.Add('Checksum', $Checksum); } [ref] $null = InvokeResourceDownload @invokeResourceDownloadParams -Force:$Force; return (Get-Item -Path $destinationPath); } #end process } #end function InvokeLabMediaHotfixDownload function Register-LabMedia { <# .SYNOPSIS Registers a custom media entry. .DESCRIPTION The Register-LabMedia cmdlet allows adding custom media to the host's configuration. This circumvents the requirement of having to define custom media entries in the DSC configuration document (.psd1). You can use the Register-LabMedia cmdlet to override the default media entries, e.g. you have the media hosted internally or you wish to replace the built-in media with your own implementation. To override a built-in media entry, specify the same media Id with the -Force switch. .LINK Get-LabMedia Unregister-LabMedia #> [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] param ( ## Specifies the media Id to register. You can override the built-in media if required. [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [System.String] $Id, ## Specifies the media's type. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('VHD','ISO','WIM')] [System.String] $MediaType, ## Specifies the source Uri (http/https/file) of the media. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.Uri] $Uri, ## Specifies the architecture of the media. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateSet('x64','x86')] [System.String] $Architecture, ## Specifies a description of the media. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Description, ## Specifies the image name containing the target WIM image. You can specify integer values. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $ImageName, ## Specifies the local filename of the locally cached resource file. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Filename, ## Specifies the MD5 checksum of the resource file. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [System.String] $Checksum, ## Specifies custom data for the media. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Collections.Hashtable] $CustomData, ## Specifies additional Windows hotfixes to install post deployment. [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNull()] [System.Collections.Hashtable[]] $Hotfixes, ## Specifies the media type. Linux VHD(X)s do not inject resources. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('Windows','Linux')] [System.String] $OperatingSystem = 'Windows', ## Specifies that an exiting media entry should be overwritten. [Parameter(ValueFromPipelineByPropertyName)] [System.Management.Automation.SwitchParameter] $Force ) process { ## Validate Linux VM media type is VHD if (($OperatingSystem -eq 'Linux') -and ($MediaType -ne 'VHD')) { throw ($localized.InvalidOSMediaTypeError -f $MediaType, $OperatingSystem); } ## Validate ImageName when media type is ISO/WIM if (($MediaType -eq 'ISO') -or ($MediaType -eq 'WIM')) { if (-not $PSBoundParameters.ContainsKey('ImageName')) { throw ($localized.ImageNameRequiredError -f '-ImageName'); } } ## Resolve the media Id to see if it's already been used $media = ResolveLabMedia -Id $Id -ErrorAction SilentlyContinue; if ($media -and (-not $Force)) { throw ($localized.MediaAlreadyRegisteredError -f $Id, '-Force'); } ## Get the custom media list (not the built in media) $existingCustomMedia = @(Get-ConfigurationData -Configuration CustomMedia); if (-not $existingCustomMedia) { $existingCustomMedia = @(); } $customMedia = [PSCustomObject] @{ Id = $Id; Filename = $Filename; Description = $Description; Architecture = $Architecture; ImageName = $ImageName; MediaType = $MediaType; OperatingSystem = $OperatingSystem; Uri = $Uri; Checksum = $Checksum; CustomData = $CustomData; Hotfixes = $Hotfixes; } $hasExistingMediaEntry = $false; for ($i = 0; $i -lt $existingCustomMedia.Count; $i++) { if ($existingCustomMedia[$i].Id -eq $Id) { WriteVerbose ($localized.OverwritingCustomMediaEntry -f $Id); $hasExistingMediaEntry = $true; $existingCustomMedia[$i] = $customMedia; } } if (-not $hasExistingMediaEntry) { ## Add it to the array WriteVerbose ($localized.AddingCustomMediaEntry -f $Id); $existingCustomMedia += $customMedia; } WriteVerbose ($localized.SavingConfiguration -f $Id); Set-ConfigurationData -Configuration CustomMedia -InputObject @($existingCustomMedia); return $customMedia; } #end process } #end function Register-LabMedia function Unregister-LabMedia { <# .SYNOPSIS Unregisters a custom media entry. .DESCRIPTION The Unregister-LabMedia cmdlet allows removing custom media entries from the host's configuration. .LINK Get-LabMedia Register-LabMedia #> [CmdletBinding(SupportsShouldProcess)] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', '')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] param ( ## Specifies the custom media Id to unregister. You cannot unregister the built-in media. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [System.String] $Id ) process { ## Get the custom media list $customMedia = Get-ConfigurationData -Configuration CustomMedia; if (-not $customMedia) { ## We don't have anything defined WriteWarning ($localized.NoCustomMediaFoundWarning -f $Id); return; } else { ## Check if we have a matching Id $media = $customMedia | Where-Object { $_.Id -eq $Id }; if (-not $media) { ## We don't have a custom matching Id registered WriteWarning ($localized.NoCustomMediaFoundWarning -f $Id); return; } } $shouldProcessMessage = $localized.PerformingOperationOnTarget -f 'Unregister-LabMedia', $Id; $verboseProcessMessage = $localized.RemovingCustomMediaEntry -f $Id; if ($PSCmdlet.ShouldProcess($verboseProcessMessage, $shouldProcessMessage, $localized.ShouldProcessWarning)) { $customMedia = $customMedia | Where-Object { $_.Id -ne $Id }; WriteVerbose ($localized.SavingConfiguration -f $Id); Set-ConfigurationData -Configuration CustomMedia -InputObject @($customMedia); return $media; } } #end process } #end function Unregister-LabMedia function Reset-LabMedia { <# .SYNOPSIS Reset the lab media entries to default settings. .DESCRIPTION The Reset-LabMedia removes all custom media entries, reverting them to default values. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Management.Automation.PSCustomObject])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess','')] param ( ) process { Remove-ConfigurationData -Configuration CustomMedia; Get-Labmedia; } } #end function Reset-LabMedia # SIG # Begin signature block # MIIXtwYJKoZIhvcNAQcCoIIXqDCCF6QCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU49Ma5IDTVqsckespn78BOIvj # P1ugghLqMIID7jCCA1egAwIBAgIQfpPr+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 # NwIBFTAjBgkqhkiG9w0BCQQxFgQUXyADhb5kEqdW4pf5mx06usCPoYIwDQYJKoZI # hvcNAQEBBQAEggEATRIr6Itu4DtfG3Tm6GT5j3ek6E1LgKhT1w1HNHz9IFua0jY6 # AgkiJoa9YPW2Aw39v2JxhtwBpMfHgvYJVMYxYo0sbTgUaokvFEIALCIKKeqIqnYL # a5e4YaVDk+lG0BtEZbgM2lrBfn0ACSfGktrS/gOhzZpWFoMp/mWgGwl97vS5Gx+d # 08f2Dt5zvBlVRLJtDTrbCe0gE46vw+jiCzzqu9iMSaSSYcaloHVIHN+qzaFOYs6G # 5cDceyr5bIOG2e18xwVxMCH9JVA7WNLm1dOgLYkf+6HX9PSi1IT4X89aF103Bf2m # QmdAx9VsSRGucxtMgZzhoPodl/Jl7yhQlL0BFKGCAgswggIHBgkqhkiG9w0BCQYx # ggH4MIIB9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBD # b3Jwb3JhdGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2 # aWNlcyBDQSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkq # hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNzAzMTIyMTAz # NTFaMCMGCSqGSIb3DQEJBDEWBBQqSUQcYAQbwSaWdw2qUn1OyqPnljANBgkqhkiG # 9w0BAQEFAASCAQAwQ91yU7/YwH0nim2QM64hMtrY0nVmt2P5xQmeu1d39y6ivl9I # qGwYdkx8jJNJcETOf8l9yaOagTYIdp58siNSXb9lCsMdfFMAjeZVPld+b3av6wjr # RAZTbIs0Evifp8jC3/8YToMjOouVHMO9Q1Nfikc1KaO8e9wm+6GJBX1e0VVbtvtf # yxxoT2m31HCAHsAk/5KMP9BQFyzMJxpn1rl3OJ+q6NSEhKa3+dG326lJUjIE3lf2 # VhP8RDgX5wOhO8icYFmT/6fN8NSY8iAEvswh/3bMVeY85dzx23TarMqQ4iqgjGsn # dkXOLKxVWI/dTWreSrRwZGJ8vFoAC50r/Pco # SIG # End signature block |