PowerBGInfo.psm1

function Get-ComputerBios { 
    <#
    .SYNOPSIS
    Retrieves BIOS information from a remote or local computer.
 
    .DESCRIPTION
    This function retrieves BIOS information from a specified computer using CIM/WMI.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve BIOS information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for communication. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'.
 
    .PARAMETER All
    Switch parameter to retrieve all available BIOS properties.
 
    .EXAMPLE
    Get-ComputerBios -ComputerName "RemoteComputer" -Protocol Wsman
    Retrieves BIOS information from a remote computer using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerBios -All
    Retrieves all available BIOS information from the local computer.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_bios'
    if ($All) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = 'PSComputerName', 'Status', 'Version', 'PrimaryBIOS', 'Manufacturer', 'ReleaseDate', 'SerialNumber', 'SMBIOSBIOSVersion', 'SMBIOSMajorVersion', 'SMBIOSMinorVersion', 'SystemBiosMajorVersion', 'SystemBiosMinorVersion'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    } else {
                        $Env:COMPUTERNAME 
                    }
                    Status       = $Data.Status
                    Version      = $Data.Version
                    VersionBIOS  = -join ($Data.SMBIOSMajorVersion, ".", $Data.SMBIOSMinorVersion, ".", $Data.SystemBiosMajorVersion, ".", $Data.SystemBiosMinorVersion)
                    PrimaryBIOS  = $Data.PrimaryBIOS
                    Manufacturer = $Data.Manufacturer
                    ReleaseDate  = $Data.ReleaseDate
                }
            }
        }
    }
}
function Get-ComputerCPU { 
    <#
    .SYNOPSIS
    Retrieves CPU information from specified computers.
 
    .DESCRIPTION
    This function retrieves CPU information from the specified computers. It provides details such as Name, DeviceID, Caption, SystemName, CurrentClockSpeed, MaxClockSpeed, ProcessorID, ThreadCount, Architecture, Status, LoadPercentage, L3CacheSize, Manufacturer, NumberOfCores, NumberOfEnabledCore, and NumberOfLogicalProcessors.
 
    .PARAMETER ComputerName
    Specifies the names of the computers for which to retrieve CPU information.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving CPU information. Valid values are 'Default', 'Dcom', and 'Wsman'.
 
    .PARAMETER All
    Indicates whether to retrieve all available CPU information.
 
    .EXAMPLE
    Get-ComputerCPU -ComputerName Server01, Server02 -Protocol Wsman -All
    Retrieves all available CPU information from remote computers Server01 and Server02 using Wsman protocol.
 
    .EXAMPLE
    Get-ComputerCPU -ComputerName "Workstation01" -Protocol Default
    Retrieves CPU information from a single remote computer named Workstation01 using the default protocol.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_processor'
    if ($All) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = 'PSComputerName', 'Name', 'DeviceID', 'Caption', 'SystemName', 'CurrentClockSpeed', 'MaxClockSpeed', 'ProcessorID', 'ThreadCount', 'Architecture', 'Status', 'LoadPercentage', 'L3CacheSize', 'Manufacturer', 'VirtualizationFirmwareEnabled', 'NumberOfCores', 'NumberOfEnabledCore', 'NumberOfLogicalProcessors'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName              = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    } else {
                        $Env:COMPUTERNAME 
                    }
                    Name                      = $Data.Name
                    DeviceID                  = $Data.DeviceID
                    Caption                   = $Data.Caption
                    CurrentClockSpeed         = $Data.CurrentClockSpeed
                    MaxClockSpeed             = $Data.MaxClockSpeed
                    ProcessorID               = $Data.ProcessorID
                    ThreadCount               = $Data.ThreadCount
                    Architecture              = $Data.Architecture
                    Status                    = $Data.Status
                    LoadPercentage            = $Data.LoadPercentage
                    Manufacturer              = $Data.Manufacturer

                    NumberOfCores             = $Data.NumberOfCores
                    NumberOfEnabledCore       = $Data.NumberOfEnabledCore
                    NumberOfLogicalProcessors = $Data.NumberOfLogicalProcessors    
                }
            }
        }
    }
}
function Get-ComputerOperatingSystem { 
    <#
    .SYNOPSIS
    Retrieves operating system information from remote computers.
 
    .DESCRIPTION
    This function retrieves operating system information from remote computers using CIM/WMI queries. It provides details such as the operating system name, version, manufacturer, architecture, language, product suite, installation date, last boot-up time, and more.
 
    .PARAMETER ComputerName
    Specifies the name of the remote computer(s) to retrieve the operating system information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for the connection (Default, Dcom, or Wsman). Default is 'Default'.
 
    .PARAMETER All
    Switch parameter to retrieve all available properties of the operating system.
 
    .EXAMPLE
    Get-ComputerOperatingSystem -ComputerName "Server01" -Protocol Wsman
    Retrieves operating system information from a single remote computer named "Server01" using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerOperatingSystem -ComputerName "Server01", "Server02" -All
    Retrieves all available operating system properties from multiple remote computers named "Server01" and "Server02".
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_operatingsystem'
    if ($All) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = 'Caption', 'Manufacturer', 'InstallDate', 'OSArchitecture', 'Version', 'SerialNumber', 'BootDevice', 'WindowsDirectory', 'CountryCode', 'OSLanguage', 'OSProductSuite', 'PSComputerName', 'LastBootUpTime', 'LocalDateTime'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    } else {
        foreach ($Data in $Information) {

            [PSCustomObject] @{
                ComputerName           = if ($Data.PSComputerName) {
                    $Data.PSComputerName 
                } else {
                    $Env:COMPUTERNAME 
                }
                OperatingSystem        = $Data.Caption
                OperatingSystemVersion = ConvertTo-OperatingSystem -OperatingSystem $Data.Caption -OperatingSystemVersion $Data.Version
                OperatingSystemBuild   = $Data.Version
                Manufacturer           = $Data.Manufacturer
                OSArchitecture         = $Data.OSArchitecture
                OSLanguage             = ConvertFrom-LanguageCode -LanguageCode $Data.OSLanguage
                OSProductSuite         = [Microsoft.PowerShell.Commands.OSProductSuite] $($Data.OSProductSuite)
                InstallDate            = $Data.InstallDate
                LastBootUpTime         = $Data.LastBootUpTime
                LocalDateTime          = $Data.LocalDateTime
                SerialNumber           = $Data.SerialNumber
                BootDevice             = $Data.BootDevice
                WindowsDirectory       = $Data.WindowsDirectory
                CountryCode            = $Data.CountryCode
            }
        }
    }
}
function Get-ComputerRAM { 
    <#
    .SYNOPSIS
    Retrieves information about the RAM of a specified computer.
 
    .DESCRIPTION
    This function retrieves detailed information about the RAM of a specified computer. It provides various properties such as Manufacturer, Model, Capacity, Speed, and more.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve RAM information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving RAM information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'.
 
    .PARAMETER All
    Indicates whether to retrieve all available properties of the RAM. If specified, all properties will be retrieved.
 
    .PARAMETER Extended
    Indicates whether to retrieve extended properties of the RAM. If specified, additional properties will be retrieved.
 
    .EXAMPLE
    Get-ComputerRAM -ComputerName "Server01" -Protocol Wsman
    Retrieves RAM information from a remote computer named Server01 using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerRAM -ComputerName "WorkstationA" -All
    Retrieves all available RAM properties from a computer named WorkstationA.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All,
        [switch] $Extended
    )
    [string] $Class = 'Win32_physicalmemory '
    if ($All) {
        [string] $Properties = '*'
    } else {
        [string[]] $Properties = @(
            'InstallDate'

            'Manufacturer'
            'Model'
            'OtherIdentifyingInfo'
            'PartNumber'
            'PoweredOn'
            'SerialNumber'
            'SKU'
            'Tag'
            'Version'
            'HotSwappable'
            'Removable'
            'Replaceable'
            'FormFactor'
            'BankLabel'
            'Capacity'

            'InterleavePosition'
            'MemoryType'

            'Speed'

            'ConfiguredClockSpeed'
            'ConfiguredVoltage'
            'DeviceLocator'

            'MaxVoltage'
            'MinVoltage'
            'SMBIOSMemoryType'
            'TypeDetail'
            'PSComputerName'
        )
    }
    $FormFactor = @{
        '0'  = 'Unknown'
        '1'  = 'Other'
        '2'  = 'SIP'
        '3'  = 'DIP'
        '4'  = 'ZIP'
        '5'  = 'SOJ'
        '6'  = 'Proprietary'
        '7'  = 'SIMM'
        '8'  = 'DIMM'
        '9'  = 'TSOP'
        '10' = 'PGA'
        '11' = 'RIMM'
        '12' = 'SODIMM'
        '13' = 'SRIMM'
        '14' = 'SMD'
        '15' = 'SSMP'
        '16' = 'QFP'
        '17' = 'TQFP'
        '18' = 'SOIC'
        '19' = 'LCC'
        '20' = 'PLCC'
        '21' = 'BGA'
        '22' = 'FPBGA'
        '23' = 'LGA'
    }
    $TypeDetails = @{
        '1'    = 'Reserved'
        '2'    = 'Other'
        '4'    = 'Unknown'
        '8'    = 'Fast-paged'
        '16'   = 'Static column'
        '32'   = 'Pseudo-static'
        '64'   = 'RAMBUS'
        '128'  = 'Synchronous'
        '256'  = 'CMOS'
        '512'  = 'EDO'
        '1024' = 'Window DRAM'
        '2048' = 'Cache DRAM'
        '4096' = 'Non-volatile'
    }
    $InterleavePosition = @{
        '0' = "Non-Interleaved"
        '1' = "First Position"
        '2' = "Second Position"
    }
    $MemoryType = @{
        '0'  = "Unknown"
        '1'  = "Other"
        '2'  = "DRAM"
        '3'  = "Synchronous DRAM"
        '4'  = "Cache DRAM"
        '5'  = "EDO"
        '6'  = "EDRAM"
        '7'  = "VRAM"
        '8'  = "SRAM"
        '9'  = "ROM"
        '10' = "ROM"
        '11' = "FLASH"
        '12' = "EEPROM"
        '13' = "FEPROM"
        '14' = "EPROM"
        '15' = "CDRAM"
        '16' = "3DRAM"
        '17' = "SDRAM"
        '18' = "SGRAM"
        '19' = "RDRAM"
        '20' = "DDR"
    }
    $MemoryTypeSMBIOS = @{
        '0'  = 'Unknown'
        '1'  = 'Other'
        '2'  = 'DRAM'
        '3'  = 'Synchronous DRAM'
        '4'  = 'Cache DRAM'
        '5'  = 'EDO'
        '6'  = 'EDRAM'
        '7'  = 'VRAM' 
        '8'  = 'SRAM' 
        '9'  = 'RAM' 
        '10' = 'ROM'    
        '11' = 'Flash' 
        '12' = 'EEPROM' 
        '13' = 'FEPROM' 
        '14' = 'EPROM' 
        '15' = 'CDRAM' 
        '16' = '3DRAM' 
        '17' = 'SDRAM' 
        '18' = 'SGRAM' 
        '19' = 'RDRAM' 
        '20' = 'DDR' 
        '21' = 'DDR2' 
        '22' = 'DDR2 FB-DIMM' 
        '24' = 'DDR3' 
        '25' = 'FBD2'
        '26' = 'DDR4'
    }

    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                $Ram = [ordered] @{
                    ComputerName       = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    } else {
                        $Env:COMPUTERNAME 
                    }
                    Manufacturer       = $Data.Manufacturer          
                    FormFactor         = $FormFactor["$($Data.FormFactor)"]            
                    SMBIOSMemoryType   = $MemoryTypeSMBIOS["$($Data.SMBIOSMemoryType)"]      
                    Size               = [math]::round($Data.Capacity / 1GB, 2)              
                    Speed              = $Data.Speed                 
                    InterleavePosition = $InterleavePosition["$($Data.InterleavePosition)"]    
                    MemoryType         = $MemoryType["$($Data.MemoryType)"]            
                    TypeDetail         = $TypeDetails["$($Data.TypeDetail)"]            
                    PartNumber         = $Data.PartNumber            
                    DeviceLocator      = $Data.DeviceLocator         
                }
                if ($Extended) {
                    $RamExtended = [ordered] @{
                        InstallDate          = $Data.InstallDate

                        Model                = $Data.Model                 
                        OtherIdentifyingInfo = $Data.OtherIdentifyingInfo  

                        PoweredOn            = $Data.PoweredOn             
                        SerialNumber         = $Data.SerialNumber          
                        SKU                  = $Data.SKU                   
                        Tag                  = $Data.Tag                   
                        Version              = $Data.Version               
                        HotSwappable         = $Data.HotSwappable          
                        Removable            = $Data.Removable             
                        Replaceable          = $Data.Replaceable           
                        BankLabel            = $Data.BankLabel             

                        ConfiguredClockSpeed = $Data.ConfiguredClockSpeed  
                        ConfiguredVoltage    = $Data.ConfiguredVoltage     

                        MaxVoltage           = $Data.MaxVoltage            
                        MinVoltage           = $Data.MinVoltage            
                    }
                    [PSCustomObject] ($Ram + $RamExtended)
                } else {
                    [PSCustomObject] $Ram
                }
            }
        }
    }
}
function ConvertFrom-LanguageCode { 
    <#
    .SYNOPSIS
    Converts a language code to its corresponding language name.
 
    .DESCRIPTION
    This function takes a language code as input and returns the corresponding language name.
 
    .PARAMETER LanguageCode
    The language code to convert to a language name.
 
    .EXAMPLE
    ConvertFrom-LanguageCode -LanguageCode 1033
    Returns: "English (United States)"
 
    .EXAMPLE
    ConvertFrom-LanguageCode -LanguageCode 1041
    Returns: "Japanese"
    #>

    [cmdletBinding()]
    param(
        [string] $LanguageCode
    )
    $LanguageCodeDictionary = @{
        '1'     = "Arabic"
        '4'     = "Chinese (Simplified)?? China"
        '9'     = "English"
        '1025'  = "Arabic (Saudi Arabia)"
        '1026'  = "Bulgarian"
        '1027'  = "Catalan"
        '1028'  = "Chinese (Traditional) Taiwan"
        '1029'  = "Czech"
        '1030'  = "Danish"
        '1031'  = "German (Germany)"
        '1032'  = "Greek"
        '1033'  = "English (United States)"
        '1034'  = "Spanish (Traditional Sort)"
        '1035'  = "Finnish"
        '1036'  = "French (France)"
        '1037'  = "Hebrew"
        '1038'  = "Hungarian"
        '1039'  = "Icelandic"
        '1040'  = "Italian (Italy)"
        '1041'  = "Japanese"
        '1042'  = "Korean"
        '1043'  = "Dutch (Netherlands)"
        '1044'  = "Norwegian (Bokmal)"
        '1045'  = "Polish"
        '1046'  = "Portuguese (Brazil)"
        '1047'  = "Rhaeto-Romanic"
        '1048'  = "Romanian"
        '1049'  = "Russian"
        '1050'  = "Croatian"
        '1051'  = "Slovak"
        '1052'  = "Albanian"
        '1053'  = "Swedish"
        '1054'  = "Thai"
        '1055'  = "Turkish"
        '1056'  = "Urdu"
        '1057'  = "Indonesian"
        '1058'  = "Ukrainian"
        '1059'  = "Belarusian"
        '1060'  = "Slovenian"
        '1061'  = "Estonian"
        '1062'  = "Latvian"
        '1063'  = "Lithuanian"
        '1065'  = "Persian"
        '1066'  = "Vietnamese"
        '1069'  = "Basque (Basque)"
        '1070'  = "Serbian"
        '1071'  = "Macedonian (FYROM)"
        '1072'  = "Sutu"
        '1073'  = "Tsonga"
        '1074'  = "Tswana"
        '1076'  = "Xhosa"
        '1077'  = "Zulu"
        '1078'  = "Afrikaans"
        '1080'  = "Faeroese"
        '1081'  = "Hindi"
        '1082'  = "Maltese"
        '1084'  = "Scottish Gaelic (United Kingdom)"
        '1085'  = "Yiddish"
        '1086'  = "Malay (Malaysia)"
        '2049'  = "Arabic (Iraq)"
        '2052'  = "Chinese (Simplified) PRC"
        '2055'  = "German (Switzerland)"
        '2057'  = "English (United Kingdom)"
        '2058'  = "Spanish (Mexico)"
        '2060'  = "French (Belgium)"
        '2064'  = "Italian (Switzerland)"
        '2067'  = "Dutch (Belgium)"
        '2068'  = "Norwegian (Nynorsk)"
        '2070'  = "Portuguese (Portugal)"
        '2072'  = "Romanian (Moldova)"
        '2073'  = "Russian (Moldova)"
        '2074'  = "Serbian (Latin)"
        '2077'  = "Swedish (Finland)"
        '3073'  = "Arabic (Egypt)"
        '3076'  = "Chinese Traditional (Hong Kong SAR)"
        '3079'  = "German (Austria)"
        '3081'  = "English (Australia)"
        '3082'  = "Spanish (International Sort)"
        '3084'  = "French (Canada)"
        '3098'  = "Serbian (Cyrillic)"
        '4097'  = "Arabic (Libya)"
        '4100'  = "Chinese Simplified (Singapore)"
        '4103'  = "German (Luxembourg)"
        '4105'  = "English (Canada)"
        '4106'  = "Spanish (Guatemala)"
        '4108'  = "French (Switzerland)"
        '5121'  = "Arabic (Algeria)"
        '5127'  = "German (Liechtenstein)"
        '5129'  = "English (New Zealand)"
        '5130'  = "Spanish (Costa Rica)"
        '5132'  = "French (Luxembourg)"
        '6145'  = "Arabic (Morocco)"
        '6153'  = "English (Ireland)"
        '6154'  = "Spanish (Panama)"
        '7169'  = "Arabic (Tunisia)"
        '7177'  = "English (South Africa)"
        '7178'  = "Spanish (Dominican Republic)"
        '8193'  = "Arabic (Oman)"
        '8201'  = "English (Jamaica)"
        '8202'  = "Spanish (Venezuela)"
        '9217'  = "Arabic (Yemen)"
        '9226'  = "Spanish (Colombia)"
        '10241' = "Arabic (Syria)"
        '10249' = "English (Belize)"
        '10250' = "Spanish (Peru)"
        '11265' = "Arabic (Jordan)"
        '11273' = "English (Trinidad)"
        '11274' = "Spanish (Argentina)"
        '12289' = "Arabic (Lebanon)"
        '12298' = "Spanish (Ecuador)"
        '13313' = "Arabic (Kuwait)"
        '13322' = "Spanish (Chile)"
        '14337' = "Arabic (U.A.E.)"
        '14346' = "Spanish (Uruguay)"
        '15361' = "Arabic (Bahrain)"
        '15370' = "Spanish (Paraguay)"
        '16385' = "Arabic (Qatar)"
        '16394' = "Spanish (Bolivia)"
        '17418' = "Spanish (El Salvador)"
        '18442' = "Spanish (Honduras)"
        '19466' = "Spanish (Nicaragua)"
        '20490' = "Spanish (Puerto Rico)"
    }
    $Output = $LanguageCodeDictionary[$LanguageCode]
    if ($Output) {
        $Output
    } else {
        "Unknown (Undocumented)"
    }
}
function ConvertTo-OperatingSystem { 
    <#
    .SYNOPSIS
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .DESCRIPTION
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .PARAMETER OperatingSystem
    Operating System as returned by Active Directory
 
    .PARAMETER OperatingSystemVersion
    Operating System Version as returned by Active Directory
 
    .EXAMPLE
    $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object {
        $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
        Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force
        $_
    }
    $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table
 
    .EXAMPLE
    $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $OperatingSystem,
        [string] $OperatingSystemVersion
    )

    if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') {
        $Systems = @{

            '10.0 (22621)' = 'Windows 11 22H2'
            '10.0 (22000)' = 'Windows 11 21H2'
            '10.0 (19045)' = 'Windows 10 22H2'
            '10.0 (19044)' = 'Windows 10 21H2'
            '10.0 (19043)' = 'Windows 10 21H1'
            '10.0 (19042)' = 'Windows 10 20H2'
            '10.0 (19041)' = 'Windows 10 2004'
            '10.0 (18898)' = 'Windows 10 Insider Preview'
            '10.0 (18363)' = "Windows 10 1909"
            '10.0 (18362)' = "Windows 10 1903"
            '10.0 (17763)' = "Windows 10 1809"
            '10.0 (17134)' = "Windows 10 1803"
            '10.0 (16299)' = "Windows 10 1709"
            '10.0 (15063)' = "Windows 10 1703"
            '10.0 (14393)' = "Windows 10 1607"
            '10.0 (10586)' = "Windows 10 1511"
            '10.0 (10240)' = "Windows 10 1507"

            '10.0.22621'   = 'Windows 11 22H2'
            '10.0.22000'   = 'Windows 11 21H2'
            '10.0.19045'   = 'Windows 10 22H2'
            '10.0.19044'   = 'Windows 10 21H2'
            '10.0.19043'   = 'Windows 10 21H1'
            '10.0.19042'   = 'Windows 10 20H2'
            '10.0.19041'   = 'Windows 10 2004'
            '10.0.18898'   = 'Windows 10 Insider Preview'
            '10.0.18363'   = "Windows 10 1909"
            '10.0.18362'   = "Windows 10 1903"
            '10.0.17763'   = "Windows 10 1809"
            '10.0.17134'   = "Windows 10 1803"
            '10.0.16299'   = "Windows 10 1709"
            '10.0.15063'   = "Windows 10 1703"
            '10.0.14393'   = "Windows 10 1607"
            '10.0.10586'   = "Windows 10 1511"
            '10.0.10240'   = "Windows 10 1507"

            '22621'        = 'Windows 11 22H2'
            '22000'        = 'Windows 11 21H2'
            '19045'        = 'Windows 10 22H2'
            '19044'        = 'Windows 10 21H2'
            '19043'        = 'Windows 10 21H1'
            '19042'        = 'Windows 10 20H2'
            '19041'        = 'Windows 10 2004'
            '18898'        = 'Windows 10 Insider Preview'
            '18363'        = "Windows 10 1909"
            '18362'        = "Windows 10 1903"
            '17763'        = "Windows 10 1809"
            '17134'        = "Windows 10 1803"
            '16299'        = "Windows 10 1709"
            '15063'        = "Windows 10 1703"
            '14393'        = "Windows 10 1607"
            '10586'        = "Windows 10 1511"
            '10240'        = "Windows 10 1507"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) {
            $System = $OperatingSystemVersion
        }
    } elseif ($OperatingSystem -like 'Windows Server*') {

        $Systems = @{

            '10.0 (20348)' = 'Windows Server 2022'
            '10.0 (19042)' = 'Windows Server 2019 20H2'
            '10.0 (19041)' = 'Windows Server 2019 2004'
            '10.0 (18363)' = 'Windows Server 2019 1909'
            '10.0 (18362)' = "Windows Server 2019 1903" 
            '10.0 (17763)' = "Windows Server 2019 1809" 
            '10.0 (17134)' = "Windows Server 2016 1803" 
            '10.0 (14393)' = "Windows Server 2016 1607"
            '6.3 (9600)'   = 'Windows Server 2012 R2'
            '6.1 (7601)'   = 'Windows Server 2008 R2'
            '5.2 (3790)'   = 'Windows Server 2003'

            '10.0.20348'   = 'Windows Server 2022'
            '10.0.19042'   = 'Windows Server 2019 20H2'
            '10.0.19041'   = 'Windows Server 2019 2004'
            '10.0.18363'   = 'Windows Server 2019 1909'
            '10.0.18362'   = "Windows Server 2019 1903" 
            '10.0.17763'   = "Windows Server 2019 1809"  
            '10.0.17134'   = "Windows Server 2016 1803" 
            '10.0.14393'   = "Windows Server 2016 1607"
            '6.3.9600'     = 'Windows Server 2012 R2'
            '6.1.7601'     = 'Windows Server 2008 R2' 
            '5.2.3790'     = 'Windows Server 2003' 

            '20348'        = 'Windows Server 2022'
            '19042'        = 'Windows Server 2019 20H2'
            '19041'        = 'Windows Server 2019 2004'
            '18363'        = 'Windows Server 2019 1909'
            '18362'        = "Windows Server 2019 1903" 
            '17763'        = "Windows Server 2019 1809" 
            '17134'        = "Windows Server 2016 1803" 
            '14393'        = "Windows Server 2016 1607"
            '9600'         = 'Windows Server 2012 R2'
            '7601'         = 'Windows Server 2008 R2'
            '3790'         = 'Windows Server 2003'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) {
            $System = $OperatingSystemVersion
        }
    } else {
        $System = $OperatingSystem
    }
    if ($System) {
        $System
    } else {
        'Unknown'
    }
}
function Get-CimData { 
    <#
    .SYNOPSIS
    Helper function for retreiving CIM data from local and remote computers
 
    .DESCRIPTION
    Helper function for retreiving CIM data from local and remote computers
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM).
 
    .PARAMETER Protocol
    Specifies the protocol to use. The acceptable values for this parametDer are: DCOM, Default, or Wsman.
 
    .PARAMETER Class
    Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names.
 
    .PARAMETER Properties
    Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated.
 
    .PARAMETER NameSpace
    Specifies the namespace for the CIM operation. The default namespace is root\cimv2. You can use tab completion to browse the list of namespaces, because PowerShell gets a list of namespaces from the local WMI server to provide a list of namespaces.
 
    .PARAMETER Credential
    Specifies a user account that has permission to perform this action. The default is the current user.
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios'
 
    .EXAMPLE
    Get-CimClass to get all classes
 
    .NOTES
    General notes
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory)][string] $Class,
        [string] $NameSpace = 'root\cimv2',
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [pscredential] $Credential,
        [string[]] $Properties = '*'
    )
    $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName'

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $CimObject = @(

        [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' }

        $Computers = $ComputersSplit[1]
        if ($Computers.Count -gt 0) {
            if ($Protocol -eq 'Default' -and $null -eq $Credential) {
                Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
            } else {
                $Option = New-CimSessionOption -Protocol $Protocol
                $newCimSessionSplat = @{
                    ComputerName  = $Computers
                    SessionOption = $Option
                    ErrorAction   = 'SilentlyContinue'
                }
                if ($Credential) {
                    $newCimSessionSplat['Credential'] = $Credential
                }
                $Session = New-CimSession @newCimSessionSplat -Verbose:$false
                if ($Session) {
                    Try {
                        $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction Stop -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                    } catch {
                        Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)"
                    }
                    try {
                        $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue
                    } catch {
                        Write-Warning -Message "Get-CimData - Failed to remove CimSession $($Session). Failed with errror: $($E.Exception.Message)"
                    }
                    $Info
                } else {
                    Write-Warning -Message "Get-CimData - Failed to create CimSession for $($Computers). Problem with credentials?"
                }
            }
            foreach ($E in $ErrorsToProcess) {
                Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)"
            }
        } else {

            $Computers = $ComputersSplit[0]
            if ($Computers.Count -gt 0) {
                $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force
                $Info
            }
            foreach ($E in $ErrorsLocal) {
                Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)"
            }
        }
    )
    $CimObject
}
function Get-ComputerSplit { 
    <#
    .SYNOPSIS
    This function splits the list of computer names provided into two arrays: one containing remote computers and another containing the local computer.
 
    .DESCRIPTION
    The Get-ComputerSplit function takes an array of computer names as input and splits them into two arrays based on whether they are remote computers or the local computer. It determines the local computer by comparing the provided computer names with the local computer name and DNS name.
 
    .PARAMETER ComputerName
    Specifies an array of computer names to split into remote and local computers.
 
    .EXAMPLE
    Get-ComputerSplit -ComputerName "Computer1", "Computer2", $Env:COMPUTERNAME
    This example splits the computer names "Computer1" and "Computer2" into the remote computers array and the local computer array based on the local computer's name.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName
    )
    if ($null -eq $ComputerName) {
        $ComputerName = $Env:COMPUTERNAME
    }
    try {
        $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName
    } catch {
        $LocalComputerDNSName = $Env:COMPUTERNAME
    }
    $ComputersLocal = $null
    [Array] $Computers = foreach ($Computer in $ComputerName) {
        if ($Computer -eq '' -or $null -eq $Computer) {
            $Computer = $Env:COMPUTERNAME
        }
        if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) {
            $Computer
        } else {
            $ComputersLocal = $Computer
        }
    }
    , @($ComputersLocal, $Computers)
}
function New-BGInfo {
    <#
    .SYNOPSIS
    Provides a simple way to create PowerBGInfo configuration.
 
    .DESCRIPTION
    Provides a simple way to create PowerBGInfo configuration.
    It allows writting useful information on your desktop background.
    Every time the script is run, it will update existing image with new information.
 
    .PARAMETER BGInfoContent
    Special parameter that works as a scriptblock. It takes input and converts it into configuration.
    By using New-BGInfoLabel and New-BGInfoValue along with other supported PowerShell commands you can create your own configuration.
 
    .PARAMETER FilePath
    Path to the image that will be used as a background. If not provided current Desktop Background will be used.
 
    .PARAMETER ConfigurationDirectory
    Path to the directory where configuration will be stored, and where image for desktop background will be placed. If not provided, it will be stored in C:\TEMP
 
    .PARAMETER FontFamilyName
    Font family name that will be used to display information for Label.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If ValueFontFamilyName is not provided it will be used as a default value for that property as well
 
    .PARAMETER Color
    Color that will be used to display information for Label.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If ValueColor is not provided it will be used as a default value for that property as well
 
    .PARAMETER FontSize
    Font size that will be used to display information for Label.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If ValueFontSize is not provided it will be used as a default value for that property as well
 
    .PARAMETER ValueColor
    Color that will be used to display information for Value.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If not provided it will be taken from Color property.
 
    .PARAMETER ValueFontSize
    Font size that will be used to display information for Value.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If not provided it will be taken from FontSize property.
 
    .PARAMETER ValueFontFamilyName
    Font family name that will be used to display information for Value.
    It's only used if New-BGInfoLabel or New-BGIInfoValue doesn't provide it's own value.
    If not provided it will be taken from FontFamilyName property.
 
    .PARAMETER SpaceBetweenLines
    Length of the space between lines
 
    .PARAMETER SpaceBetweenColumns
    Length of the space between columns (Label and Value)
 
    .PARAMETER PositionX
    Position of the first column on the X axis.
 
    .PARAMETER PositionY
    Position of the first column on the Y axis.
 
    .PARAMETER MonitorIndex
    Index of the monitor that will be used to display the background image. By default it will be 0 (first monitor)
 
    .PARAMETER WallpaperFit
    WHat to do with the image if it is not the same size as the monitor resolution. It can be one of the following: 'Center', 'Fit', 'Stretch', 'Tile', 'Span', 'Fill'
 
    .EXAMPLE
    New-BGInfo -MonitorIndex 0 {
        # Lets add computer name, but lets use builtin values for that
        New-BGInfoValue -BuiltinValue HostName -Color Red -FontSize 20 -FontFamilyName 'Calibri'
        New-BGInfoValue -BuiltinValue FullUserName
        New-BGInfoValue -BuiltinValue CpuName
        New-BGInfoValue -BuiltinValue CpuLogicalCores
        New-BGInfoValue -BuiltinValue RAMSize
        New-BGInfoValue -BuiltinValue RAMSpeed
 
        # Lets add Label, but without any values, kinf of like section starting
        New-BGInfoLabel -Name "Drives" -Color LemonChiffon -FontSize 16 -FontFamilyName 'Calibri'
 
        # Lets get all drives and their labels
        foreach ($Disk in (Get-Disk)) {
            $Volumes = $Disk | Get-Partition | Get-Volume
            foreach ($V in $Volumes) {
                New-BGInfoValue -Name "Drive $($V.DriveLetter)" -Value $V.SizeRemaining
            }
        }
    } -FilePath $PSScriptRoot\Samples\PrzemyslawKlysAndKulkozaurr.jpg -ConfigurationDirectory $PSScriptRoot\Output -PositionX 100 -PositionY 100 -WallpaperFit Center
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory)][scriptblock] $BGInfoContent,
        [string] $FilePath,
        [parameter(Mandatory)][string] $ConfigurationDirectory,
        [string] $FontFamilyName = 'Calibri',
        [SixLabors.ImageSharp.Color] $Color = [SixLabors.ImageSharp.Color]::Black,
        [int] $FontSize = 16,
        [SixLabors.ImageSharp.Color] $ValueColor = [SixLabors.ImageSharp.Color]::Black,
        [float] $ValueFontSize = 16,
        [string] $ValueFontFamilyName = 'Calibri',
        [int] $SpaceBetweenLines = 10,
        [int] $SpaceBetweenColumns = 30,
        [int] $PositionX = 10,
        [int] $PositionY = 10,
        [int] $MonitorIndex = 0,
        [ValidateSet('Center', 'Fit', 'Stretch', 'Tile', 'Span', 'Fill')][string] $WallpaperFit
    )

    $ConfigurationPath = [io.path]::Combine($ConfigurationDirectory, "PowerBGInfoConfiguration.xml")
    if (Test-Path -LiteralPath $ConfigurationPath) {
        $Configuration = Import-Clixml -LiteralPath $ConfigurationPath
    } else {
        $Configuration = [ordered] @{
            OriginalImage = ''
        }
    }

    if ($FilePath -eq "") {
        $WallpaperPath = Get-DesktopWallpaper -Index $MonitorIndex
    } else {
        $WallpaperPath = $FilePath
    }
    if ($WallpaperPath -eq "" -or (Test-Path -LiteralPath $WallpaperPath) -eq $false) {
        Write-Warning -Message "New-BGInfo - Wallpaper ($WallpaperPath) not found. Provide new wallpaper, or make sure one is already set."
        return
    }

    # if ($Configuration['OriginalImage'] -ne "") {
    # Write-Verbose -Message "New-BGInfo - Wallpaper ($WallpaperPath) already has BGInfo applied, reusing what is set."
    #} else {
    $Configuration['OriginalImage'] = $WallpaperPath
    #}

    # Copy wallpaper to use as a base
    $FileName = [io.path]::GetFileName($Configuration['OriginalImage'])
    $FileNameWithoutExtension = [io.path]::GetFileNameWithoutExtension(($Configuration['OriginalImage']))
    $FileNameExtension = [io.path]::GetExtension($FileName)
    $NewFileName = "$($FileNameWithoutExtension)_PowerBgInfo" + $FileNameExtension
    $FilePathOutput = [io.path]::Combine($ConfigurationDirectory, $NewFileName)

    # Wallpaper and output are the same file, if so, we already applied BGInfo at least once
    if ($FilePathOutput -ne $Configuration['OriginalImage'] ) {
        Copy-Item -Path $Configuration['OriginalImage'] -Destination $FilePathOutput -Force
    }
    # We need to check if file exists, because Copy-Item may not do what it's supposed to do
    if ($FilePathOutput -eq "" -or (Test-Path -LiteralPath $FilePathOutput) -eq $false) {
        Write-Warning -Message "New-BGInfo - Wallpaper ($FilePathOutput) not found. Copying failed?"
        return
    }
    # Load the file
    $Image = Get-Image -FilePath $FilePathOutput

    $BGContent = & $BGInfoContent

    # Do assesment of the longest text so we can make sure columns are not overlapping
    $HighestWidth = 0
    $HighestHeight = 0
    foreach ($Info in $BGContent) {

        if ($Info.Color) {
            #$SetColor = $Info.Color
        } else {
            $Info.Color = $Color
        }
        if ($Info.FontSize) {
            #$SetFontSize = $Info.FontSize
        } else {
            $Info.FontSize = $FontSize
        }
        if ($Info.FontFamilyName) {
            #$SetFontFamilyName = $Info.FontFamilyName
        } else {
            $Info.FontFamilyName = $FontFamilyName
        }
        if ($Info.Type -ne 'Label') {
            if ($Info.ValueColor) {
                #$SetValueColor = $Info.ValueColor
            } else {
                if ($Info.Color) {
                    $Info.ValueColor = $Info.Color
                } else {
                    $Info.ValueColor = $ValueColor
                }
            }
            if ($Info.ValueFontSize) {
                #$SetValueFontSize = $Info.ValueFontSize
            } else {
                if ($Info.FontSize) {
                    $Info.ValueFontSize = $Info.FontSize
                } else {
                    $Info.ValueFontSize = $ValueFontSize
                }
            }
            if ($Info.ValueFontFamilyName) {
                # $SetValueFontFamilyName = $Info.ValueFontFamilyName
            } else {
                if ($Info.FontFamilyName) {
                    $Info.ValueFontFamilyName = $Info.FontFamilyName
                } else {
                    $Info.ValueFontFamilyName = $ValueFontFamilyName
                }
            }
        }
        $SizeOfText = $Image.GetTextSize($Info.Name, $Info.FontSize, $Info.FontFamilyName)
        if ($SizeOfText.Width -gt $HighestWidth) {
            $HighestWidth = $SizeOfText.Width
        }
        if ($SizeOfText.Height -gt $HighestHeight) {
            $HighestHeight = $SizeOfText.Height
        }
    }
    # Add text
    foreach ($Info in $BGContent) {
        if ($Info.Type -eq 'Label') {
            $Image.AddText($PositionX, $PositionY, $Info.Name, $Info.Color, $Info.FontSize, $Info.FontFamilyName)
        } else {
            $Image.AddText($PositionX, $PositionY, $Info.Name, $Info.Color, $Info.FontSize, $Info.FontFamilyName)
            $Image.AddText($PositionX + $HighestWidth + $SpaceBetweenColumns, $PositionY, $Info.Value, $Info.ValueColor, $Info.ValueFontSize, $Info.ValueFontFamilyName)
        }
        $PositionY += $HighestHeight + $SpaceBetweenLines
    }
    # lets now save image after modifications
    Save-Image -Image $Image -FilePath $FilePathOutput

    # finally lets set the image as wallpapeer
    if ($WallpaperFit) {
        Set-DesktopWallpaper -Index $MonitorIndex -FilePath $FilePathOutput -Position $WallpaperFit
    } else {
        Set-DesktopWallpaper -Index $MonitorIndex -FilePath $FilePathOutput
    }

    # lets export configuration, so we know what was done
    $Configuration | Export-Clixml -LiteralPath $ConfigurationPath -Force
}
function New-BGInfoLabel {
    <#
    .SYNOPSIS
    Provides ability to set label without value. It can be used to separate different sections of information.
 
    .DESCRIPTION
    Provides ability to set label without value. It can be used to separate different sections of information.
 
    .PARAMETER Name
    Name of the label/section
 
    .PARAMETER Color
    Color for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .PARAMETER FontSize
    Font size for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .PARAMETER FontFamilyName
    Font family name for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .EXAMPLE
    # Lets add Label, but without any values, kinf of like section starting
    New-BGInfoLabel -Name "Drives" -Color LemonChiffon -FontSize 16 -FontFamilyName 'Calibri'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Name,
        [SixLabors.ImageSharp.Color] $Color,
        [float] $FontSize,
        [string] $FontFamilyName
    )
    [PSCustomObject] @{
        Type           = 'Label'
        Name           = $Name
        Color          = $Color
        FontSize       = $FontSize
        FontFamilyName = $FontFamilyName
    }
}
function New-BGInfoValue {
    <#
    .SYNOPSIS
    Special function that provides a way to create a value that will be displayed on the background image.
 
    .DESCRIPTION
    Special function that provides a way to create a value that will be displayed on the background image.
    It allows using builtin values, or custom values depending on user needs.
 
    .PARAMETER Name
    Label that will be used on the left side of the value.
 
    .PARAMETER Value
    Cystom Value that will be displayed on the right side of the label.
 
    .PARAMETER BuiltinValue
    Builtin value that will be displayed on the right side of the label. It can be one of the following:
    - UserName - Current user name
    - HostName - Current host name
    - FullUserName - Current user name with domain
    - CpuName - CPU name
    - CpuMaxClockSpeed - CPU max clock speed
    - CpuCores - CPU cores
    - CpuLogicalCores - CPU logical cores
    - RAMSize - RAM size
    - RAMSpeed - RAM speed
    - RAMPartNumber - RAM part number
    - BiosVersion - BIOS version
    - BiosManufacturer - BIOS manufacturer
    - BiosReleaseDate - BIOS release date
    - OSName - OS name
    - OSVersion - OS version
    - OSArchitecture - OS architecture
    - OSBuild - OS build
    - OSInstallDate - OS install date
    - OSLastBootUpTime - OS last boot up time
    - UserDNSDomain - User DNS domain
    - FQDN - Fully qualified domain name
    - IPv4Address - IPv4 address
    - IPv6Address - IPv6 address
 
    .PARAMETER Color
    Color for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .PARAMETER FontSize
    Font size for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .PARAMETER FontFamilyName
    Font family name for the label. If not provided it will be taken from the parent New-BGInfo command.
 
    .PARAMETER ValueColor
    Color for the value. If not provided it will be taken first from Color of the label and if that is not provided from the parent New-BGInfo command.
 
    .PARAMETER ValueFontSize
    Font size for the value. If not provided it will be taken first from FontSize of the label and if that is not provided from the parent New-BGInfo command.
 
    .PARAMETER ValueFontFamilyName
    Font family name for the value. If not provided it will be taken first from FontFamilyName of the label and if that is not provided from the parent New-BGInfo command.
 
    .EXAMPLE
    New-BGInfoValue -BuiltinValue HostName -Color Red -FontSize 20 -FontFamilyName 'Calibri'
    New-BGInfoValue -BuiltinValue FullUserName
    New-BGInfoValue -BuiltinValue CpuName
 
    .EXAMPLE
    # Lets get all drives and their labels
    foreach ($Disk in (Get-Disk)) {
        $Volumes = $Disk | Get-Partition | Get-Volume
        foreach ($V in $Volumes) {
            New-BGInfoValue -Name "Drive $($V.DriveLetter)" -Value $V.SizeRemaining
        }
    }
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Values')]
    param(
        [parameter(ParameterSetName = 'Builtin')]
        [parameter(ParameterSetName = 'Values', Mandatory)]
        [string] $Name,
        [parameter(ParameterSetName = 'Values', Mandatory)]
        [string] $Value,
        [parameter(ParameterSetName = 'Builtin', Mandatory)]
        [ValidateSet(
            'UserName', 'HostName', 'FullUserName',
            'CpuName', 'CpuMaxClockSpeed', 'CpuCores', 'CpuLogicalCores',
            'RAMSize', 'RAMSpeed', 'RAMPartNumber',
            'BiosVersion', 'BiosManufacturer', 'BiosReleaseDate',
            'OSName', 'OSVersion', 'OSArchitecture', 'OSBuild', 'OSInstallDate', 'OSLastBootUpTime',
            'UserDNSDomain', 'FQDN',
            'IPv4Address', 'IPv6Address'
        )][string] $BuiltinValue,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [SixLabors.ImageSharp.Color] $Color,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [float] $FontSize,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [string] $FontFamilyName,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [SixLabors.ImageSharp.Color] $ValueColor,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [float] $ValueFontSize,
        [parameter(ParameterSetName = 'Values')]
        [parameter(ParameterSetName = 'Builtin')]
        [string] $ValueFontFamilyName
    )

    if ($BuiltinValue) {

        if ($BuiltinValue -in 'CPUName', 'CpuMaxClockSpeed', 'CpuCores', 'CpuLogicalCores') {
            $ComputerCPU = Get-ComputerCPU
        } elseif ($BuiltinValue -in 'RAMSize', 'RAMSpeed', 'RAMPartNumber') {
            $ComputerRAM = Get-ComputerRAM
        } elseif ($BuiltinValue -in 'BiosVersion', 'BiosManufacturer', 'BiosReleaseDate') {
            $ComputerBios = Get-ComputerBios
        } elseif ($BuiltinValue -in 'OSName', 'OSVersion', 'OSArchitecture', 'OSBuild', 'OSInstallDate', 'OSLastBootUpTime') {
            $ComputerOS = Get-ComputerOperatingSystem
        }
        if ($BuiltinValue -eq 'UserName') {
            $SetValue = $env:USERNAME
        } elseif ($BuiltinValue -eq 'HostName') {
            $SetValue = $env:COMPUTERNAME
        } elseif ($BuiltinValue -eq 'FullUserName') {
            $SetValue = $env:USERDOMAIN + '\' + $env:USERNAME
        } elseif ($BuiltinValue -eq 'CPUName') {
            $SetValue = $ComputerCPU.Name
        } elseif ($BuiltinValue -eq 'CpuMaxClockSpeed') {
            $SetValue = $ComputerCPU.MaxClockSpeed
        } elseif ($BuiltinValue -eq 'CpuCores') {
            $SetValue = $ComputerCPU.NumberOfEnabledCore
        } elseif ($BuiltinValue -eq 'CpuLogicalCores') {
            $SetValue = $ComputerCPU.NumberOfLogicalProcessors
        } elseif ($BuiltinValue -eq 'RAMSize') {
            $SetValue = ($ComputerRAM.Size | ForEach-Object { $_.ToString('N0') + 'GB' }) -join " / "
        } elseif ($BuiltinValue -eq 'RAMSpeed') {
            $SetValue = ($ComputerRAM.Speed | ForEach-Object { $_.ToString('N0') + 'MHz' }) -join " / "
        } elseif ($BuiltinValue -eq 'RAMPartNumber') {
            $SetValue = $ComputerRAM.PartNumber.Trim() -join ", "
        } elseif ($BuiltinValue -eq 'BiosVersion') {
            $SetValue = $ComputerBios.Version
        } elseif ($BuiltinValue -eq 'BiosManufacturer') {
            $SetValue = $ComputerBios.Manufacturer
        } elseif ($BuiltinValue -eq 'BiosReleaseDate') {
            $SetValue = $ComputerBios.ReleaseDate
        } elseif ($BuiltinValue -eq 'OSName') {
            $SetValue = $ComputerOS.OperatingSystem
        } elseif ($BuiltinValue -eq 'OSVersion') {
            $SetValue = $ComputerOS.OperatingSystemVersion
        } elseif ($BuiltinValue -eq 'OSArchitecture') {
            $SetValue = $ComputerOS.OSArchitecture
        } elseif ($BuiltinValue -eq 'OSBuild') {
            $SetValue = $ComputerOS.OperatingSystemBuild
        } elseif ($BuiltinValue -eq 'OSInstallDate') {
            $SetValue = $ComputerOS.InstallDate
        } elseif ($BuiltinValue -eq 'OSLastBootUpTime') {
            $SetValue = $ComputerOS.LastBootUpTime
        } elseif ($BuiltinValue -eq 'UserDNSDomain') {
            $SetValue = $env:USERDNSDOMAIN
        } elseif ($BuiltinValue -eq 'FQDN') {
            $SetValue = ((Get-CimInstance win32_computersystem).name + '.' + (Get-CimInstance win32_computersystem).domain).ToLower()
        } elseif ($BuiltinValue -eq 'IPv4Address') {
            $SetValue = (Get-NetIPConfiguration | Where-Object { $null -ne $_.IPv4DefaultGateway -and $_.NetAdapter.Status -ne "Disconnected" }).IPv4Address.IPAddress
        } elseif ($BuiltinValue -eq 'IPv6Address') {
            $SetValue = (Get-NetIPConfiguration | Where-Object { $null -ne $_.IPv4DefaultGateway -and $_.NetAdapter.Status -ne "Disconnected" }).IPv6Address.IPAddress
        }
        if ($Name) {
            $SetName = $Name
        } else {
            $SetName = $BuiltinValue
        }
    } else {
        $SetValue = $Value
        $SetName = $Name
    }

    [PSCustomObject] @{
        Type                = 'Values'
        Name                = $SetName
        Value               = $SetValue
        Color               = $Color
        FontSize            = $FontSize
        FontFamilyName      = $FontFamilyName
        ValueColor          = $ValueColor
        ValueFontSize       = $ValueFontSize
        ValueFontFamilyName = $ValueFontFamilyName
    }
}

if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -lt 461808) { Write-Warning "This module requires .NET Framework 4.7.2 or later."; return } 

# Export functions and aliases as required
Export-ModuleMember -Function @('New-BGInfo', 'New-BGInfoLabel', 'New-BGInfoValue') -Alias @()
# SIG # Begin signature block
# MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhgTmweMfHkmF6
# kkRxeVbKF3K0UUwtjgp0TpDsuTSleaCCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp
# BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy
# NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD
# VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD
# ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD
# Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo
# 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO
# H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R
# eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX
# j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV
# DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J
# idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C
# acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N
# Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X
# CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB
# AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s
# BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X
# LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l
# U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho
# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl
# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU
# aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS
# R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB
# b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC
# zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1
# UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp
# ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE
# sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8
# a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF
# YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q
# EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE
# deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J
# ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq
# F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX
# DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz
# dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ
# cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA
# 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh
# vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn
# 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr
# W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w
# iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP
# lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf
# QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28
# eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4
# fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0
# 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC
# AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE
# FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz
# ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j
# cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu
# Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB
# CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E
# BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc
# 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ
# f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51
# 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev
# xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx
# h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY
# D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/
# Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq
# G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH
# v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP
# AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw
# PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2
# IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF
# AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor
# BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
# DQEJBDEiBCCdPZGd5swJmKq1NRz5colGJb17MYZHWrhICg7tfpfguTANBgkqhkiG
# 9w0BAQEFAASCAgBdB62fBkLGjMOpd8GEOlcUfoMQUaa2n3QoOTpdjXZ6upyEzyUv
# SwxISryl1zehDToxKhwwkt/KzhG+CkV/eVU1bwiSw2mYFfW4DYNl0T51VnXjbc2O
# yG6xB0jxb0exvCbqwRCX/0mOrAldGudDJ0tzgjoRTmfQnkJ9P4XPdbxNgEtmmeea
# cid8Ce6awvmjveTYT10AIvGKIMOJc4Z8ednJIdZJmJnACYxWph+mSlZNPxwqmFe3
# Npqbi9pEawNPRkVEvUvGvD71Jn5nyhEiHqIRfewB6lEDwrLHFcEyFgJlHyjEbJ+/
# gyZTROAuc/9wBVj5dMSjavRVoj6MzsftVQhem29HZgDk2GMSlu53VUVcKix3UP6C
# qolKX+S3DOzViK/Q1FupqMQfhNhk3XSvHMYmh3SppaT0++E2KfUfCxEW0DNYri3v
# BCYKVc24xCibF9TLtaGD/Panys29gW7r0jcduq9rWhKH45njJgrLuTzvXUtf22Jc
# hugNJPh/uw6WACet0y6sdjfAmAtprxodVw9k/v8TPR2txEJzp0dssU43fGDwsqHp
# f4UQEZkMI64d8ooD128Az+1EzGYSxx/qrIhOupTIb/xq1RNRVwQdGJDI1RneAcFN
# jrEFk4/JlkCxoTA3xNl5T6C+cYyMJmqA32qMUM3O9QEHKoLQiVEy0KlR0qGCAyAw
# ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH
# NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj
# MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNDExMTkwODE5MjNaMC8GCSqGSIb3DQEJBDEiBCCWuGcL
# GTaEokwyv/aFTDKACuoungGtFk8c+MXlRZ11hTANBgkqhkiG9w0BAQEFAASCAgBJ
# ZXtQ+XIN3u1JNpqxQR2Sa1rVsh9bw/vdQuoFEGBZVgJJdasbsssMaZazd7ZYIA0I
# fWBZx0J6wb7kQ1vOL896l0HKEQhW7wpJqcB14DzxFBeUgE9FLr3tN/KtzLFHfA6i
# e4+FUP+dSk46yLmZlMILtiCOoxOgedEmpdzKbkLv54alImLQkf2wgUqf8fn2vtBv
# wDIKTN8Rk1Hn1J4REXzXwdkDLIWXahMnjK1szy/EbywskAmpyk61lKlOC5uFv5ip
# +5mNKQNJztiSxVs9OwgI5MCXUdfKzhtMNxPXfXsh2rGXw6UW74txHaCv6U5Lm4mb
# 298q72vQ0yN/ImYmkI8Skwp8W7gXI8rvwo/9Om9a91hrg5ILs1zXNqe7AeeToNDN
# H4Zf8qkmQx3WOsTjup+VKr8dMyC3Fs1O90+2F1zZJWx7ZkuREERi6dUAMadAoBAS
# QARBRF/JY8wPIdZ9uXsd/fPOd/r1yVYXs5Sk+3fnox2lXdi+FdKr4qQ4rY5VYczx
# LFThxoZDYbryxzlupm/Y03QGieI29wtWhToTPIJwCWP5maETu6h17L3iXbVLmPYH
# XgnnT++Lw4/iyoReEEvOyw5T0lljTL8xXvQgpTBcoOkzQNgzfVlpnNE1EodYtKgo
# WRIEjFXU1FyNwMXQUFpxU4vVCexQAVfznT326UM5GA==
# SIG # End signature block