Src/LabNode.ps1

<# DEPRECATED
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
#>


<# DEPRECATED
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
#>


<# DEPRECATED
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 = Resolve-NodePropertyValue -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, (Get-ConfigurationData -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);
                $testLabResourceIsLocalParams = @{
                    ConfigurationData = $ConfigurationData;
                    ResourceId = $resourceId;
                    LocalResourcePath = $DestinationPath;
                }
                $isAvailableLocally = TestLabResourceIsLocal @testLabResourceIsLocalParams;
                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
#>


<# DEPRECATED
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.
    .NOTES
        Deprecated functionality. This will move into the LabilityBootstrap module.
    .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 = Resolve-NodePropertyValue -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, (Get-ConfigurationData -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) {
                    #InvokeModuleCacheDownload
                    #ExpandModuleCache
                    # 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
#>


<# DEPRECATED
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 = Resolve-NodePropertyValue -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
#>



<# DEPRECATED
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
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQURDRhZc/LzaVa5YrI6KKytgLi
# 08igghLqMIID7jCCA1egAwIBAgIQfpPr+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
# NwIBFTAjBgkqhkiG9w0BCQQxFgQUo4SMx+J9laqb37qwZfJ93mLLjikwDQYJKoZI
# hvcNAQEBBQAEggEApI35CZnQkunRi0pPYQPigYPN8AXvazPqdZiBufXGytsfSYFg
# ticQoBVDvtfHwbzSarWjUFehdMlspP1Tx2xp+k9N6y2nwIuWvo5xvfT+hf9joSmY
# 58mg/rEsTcFMRTqygkk2laHrq85CRSabyLtUMmOvhG4DcheyfVc1CwIIJEux+Zlr
# 8ObfWKgo9excMHQOVLwWT1GWTwy+DSnbxkBhWa2lZXIW3kjT6HvInIP7AmKuQbMe
# xZitpjbego0oC8Sqv+KYQuqiPPLGmPnQufsS6wQU9hgIhZvpJ5ITds+rTO8PUKNg
# CEa0Ladwn7qBkariW8tU9/xNum6rTwLk5cxAraGCAgswggIHBgkqhkiG9w0BCQYx
# ggH4MIIB9AIBATByMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBD
# b3Jwb3JhdGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2
# aWNlcyBDQSAtIEcyAhAOz/Q4yP6/NW4E2GqYGxpQMAkGBSsOAwIaBQCgXTAYBgkq
# hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNzAzMTIyMTAz
# NTJaMCMGCSqGSIb3DQEJBDEWBBRtixhtjcTvMBKsOdO45RrY0qEr7TANBgkqhkiG
# 9w0BAQEFAASCAQB4ThoQUkeu4n8FEnxvhztlqU5S2jkQwJp1ViVFUzNt7/EUAbna
# BUZ+rTPuLhmGZsSF3kGgrkV/ZYfb1PjmvrmepIIsyxvJOp4UotiYcoMZkycs9sD8
# +HIjqjETAsi93HVpcQit63J0lTP7HqaRZgoRY8JsdfLvYvCtHFtsjDiwr672vJme
# Y8bpOh31Fy/NgQkBxNYCWbrROMi78GVVufYVk/7zzFPBayuGwPrF7d0Kt/EnLSgc
# chuIvGc3wCF88cRmuRBs650NfqqW5UiMJJ8Q0C+zD89K4V+ziudoG0I3QeRyvy1b
# MVz2Ar9Ik7nm0jWg8pSESs1Epn+XZRJtZML6
# SIG # End signature block