Get-SystemInfo.ps1

<#PSScriptInfo
 
.VERSION 2.0.1
 
.GUID 21f7b5b3-f9bd-4611-a846-9372c3a89275
 
.AUTHOR asheroto
 
.COMPANYNAME asheroto
 
.TAGS PowerShell Windows get system info information hardware firmware details disk memory network pending reboot usage shutdown event last
 
.PROJECTURI https://github.com/asheroto/Get-SystemInfo
 
.RELEASENOTES
[Version 1.0.0] - Initial Release.
[Version 1.0.1] - Added TPM information support.
[Version 1.0.2] - Added graphics card information support.
[Version 2.0.0] - Redesigned for improved functionality with robust support for object-oriented usage, allowing easy access to specific diagnostic sections.
[Version 2.0.1] - Fixed issue with parameters.
 
#>


<#
.SYNOPSIS
    Gathers detailed system diagnostics, including configuration, hardware, network, and OS status.
 
.DESCRIPTION
    This function provides a complete overview of system information, hardware specifications, network details, and pending reboot status. It is designed to be used either as a standalone script for console output or programmatically as a function to retrieve diagnostics as an object.
 
.PARAMETER CheckForUpdate
    Checks if there is an update available for the script. The latest version information is retrieved from GitHub.
 
.PARAMETER UpdateSelf
    Updates the script to the latest version available in the PowerShell Gallery.
 
.PARAMETER Version
    Displays the current version of the script.
 
.PARAMETER Help
    Displays detailed help information for the script, including usage examples.
 
.PARAMETER Silent
    Suppresses console output when running the script. Useful for retrieving the diagnostics as an object without any visible output.
 
.EXAMPLE
    .\Get-SystemInfo.ps1
    Runs the script and displays all diagnostics in a well-formatted console output.
 
.EXAMPLE
    . .\Get-SystemInfo.ps1 -Silent
    $info = Get-SystemInfo
    Retrieves all diagnostics as an object for programmatic access.
 
.EXAMPLE
    Get-SystemInfo -CheckForUpdate
    Checks for updates to the script.
 
.EXAMPLE
    Get-SystemInfo -UpdateSelf
    Updates the script to the latest version from the PowerShell Gallery.
 
.INPUTS
    None.
 
.OUTPUTS
    [PSCustomObject]
    Returns a custom object containing system diagnostics, including sections such as System, Hardware, TPM, OS, CPU, Memory, Disks, Graphics, NetworkAdapters, PendingReboot, and ShutdownEvents.
 
.NOTES
    Author: asheroto
    Version: 2.0.1
    Repository: https://github.com/asheroto/Get-SystemInfo
 
.LINK
    https://github.com/asheroto/Get-SystemInfo
#>


[CmdletBinding()]
param (
    [switch]$CheckForUpdate,
    [switch]$UpdateSelf,
    [switch]$Version,
    [switch]$Help,
    [switch]$Silent
)

# Script information
$CurrentVersion = '2.0.1'
$RepoOwner = 'asheroto'
$RepoName = 'Get-SystemInfo'
$PowerShellGalleryName = 'Get-SystemInfo'

# Preferences
$ProgressPreference = 'SilentlyContinue'
$ConfirmPreference = 'None'

function ExitWithDelay {
    <#
        .SYNOPSIS
            Exits the script with a specified exit code after a specified delay, 10 seconds by default.
 
        .DESCRIPTION
            This function takes an exit code as an argument, waits for 10 seconds unless specified, and then exits the script with the given exit code.
 
        .PARAMETER ExitCode
            The exit code to use when exiting the script.
 
        .EXAMPLE
            ExitWithDelay -ExitCode 1
            Waits for 10 seconds (default) and then exits the script with an exit code of 1.
 
        .EXAMPLE
            ExitWithDelay -ExitCode 2 -Seconds 5
            Waits for 5 seconds and then exits the script with an exit code of 2.
        .NOTES
            Use this function to introduce a delay before exiting the script, allowing time for any cleanup or logging activities.
    #>


    param (
        [int]$ExitCode,
        [int]$Seconds = 10
    )

    # Debug mode output
    if ($Debug -and $Wait) {
        Write-Warning "Wait specified, waiting several seconds..."
    } elseif ($Debug -and !$Wait) {
        Write-Warning "Wait not specified, exiting immediately..."
    }

    # If Wait is specified, wait for x seconds before exiting
    if ($Wait) {
        # Waiting for x seconds output
        Write-Output "`nWaiting for $Seconds seconds before exiting..."
        Start-Sleep -Seconds $Seconds
    }

    # If NoExit is specified, do not exit the script
    if ($NoExit) {
        Write-Output "Script completed. Pausing indefinitely. Press any key to exit..."
        Read-Host
    }

    # Exit the script with exit code
    if ($MyInvocation.CommandOrigin -eq "Runspace") {
        Break
    } else {
        Exit $ExitCode
    }
}

function Get-SystemInfo {
    $result = [PSCustomObject]@{
        System          = [PSCustomObject]@{
            Hostname = $env:COMPUTERNAME
        }
        Hardware        = [PSCustomObject]@{
            MakeModel            = $null
            SerialNumber         = $null
            FirmwareManufacturer = $null
            FirmwareVersion      = $null
        }
        TPM             = [PSCustomObject]@{
            IsActivated = $false
            IsEnabled   = $false
            IsOwned     = $false
            Version     = $null
        }
        OS              = [PSCustomObject]@{
            Version        = $null
            DisplayVersion = $null
            Architecture   = $null
            Type           = $null
            InstallDate    = $null
            LastBootTime   = $null
            Uptime         = $null
        }
        CPU             = [PSCustomObject]@{
            MakeModel = $null
            SpeedGHz  = $null
            Usage     = $null
            Cores     = $null
            Threads   = $null
        }
        Memory          = [PSCustomObject]@{
            TotalGB      = $null
            UsedGB       = $null
            UsagePercent = $null
            DIMMs        = @()
        }
        Disks           = @()
        Graphics        = @()
        NetworkAdapters = @()
        PendingReboot   = @()
        ShutdownEvents  = @()
    }

    # Populate Hardware Information
    $cs = Get-CimInstance -ClassName Win32_ComputerSystem
    $bios = Get-CimInstance -ClassName Win32_BIOS
    $result.Hardware.MakeModel = "$($cs.Manufacturer) $($cs.Model)"
    $result.Hardware.SerialNumber = $bios.SerialNumber
    $result.Hardware.FirmwareManufacturer = $bios.Manufacturer
    $result.Hardware.FirmwareVersion = $bios.SMBIOSBIOSVersion

    # Populate TPM Information
    $tpm = Get-CimInstance -Namespace "root\cimv2\security\microsofttpm" -ClassName Win32_Tpm
    if ($tpm) {
        $result.TPM.IsActivated = $tpm.IsActivated_InitialValue
        $result.TPM.IsEnabled = $tpm.IsEnabled_InitialValue
        $result.TPM.IsOwned = $tpm.IsOwned_InitialValue
        $result.TPM.Version = ($tpm.SpecVersion -split ',')[0]
    }

    # Populate OS Information
    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    $uptime = (Get-Date) - $os.LastBootUpTime
    $displayVersion = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name DisplayVersion).DisplayVersion

    $result.OS.Version = $os.Caption
    $result.OS.DisplayVersion = $displayVersion
    $result.OS.Architecture = $os.OSArchitecture
    $result.OS.InstallDate = $os.InstallDate
    $result.OS.LastBootTime = $os.LastBootUpTime
    $result.OS.Uptime = "$([math]::Floor($uptime.TotalDays)) days, $($uptime.Hours) hours, $($uptime.Minutes) minutes"

    # Determine OS type (Workstation/Server)
    $result.OS.Type = if ($os.ProductType -eq 1) {
        "Workstation"
    } elseif ($os.ProductType -eq 2 -or $os.ProductType -eq 3) {
        "Server"
    } else {
        "Unknown"
    }

    # Populate CPU Information
    $cpu = Get-CimInstance -ClassName Win32_Processor
    $result.CPU.MakeModel = $cpu.Name
    $result.CPU.SpeedGHz = [math]::Round($cpu.MaxClockSpeed / 1000, 2)
    $result.CPU.Usage = "$(($cpu | Measure-Object -Property LoadPercentage -Average).Average)%"
    $result.CPU.Cores = $cpu.NumberOfCores
    $result.CPU.Threads = $cpu.NumberOfLogicalProcessors

    # Populate Memory Information
    $result.Memory.TotalGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
    $result.Memory.UsedGB = $result.Memory.TotalGB - [math]::Round($os.FreePhysicalMemory / 1MB, 2)
    $result.Memory.UsagePercent = [math]::Round(($result.Memory.UsedGB / $result.Memory.TotalGB) * 100, 2)
    $result.Memory.DIMMs = Get-CimInstance -ClassName Win32_PhysicalMemory | ForEach-Object {
        [PSCustomObject]@{
            DIMMNumber   = $_.DeviceLocator
            SizeGB       = if ($_.Capacity) { [math]::Round($_.Capacity / 1GB, 2) } else { "N/A" }
            Model        = if ($_.PartNumber) { $_.PartNumber } else { "Unknown" }
            SpeedMHz     = if ($_.Speed) { $_.Speed } else { "Unknown" }
            Manufacturer = if ($_.Manufacturer) { $_.Manufacturer } else { "Unknown" }
        }
    }

    # Populate Disk Information
    $result.Disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
        [PSCustomObject]@{
            Drive        = $_.DeviceID
            TotalSizeGB  = [math]::Round($_.Size / 1GB, 2)
            FreeSpaceGB  = [math]::Round($_.FreeSpace / 1GB, 2)
            UsagePercent = [math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 2)
        }
    }

    # Populate Graphics Card Information
    $result.Graphics = Get-CimInstance -ClassName Win32_VideoController | ForEach-Object {
        [PSCustomObject]@{
            Name          = $_.Name
            MemoryGB      = [math]::Round($_.AdapterRAM / 1GB, 2)
            DriverVersion = $_.DriverVersion
            DriverDate    = $_.DriverDate
        }
    }

    $result.NetworkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true } | ForEach-Object {
        $adapter = Get-CimInstance -Query "SELECT * FROM Win32_NetworkAdapter WHERE Index = $($_.Index)"
        [PSCustomObject]@{
            Adapter     = $_.Description
            IP          = $_.IPAddress -join ", "
            MAC         = $_.MACAddress
            SpeedMbps   = if ($adapter.Speed) { [math]::Round($adapter.Speed / 1e6, 2) } else { "Unknown" }
            DHCPEnabled = $_.DHCPEnabled
        }
    }

    # Populate Pending Reboot Information
    $result.PendingReboot = @()

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") {
        $result.PendingReboot += [PSCustomObject]@{
            Source  = "Windows Update"
            Details = "Reboot required for pending updates."
            Path    = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired"
        }
    }

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") {
        $result.PendingReboot += [PSCustomObject]@{
            Source  = "Component-Based Servicing"
            Details = "Reboot required for servicing stack changes."
            Path    = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending"
        }
    }

    if ((Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager").PendingFileRenameOperations) {
        $result.PendingReboot += [PSCustomObject]@{
            Source  = "Pending File Rename Operations"
            Details = "Reboot required for file operations."
            Path    = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager"
        }
    }

    if (Test-Path "HKLM:\SOFTWARE\Microsoft\Cluster\PendingReboot") {
        $result.PendingReboot += [PSCustomObject]@{
            Source  = "Cluster Pending Reboot"
            Details = "Cluster changes require a reboot."
            Path    = "HKLM:\SOFTWARE\Microsoft\Cluster\PendingReboot"
        }
    }

    # Populate Shutdown Events
    $result.ShutdownEvents = Get-WinEvent -FilterHashtable @{ LogName = 'System'; ID = 1074 } |
    Select-Object -First 5 -Property TimeCreated, Message

    return $result
}

function Get-GitHubRelease {
    <#
        .SYNOPSIS
        Fetches the latest release information of a GitHub repository.
 
        .DESCRIPTION
        This function uses the GitHub API to get information about the latest release of a specified repository, including its version and the date it was published.
 
        .PARAMETER Owner
        The GitHub username of the repository owner.
 
        .PARAMETER Repo
        The name of the repository.
 
        .EXAMPLE
        Get-GitHubRelease -Owner "asheroto" -Repo "Get-SystemInfo"
        This command retrieves the latest release version and published datetime of the Get-SystemInfo repository owned by asheroto.
    #>

    [CmdletBinding()]
    param (
        [string]$Owner,
        [string]$Repo
    )
    try {
        $url = "https://api.github.com/repos/$Owner/$Repo/releases/latest"
        $response = Invoke-RestMethod -Uri $url -ErrorAction Stop

        $latestVersion = $response.tag_name
        $publishedAt = $response.published_at

        # Convert UTC time string to local time
        $UtcDateTime = [DateTime]::Parse($publishedAt, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind)
        $PublishedLocalDateTime = $UtcDateTime.ToLocalTime()

        [PSCustomObject]@{
            LatestVersion     = $latestVersion
            PublishedDateTime = $PublishedLocalDateTime
        }
    } catch {
        Write-Error "Unable to check for updates.`nError: $_"
        exit 1
    }
}

function CheckForUpdate {
    param (
        [string]$RepoOwner,
        [string]$RepoName,
        [version]$CurrentVersion,
        [string]$PowerShellGalleryName
    )

    $Data = Get-GitHubRelease -Owner $RepoOwner -Repo $RepoName

    Write-Output ""
    Write-Output ("Repository: {0,-40}" -f "https://github.com/$RepoOwner/$RepoName")
    Write-Output ("Current Version: {0,-40}" -f $CurrentVersion)
    Write-Output ("Latest Version: {0,-40}" -f $Data.LatestVersion)
    Write-Output ("Published at: {0,-40}" -f $Data.PublishedDateTime)

    if ($Data.LatestVersion -gt $CurrentVersion) {
        Write-Output ("Status: {0,-40}" -f "A new version is available.")
        Write-Output "`nOptions to update:"
        Write-Output "- Download latest release: https://github.com/$RepoOwner/$RepoName/releases"
        if ($PowerShellGalleryName) {
            Write-Output "- Run: $RepoName -UpdateSelf"
            Write-Output "- Run: Install-Script $PowerShellGalleryName -Force"
        }
    } else {
        Write-Output ("Status: {0,-40}" -f "Up to date.")
    }
    exit 0
}

function UpdateSelf {
    try {
        # Get PSGallery version of script
        $psGalleryScriptVersion = (Find-Script -Name $PowerShellGalleryName).Version

        # If the current version is less than the PSGallery version, update the script
        if ($CurrentVersion -lt $psGalleryScriptVersion) {
            Write-Output "Updating script to version $psGalleryScriptVersion..."

            # Install NuGet PackageProvider if not already installed
            if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) {
                Install-PackageProvider -Name "NuGet" -Force
            }

            # Trust the PSGallery if not already trusted
            $psRepoInstallationPolicy = (Get-PSRepository -Name 'PSGallery').InstallationPolicy
            if ($psRepoInstallationPolicy -ne 'Trusted') {
                Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null
            }

            # Update the script
            Install-Script $PowerShellGalleryName -Force

            # If PSGallery was not trusted, reset it to its original state
            if ($psRepoInstallationPolicy -ne 'Trusted') {
                Set-PSRepository -Name 'PSGallery' -InstallationPolicy $psRepoInstallationPolicy | Out-Null
            }

            Write-Output "Script updated to version $psGalleryScriptVersion."
            exit 0
        } else {
            Write-Output "Script is already up to date."
            exit 0
        }
    } catch {
        Write-Output "An error occurred: $_"
        exit 1
    }
}

if ($Version.IsPresent) {
    $CurrentVersion
    ExitWithDelay 0
}

if ($Help) {
    Get-Help -Name $MyInvocation.MyCommand.Source -Full
    ExitWithDelay 0
}

if ($CheckForUpdate) {
    CheckForUpdate -RepoOwner $RepoOwner -RepoName $RepoName -CurrentVersion $CurrentVersion -PowerShellGalleryName $PowerShellGalleryName
}

if ($UpdateSelf) {
    UpdateSelf
}

# Display $PSVersionTable and Get-Host if -Verbose is specified
if ($PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose']) {
    $PSVersionTable
    Get-Host
}

# Set debug preferences if -Debug is specified
if ($PSBoundParameters.ContainsKey('Debug') -and $PSBoundParameters['Debug']) {
    $DebugPreference = 'Continue'
    $ConfirmPreference = 'None'
}

# ============================================================================ #
# Main
# ============================================================================ #

$info = Get-SystemInfo

if (-not $Silent) {

    function Write-Section($text) {
        <#
            .SYNOPSIS
            Prints a text block surrounded by a section divider for enhanced output readability.
 
            .DESCRIPTION
            This function takes a string input and prints it to the console, surrounded by a section divider made of hash characters.
            It is designed to enhance the readability of console output.
 
            .PARAMETER text
            The text to be printed within the section divider.
 
            .EXAMPLE
            Write-Section "Downloading Files..."
            This command prints the text "Downloading Files..." surrounded by a section divider.
        #>

        Write-Output ""
        Write-Output ("#" * ($text.Length + 4))
        Write-Output "# $text #"
        Write-Output ("#" * ($text.Length + 4))
        Write-Output ""
    }

    Write-Section "System Information"
    $info.System | Format-List

    Write-Section "Hardware Information"
    $info.Hardware | Format-List

    Write-Section "TPM Information"
    $info.TPM | Format-List

    Write-Section "OS Information"
    $info.OS | Format-List

    Write-Section "CPU Information"
    $info.CPU | Format-List

    Write-Section "Memory Information"
    $info.Memory | Select-Object -Property * -ExcludeProperty DIMMs | Format-List
    $info.Memory.DIMMs | Format-Table -AutoSize

    Write-Section "Disk Information"
    $info.Disks | Format-Table -AutoSize

    Write-Section "Graphics Information"
    $info.Graphics | Format-Table -AutoSize

    Write-Section "Network Adapters"
    $info.NetworkAdapters | Format-Table -AutoSize

    Write-Section "Pending Reboot Information"
    $info.PendingReboot | Format-Table -AutoSize

    Write-Section "Last Shutdown Events"
    $info.ShutdownEvents | Format-Table -AutoSize
}
# SIG # Begin signature block
# MIIhEwYJKoZIhvcNAQcCoIIhBDCCIQACAQExDzANBglghkgBZQMEAgIFADCBiQYK
# KwYBBAGCNwIBBKB7MHkwNAYKKwYBBAGCNwIBHjAmAgMBAAAEEB/MO2BZSwhOtyTS
# xil+81ECAQACAQACAQACAQACAQAwQTANBglghkgBZQMEAgIFAAQwr/6igk/z87wD
# IpBkejPx6bDR6ZLwUs2rWXqLoLwABzb9kIUfeLJzZcLC+vF6JD6SoIIHZDCCA1kw
# ggLfoAMCAQICEA+4p0C5FY0DUUO8WdnwQCkwCgYIKoZIzj0EAwMwYTELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEgMB4GA1UEAxMXRGlnaUNlcnQgR2xvYmFsIFJvb3QgRzMwHhcNMjEw
# NDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBkMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xPDA6BgNVBAMTM0RpZ2lDZXJ0IEdsb2JhbCBHMyBD
# b2RlIFNpZ25pbmcgRUNDIFNIQTM4NCAyMDIxIENBMTB2MBAGByqGSM49AgEGBSuB
# BAAiA2IABLu0rCelSA2iU1+PLoE+L1N2uAiUopqqiouYtbHw/CoVu7mzpSIv/WrA
# veJVaGBrlzTBZlNxI/wa1cogDwJAoqNKWkajkVMrlfID6aum04d2L+dkn541UfzD
# YzV4duT4d6OCAVcwggFTMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJtf
# sDa6nQauGSe9wKAiwIuLOHftMB8GA1UdIwQYMBaAFLPbSKT5ocXYrjZBzBFjaWIp
# vEvGMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB2BggrBgEF
# BQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBA
# BggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
# R2xvYmFsUm9vdEczLmNydDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290RzMuY3JsMBwGA1UdIAQVMBMw
# BwYFZ4EMAQMwCAYGZ4EMAQQBMAoGCCqGSM49BAMDA2gAMGUCMHi9SZVlcQHQRldo
# ZQ5oqdw2CMHu/dSO20BlPw3/k6/CrmOGo37LtJFaeOwHA2cHfAIxAOefH/EHW6w0
# xji8taVQzubqOH4+eZDkpFurAg3oB/xWplqK3bNQst3y+mZ0ntAWYzCCBAMwggOJ
# oAMCAQICEAExw+sKUABDj0yZt5afTZQwCgYIKoZIzj0EAwMwZDELMAkGA1UEBhMC
# VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTwwOgYDVQQDEzNEaWdpQ2VydCBH
# bG9iYWwgRzMgQ29kZSBTaWduaW5nIEVDQyBTSEEzODQgMjAyMSBDQTEwHhcNMjQw
# MzA3MDAwMDAwWhcNMjUwMzA4MjM1OTU5WjBvMQswCQYDVQQGEwJVUzERMA8GA1UE
# CBMIT2tsYWhvbWExETAPBgNVBAcTCE11c2tvZ2VlMRwwGgYDVQQKExNBc2hlciBT
# b2x1dGlvbnMgSW5jMRwwGgYDVQQDExNBc2hlciBTb2x1dGlvbnMgSW5jMHYwEAYH
# KoZIzj0CAQYFK4EEACIDYgAExsP0nyCZ1QtY7aXin+tdZVcF0uPHJJjRpjVVgUmb
# 3iKJeKapvWBSAbroBouKIP9+Qoz197aNbZCSOBQsWX53SUyTu1Trvwku7ksL+eQh
# bJvnRJ20UqF566z5KbniyLrAo4IB8zCCAe8wHwYDVR0jBBgwFoAUm1+wNrqdBq4Z
# J73AoCLAi4s4d+0wHQYDVR0OBBYEFNdgDYHKEBunNDYgivfxKeS4YX0/MD4GA1Ud
# IAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMw
# gasGA1UdHwSBozCBoDBOoEygSoZIaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0R2xvYmFsRzNDb2RlU2lnbmluZ0VDQ1NIQTM4NDIwMjFDQTEuY3JsME6g
# TKBKhkhodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxHM0Nv
# ZGVTaWduaW5nRUNDU0hBMzg0MjAyMUNBMS5jcmwwgY4GCCsGAQUFBwEBBIGBMH8w
# JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBXBggrBgEFBQcw
# AoZLaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsRzND
# b2RlU2lnbmluZ0VDQ1NIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAwCgYIKoZI
# zj0EAwMDaAAwZQIxAJHtFqbIBTSZ6AiYEyHsjjlZ7treTZfTSPiyyr8KAKBPKVXt
# B2859Jj8A3c9lEXrLgIwGTu2YV8DhFy9OqIDwkCZfoYH8oMo1LRtYhYZtVzkr3WF
# er8mkmAdOyNbW/DI0pZPMYIY9DCCGPACAQEweDBkMQswCQYDVQQGEwJVUzEXMBUG
# A1UEChMORGlnaUNlcnQsIEluYy4xPDA6BgNVBAMTM0RpZ2lDZXJ0IEdsb2JhbCBH
# MyBDb2RlIFNpZ25pbmcgRUNDIFNIQTM4NCAyMDIxIENBMQIQATHD6wpQAEOPTJm3
# lp9NlDANBglghkgBZQMEAgIFAKCBjDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG
# 9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIB
# FTA/BgkqhkiG9w0BCQQxMgQwjzg9Plx7HIfxTVcBlqmOILwt8SxwPOMdIOWYUG+3
# /Tz31KH/+dCl6esLi6j8X0obMAsGByqGSM49AgEFAARnMGUCMQCaHafUXgt+BwW/
# cpQZALtmr/XUkw5QfVKuePn95AHLAV8+KA1jB/F361RbwQWY8fUCMAgvm4Agt+Vw
# /Bl2J+qFfCwRqWQQcrqrgkfys+iN0zK5JHbOEdnvxUd0MVz7WMMIp6GCF1swghdX
# BgorBgEEAYI3AwMBMYIXRzCCF0MGCSqGSIb3DQEHAqCCFzQwghcwAgEDMQ8wDQYJ
# YIZIAWUDBAICBQAwgYgGCyqGSIb3DQEJEAEEoHkEdzB1AgEBBglghkgBhv1sBwEw
# QTANBglghkgBZQMEAgIFAAQwYcAxhvxuSyQanCV7zCy0/9qT6jcjcLZGxLexCgnB
# D4lf+zhzAOVBKL7p02/uo2ehAhEAt00gxrRp21TnUBMhBIWaRxgPMjAyNTAxMTUw
# MDA2NDlaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+Vh8b56QTjMwQwDQYJKoZI
# hvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp
# bWVTdGFtcGluZyBDQTAeFw0yNDA5MjYwMDAwMDBaFw0zNTExMjUyMzU5NTlaMEIx
# CzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEgMB4GA1UEAxMXRGlnaUNl
# cnQgVGltZXN0YW1wIDIwMjQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQC+anOf9pUhq5Ywultt5lmjtej9kR8YxIg7apnjpcH9CjAgQxK+CMR0Rne/i+ut
# MeV5bUlYYSuuM4vQngvQepVHVzNLO9RDnEXvPghCaft0djvKKO+hDu6ObS7rJcXa
# /UKvNminKQPTv/1+kBPgHGlP28mgmoCw/xi6FG9+Un1h4eN6zh926SxMe6We2r1Z
# 6VFZj75MU/HNmtsgtFjKfITLutLWUdAoWle+jYZ49+wxGE1/UXjWfISDmHuI5e/6
# +NfQrxGFSKx+rDdNMsePW6FLrphfYtk/FLihp/feun0eV+pIF496OVh4R1TvjQYp
# AztJpVIfdNsEvxHofBf1BWkadc+Up0Th8EifkEEWdX4rA/FE1Q0rqViTbLVZIqi6
# viEk3RIySho1XyHLIAOJfXG5PEppc3XYeBH7xa6VTZ3rOHNeiYnY+V4j1XbJ+Z9d
# I8ZhqcaDHOoj5KGg4YuiYx3eYm33aebsyF6eD9MF5IDbPgjvwmnAalNEeJPvIeoG
# JXaeBQjIK13SlnzODdLtuThALhGtyconcVuPI8AaiCaiJnfdzUcb3dWnqUnjXkRF
# wLtsVAxFvGqsxUA2Jq/WTjbnNjIUzIs3ITVC6VBKAOlb2u29Vwgfta8b2ypi6n2P
# zP0nVepsFk8nlcuWfyZLzBaZ0MucEdeBiXL+nUOGhCjl+QIDAQABo4IBizCCAYcw
# DgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYB
# BQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMB8GA1UdIwQY
# MBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSfVywDdw4oFZBmpWNe
# 7k+SH3agWzBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0Eu
# Y3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Au
# ZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0cy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5n
# Q0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQA9rR4fdplb4ziEEkfZQ5H2EdubTggd
# 0ShPz9Pce4FLJl6reNKLkZd5Y/vEIqFWKt4oKcKz7wZmXa5VgW9B76k9NJxUl4Jl
# KwyjUkKhk3aYx7D8vi2mpU1tKlY71AYXB8wTLrQeh83pXnWwwsxc1Mt+FWqz57yF
# q6laICtKjPICYYf/qgxACHTvypGHrC8k1TqCeHk6u4I/VBQC9VK7iSpU5wlWjNlH
# lFFv/M93748YTeoXU/fFa9hWJQkuzG2+B7+bMDvmgF8VlJt1qQcl7YFUMYgZU1WM
# 6nyw23vT6QSgwX5Pq2m0xQ2V6FJHu8z4LXe/371k5QrN9FQBhLLISZi2yemW0P8Z
# Zfx4zvSWzVXpAb9k4Hpvpi6bUe8iK6WonUSV6yPlMwerwJZP/Gtbu3CKldMnn+Lm
# mRTkTXpFIEB06nXZrDwhCGED+8RsWQSIXZpuG4WLFQOhtloDRWGoCwwc6ZpPddOF
# kM2LlTbMcqFSzm4cd0boGhBq7vkqI1uHRz6Fq1IX7TaRQuR+0BGOzISkcqwXu7nM
# pFu3mgrlgbAW+BzikRVQ3K2YHcGkiKjA4gi4OA/kz1YCsdhIBHXqBzR0/Zd2QwQ/
# l4Gxftt/8wY3grcc/nS//TVkej9nmUYu83BDtccHHXKibMs/yXHhDXNkoPIdynhV
# Aku7aRZOwqw6pDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZI
# hvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
# MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1
# c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJE
# aWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBD
# QTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVccl
# A8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9Q
# Ewsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDW
# VtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0
# UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huo
# wWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZw
# mCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rn
# H1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC
# 3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jz
# RWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEm
# CPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4w
# Er1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/
# AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs
# 1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYI
# KwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2
# oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290
# RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG
# 9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3i
# Syn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKo
# Fr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9
# jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JE
# rpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOA
# CcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9r
# p/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvE
# lXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2
# uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRi
# CQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlH
# K+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggWN
# MIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJ
# BgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
# aWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBD
# QTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZI
# hvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK
# 2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/G
# nhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJ
# IB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4M
# K7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN
# 2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I
# 11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KIS
# G2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9
# HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4
# pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpy
# FiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS31
# 2amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs
# 1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd
# 823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzAB
# hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9j
# YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQw
# RQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZI
# hvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4
# hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3
# rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs
# 9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K
# 2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0n
# ftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggOGMIIDggIBATB3MGMxCzAJ
# BgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGln
# aUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EC
# EAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAICBQCggeEwGgYJKoZIhvcNAQkD
# MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAxMTUwMDA2NDlaMCsG
# CyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTVhltFMDcGCyqG
# SIb3DQEJEAIvMSgwJjAkMCIEIHZ2n6jyYy8fQws6IzCu1lZ1/tdz2wXWZbkFk5hD
# j5rbMD8GCSqGSIb3DQEJBDEyBDC3t9xewmf05HVcvmTTUOnLUtyqKSRY4cM9Ecqa
# 0jFCWZq2aLK28YAb+F/n/imK5QEwDQYJKoZIhvcNAQEBBQAEggIAuvScMwV8Mg/W
# SRploegPAgryssagvF6amxDvySaDubbvHwmlQarlP3clKfYgAFRyNh/cduyN4+1k
# dhi5RgsP61cVjXy2NY+4t4Ym1F5OqMj07tILc1EtS7NMrePvzJk3hoPXuXBeGDnS
# jRhm3JEKJpLxXMEheMBTL/t3NtoqQDjm2Mu0NFGRrmQYxGI5wlE+19coTngcWGAc
# RzyhGNoekZEtznt1XNb/qJEV9DvtewyFXSd8XIUqBLXoVxbBaOaXve+DldxkwGSH
# b+hgXE4gfhrhYqzl5c0wXrNo8xnLpZFU/2k3RB6S1DS07CzGHjkLhLNxYMrTYy3+
# fMBzVqqrBidzn+vX4IolevBpY7KXOyYKSQvpemOmBYHqySzoCX7VmjUTJ9/jPMzD
# 9Gjkca2OfZ3iZkGoirT8NzJ3dFU7z5TXNrXs2btCscByXVtePiSNvw4H3cZu1JVz
# 0XVoKpSUCt7zQE03Z6nqeyGZtlg9RxY5uIdx3GA5oeC4J6Mir56SLhuj1pNGLorB
# W0z9UQQJXi7P5/TXQsX9xI+9EMX34skW9L4HAQaVfaSWxKcvB9AVCfALJMPrsl+X
# a3Dqj3IcQAweEC7TgmAuSZr9ikE3MsXPOv4ke/Unv6XuCnK8oCNyLchinnRIVPCO
# zN3GTOklXwJK79FQkUB5+W6T9BgFlqg=
# SIG # End signature block