ozo-ad-lab-implement-customization-prerequisites.ps1

#Requires -Modules @{ModuleName="OZO";ModuleVersion="1.5.1"},@{ModuleName="OZOLogger";ModuleVersion="1.1.0"} -RunAsAdministrator

<#PSScriptInfo
    .VERSION 0.1.0
    .GUID 2a8769c1-6be2-44f3-ae17-47b4138ea2fa
    .AUTHOR Andy Lievertz <alievertz@onezeroone.dev>
    .COMPANYNAME One Zero One
    .COPYRIGHT This script is released under the terms of the GNU General Public License ("GPL") version 2.0.
    .TAGS
    .LICENSEURI https://github.com/onezeroone-dev/OZO-AD-Lab-Implement-Prerequisites/blob/main/LICENSE
    .PROJECTURI https://github.com/onezeroone-dev/OZO-AD-Lab-Implement-Prerequisites
    .ICONURI
    .EXTERNALMODULEDEPENDENCIES
    .REQUIREDSCRIPTS
    .EXTERNALSCRIPTDEPENDENCIES
    .RELEASENOTES https://github.com/onezeroone-dev/OZO-AD-Lab-Implement-Prerequisites/blob/main/CHANGELOG.md
    .PRIVATEDATA
#>


<#
    .SYNOPSIS
    See description.
    .DESCRIPTION
    Implements the prerequisites for the One Zero One AD Lab.
    .EXAMPLE
    ozo-ad-lab-implement-prerequisites
    .LINK
    https://github.com/onezeroone-dev/OZO-AD-Lab-Implement-Prerequisites/blob/main/README.md
#>


Class ADLIP {
    # PROPERTIES: Booleans, Strings
    [Boolean] $Relog             = $true
    [String]  $currentUser       = $null
    [String]  $downloadsDir      = $null
    [String]  $featureName       = $null
    [String]  $gitExePath        = $null
    [String]  $localGroup        = $null
    [String]  $ozoAdLabDirLike   = $null
    [String]  $ozoAdLabPath      = $null
    [String]  $ozoAdLabZipPath   = $null
    [String]  $ozoAdLabZipUri    = $null
    [String]  $winAdkFileName    = $null
    [String]  $winAdkPath        = $null
    [String]  $winAdkFileUri     = $null
    [String]  $wingetExePath     = $null
    # PROPERTIES: PSCustomObjects
    [PSCustomObject] $ozoLogger = @()
    # PROPERTIES: Lists
    [System.Collections.Generic.List[PSCustomObject]] $ozoADLabISOs = @()
    # METHODS
    # Constructor method
    ADLIP() {
        # Set properties
        $this.currentUser       = ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
        $this.downloadsDir      = (Join-Path -Path $Env:USERPROFILE -ChildPath "Downloads")
        $this.featureName       = "Microsoft-Hyper-V-All"
        $this.localGroup        = "Hyper-V Administrators"
        $this.ozoAdLabDirLike   = "onezeroone-dev-OZO-AD-Lab*"
        $this.ozoAdLabPath      = (Join-Path -Path $Env:SystemDrive -ChildPath "ozo-ad-lab")
        $this.ozoAdLabZipPath   = (Join-Path -Path $Env:USERPROFILE -ChildPath "Downloads\ozo-ad-lab-latest.zip")
        $this.ozoAdLabZipUri    = "https://api.github.com/repos/onezeroone-dev/OZO-AD-Lab/releases/latest"
        $this.winAdkFileName    = "adksetup.exe"
        $this.winAdkPath         = (Join-Path -Path $this.downloadsDir -Childpath $this.winAdkFileName)
        $this.winAdkFileUri     = "https://download.microsoft.com/download/2/d/9/2d9c8902-3fcd-48a6-a22a-432b08bed61e/ADK/adksetup.exe"
        $this.wingetExePath     = (Join-Path -Path $Env:LOCALAPPDATA -ChildPath "Microsoft\WindowsApps\winget.exe")
        # Populate the ozoADLabISOs list
        $this.ozoADLabISOs.Add([PSCustomObject]@{Name="almalinux-boot.iso";Uri="https://repo.almalinux.org/almalinux/9.5/isos/x86_64/AlmaLinux-9.5-x86_64-boot.iso"})
        $this.ozoADLabISOs.Add([PSCustomObject]@{Name="microsoft-windows-11-enterprise-evaluation.iso";Uri="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1742.240906-0331.ge_release_svc_refresh_CLIENTENTERPRISEEVAL_OEMRET_x64FRE_en-us.iso"})
        $this.ozoADLabISOs.Add([PSCustomObject]@{Name="microsoft-windows-11-laof.iso";Uri="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_amd64fre_CLIENT_LOF_PACKAGES_OEM.iso"})
        $this.ozoADLabISOs.Add([PSCustomObject]@{Name="microsoft-windows-server-2022-evaluation.iso";Uri="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso"})
        # Create a logger object
        $this.ozoLogger = (New-OZOLogger)
        # Declare ourselves to the world
        $this.ozoLogger.Write("Process starting.","Information")
        # Call ValidateEnvironment to determine if we can proceed
        If ($this.ValidateEnvironment() -eq $true) {
            # Environment validates; report and call ProcessPrerequisites to ...process the prerequisites
            $this.ozoLogger.Write("Environment validates.","Information")
            $this.ProcessPrerequisites()
        } Else {
            # Environment did not validate
            $this.ozoLogger.Write("The environment did not validate.","Error")
        }
        # Bid adieu to the world
        $this.ozoLogger.Write("Process complete.","Information")
    }
    # Process prerequisites method
    Hidden [Void] ProcessPrerequisites() {
        # Environment validates; install Hyper-V features
        $this.ozoLogger.Write("Installing Hyper-V features.","Information")
        If ($this.InstallHyperV() -eq $true) {
            # Hyper-V features are installed; install the Debian WSL distribution
            If ($this.InstallWSLDebian() -eq $true) {
                # WSL Debian distribution is installed; determine if a reboot is not required
                $this.ozoLogger.Write("Determining if a restart is required.","Information")
                If ($this.RestartRequired() -eq $false) {
                    # Restart is not required; add the local user to the Hyper-V Administrators group
                    $this.ozoLogger.Write("Adding user to the local Hyper-V Administrators group.","Information")
                    If ($this.ManageLocalHyperVAdministratorsGroup() -eq $true) {
                        # Local user is added to the local Hyper-V Administrators group; create the VM switches
                        $this.ozoLogger.Write("Creating the Hyper-V VMSwitches.","Information")
                        If ($this.CreateVMSwitches() -eq $true) {
                            # VM switches are created; installed the Microsoft SDK
                            $this.ozoLogger.Write("Installing the Microsoft ADK (Deployment Tools).","Information")
                            If ($this.InstallMicrosoftADK() -eq $true) {
                                # Microsoft SDK is installed; install Git for Windows
                                $this.ozoLogger.Write("Downloading and extracting the latest release of the OZO AD Lab resources.","Information")
                                If ($this.GetADLabResources() -eq $true) {
                                    # Got AD Lab resources; download the ISOs
                                    $this.ozoLogger.Write("Downloading the source ISOs (this could take some time).","Information")
                                    If ($this.DownloadISOs() -eq $true) {
                                        # ISOs are downloaded; report all prerequisites satisfied
                                        $this.ozoLogger.Write("All prerequistes are satisified. Please see https://onezeroone.dev/active-directory-lab-customize-the-windows-installer-isos for the next steps.","Information")
                                    } Else {
                                        # Download error
                                        $this.ozoLogger.Write("Error downloading ISOs. Please manually download the required ISOs. Then see https://onezeroone.dev/active-directory-lab-customize-the-windows-installer-isos for the next steps.","Error")
                                    }
                                } Else {
                                    # Unable to get AD Lab Resources
                                    $this.ozoLogger.Write("Error downloading and extracting the latest OZO AD Lab resources. Please manually download and extract the latest release and run this script again to continue. See https://onezeroone.dev/active-directory-lab-prerequisites for more information.","Error")
                                }
                            } Else {
                                # Microsoft SDK installation error
                                $this.ozoLogger.Write("Error attempting to download and install the Microsoft ADK. Please manually download and install and then run this script again to continue. See https://onezeroone.dev/active-directory-lab-prerequisites for more information.","Error")
                            }
                        } Else {
                            # VMSwitch creation error
                            $this.ozoLogger.Write("Error creating the VM switches. Please manually create these switches then run this script again to continue. See https://onezeroone.dev/active-directory-lab-prerequisites for more information.","Error")
                        }
                    } Else {
                        # Error adding user to local Hyper-V Administrators group
                        $this.ozoLogger.Write(("Failure adding user " + $this.currentUser + " to the " + $this.localGroup + " group. Please manually add this user to this group then run this script again to continue. See https://onezeroone.dev/active-directory-lab-prerequisites for more information."),"Error")
                    }
                } Else {
                    # Restart is required
                    $this.ozoLogger.Write("Please restart to complete the feature installation and then run this script again to continue.","Warning")
                    # Get restart decision
                    If ((Get-OZOYesNo) -eq "y") {
                        # User elects to restart
                        Restart-Computer
                    }
                }
            } Else {
                # Error installing WSL Debian
                $this.ozoLogger.Write(("Error installing the WSD Debian distribution. Please manually install this distribution and then run this script again to continue."),"Error")    
            }
        } Else {
            # Error installing Hyper-V Feature
            $this.ozoLogger.Write(("Error installing the " + $this.featureName + " feature. Please manually install this feature and then run this script again to continue."),"Error")
        }
    }
    # Environment validation method
    Hidden [Boolean] ValidateEnvironment() {
        # Control variable
        [Boolean] $Return = $true
        # Determine if this a user-interactive session
        If ((Get-OZOUserInteractive) -eq $false) {
            # Session is not user-interactive
            $this.ozoLogger.Write("Please run this script in a user-interactive session.","Error")
            $Return = $false
        }
        # Determine if user is an Administrator
        If ((Test-OZOLocalAdministrator) -eq $false) {
            # User is not a local administrator
            $this.ozoLogger.Write("Please run this script in an Administrator PowerShell","Error")
            $Return =$false
        }
        # Determine of winget.exe does not exist
        If ((Test-Path -Path $this.wingetExePath) -eq $false) {
            # Did not find winget.exe
            $this.ozoLogger.Write("Missing winget.exe","Error")
            $Return = $false
        }
        # Determine if there is already an "ozo-ad-lab" folder off the root of the SystemDrive
        If ((Test-Path -Path $this.ozoAdLabPath) -eq $true) {
            # There is already an "ozo-ad-lab" folder
            $this.ozoLogger.Write(("Found " + $this.ozoAdLabPath + ". This directory must be removed before proceeding."),"Error")
            $Return = $false
        }
        # Make sure any previous downloaded + extracted releases of OZO-AD-Lab are wiped
        (Get-ChildItem -Path $Env:TEMP | Where-Object {$_.Name -Like $this.ozoAdLabDirLike}) | Remove-Item -Recurse -Force
        # Return
        return $Return
    }
    # Install Hyper-V method
    Hidden [Boolean] InstallHyperV() {
        # Control variable
        [Boolean] $Return = $true
        # Determine if the feature is present
        If ([Boolean](Get-WindowsOptionalFeature -Online -FeatureName $this.featureName) -eq $false) {
            # Feature is not present; try to install it
            Try {
                Enable-WindowsOptionalFeature -Online -FeatureName $this.featureName -ErrorAction Stop
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
        # Return
        return $Return
    }
    Hidden [Boolean] InstallWSLDebian() {
        # Control variable
        [Boolean] $Return = $true
        # Check if WSL is installed/available and can install Debian
        Try {
            & wsl -l --Debian
            # Success
        } Catch {
            # Failure
            $Return = $false
        }
        # Return
        return $Return
    }
    # Reboot required method
    Hidden [Boolean] RestartRequired() {
        # Control variable
        [Boolean] $Return = $false
        # Determine if feature is present
        If ((Get-WindowsOptionalFeature -Online -FeatureName $this.featureName).RestartRequired -eq "Required") {
            # Restart is required
            $this.Return = $true   
        }
        # Return
        return $Return
    }
    # Manage local Hyper-V Administrators group membership
    Hidden [Boolean] ManageLocalHyperVAdministratorsGroup() {
        # Control variable
        [Boolean] $Return = $true
        # Determine if the current user is a member of the local Hyper-V Administrators group
        If ((Get-LocalGroupMember -Name $this.localGroup).Name -NotContains $this.currentUser) {
            # User is not in the local group; try to add them
            Try {
                Add-LocalGroupMember -Group "Hyper-V Administrators" -Member $this.currentUser
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
        # Return
        return $Return
    }
    # Create VM switches method
    Hidden [Boolean] CreateVMSwitches() {
        # Control variable
        [Boolean] $Return          = $true
        [String]  $externalAdapter = $null
        # Determine if the private switch already exists
        If ([Boolean](Get-VMSwitch -Name "AD Lab Private") -eq $false) {
            # Private switch does not exist; try to create it
            Try {
                New-VMSwitch -Name "AD Lab Private" -SwitchType Private -ErrorAction Stop
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
        # Determine if the external switch already exists
        If ([Boolean](Get-VMSwitch -Name "AD Lab External") -eq $false) {
            # External switch does not exist; call Get-NetAdapter to display available network connections
            Write-Host (Get-NetAdapter)
            # Prompt the user for the name of the external network connection until they correctly identify an adapter
            Do {
                $externalAdapter = (Read-Host "Above is the output of the Get-NetAdapter command. Type the Name of the network adapter that corresponds with your external network (Internet) connection")
            } Until ((Get-NetAdapter).Name -Contains $externalAdapter)
            # Try to create the external switch
            Try {
                New-VMSwitch -Name "AD Lab External" -NetAdapterName $externalAdapter -ErrorAction Stop
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
        # Return
        return $Return
    }
    # Install Microsoft SDK method
    Hidden [Boolean] InstallMicrosoftADK() {
        # Control variable
        [Boolean] $Return = $true
        # Local variables
        [String] $oscdimgExePath = (Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe")
        [String] $simExePath     = (Join-Path -Path ${Env:ProgramFiles(x86)} -ChildPath "Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\WSIM\x86\imgmgr.exe")
        # Determine if oscdimg.exe is note present
        If ((Test-Path -Path $oscdimgExePath) -eq $false -Or (Test-Path $simExePath) -eq $false) {
            # Did not find oscdimg.exe; try to download and install
            Try {
                Invoke-WebRequest -Uri $this.winAdkFileUri -OutFile $this.winAdkPath -ErrorAction Stop
                # Success; try to install
                Try {
                    Invoke-Command -ScriptBlock { & $this.winAdkPath /quiet /norestart /features OptionId.DeploymentTools } -ErrorAction Stop | Out-Null
                    # Success; sleep until the installation is complete
                    Do {
                        Start-Sleep -Seconds 1
                    } Until ((Test-Path -Path $oscdimgExePath) -eq $true -And (Test-Path $simExePath) -eq $true)
                } Catch {
                    # Failure
                    $Return = $false
                }
            } Catch {
                # Failure; report
                $Return = $false
            }
        }
        # Return
        return $Return
    }
    # Get AD Lab resources method
    Hidden [Boolean] GetADLabResources() {
        # Control variable
        [Boolean] $Return = $true
        # Try to get the latest zipball
        Try {
            Invoke-WebRequest -Uri (Invoke-WebRequest -Uri $this.ozoAdLabZipUri -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop).zipball_url -OutFile $this.ozoAdLabZipPath -ErrorAction Stop
            # Success; expand the archive
            Expand-Archive -Path $this.ozoAdLabZipPath -DestinationPath $Env:TEMP -ErrorAction Stop
            # Remove the archive
            Remove-Item -Path $this.ozoAdLabZipPath -Force
            # Move the extracted folder to the ozoAdLab
            Move-Item -Path (Get-ChildItem -Path $Env:TEMP -ErrorAction Stop | Where-Object {$_.Name -Like $this.ozoAdLabDirLike} | Select-Object -First 1).FullName -Destination $this.ozoAdLabPath
            # Create required (empty) Mount subdirectory
            New-Item -ItemType Directory -Path (Join-Path -Path $this.ozoAdLabPath -ChildPath "Mount") -ErrorAction Stop
        } Catch {
            #Failure
            $Return = $false
        }

        # Return
        return $Return
    }
    # Download ISOs method
    Hidden [Boolean] DownloadISOs() {
        # Control variable
        [Boolean] $Return = $true
        # Iterate through the ISOs
        ForEach ($ozoAdLabIso in $this.ozoADLabISOs) {
            # Try to get the ISO
            Try {
                Invoke-WebRequest -Uri $ozoAdLabIso.Uri -OutFile (Join-Path -Path $this.ozoAdLabPath -ChildPath (Join-Path -Path "ISO" -ChildPath $ozoAdLabIso.Name)) -ErrorAction Stop
                # Success
            } Catch {
                # Failure
                $Return = $false
            }
        }
        # Return
        return $Return
    }
}

Function Get-OZOYesNo {
    # Prompt the user to restart and return the lowercase of the first letter of their response
    [String]$response = $null
    Do {
        $response = (Read-Host "(Y/N)")[0].ToLower()
    } Until ($response -eq "y" -Or $response -eq "n")
    # Return response
    return $response
}

Function New-WSLPathFromWindowsPath {
    param(
        [Parameter(Mandatory=$true,HelpMessage="The Windows path to convert")][String]$WindowsPath
    )
    # Local variables
    return ($WindowsPath.Replace("\","/")).Replace($Env:SystemDrive,("/mnt/" + ($Env:SystemDrive.Split(":"))[0].ToLower()))
}

# MAIN
[ADLIP]::new() | Out-Null

# SIG # Begin signature block
# MIIfcgYJKoZIhvcNAQcCoIIfYzCCH18CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC+OOTY7NFduyaX
# QCCkPJ5JJKcJ6S7sqkHYwBZfOnTzV6CCDPgwggZyMIIEWqADAgECAghkM1HTxzif
# 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+RiBRqmsj29fjGCEdAwghHMAgEBMIGM
# MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv
# bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu
# aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ
# YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK
# KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG
# 9w0BCQQxIgQgWUquKsP0WqoguEa9/UWovO3Qtlfcf7ToSDl4vL8OfG0wDQYJKoZI
# hvcNAQEBBQAEggGArnyXh9XPx9W0K5negSTQnJlMjfaLz7bwJ3jgpd54f7AqSiZo
# /MjkbdzV8JkeaWlcg7Ci6H4zAqSF/hiZlKfCcIUYD7jEx/+wn3l0+kB4QcUguZU9
# nsmauZnYi5AoR4OGkesRdqwgLBC1rxDfON7zNCxj4DY6RBIfWrJXcjbFi8R0r2Hi
# yKQBk7XghTEW+FB57QpD/NDuab8BARlgRWV70Ag8Tx/3T+SPr1V60TMn6CmStoTi
# WyLVZax37eZfCJQh+e1FyvhJcJVlt6tRqzF5w7HRxD9n8ZxFkrD0ldmNQ+T6NqWU
# Iub4apsbJIqB/Q+B/Lmp2V/9E6lXlifsWiwk2lid7vZV+oALWLxwLTdoxeplgI2h
# hhqGGqidl1zMvwMcJRChL5RgBU32ekvx4Bkh+BeBE8IE4bY4F9igcsz9kXzAkqov
# fa1Bluedcvg1RYnJX1+yQNMDrlFmpXwkMzNoxMWIy3/DUOPIq3Cxo0cFV27bkkJA
# 2QfUsIq/njTfrp3yoYIPFjCCDxIGCisGAQQBgjcDAwExgg8CMIIO/gYJKoZIhvcN
# AQcCoIIO7zCCDusCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm
# MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCDqFZriXhy5NKZ7
# RO3PccLH5iPnmRW7RvNyXtFGtMjPAgIIDWdZ9NB0ZX4YDzIwMjUwMzAyMTY1OTQ4
# 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
# HZf4peSrHdL4RjGCAlgwggJUAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
# DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt
# BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa
# WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAzMDIxNjU5NDhaMCgGCSqG
# SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ
# BDEiBCAy4Dmda6fZ8JuKAG55D9d/7Ktjc8qyHYVuxbpyAmBf+DCByQYLKoZIhvcN
# AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29
# iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t
# IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9
# xzAKBggqhkjOPQQDAgRHMEUCIQDnKMGBWoZHX65bIX4t5NjZ3MZyeMNANiU68yfI
# 7gKpQAIgROo5Cil79pmclH7pLyTeX0++3T9+mBXHOffZkoi+yfw=
# SIG # End signature block