Microsoft.AzureStack.HCI.CSSTools.psm1

#################################################################
# #
# Copyright (C) Microsoft Corporation. All rights reserved. #
# #
#################################################################

[CmdletBinding()]
param (
    [Parameter(Mandatory = $false, Position = 0)]
    [Bool]$SkipStartupActions = $false
)
$skipExplicitlySet = $PSBoundParameters.ContainsKey('SkipStartupActions')
[void]$PSBoundParameters.Remove('SkipStartupActions')

# check if we are within a PSSession or runspace
# if we are and SkipStartupActions wasn't explicitly set, we skip the startup actions
if ($PSSenderInfo -and -not $skipExplicitlySet) {
    $SkipStartupActions = $true
}

#################################################################
# #
# STARTUP ACTIONS ON IMPORT #
# #
#################################################################

Import-LocalizedData -BindingVariable 'msg' -BaseDirectory "$PSScriptRoot\locale" -UICulture (Get-Culture) -WarningAction SilentlyContinue
New-Variable -Name 'CSSTools_AzsSupport' -Scope 'Global' -Force -Value @{
    Cache           = @{}
    EnvironmentInfo = @{
        WindowsProductName = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name 'ProductName' -ErrorAction Ignore).ProductName
        OSDisplayVersion = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name 'DisplayVersion' -ErrorAction Ignore).DisplayVersion
        CloudName = [string]::Empty # this is set later
    }
    ModuleVersion   = $null
}

#################################################################
# #
# ENUMS AND CLASSES #
# #
#################################################################

enum Component {
    OS
    AzureArcResourceBridge
}

#################################################################
# #
# FUNCTIONS #
# #
#################################################################

function Get-AzsSupportStampInformation {
    <#
    .SYNOPSIS
        Gets common stamp information
    .DESCRIPTION
        Queries for common stamp information properties such as DeplymentID, OEMVersion and CloudID
    .EXAMPLE
        PS> Get-AzsSupportStampInformation
    .OUTPUTS
        Outputs the Stamp Information
    #>


    [CmdletBinding()]
    param()

    try {
        $stampInfo = Get-StampInformation -ErrorAction Stop
        $stampInformation = [Ordered]@{
            DeploymentID           = $stampInfo.DeploymentID
            OemVersion             = $stampInfo.OemVersion
            StampVersion           = $stampInfo.StampVersion
            ServicesVersion        = $stampInfo.ServicesVersion
            PlatformVersion        = $stampInfo.PlatformVersion
            InitialDeployedVersion = $stampInfo.InitialDeployedVersion
            NetworkSchemaVersion   = $stampInfo.NetworkSchemaVersion
            Prefix                 = $stampInfo.Prefix
            CompanyName            = $stampInfo.CompanyName
            ServerSku              = $stampInfo.ServerSku
            Topology               = $stampInfo.Topology
            TimeZone               = $stampInfo.TimeZone
            HardwareOEM            = $stampInfo.HardwareOEM
            RegionName             = $stampInfo.RegionName
            DomainNetBIOSName      = $stampInfo.DomainNetBIOSName
            DomainFQDN             = $stampInfo.DomainFQDN
            TimeServer             = $stampInfo.TimeServer
            NumberOfNodes          = $stampInfo.NumberOfNodes
            CloudID                = $stampInfo.CloudID
            RingName               = $stampInfo.RingName
            InstallationMethod     = $stampInfo.InstallationMethod
            HardwareClass          = $stampInfo.HardwareClass
        }
    }
    catch {
        $_ | Write-Error
    }

    return $stampInformation
}

function Get-ModuleVersion {
    $manifest = Test-ModuleManifest -Path "$PSScriptRoot\Microsoft.AzureStack.HCI.CSSTools.psd1"
    $Global:CSSTools_AzsSupport.ModuleVersion = $manifest.Version.ToString()
    return $manifest.Version.ToString()
}


function Get-EntryText {
@'
 
#################################################################################
                _ _ _
               / \ _____ _ _ __ ___ | | ___ ___ __ _| |
              / _ \ |_ / | | | '__/ _ \ | | / _ \ / __/ _` | |
             / ___ \ / /| |_| | | | __/ | |__| (_) | (_| (_| | |
            /_/ \_\/___|\__,_|_| \___| |_____\___/ \___\__,_|_|
 
#################################################################################
 
Provide feedback or suggestions to azscssdiag@microsoft.com
 
Tool Tips:
    List CSSTools commands: Get-Command -Module Microsoft.AzureStack.HCI.CSSTools
    Get help with examples: Get-Help 'Verb-Command' -Full
    Check for common issues: Invoke-AzsSupportInsight
    Check for new updates: Find-Module -Name Microsoft.AzureStack.HCI.CSSTools
    Install latest version: Update-Module -Name Microsoft.AzureStack.HCI.CSSTools -Force
 
Clean up the working directory after you are done to reclaim disk space:
    Clear-AzsSupportDirectory
 
'@
 | Write-Host -ForegroundColor:Green

    # display primary stamp info properties
    $stampInfo = Get-AzsSupportStampInformation
    if ($stampInfo) {
        $stampInfo += @{
            CSSToolsVersion = $Global:CSSTools_AzsSupport.ModuleVersion
            CloudName       = $Global:CSSTools_AzsSupport.EnvironmentInfo.CloudName
        }

        $maxKeyLength = ($stampInfo.Keys | Measure-Object -Property Length -Maximum).Maximum
        $stampInfo.Keys | ForEach-Object {
            $key = $_.PadRight($maxKeyLength)
            $value = "N/A"
            if (![string]::IsNullOrEmpty($stampInfo[$_])) {
                $value = $stampInfo[$_]
            }

            # Print aligned output
            "{0} --> {1}" -f $key, $value | Write-Host -ForegroundColor:Gray
        }
    }
    else {
        $msg.stampInfoNotAvailable | Write-Warning
    }
    "" | Write-Host
}

function New-AzsSupportDataBundle () {

    param (
        # AUTOMATIC DATA COLLECTION SPECS
        [Parameter(ParameterSetName = 'DataCollectAuto')]
        [Component] $Component,

        # MANUAL DATA COLLECTION SPECS
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $ClusterCommands,
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $NodeCommands,
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $NodeEvents,
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $NodeRegistry,
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $NodeFolders,
        [Parameter(ParameterSetName = 'DataCollectManual')]
        [array] $ComputerName
    )

    Trace-Output -Level:Information -Message $msg.startingNewDataBundle
    $runtime = Register-CommandRuntime

    switch ($PSCmdlet.ParameterSetName.ToLower()) {
        "datacollectmanual" {

            if(($NodeCommands -or $NodeEvents -or $NodeRegistry -or $NodeFolders) -and (-Not $ComputerName)) {
                Trace-Output -Level:Error -Message $msg.errComputerNameNotSupplied
            } else {
                Invoke-DataCollection `
                    -runtime            $runtime `
                    -clusterCommands    $ClusterCommands `
                    -nodeCommands       $NodeCommands `
                    -nodeEvents         $NodeEvents `
                    -nodeRegistry       $NodeRegistry `
                    -nodeFolders        $NodeFolders `
                    -ComputerName       $ComputerName
            }

        }

        "datacollectauto" {
            Trace-Output -Level:Information -Message $msg.startingAutomaticCollection
            Invoke-AutoDataCollection -runtime $runtime -Component $Component
        }

        Default {}
    }

}

function Invoke-DataCollection() {
    param(
        [string] $runtime,
        [array] $ClusterCommands,
        [array] $NodeCommands,
        [array] $NodeEvents,
        [array] $NodeRegistry,
        [array] $NodeFolders,
        [array] $ComputerName
    )

    Trace-Output -Level:Information -Message $msg.startingDataCollection
    $name = "SupportDataBundle"

    # important default information we want to always collect.
    $NodeCommands = $NodeCommands + @("Get-ChildItem env:*", "gpresult /h STORAGE_DEST/gpresultoutput.html")

    # manually collecting data
    Collect-SupportData `
        -runtime            $runtime `
        -clusterCommands    $ClusterCommands `
        -nodeCommands       $NodeCommands `
        -nodeEvents         $NodeEvents `
        -nodeRegistry       $NodeRegistry `
        -nodeFolders        $NodeFolders `
        -ComputerName       $ComputerName `
        -customName         $name
}

function Invoke-AutoDataCollection() {
    param(
        [string] $runtime,
        [Component] $Component
    )
    $storage = Get-WorkingDirectory
    $storageTemp = ('{0}\temp' -f $storage)

    if((Test-Path -path $storageTemp) -eq $false) {
        New-Item -Type:Directory -Path $storageTemp | Out-Null
    }

    Trace-Output -Level:Verbose -Message $Component

    switch ($Component) {
        ([Component]::OS) {
            # Automatic OS log collection
            $cmd = Get-Command -Name "Send-DiagnosticData" -ErrorAction Ignore

            if($cmd) {
                Collect-SupportData `
                    -nodeCommands @('Send-DiagnosticData -SaveToPath STORAGE_DEST -FromDate ((Get-Date).AddDays(-1)) -ToDate (Get-Date) -CollectSddc:$true') `
                    -ComputerName (Get-ClusterNode)
            } else {
                Trace-Output -Level:Error -Message $msg.errSendDiagDataNotAvailable
            }
        }
        ([Component]::AzureArcResourceBridge) {
            Trace-Output -Level:Verbose -Message "Starting Resource Bridge Data Collection"
            Trace-Output -Level:Information -Message $msg.arcApplianceLogStart
            $destPath = ("{0}\SupportDataBundle-{1}" -f $storage, (Get-Date -Format "HH-mm_dd-MM-yyyy"))

            $azTenant = Read-Host -Prompt $msg.questionTenantLogin

            Trace-Output -Level:Information -Message $msg.arcApplianceLogConfig
            # get environment information
            $azStackInfo = Get-AzureStackHCI
            $azStackUri = $azStackInfo.AzureResourceUri.split("/")
            $azSub = $azStackUri[2] # xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
            $azReg = $azStackUri[4] # sample-rg

            $arcHciConfigObject = Get-ArcHciConfig
            $arcHciConfigPath = ("{0}/hci-resource.yaml" -f $arcHciConfigObject.workingDir)
            $arcHciConfig = Get-Content -Path $arcHciConfigPath
            $arcHciConfig = $arcHciConfig.trim()
            $arcHciConfigApplianceName = ""


            foreach($arcHciConfigEntry in $arcHciConfig) {
                if($arcHciConfigEntry.indexOf("name:") -ne -1){
                    $arcHciConfigApplianceName = $arcHciConfigEntry.split(":")[1].trim()
                }
            }

            Trace-Output -Level:Information -Message $msg.arcApplianceLogLogin
            az login --tenant $azTenant --use-device-code
            az account set --subscription $azSub

            Trace-Output -Level:Information -Message $msg.arcApplianceLogCollectionStart
            az arcappliance get-credentials -g $azReg -n $arcHciConfigApplianceName --overwrite-existing --credentials-dir $storageTemp

            Get-ChildItem -path $storageTemp | ForEach-Object {
                Icacls $storageTemp /c /t /Inheritance:d                | Out-Null
                Icacls $storageTemp /c /t /Grant ${env:UserName}:F      | Out-Null
                TakeOwn /F $storageTemp                                 | Out-Null
                Icacls $storageTemp /c /t /Grant:r ${env:UserName}:F    | Out-Null
                Icacls $storageTemp /c /t /Remove:g Administrator "Authenticated Users" BUILTIN\Administrators BUILTIN Everyone System Users | Out-Null
            } | Out-Null

            az arcappliance logs hci `
            --cloudagent $arcHciConfigObject.cloudFqdn `
            --credentials-dir $storageTemp `
            --ip $arcHciConfigObject.controlPlaneIP `
            --kubeconfig ("{0}\kubeconfig" -f $arcHciConfigObject.workingDir) `
            --loginconfigfile ("{0}\kvatoken.tok" -f $arcHciConfigObject.workingDir) `
            --out-dir $destPath

            if($destPath) {
                $destZipPath = ("{0}.zip" -f $destPath)
                Compress-Archive -Path $destPath -DestinationPath ("{0}.zip" -f $destZipPath)
                Remove-Item -Path $destPath -Recurse -Force
                Trace-Output -Level:Success -Message ($msg.CollectionDataEnd -f $destZipPath)
            } else {
                Trace-Output -Level:Error -Message $msg.arcApplianceLogCollectionError
            }

            Remove-Item -Path $storageTemp -Recurse -Force
        }
        Default {
            Trace-Output -Level:Error -Message $msg.errComponentNotFound
        }
    }
}

function Confirm-AzsSupportOSVersion {
    <#
    .SYNOPSIS
        Validates the OS version against a specified version or minimum version.
    .DESCRIPTION
        This function checks the current OS version against a specified version or minimum version.
        It throws an error if the current OS version does not match the specified version or is below the minimum version.
    .PARAMETER Version
        The exact OS version to confirm against the current OS version.
    .PARAMETER MinimumVersion
        The minimum OS version that the current OS version must meet or exceed.
    .EXAMPLE
        Confirm-AzsSupportOSVersion -Version "23H2"
        # This will throw a terminating exception if the current OS version is not "23H2".
    .EXAMPLE
        Confirm-AzsSupportOSVersion -MinimumVersion "22H2"
        # This will throw a terminating exception if the current OS version is below "22H2".
    #>

    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ConfirmOSVersion')]
        [ValidateSet("22H2", "23H2", "24H2", "25H2")]
        [string]$Version,

        [Parameter(Mandatory = $true, ParameterSetName = 'ConfirmMinimumOSVersion')]
        [ValidateSet("22H2", "23H2", "24H2", "25H2")]
        [string]$MinimumVersion
    )

    $osOrder = @("22H2", "23H2", "24H2", "25H2")
    $currentVersion = $Global:CSSTools_AzsSupport.EnvironmentInfo.OSDisplayVersion

    switch ($PSCmdlet.ParameterSetName) {
        'ConfirmOSVersion' {
            if ($currentVersion -ne $Version) {
                throw ($msg.osNotSupported -f $Version)
            }
        }
        'ConfirmMinimumOSVersion' {
            $minIndex = $osOrder.IndexOf($MinimumVersion)
            $curIndex = $osOrder.IndexOf($currentVersion)
            if ($curIndex -lt 0) {
                throw ($msg.osNotSupportedGeneric)
            }
            if ($curIndex -lt $minIndex) {
                throw ($msg.osNotSupported -f $MinimumVersion)
            }
        }
    }
}

#################################################################
# #
# SECONDARY STARTUP ACTIONS ON IMPORT #
# #
#################################################################

$null = Get-ModuleVersion
try {
    $cloudData = az cloud Show | ConvertFrom-Json
    if ($cloudData) {
        $Global:CSSTools_AzsSupport.EnvironmentInfo.CloudName = $cloudData.Name
    }
}
catch {
    $_.Exception.Message | Write-Warning
}

# print the entry text if we are not skipping the startup actions
if (!$SkipStartupActions) {
    Get-EntryText

    Write-Host "" # just a spacer line
    Write-Information -MessageData "Microsoft.AzureStack.HCI.CSSTools is being deprecated. For future releases, please use the new Microsoft.AzLocal.CSSTools module." -InformationAction:Continue
    Write-Host "" # just a spacer line
}

Set-Location -Path (Get-AzsSupportWorkingDirectory)

# Remove any existing PSSessions that were created by this module
# This is to ensure that we do not have any stale sessions that could cause issues
# when running commands in the module.
Remove-AzsSupportPSSession

# SIG # Begin signature block
# MIIoUgYJKoZIhvcNAQcCoIIoQzCCKD8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAYrFZkNHVFZ09A
# 9jIrfJLcoi6VkWyPqBPVf7UVrBunAaCCDYUwggYDMIID66ADAgECAhMzAAAEhJji
# EuB4ozFdAAAAAASEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM1WhcNMjYwNjE3MTgyMTM1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDtekqMKDnzfsyc1T1QpHfFtr+rkir8ldzLPKmMXbRDouVXAsvBfd6E82tPj4Yz
# aSluGDQoX3NpMKooKeVFjjNRq37yyT/h1QTLMB8dpmsZ/70UM+U/sYxvt1PWWxLj
# MNIXqzB8PjG6i7H2YFgk4YOhfGSekvnzW13dLAtfjD0wiwREPvCNlilRz7XoFde5
# KO01eFiWeteh48qUOqUaAkIznC4XB3sFd1LWUmupXHK05QfJSmnei9qZJBYTt8Zh
# ArGDh7nQn+Y1jOA3oBiCUJ4n1CMaWdDhrgdMuu026oWAbfC3prqkUn8LWp28H+2S
# LetNG5KQZZwvy3Zcn7+PQGl5AgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUBN/0b6Fh6nMdE4FAxYG9kWCpbYUw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwNTM2MjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AGLQps1XU4RTcoDIDLP6QG3NnRE3p/WSMp61Cs8Z+JUv3xJWGtBzYmCINmHVFv6i
# 8pYF/e79FNK6P1oKjduxqHSicBdg8Mj0k8kDFA/0eU26bPBRQUIaiWrhsDOrXWdL
# m7Zmu516oQoUWcINs4jBfjDEVV4bmgQYfe+4/MUJwQJ9h6mfE+kcCP4HlP4ChIQB
# UHoSymakcTBvZw+Qst7sbdt5KnQKkSEN01CzPG1awClCI6zLKf/vKIwnqHw/+Wvc
# Ar7gwKlWNmLwTNi807r9rWsXQep1Q8YMkIuGmZ0a1qCd3GuOkSRznz2/0ojeZVYh
# ZyohCQi1Bs+xfRkv/fy0HfV3mNyO22dFUvHzBZgqE5FbGjmUnrSr1x8lCrK+s4A+
# bOGp2IejOphWoZEPGOco/HEznZ5Lk6w6W+E2Jy3PHoFE0Y8TtkSE4/80Y2lBJhLj
# 27d8ueJ8IdQhSpL/WzTjjnuYH7Dx5o9pWdIGSaFNYuSqOYxrVW7N4AEQVRDZeqDc
# fqPG3O6r5SNsxXbd71DCIQURtUKss53ON+vrlV0rjiKBIdwvMNLQ9zK0jy77owDy
# XXoYkQxakN2uFIBO1UNAvCYXjs4rw3SRmBX9qiZ5ENxcn/pLMkiyb68QdwHUXz+1
# fI6ea3/jjpNPz6Dlc/RMcXIWeMMkhup/XEbwu73U+uz/MIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGiMwghofAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAASEmOIS4HijMV0AAAAA
# BIQwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINaf
# v2fgZ8uSmG7y08Vjhj4WqSo+9mWuJ9kk9PvcLtuhMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAJ339DGMq+d/4+/J3Jj4tteYBcCHMhMcsM0Oo
# 6s9aGDuFJHYIDtsPOdt2/5qj8P59WwNP6EOFx0ZkLhkRznaF2qif6S8DoDhbtGMT
# /g5RD2Z5WR99ufb+TM1RGGR+AQUTKMlVVBVja6XkYhNbADvYLRTSAEuB+vK1aHJ8
# 77QSzs1YWYQFCwpBElFME3S+q4NjQ0DcOZIE00DES09d5Cbvw48nzHR9Z6sYK8Fi
# bvlMzF+ufgVBNGnY9LNpeUOPzWY8ZBKCqFq3Mv7A2EXnLwVmn2kn+caEEm+cRy9I
# lvfFAiWHQIsJ9MhOyABpTFC4O7lIwTN5drIERjWWcUn3jTz8PKGCF60wghepBgor
# BgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCDN55x7wdpiugBD2nEZV2RIGr5FTSbp5PKX
# ADW0qRwVugIGaPedHbHiGBMyMDI1MTAyODIwNDEwMy4xMjlaMASAAgH0oIHZpIHW
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAACF3H7
# LqWvAR3qAAEAAAIXMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyM1oXDTI2MTExMzE4NDgyM1owgdMxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
# ZCBUU1MgRVNOOjUyMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# wM82sEw+39vYR7iGCIFDnYNhRM+BzF2AYiq5dUpZpJFPRjCcipQ6RUbI+RAYNRAp
# Exx5ygrXbaWtuwvqsqAVSWbU/W6fecujjILkPqn9pngtWRkfQgbYgvaXALl6PY2y
# OH9f72MD+6AyxQenSpAMdUzY/Qk/jtjsHdFXVBe+tshlIkSJ3GZw8VVKqTg3GZEl
# ztwbJWNtrhBEvhf6anxMegQMJP7tO8/BJ7ITs4/AV3D2bv8eHk81Y+fOmQ8mQ61W
# Lq2wItvlzIT5bzelK9LvEycf5x1lXxAwEw5a7dpS+CKTanhtv+Q2mwebAybjf9io
# 4k48stTaq1rtcrOiDwddqVm1S9e8h1TszXFzjLLvE9EmjnNfIewsY+RChUaHnY4F
# FwwJEnEv/JS76oHT0oGdy7+J60fGOl7A1UoUyAkhpb2Bja+SwSIiHbQ4FDyJiLlZ
# 6drZZ84MoJ852JSxM0hBjGO6FZlPO8iuNyk680Di8VnbSNpIdJN+DhlepeTUMBDH
# qCmd0mVWRWZPm1pvgty93asNt/Ng6o4m2dnooWOdM3yKsJaWjyHqic9gfTrZBM+P
# CXqeTaO1oEiaQ+h4w0nHVdV+XSvI2m1yN4iibqjm5HPaAO3OJ+OmNLftNVmr4Z6U
# 2T6pIcLBysoKcDUvCqycXj4C/+n1KFBpDGdDMw9gmu8CAwEAAaOCAUkwggFFMB0G
# A1UdDgQWBBRQrN9jlwNOoeE5ZQqnF5x8S1bJQzAfBgNVHSMEGDAWgBSfpxVdAF5i
# XYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
# JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRp
# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1Ud
# JQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
# AAOCAgEARmgFdhB7xIAIHEEg5I/5S+gx67aR6RiW8ZAwtE3mz8o0dyn+pIP+lidN
# R1IKQQ0r+RjYgI9cZ6mbvAyvh3e2q/BV8rjHE3ud9PyYyq32euFgdZ3vX4b5QXeP
# WlpBAYrdziR27rHz6WwpH5dZsSypbXDBbQkWkNl6g82yTy3AbBbKDXBdzxZsEaua
# OplatK7Er4dhglKBex8JQ2dMSkSZweCNDXqd9r/9W2VdRZsDJKP/Xc4UyQlVsboB
# otKtYESXFkjwR1HVsH+Q0C69/N5CP/Tq3YgI1ub4b9+3MJFKWhJXCcJGFZkcLwUm
# YwoFg1XLo7DLJdGjrIH1jsI2NFXJFQHef6AdRe1ERvYQeqtyrBvxIvR+P/83FNYy
# zx04inUT9TF2AwTOuqCC6Z67oNwR4pEEJyAIEREvkdhjjfWcgsk/nGTlfahvNY/S
# OHrNRKo49KDlccNzRCJQyQ+D59r7/qebNSyQPTfwI9++jEY0Q/UWKVNLhio55GYB
# seJ99s7NzkdxOr9Uftp597HEovbA69qGlZ3OpUE3H1RBGDVp/FvM2uXTum8LrMkP
# Xx5Ap/kbPASsC9ju9oMCe2IEXO2SeD1aD3IqvAOdHFKHg1vpbPUQSWb6g2xfBV30
# wFcqaPYgzcbxPWPyZqK+S8l7zw64aO5hmJ7eQwoMfTu0Vay6r48wggdxMIIFWaAD
# AgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIy
# MjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5
# vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64
# NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhu
# je3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl
# 3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPg
# yY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I
# 5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2
# ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/
# TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy
# 16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y
# 1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6H
# XtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMB
# AAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQW
# BBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30B
# ATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYB
# BAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMB
# Af8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBL
# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNS
# b29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1Vffwq
# reEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27
# DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pv
# vinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9Ak
# vUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWK
# NsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2
# kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+
# c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep
# 8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+Dvk
# txW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1Zyvg
# DbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/
# 2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHW
# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
# Hm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAabKAFaKt2haU
# dqkHfFYzAzfgSMuggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDANBgkqhkiG9w0BAQsFAAIFAOyrVXowIhgPMjAyNTEwMjgxNDQ1MTRaGA8yMDI1
# MTAyOTE0NDUxNFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA7KtVegIBADAHAgEA
# AgICJjAHAgEAAgISOjAKAgUA7Kym+gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor
# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUA
# A4IBAQCFqHNBTRfP+zUwUnQKUc8QFsWkUD893J6uL0HNJq4Gsu0PhSrXW6/oDN2p
# IdPT5zVvgcRa9rnTu8zAAoV7v/sU3AAInJrSYvTXCvRyrfNqe/rDGJRyiE3m2GN/
# HvoiRQpdIZ9KsLDAvZ2nl2Tsm3FaUT+WV4HhgAzZ8V2tNHBIoRlY4Pkv3QusquiZ
# eGip2m/oSxVvGPUVNP9e9XVeVa5Kh/csPC7RKxL3boy9uSAHUsq45qLXk7TVU0w8
# 54NB3nPpRZJKV9pmV1UoAoKxV+g8KSlAS0ceyDBwpdQJbapKMF+AiEeWUldyAOSn
# 7E2/DXl4QlTGrvS6y4RcuYN9M3sUMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAIXcfsupa8BHeoAAQAAAhcwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQg0JJcz6CEbXYLCrQ3BO15uZQbUbSPUEFyf2eHYAtOsxAwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCDQ8lBgPl23yZ0SzUSt5phOIegHPywrkNwevxe2
# k+RaWzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAC
# F3H7LqWvAR3qAAEAAAIXMCIEIIP42c6AjaR2eEAWNp2RinULnCtTAy329lRNrmIu
# 71RXMA0GCSqGSIb3DQEBCwUABIICAHR3SP8OJV2nJEqM6FT1sIQhGVxUasw+Gj5R
# 8/Vrh6q8QGbzK2rcCHo5cl+z8TVmCWGWimEEZqQCvVszIt/1Kc/aRkkmrY2tDRfc
# vQsEaFl+2jhLgimd7dajm9zqOBQ1v4W1Nein0OmsaC5Z7PrH+sZ9v6oiVlzKa+g0
# 0Ldwj0KLlXqRLC4fhWutudxMqT4fYv/Cxlc9RcXUlRyFHcblZGaT6cAi//6taARh
# DsrSX6t4DI7GqudrkTLJBlwgUyi55OYgD8uSS8oUNtg2bV+Jb7PJZ2K8ZmY+TsH9
# TNstK9NqZq/ML3waiAFrRK82qJ03dvTKFyhCpDGXXsYrgvkJrKtLeHnMqGIF8pi1
# swaRgylKudVg7YG9YXk5W4GyE5x/DhGnNDhCRUrZfdsdWnjBoFLbSTp37AO/hypf
# mBCjVvh0u7Jy0lgKGaq5mutyKG9rC8w5lqQsA7R0FoI+th8himU7K7Pfwj6ij26K
# C2JgatHTb5uFZWapopvUWs8lXmULKM2u3HyQLEF+1jGhJJ7S2gRsJanFluMErpj4
# A3/b3CtCpstG/fAUK3arFlcvAxk+IdN0rollsiBf9HP8mARrvKYf3HhCGRv6gIHV
# VuyjN1onavWCnjzT4XvnQ8zO2HWuIC9IzyXmbBgAUo0LveFla1bqfdH0jTXhXTkM
# xv+U7PwX
# SIG # End signature block