OZO.psm1

Function Get-OZO64BitPowerShell {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns True if the PowerShell environment is 64-bit and False if not.
        .EXAMPLE
        Get-OZO64BitPowerShell
        True
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZO64BitPowerShell.md
    #>

    return [System.Environment]::Is64BitProcess
}

Function Get-OZO8601Date {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns a formatted ISO 8601 date string.
        .PARAMETER Pretty
        Include punctuation and spacing for a more human-readable date string.
        .PARAMETER Time
        Include the time.
        .EXAMPLE
        Get-OZO8601Date
        20250215
        .EXAMPLE
        Get-OZO8601Date -Pretty
        2025-02-15
        .EXAMPLE
        Get-OZO8601Date -Time
        20250215171532
        .EXAMPLE
        Get-OZO8601Date -Pretty -Time
        2025-02-15 17:15:32
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZO8601Date.md
    #>

    param(
        [Parameter(Mandatory=$false,HelpMessage="Include punctuation and spacing")][Switch]$Pretty,
        [Parameter(Mandatory=$false,HelpMessage="Include the time")][Swtich]$Time
    )
    # Get the datetime object for the current date and time
    [DateTime]$dateTime = (Get-Date)
    # Determine if Pretty and Time were specified
    If ($Pretty -eq $true -And $Time -eq $true) {
        # Pretty and Time were specified
        return $dateTime.ToString("yyyy-MM-dd HH:mm:ss")
    } ElseIf ($Pretty -eq $true -And $Time -eq $false) {
        # Only Pretty was specified
        return $dateTime.ToString("yyyy-MM-dd")
    } ElseIf ($Pretty -eq $false -And $Time -eq $true) {
        # Only Time was specified
        return $dateTime.ToString("yyyyMMddHHmmss")
    } Else {
        # Neither Pretty or Time were specified
        return $dateTime.ToString("yyyyMMdd")
    }
}

Function Get-OZOChildWriteTime {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns the newest or oldest write time for all files within a given path. Returns the newest write time when executed with no parameters.
        .PARAMETER Oldest
        Return the oldest date time.
        .PARAMETER Path
        The path to inspect. Defaults to the current directory. If Path is invalid or inaccessible, the script returns a datetime object representing 1970-01-01 00:00:00.
        .EXAMPLE
        Get-OZOChildWriteTime -Path (Join-Path -Path $Env:USERPROFILE -ChildPath "Git")
        Saturday, February 15, 2025 17:44:45
        .EXAMPLE
        Get-OZOChildWriteTime -Path (Join-Path -Path $Env:USERPROFILE -ChildPath "Git") -Oldest
        Friday, February 9, 2024 18:03:56
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZOChildWriteTime.md
    #>

    param(
        [Parameter(Mandatory=$false,HelpMessage="Include punctuation and spacing")][Switch]$Oldest,
        [Parameter(Mandatory=$false,HelpMessage="The path to inspect")][String]$Path = (Get-Location)
        
    )
    # Get datetime objects
    [DateTime] $newestChildWriteTime = (Get-Date -Year 1970 -Month 01 -Day 01 -Hour 00 -Minute 00 -Second 00)
    [DateTime] $oldestChildWriteTime = (Get-Date)
    # Determine if the path is valid
    If ((Test-OZOPath -Path $Path) -eq $true) {
        # Iterate through the children of the path
        ForEach ($childItem in (Get-ChildItem -Recurse -Path $Path)) {
            # Determine if the write time is newer than the current newestChildWriteTime
            If ($childItem.LastWriteTime -gt $newestChildWriteTime) {
                # Write time is newer; update newestChildWriteTime
                $newestChildWriteTime = $childItem.LastWriteTime
            }
            # Determine if the write time is older than the current oldestChildWriteTime
            If ($childItem.LastWriteTime -lt $oldestChildWriteTIme) {
                # Write time is older; update oldestChildWriteTime
                $oldestChildWriteTime = $childItem.LastWriteTime
            }
        }
        # Determine if Oldest was specified
        If ($Oldest -eq $true) {
            # Oldest was specified; return with oldestChildWriteTime
            return $oldestChildWriteTime
        } Else {
            # Oldest was not specified; return with newestChildWriteTime
            return $newestChildWriteTime
        }
    } Else {
        # Path is invalid; return datetime object representing 1970-01-01 00:00:00
        return $newestChildWriteTime
    }
}

Function Get-OZOFileToBase64 {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns a Base-64 string representing a valid file, or "File not found" if the file does not exist or cannot be read.
        .PARAMETER Path
        The path to the file to convert to a base-64 string.
        .EXAMPLE
        Get-OZOFileToBase64 -Path .\README.md
        IyBPWk8gUG93ZXJTaGVsbCBNb2R1bGUgSW5zdGFsbGF... <snip>
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZOFileToBase64.md
    #>

    param(
        [Parameter(Mandatory=$true,HelpMessage="The path to the file to convert to a base-64 string",ValueFromPipeline=$true)][String]$Path
    )
    # Determine if Path is readable
    If ((Test-OZOPath -Path $Path) -eq $true) {
        # Path is readable; convert file to base-64 string
        return [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes((Resolve-Path -Path $Path)))
    } Else {
        # Path is not readable; report
        return "File not found"
    }
}

Function Get-OZOHostname {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns the hostname for a given fully qualified domain name ("FQDN"). If executed without parameters, it returns the hostname of the running system.
        .PARAMETER FQDN
        The fully qualified domain name.
        .EXAMPLE
        Get-OZOHostname -FQDN "example.contoso.com"
        example
        .EXAMPLE
        Get-OZOHostname
        DESKTOP-OZO80202
        .OUTPUTS
        System.String
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documenation/Get-OZOHostname.md
    #>

    # Parameters
    param (
        [Parameter(Mandatory=$false,HelpMessage="The fully qualified domain name",ValueFromPipeline=$true)][String]$FQDN
    )
    # Determine if FQDN is null or empty
    If ([String]::IsNullOrEmpty($FQDN)) {
        # FQDN is null or empty; return the local hostname
        return $Env:COMPUTERNAME
    } Else {
        # FQDN is not null or empty; parse and return the hostname
        return ($FQDN -Split "\.",2)[0]
    }
}

Function Get-OZONumberIsOdd {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Evaluates an integer and returns True if the number is odd or False if the number is even.
        .PARAMETER Number
        The number to evaluate. Accepts pipeline input.
        .EXAMPLE
        Get-OZONumberIsOdd -Number 5
        True
        .EXAMPLE
        Get-OZONumberIsOdd -Number 4
        False
        .OUTPUTS
        System.Boolean
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZONumberIsOdd.md
    #>

    # Parameters
    param (
        [Parameter(Mandatory=$true,HelpMessage="The number to evaluate",ValueFromPipeline=$true)][Int32]$Number
    )
    # Return
    return [Boolean]($Number%2)
}

Function Get-OZOUserInteractive {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns TRUE if the PowerShell session is user-interactive and FALSE if not.
        .EXAMPLE
        Get-OZOUserInteractive
        True
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Get-OZOUserInteractive.md
    #>

    return [System.Environment]::UserInteractive
}

Function New-OZOSecurePassword {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns a secure string (password).
        .PARAMETER CharacterCount
        The total number of characters in the string. Defaults to 16.
        .PARAMETER SpecialsCount
        The number of special characters. If you do not specify this parameter, or if you specify a value that is higher than the total number of characters, the secure string will contain 2 special characters.
        .EXAMPLE
        New-OZOSecurePassword
        z?p/1zD-d(:Xd[R|
        .EXAMPLE
        New-OZOSecurePassword -CharacterCount 64 -SpecialsCount 16
        t.kgL@yoBv+f68oYEGVRTpBTZ{>.:qQ=RABH/F%X1g*U6]rX|2|KWErZ@b#m{i$o
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/New-OZOSecurePassword.md

    #>

    param(
        [Parameter(Mandatory=$false,HelpMessage="The number of characters in the string")][Int16]$CharacterCount = 16,
        [Parameter(Mandatory=$false,HelpMessage="The number of special characters")][Int16]$SpecialsCount = 2
    )
    # Load the required assembly
    [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
    # Determine if SpecialsCount is greater than or equal to CharacterCount; and if yes, set it to 2
    If ($SpecialsCount -ge $CharacterCount) { $SpecialsCount = 2}
    # return the secure string
    return [System.Web.Security.Membership]::GeneratePassword($CharacterCount,$SpecialsCount)
}

Function Send-OZOMail {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Sends an email message using an anonymous, unencrypted SMTP relay. Returns TRUE on success and FALSE on failure.
        .PARAMETER To
        A comma-separated list of message recipients. You must supply at least one recipient. You may supply a simple email address e.g., "noreply@onezeroone.dev" or you can use the "One Zero One Noreply <noreply@onezeroone.dev>" syntax for "prettier" headers.
        .PARAMETER Cc
        A comma-separated list of additional recipients. Addresses may be formatted as described in the "To" parameter.
        .PARAMETER Bcc
        A comma-separated list of additional [hidden] recipients. Addresses may be formatted as described in the "To" parameter.
        .PARAMETER From
        The message sender. Addresses may be formatted as described in the "To" parameter.
        .PARAMETER Subject
        The message subject.
        .PARAMETER Body
        The message body.
        .PARAMETER Attachments
        A comma-separated list of file paths to attach. If a file is not found or cannot be read, the message will not be sent.
        .PARAMETER MailServer
        The SMTP relay server to use.
        .OUTPUTS
        System.Boolean
        .EXAMPLE
        Send-OZOMail -To "OZO Info <info@onezeroone.dev>" -From "OZO Noreply <noreply@onezeroone.dev" -Subject "Test" -Body "This is a test." -MailServer "smtp.onezerone.dev"
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Send-OZOMail.md

    #>

    param(
        [Parameter(Mandatory=$true,HelpMessage="A list of message recipients")][Array]$To,
        [Parameter(Mandatory=$false,HelpMessage="A list of additional recipients")][Array]$Cc = $null,
        [Parameter(Mandatory=$false,HelpMessage="A list of additional [hidden] recipients")][Array]$Bcc = $null,
        [Parameter(Mandatory=$true,HelpMessage="The message sender")][String]$From,
        [Parameter(Mandatory=$true,HelpMessage="The message subject")][String]$Subject,
        [Parameter(Mandatory=$true,HelpMessage="The message body.")][String]$Body,
        [Parameter(Mandatory=$false,HelpMessage="A list of files to attach")][Array]$Attachments = $null,
        [Parameter(Mandatory=$true,HelpMessage="The SMTP relay server to use")][String]$MailServer
    )
    # Variables
    [Boolean] $Return   = $true
    [Boolean] $Send     = $true
    [Int16]   $MailPort = 25
    # Determine that we can reach the SMTP server on port 25
    If ((Test-NetConnection -ComputerName $MailServer -Port $MailPort) -eq $true) {
        # Reached SMTP server on port 25
        # Construct a mail message object
        $ozoMail = New-Object Net.Mail.MailMessage
        # Add the To recipients
        ForEach ($Recipient in $To) { $ozoMail.To.Add($Recipient)}
        # Add the From sender
        $ozoMail.From = $From
        # Add the Subject
        $ozoMail.Subject = $Subject
        # Add the Body
        $ozoMail.Body = $Body
        # Add the Cc recipients (if any)
        If ($null -ne $Cc) {ForEach ($Recipient in $Cc) { $ozoMail.CC.Add($Receipient)}}
        # Add the Bcc recipients (if any)
        If ($null -ne $Bcc) {ForEach ($Recipient in $Bcc) { $ozoMail.Bcc.Add($Recipient)}}
        # Add the Attachments (if any)
        If ($null -ne $Attachments) {
            # Iterate through the attachments
            ForEach ($Path in $Attachments) {
                If ((Test-Path -Path (Resolve-Path -Path $Path)) -eq $true) {
                    $ozoMail.Attachments.Add((New-Object Net.Mail.Attachment((Resolve-Path -Path $Path))))
                } Else {
                    $Send = $false
                }
            }
        }
        # Determine if Send is true (all prerequisites satisfied)
        If ($Send -eq $true) {
            # Establish an SMTP connection
            $ozoSMTP = New-Object Net.Mail.SmtpClient($MailServer,$MailPort)
            # Attempt to send the message
            Try {
                $ozoSMTP.Send($ozoMail)
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
    } Else {
        # Unable to reach mail server
        $Return = $false
    }
    # Return
    return $Return
}

Function Set-OZOBase64ToFile {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Writes a base-64 string to disk as a file. Returns TRUE on success and FALSE on failure.
        .PARAMETER Base64
        The base-64 string to convert.
        .PARAMETER Path
        The output file path. If the file exists, it will be overwritten.
        .EXAMPLE
        Set-OZOBase64ToFile -Base64 "IyBPWk8gUG93ZXJTaGVsbCBNb2R1bGUgSW5zdGFsbGF..." -Path .\README.md
        True
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Set-OZOBase64ToFile.md
    #>

    param(
        [Parameter(Mandatory=$true,HelpMessage="The base-64 string",ValueFromPipeline=$true)][String]$Base64,
        [Parameter(Mandatory=$true,HelpMessage="The output file path")][String]$Path
    )
    # Split Directory from Path
    [String] $Directory = (Split-Path -Path $Path -Parent)
    # Ensure the Directory exists and is writable
    If ((Test-OZOPath -Path $Directory -Writable) -eq $true) {
        # Path
        [System.IO.File]::WriteAllBytes($Path,[Convert]::FromBase64String($Base64))
        # Determine if the file exists
        If ((Test-Path -Path $Path) -eq $true) {
            # File exists
            return $true
        } Else {
            # File does not exist
            return $false
        }
    }
}

Function Test-OZOLocalAdministrator {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Returns TRUE if the current user is a local administrator and FALSE if not.
        .EXAMPLE
        Test-OZOLocalAdministrator
        True
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Test-OZOLocalAdministrator.md
    #>

    return (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

Function Test-OZOPath {
    <#
        .SYNOPSIS
        See description.
        .DESCRIPTION
        Determines if a path exists and is readable. Optionally tests if the path is writable.
        .PARAMETER Path
        The path to test. Returns TRUE if the path exists and is readable and otherwise returns FALSE.
        .PARAMETER Writable
        Determines if the path is writable. Returns TRUE if the path is writable and otherwise returns FALSE.
        .EXAMPLE
        Test-OZOPath -Path .\README.md
        True
        .LINK
        https://github.com/onezeroone-dev/OZO-PowerShell-Module/blob/main/Documentation/Test-OZOPath.md
    #>

    param(
        [Parameter(Mandatory=$true,HelpMessage="The path to test",ValueFromPipeline=$true)][String]$Path,
        [Parameter(Mandatory=$false,HelpMessage="Test if Path is writable")][Switch]$Writable
    )
    # Booleans for readable and writable
    [Boolean] $isReadable = $false
    [Boolean] $isWritable = $false
    # Object to hold path properties
    [System.IO.FileSystemInfo] $Item = $null
    # Try to get the item
    Try {
        $Item = Get-Item -Path $Path -ErrorAction Stop
        # Success; Determine if path is a File
        If ((Test-Path -Path $Path -PathType Leaf -ErrorAction SilentlyContinue) -eq $true) {
            # File; if not read only, set readable and writable
            $isReadable = -Not $Item.IsReadOnly
            $isWritable = $Item.IsReadOnly
        } Else {
            # Directory
            [String] $TestPath = (Join-Path -Path $Path -ChildPath (New-Guid).Guid)
            # Set readable
            $isReadable = [Boolean](Get-ChildItem -Path $Path -ErrorAction SilentlyContinue)
            # Try to write a file
            Try {
                New-Item -ItemType File -Path $TestPath -ErrorAction Stop | Out-Null
                # Success; set writable to True and clean up
                $isWritable = $true
                Remove-Item -Path $TestPath -ErrorAction Stop
            } Catch {
                # Failure; set writable to False
                $isWritable = $false
            }
        }
    } Catch {
        # Failure; path does not exist or is not accessible; set readable and writable
        $isReadable = $false
        $isWritable = $false
    }
    # Determine if Writable was specified
    If ($Writable -eq $true) {
        # return Writable
        return $isWritable
    } Else {
        # return Readable
        return $isReadable
    }
}

Export-ModuleMember -Function Get-OZO64BitPowerShell,Get-OZO8601Date,Get-OZOChildWriteTime,Get-OZOFileToBase64,Get-OZOHostname,Get-OZONumberIsOdd,Get-OZOUserInteractive,New-OZOSecurePassword,Send-OZOMail,Set-OZOBase64ToFile,Test-OZOLocalAdministrator,Test-OZOPath

# SIG # Begin signature block
# MIIfcQYJKoZIhvcNAQcCoIIfYjCCH14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhc46BPmLfGk5A
# XcFuP9AatZe/1ZxlbG/XNaYMumVTSKCCDPgwggZyMIIEWqADAgECAghkM1HTxzif
# CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
# EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G
# A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe
# Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w
# DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv
# cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD
# QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG
# bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU
# Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX
# 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC
# GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB
# wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk
# jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X
# gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg
# Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge
# VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR
# 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV
# t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH
# AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE
# CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g
# LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G
# A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ
# KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH
# 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x
# xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT
# V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P
# Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc
# +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ
# LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV
# HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3
# NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9
# XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz
# iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od
# MIIGfjCCBGagAwIBAgIQZ2iSsNbwOsjnLExSAX6F6DANBgkqhkiG9w0BAQsFADB4
# MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x
# ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu
# ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI0MTExNjEwMzUyOFoXDTI1MTEx
# NjEwMzUyOFowZTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYD
# VQQHDAZEZW52ZXIxGDAWBgNVBAoMD0FuZHJldyBMaWV2ZXJ0ejEYMBYGA1UEAwwP
# QW5kcmV3IExpZXZlcnR6MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA
# vIBAQzK0aahepOrPmvCEqfd6dMZC4GvV7kflKwrn4QPJGfqhFmUtadP1e3ange8O
# QZ3/w7UjOTAUNUHfhjbSgUBlKjbS6EWQKZuRFzI3SNkMJkcjTX4uS2P4QsnwM+SW
# IE5me3CTssdjtgue+Iiy53TMgW8JpoxiULVxmm3bhCRUAgxWeT6tzjytR1UyGcMc
# cm/YE6TOgsCHiZoo4X4HJD9iHDrNldArq04Jl6FsADxEswttKyfqpIRJLoAysVl1
# f8CEDBwhszJrEXBnAlWViJFfNY+dKP4jhf7lLqSvPCuADqP2jvM0Ym5I8qDGMz9j
# XPSMLF58MFB4vM4viS7nLRFJ8S1Q98vQvB8W4kk0WPuiZbZTHsROzohE1VSbLnIY
# ag5dDOWI8L6yutAsfdZFYFmSTKcMSiOj5VbK4LhAJUL2G8vPwpTGFgr+cEp0p62F
# P0WXK+/cRfGqodI5S+bg+9rQTD9zf829DwraSRAt5P5zrQk4WPst3JW/vIKNx7cV
# AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFTC/hCVAJPN
# avXnwNfZsku4jwzjMHoGCCsGAQUFBwEBBG4wbDBIBggrBgEFBQcwAoY8aHR0cDov
# L2NlcnQuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQwOTYt
# UjEuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTBRBgNVHSAE
# SjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEwLDAqBggrBgEFBQcCARYeaHR0
# cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMD
# ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMY29tLVN1
# YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNybDAdBgNVHQ4EFgQUSj8HrSK7
# f/j+Dz31jJFhOF7rJUMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IC
# AQBf4lcc6FUJ1W/opNz8yjS9qLUy9cQt0s35BhasB5QoTbDaW4jv9xnFGhQVg6n+
# jhL0i94Vsywd/MRBb8lYGpuBZnS/7LHuRZu7qUuud+IMDyRHIyBK6koN5bfyA5VY
# c7bFbNpbe1s1hMWke8di4qgMLZKDfyG/RtA0swf5t4UgQLPP0h+koZ8X8V5+P0V0
# 1HsdXyXd+ojo38EoZyCKfQL2aAwMPwzZfCbmI5SRXNOc6K8oqXzQcendhlKSfVBo
# Zgpi+1updqbD4jmJfYdK5AYPxJ3YH6td6ETtr8owL+bmX8lQjlXPOwVnC11rVlNB
# VjqtaJRUClLtiNiYSTKVfjdmGVJ4+sNov0dWhHc0A9o5NX/05VVYTlImuJpnG5Og
# o7w6kWRdsgE8gM58jWf7XfI6aQS0Np/z2B+ZBj0K93khEHBX7cvvORa92LCHiVeP
# km+zEAMXgxIPs/e8cmcc/o3CORgzEwxlH9Z3UOWCuXSHD3P2RPNDAY+WPdjSHm9f
# JFlGq+f9iKyedxYa/NNjNag/5EbZ+Z2NldtSMNeFdsejGJ/TJHF1PyJd4aXx9J1i
# B/IZBOoJYyh9xpQ3ljZUKE/4otPi7INpuDFwgWiUHZZJVvrGTWwxH1Yhf8P+VpFf
# aNqsBuvklUcUDs3RNE0f1qlgFfcnAepFF+RiBRqmsj29fjGCEc8wghHLAgEBMIGM
# MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv
# bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu
# aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ
# YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK
# KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG
# 9w0BCQQxIgQgoimyRK5WHS7Uk+6iMsuglGzq8kiS6J1aYBVRTXKYBCIwDQYJKoZI
# hvcNAQEBBQAEggGAl7KTTI8A83ooZAUSV601IpC5kWfjEkBi/FgNaZUipNpclK8t
# obw04zVfAW3GbNdU5SlL0Mg9eCz/ImFV2bMpsCrckAbjTjedq5nwBJQGI1VptSgG
# t5cNLzZbBFaxQF+6bLnWikJcg8ctQqCmjqPIYswISrk0lPOVdl5zXxElDg5d83z7
# 0Bga/HdidbjHjRdFwDxhf/K+HcV+bB5+hr2i8tu1tLVq04xtroxdGGsHDYr9rS5q
# d6QwEl1+Uj6EpIcSwlXltF3SENwJFPr0LKX5jpqwCDAWrex1migJULt+PiO/nN7Y
# 3ZtzrHMV1MTb+CzK7gaJi0/UuA+rD70p7/dKV+WrbIpq2DhEMY22iSUEsZdXW/L2
# rhhQGSUyfF5EOrrG7XnFztSofKjgqURtrFSAvwYt7ob2bmV1biHlmIp57PTdmbeJ
# YCROQZPm0kdky7ScvVsyJ06Q9/TPIK438Qalj8xnjZUGWAi9cmVoAYRJuLJmvqlX
# zniPx4HpX+vC1EI+oYIPFTCCDxEGCisGAQQBgjcDAwExgg8BMIIO/QYJKoZIhvcN
# AQcCoIIO7jCCDuoCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm
# MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCAJAitvjEreXJHL
# HbFipAVMUF4ZBBtUyAd/tWcGeQsOlQIIfFY5CaMYVm0YDzIwMjUwMjE2MDUwMTIw
# WjADAgEBoIIMADCCBPwwggLkoAMCAQICEFparOgaNW60YoaNV33gPccwDQYJKoZI
# hvcNAQELBQAwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQH
# DAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBU
# aW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjEwHhcNMjQwMjE5MTYxODE5WhcN
# MzQwMjE2MTYxODE4WjBuMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
# BgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMSowKAYDVQQDDCFTU0wu
# Y29tIFRpbWVzdGFtcGluZyBVbml0IDIwMjQgRTEwWTATBgcqhkjOPQIBBggqhkjO
# PQMBBwNCAASnYXL1MOl6xIMUlgVC49zonduUbdkyb0piy2i8t3JlQEwA74cjK8g9
# mRC8GH1cAAVMIr8M2HdZpVgkV1LXBLB8o4IBWjCCAVYwHwYDVR0jBBgwFoAUDJ0Q
# JY6apxuZh0PPCH7hvYGQ9M8wUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVo
# dHRwOi8vY2VydC5zc2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIx
# LmNlcjBRBgNVHSAESjBIMDwGDCsGAQQBgqkwAQMGATAsMCoGCCsGAQUFBwIBFh5o
# dHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkwCAYGZ4EMAQQCMBYGA1UdJQEB
# /wQMMAoGCCsGAQUFBwMIMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jcmxzLnNz
# bC5jb20vU1NMLmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY3JsMB0GA1UdDgQW
# BBRQTySs77U+YxMjCZIm7Lo6luRdIjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcN
# AQELBQADggIBAJigjwMAkbyrxGRBf0Ih4r+rbCB57lTuwViC6nH2fZSciMogpqSz
# rSeVZ2eIb5vhj9rT7jqWXZn02Fncs4YTrA1QyxJW36yjC4jl5/bsFCaWuXzGXt2Y
# 6Ifp//A3Z0sNTMWTTBobmceM3sqnovdX9ToRFP+29r5yQnPcgRTI2PvrVSqLxY9E
# yk9/0cviM3W29YBl080ENblRcu3Y8RsfzRtVT/2snuDocRxvRYmd0TPaMgIj2xII
# 651QnPp1hiq9xU0AyovLzbsi5wlR5Ip4i/i8+x+HwYJNety5cYtdWJ7uQP6YaZtW
# /jNoHp76qNftq/IlSx6xEYBRjFBxHSq2fzhUQ5oBawk2OsZ2j0wOf7q7AqjCt6t/
# +fbmWjrAWYWZGj/RLjltqdFPBpIKqdhjVIxaGgzVhaE/xHKBg4k4DfFZkBYJ9BWu
# P93Tm+paWBDwXI7Fg3alGsboErWPWlvwMAmpeJUjeKLZY26JPLt9ZWceTVWuIyuj
# erqb5IMmeqLJm5iFq/Qy4YPGyPiolw5w1k9OeO4ErmS2FKvk1ejvw4SWR+S1VyWn
# ktY442WaoStxBCCVWZdMWFeB+EpL8uoQNq1MhSt/sIUjUudkyZLIbMVQjj7b6gPX
# nD6mS8FgWiCAhuM1a/hgA+6o1sJWizHdmcpYDhyNzorf9KVRE6iR7rcmMIIG/DCC
# BOSgAwIBAgIQbVIYcIfoI02FYADQgI+TVjANBgkqhkiG9w0BAQsFADB8MQswCQYD
# VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNV
# BAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp
# ZmljYXRpb24gQXV0aG9yaXR5IFJTQTAeFw0xOTExMTMxODUwMDVaFw0zNDExMTIx
# ODUwMDVaMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwH
# SG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGlt
# ZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEArlEQE9L5PCCgIIXeyVAcZMnh/cXpNP8KfzFI6HJaxV6oYf3x
# h/dRXPu35tDBwhOwPsJjoqgY/Tg6yQGBqt65t94wpx0rAgTVgEGMqGri6vCI6rEt
# SZVy9vagzTDHcGfFDc0Eu71mTAyeNCUhjaYTBkyANqp9m6IRrYEXOKdd/eREsqVD
# mhryd7dBTS9wbipm+mHLTHEFBdrKqKDM3fPYdBOro3bwQ6OmcDZ1qMY+2Jn1o0l4
# N9wORrmPcpuEGTOThFYKPHm8/wfoMocgizTYYeDG/+MbwkwjFZjWKwb4hoHT2WK8
# pvGW/OE0Apkrl9CZSy2ulitWjuqpcCEm2/W1RofOunpCm5Qv10T9tIALtQo73GHI
# lIDU6xhYPH/ACYEDzgnNfwgnWiUmMISaUnYXijp0IBEoDZmGT4RTguiCmjAFF5OV
# NbY03BQoBb7wK17SuGswFlDjtWN33ZXSAS+i45My1AmCTZBV6obAVXDzLgdJ1A1r
# yyXz4prLYyfJReEuhAsVp5VouzhJVcE57dRrUanmPcnb7xi57VPhXnCuw26hw1Hd
# +ulK3jJEgbc3rwHPWqqGT541TI7xaldaWDo85k4lR2bQHPNGwHxXuSy3yczyOg57
# TcqqG6cE3r0KR6jwzfaqjTvN695GsPAPY/h2YksNgF+XBnUD9JBtL4c34AcCAwEA
# AaOCAYEwggF9MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU3QQJB6L1
# en1SUxKSle44gCUNplkwgYMGCCsGAQUFBwEBBHcwdTBRBggrBgEFBQcwAoZFaHR0
# cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tUm9vdENlcnRpZmljYXRp
# b25BdXRob3JpdHlSU0EuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3Ns
# LmNvbTA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cHM6Ly93
# d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMIMDsGA1Ud
# HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5jb20vc3NsLmNvbS1yc2EtUm9v
# dENBLmNybDAdBgNVHQ4EFgQUDJ0QJY6apxuZh0PPCH7hvYGQ9M8wDgYDVR0PAQH/
# BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSGXUNplpCzxkH2fL8lPrAm/AV6USW
# Wi9xM91Q5RN7mZN3D8T7cm1Xy7qmnItFukgdtiUzLbQokDJyFTrF1pyLgGw/2hU3
# FJEywSN8crPsBGo812lyWFgAg0uOwUYw7WJQ1teICycX/Fug0KB94xwxhsvJBiRT
# pQyhu/2Kyu1Bnx7QQBA1XupcmfhbQrK5O3Q/yIi//kN0OkhQEiS0NlyPPYoRboHW
# C++wogzV6yNjBbKUBrMFxABqR7mkA0x1Kfy3Ud08qyLC5Z86C7JFBrMBfyhfPpKV
# lIiiTQuKz1rTa8ZW12ERoHRHcfEjI1EwwpZXXK5J5RcW6h7FZq/cZE9kLRZhvnRK
# tb+X7CCtLx2h61ozDJmifYvuKhiUg9LLWH0Or9D3XU+xKRsRnfOuwHWuhWch8G7k
# EmnTG9CtD9Dgtq+68KgVHtAWjKk2ui1s1iLYAYxnDm13jMZm0KpRM9mLQHBK5Gb4
# dFgAQwxOFPBslf99hXWgLyYE33vTIi9p0gYqGHv4OZh1ElgGsvyKdUUJkAr5hfbD
# X6pYScJI8v9VNYm1JEyFAV9x4MpskL6kE2Sy8rOqS9rQnVnIyPWLi8N9K4GZvPit
# /Oy+8nFL6q5kN2SZbox5d69YYFe+rN1sDD4CpNWwBBTI/q0V4pkgvhL99IV2Xasj
# HZf4peSrHdL4RjGCAlcwggJTAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
# DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt
# BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa
# WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAyMTYwNTAxMjBaMCgGCSqG
# SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ
# BDEiBCCdv74fMfnonT5l9UdCP9KJJZAQW2ptcagl/BwyLw/UuzCByQYLKoZIhvcN
# AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29
# iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t
# IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9
# xzAKBggqhkjOPQQDAgRGMEQCIAXT9ZRA8IYFxpVz5nmclzoMT1FQDJ2f/yVUuBiW
# KcFsAiAhXqFOLHedmbDzTaHEYl7ohCZCBSfRjLUgKOnFsPqPeA==
# SIG # End signature block