public/Grant-UcmOffice365UserLicence.ps1

#PerformScriptSigning
Function Grant-UcmOffice365UserLicence
{
    <#
            .SYNOPSIS
            Assigns a licence to an Office365 user
 
            .DESCRIPTION
            Function will check for available licences and assign one to the supplied user
            This function will also notify you if you are below 5% of your below licences, a similar warning will be generated if you have less than 5 licences available
            For a full list of Licence codes, see https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
 
            .EXAMPLE
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOEV' -Country 'AU'
            Grants the Microsoft Phone System Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOCAP' -Country 'AU'
            Grants the Teams Common Area Phone Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOPSTN1' -Country 'AU'
            Grants the Microsoft Domestic Calling Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOPSTN2' -Country 'AU'
            Grants the Microsoft Domestic/International Calling Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOPSTNEAU2' -Country 'AU'
            Grants the TELSTRA CALLING FOR O365 Licence to the user Button Mash (Australia Only)
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MEETING_ROOM' -Country 'AU'
            Grants the Teams Meeting Room Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'MCOMEETADV' -Country 'AU'
            Grants the Teams Advanced Meeting Room Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'ENTERPRISEPREMIUM' -Country 'AU'
            Grants the Microsoft E5 Licence to the user Button Mash
 
            PS> Grant-UcmOffice365UserLicence -upn 'button.mash@contoso.com' -LicenceType 'ENTERPRISEPACK' -Country 'AU'
            Grants the Microsoft E3 Licence to the user Button Mash
 
            .INPUTS
            This function accepts both parameter and pipline input
 
            .PARAMETER upn
            The UPN of the user you wish to enable the licence on, eg: button.mash@contoso.com
 
            .PARAMETER LicenceType
            The licence you wish to assign, eg: 'MCOEV'
 
            .PARAMETER Country
            The 2 letter country code for the users country, must be in capitals. eg: AU
 
            .OUTPUT
            This Cmdet returns a PSCustomObject with multiple Keys to indicate status
            $Return.Status
            $Return.Message
 
            Return.Status can return one of four values
            "OK" : The licence has been assigned, or is already assigned to the user.
            "Warn" : The licence was assigned, but there was an issue. for example, low availability of licences. Check $return.message for more information
            "Error" : Unable to assign licence. check $return.message for more information
            "Unknown" : Cmdlet reached the end of the fucntion without returning anything, this shouldnt happen, if it does please log an issue on Github
 
            Return.Message returns descriptive text based on the outcome, mainly for logging or reporting
 
            .NOTES
            Version: 1.4
            Date: 23/03/2023
 
            .VERSION HISTORY
            1.4: Added Country Validation
             
            1.3: Updated Comment based help to include PARAMETER tags
 
            1.2: Updated Examples to include more licences
            Added link to Microsoft licence documentation
 
            1.1: Updated to "Ucm" naming convention
            1.0: Initial Public Release
 
            .REQUIRED FUNCTIONS/MODULES
            Modules
            AzureAD (Install-Module AzureAD)
            MSOnline (Install-Module MSOnline)
            UcmPSTools (Install-Module UcmPSTools) Includes Cmdlets below.
 
            Cmdlets
            Write-UcmLog: https://github.com/Atreidae/UcmPSTools/blob/main/public/Write-UcmLog.ps1
            Test-UcmMSOLConnection: https://github.com/Atreidae/UcmPSTools/blob/main/public/Test-UcmMSOLConnection.ps1
            New-UcmMSOLConnection: https://github.com/Atreidae/UcmPSTools/blob/main/public/New-UcmMSOLConnection.ps1
            Write-UcmHTMLReport: https://github.com/Atreidae/UcmPSTools/blob/main/public/Write-UcmHTMLReport.ps1
 
            .REQUIRED PERMISSIONS
            'Office365 User Admin' or better
 
            .LINK
            https://www.UcMadScientist.com
            https://github.com/Atreidae/UcmPSTools
 
            .ACKNOWLEDGEMENTS
            Assign licenses for specific services in Office 365 using PowerShell:
 
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseProcessBlockForPipelineCommand', '', Scope='Function')] #todo, https://github.com/Atreidae/UcmPSTools/issues/23

    Param
    (
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=1,HelpMessage='The UPN of the user you wish to enable the licence on, eg: button.mash@contoso.com')] [string]$UPN,
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=2,HelpMessage='The licence you wish to assign, eg: MCOEV')] [string]$LicenceType,
        [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory, Position=3,HelpMessage='The 2 letter country code for the users country, must be in capitals. eg: AU')] [ValidateSet("AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BQ","BA","BW","BV","BR","IO","BN","BG","BF","BI","CV","KH","CM","CA","KY","CF","TD","CL","CN","CX","CC","CO","KM","CG","CD","CK","CR","CI","HR","CU","CW","CY","CZ","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","SZ","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI","KP","KR","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","NC","NZ","NI","NE","NG","NU","NF","MK","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL","SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SX","SK","SI","SB","SO","ZA","GS","SS","ES","LK","SD","SR","SJ","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","UY","UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW")][String]$Country
    )


    #region FunctionSetup, Set Default Variables for HTML Reporting and Write Log
    $Function = 'Grant-UcmOffice365UserLicence'
    [hashtable]$Return = @{}
    $return.function = $Function
    $return.Status = "Unknown"
    $return.Message = "Function did not return a status message"

    # Log why we were called
    Write-UcmLog -Message "$($MyInvocation.InvocationName) called with $($MyInvocation.Line)" -Severity 1 -Component $function
    Write-UcmLog -Message "Parameters" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$($PsBoundParameters.Keys)" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "Parameters Values" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$($PsBoundParameters.Values)" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "Optional Arguments" -Severity 1 -Component $function -LogOnly
    Write-UcmLog -Message "$Args" -Severity 1 -Component $function -LogOnly


    #endregion functionSetup


    #region FunctionWork

    #Check to see if we are connected to MSOL
    $Test = (Test-UcmMSOLConnection -Reconnect)
    If ($Test.Status -ne "OK")
    {
        #MSOL check failed, return an error.
        Write-UcmLog -Message "Something went wrong granting $UPN's licence" -Severity 3 -Component $function
        Write-UcmLog -Message "Test-UcmMSOLConnection could not locate an MSOL connection" -Severity 2 -Component $function
        $Return.Status = "Error"
        $Return.Message = "No MSOL Connection"
        Return $Return
    }

    #MSOL Connection test passed, check the tenant has the requested licence.
    Write-UcmLog -Message "Verifying $LicenceType is available" -Severity 2 -Component $function
    Try
    {
        #Office365 does this thing where it preprends the tenant name on the licence
        #For example PHONESYSTEM_VIRTUALUSER would be "contoso:PHONESYSTEM_VIRTUALUSER"
        #So we need to learn the prefix, we do this by looking for the licence and storing its name
        $O365AcctSku = $null
        $O365AcctSku = Get-MsolAccountSku | Where-Object {$_.SkuPartNumber -like $LicenceType}

        #Using the stored details, build the full licence name
        $LicenceToAssign = "$($O365AcctSku.AccountName):$LicenceType"
    }

    Catch
    {
        #We couldnt get the licence details, it could be a permissions issue or the connection might be broken. Return an error.
        Write-UcmLog -Message "Error Running Get-MsolAccountSku" -Severity 3 -Component $function
        Write-UcmLog -Message $error[0]  -Severity 3 -Component $function
        $Return.Status = "Error"
        $Return.Message = "Unable to run Get-MsolAccountSku to obtain tenant prefix"
        Return $Return
    }

    If ($null -eq $O365AcctSku)
    {
        #The licence requested doesnt exist on the tenant, return an error
        Write-UcmLog -Message "Unable to locate requested licence on the current tenant" -Severity 3 -Component $function
        $Return.Status = "Error"
        $Return.Message = "Unable to locate $LicenceType licence"
        Return $Return
    }

    #The tenant has the requested licence, check to see any are free. Trigger a warning when there is less than 5 or 5% available
    $LicenceUsedPercent = (($O365AcctSku.ConsumedUnits / $O365AcctSku.ActiveUnits) * 100)
    $AvailableLicenceCount = ($O365AcctSku.ActiveUnits - $O365AcctSku.ConsumedUnits)
    If (($LicenceUsedPercent -ge 95) -or ($AvailableLicenceCount -le 5))
    {
        Write-UcmLog -Message "Only $AvailableLicenceCount $LicenceType Licences Left" -Severity 3 -Component $function
        Write-UcmLog -Message "Available licence count low..." -Severity 3 -Component $function

        #We encountered something we need to report on, set the warning flag and store a warning message.
        $WarningFlag = $True
        $WarningMessage = "Low Licence Count"
    }

    #There are licences free, check to see if the specified user exists.
    Try
    {
        Write-UcmLog -Message "Checking for existing User $UPN ..." -Severity 2 -Component $function
        $O365User = (Get-MsolUser -UserPrincipalName $UPN -ErrorAction Stop)
        Write-UcmLog -Message "User exists. checking their assigned licences..." -Severity 2 -Component $function

        #Found the user, check to see if they already have the licence.
        If ($O365User.Licenses.accountSkuID -contains $LicenceToAssign)
        {
            #Looks like the user already has that licence
            Write-UcmLog -Message "User already has that licence, Skipping" -Severity 3 -Component $function

            #Check to see if we encountered a warning during the run and inject it into the status message
            If ($warningFlag)
            {
                #Yes, we did, return the warning
                $Return.Status = "Warning"
                $Return.Message = "Skipped: Already Licenced, Warning Message $WarningMessage"
                Return $Return
            }
            Else
            {
                #No warning, just return OK
                $Return.Status = "OK"
                $Return.Message = "Skipped: Already Licenced"
                Return $Return
            }
        }

        #User exists, and doesnt already have the licence, try assigning the licence to them
        Try
        {
            Write-UcmLog -Message "User Exists, Grant Licence" -Severity 2 -Component $function

            #Set the user location, this is required to set the relevant licences. Users can be created without setting a country mistakenly.
            Write-UcmLog -Message "Setting Location" -Severity 2 -Component $function
            [void] (Set-MsolUser -UserPrincipalName $UPN -UsageLocation $Country)

            #Try assigning the licence to the user
            [Void] (Set-MsolUserLicense -UserPrincipalName $UPN -AddLicenses $LicenceToAssign -ErrorAction stop)
            Write-UcmLog -Message "Licence Granted" -Severity 2 -Component $function

            #Licence assigned OK. Check to see if we encountered a warning during the run and inject it into the status message
            If ($warningFlag)
            {
                #Yes, we did, return the warning
                $Return.Status = "Warning"
                $Return.Message = "Licence Granted, Warning Message $WarningMessage"
                Return $Return
            }
            Else
            {
                #No warning, just return OK
                $Return.Status = "OK"
                $Return.Message = "Licence Granted"
                Return $Return
            }
        }
        #Something Failed either setting the licence or the country
        Catch
        {
            #Return an error
            Write-UcmLog -Message "Something went wrong licencing user $UPN" -Severity 3 -Component $function
            Write-UcmLog -Message $Error[0] -Severity 3 -Component $function
            $Return.Status = "Error"
            $Return.Message = $Error[0]
            Return $Return
        }
    } #End User Check try block #Todo, split this up into seperate blocks to minimise nesting.

    #User doesnt exist, throw error message
    Catch
    {
        #Return an error
        Write-UcmLog -Message "Something went wrong granting $UPN's licence" -Severity 3 -Component $function
        Write-UcmLog -Message "Could not locate user $UPN" -Severity 2 -Component $function
        $Return.Status = "Error"
        $Return.Message = "User Not Found"
        Return $Return
    }
    #endregion FunctionWork


    #region FunctionReturn

    #Default Return Variable for my HTML Reporting Fucntion
    Write-UcmLog -Message "Reached end of $function without a Return Statement" -Severity 3 -Component $function
    $return.Status = "Unknown"
    $return.Message = "Write-UcmLog did not encounter return statement"
    Return $Return
    #endregion Write-UcmLogReturn
}

# SIG # Begin signature block
# MIIRwgYJKoZIhvcNAQcCoIIRszCCEa8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU+LI+hRyRJAAeUBCGBVe5/UH0
# mVqggg4OMIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0B
# AQwFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk
# IFJvb3QgRzQwHhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5
# WRuxiEL1M4zrPYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJP
# DqFX/IiZwZHMgQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXz
# ENOLsvsI8IrgnQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bq
# HPNlaJGiTUyCEUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTC
# fMjqGzLmysL0p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaD
# G7dqZy3SvUQakhCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urO
# kfW+0/tvk2E0XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7AD
# K5GyNnm+960IHnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4
# R+Z1MI3sMJN2FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlN
# Wdt4z4FKPkBHX8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0I
# U0F8WD1Hs/q27IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwHQYDVR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaA
# FOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4
# oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJv
# b3RHNC5jcmwwHAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcN
# AQEMBQADggIBADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcT
# Ep6QRJ9L/Z6jfCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WT
# auPrINHVUHmImoqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9
# ntSZz0rdKOtfJqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np37
# 5SFTWsPK6Wrxoj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0
# HKKlS43Nb3Y3LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL
# 6TEa/y4ZXDlx4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+1
# 6oh7cGvmoLr9Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8
# M4+uKIw8y4+ICw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrF
# hsP2JjMMB0ug0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy
# 1lKQ/a+FSCH5Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIHVjCC
# BT6gAwIBAgIQDyLHeeRvkUFg5QtSFTT8FjANBgkqhkiG9w0BAQsFADBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg
# Q0ExMB4XDTIzMDIyMzAwMDAwMFoXDTI2MDIyMzIzNTk1OVowXjELMAkGA1UEBhMC
# QVUxETAPBgNVBAgTCFZpY3RvcmlhMRAwDgYDVQQHEwdCZXJ3aWNrMRQwEgYDVQQK
# EwtKYW1lcyBBcmJlcjEUMBIGA1UEAxMLSmFtZXMgQXJiZXIwggIiMA0GCSqGSIb3
# DQEBAQUAA4ICDwAwggIKAoICAQC47oExh25TrxvApIYdMRYvjOdZCb8WwgeTemm3
# ZY7BElIWu6+gzRGqQe8RFsN7oIgin5pvjTYIToxt1CCag2A5o8L0NtULmxJEegc+
# VaF24DZQqI4qGQGH/Qnglqys6+yPkwLnfeSxpeWe4u49HUUGDFIxHCh42MlCLp/f
# fHT49QhhpO+LyeLnDoUs6DmahyIb6NeE2cW5AYRXEesW7GRNfXzygBSlVWJOgvcy
# V5Y4IvAZVx2hKKMTjYFIz4/RYMg7fwYZEJ2LRJ/GnVazobKAvh6ZBet5KwVNI9EI
# 29DtWQyK/RoPOguTRcB5VuiZVlv0xjBYM7iJuH2Soa3StQYVxL/5gjZCC9WOs4NR
# EIGU3XmHoogFDvoT1vf1izMPFQzdZfgPvy/XXsbgTVo5ncesJ6WtZwqwCXG1K0XW
# IPZqTHolc1MyU6K1bEHO+7YWLpKgM9THl644G7PEhcKpNDsHlfvLVQdYhI55UJtc
# iyMrTw11CNECvk3GK1mrluvKsrxdaH6G3Sp9VVHRtef6OZ5SlzkM5ID4egB2bXRb
# R/69bEuZr5hhm+v2lBSWIbZj/Mva6i/a/TAvy4vvPLo3DRcASkYZDC4T8gDMzmpG
# Xs4jAc9sfTL9z+o5u1PLJHFGRjJ+Wa2CgSftCdbKLjn+AY9m8ipc8jmOBKNY9yGI
# pQWapQIDAQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0
# TkIwHQYDVR0OBBYEFOBsg1xudlbXVSql8pWbiHoTyZS/MA4GA1UdDwEB/wQEAwIH
# gDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu
# Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hB
# Mzg0MjAyMUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcC
# ARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCB
# hDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUF
# BzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk
# RzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAA
# MA0GCSqGSIb3DQEBCwUAA4ICAQBOh5vRXqTCQzv0T1F2EgDkq5VnljlHsa7Ov/d/
# lpOvr8NebBiatxbhfhPCnToY7BD2f7YVsUuQ+VDdcIYsskcU5spBHcFYidg2jGu4
# 59FGMaS765XStDwGGTN/360gEsNYSnKWYL4+8jYWHlzRO0jHloyWz+gF5dYWzdDJ
# u1dudLIJ0RgrEVJeLSgIBWygLL5EyIzOPlrxztsILMSbdPTQLeBIm7ipOk4EACx1
# hhBVUsUoCAlASH+yCKDU4v2HFd7SzrkRUrf7XJ2Na2YsiHjiTGqHIE86KyvxGDhT
# 3n2/jX23Nh/bkWHurHwTfaTCOQ44ZlAbnZQjBlmrFn5hPMXRpciiQFmrKTPD/nuo
# 9MVnCciHEpHJ63/JZNF/eno1122/wVkL7MuRlCVHN7L/wuNQxQk3ARdIju6OD/Gi
# Mwg0Qih6HVWJtkHK3ExoUKKKUZCOvIeHxzp+K6FWUupPZKUgWzn4AHMxm6zr+Sde
# laIAACqAkxYsDYKbM7WlNi3uIH2HeXqU9uSDt5tgPpImrog/ab4HrhpDfITRgT1c
# cxaWQezpJEPC+kqVD41T3wlEie1Qm4vYWg+oBVEMBxVLh6CYbeppCRTEXRGnAiCH
# /Ma1uwyWnNCWxrhd1uSi6sj4ISzgnFyGCvsI0gavKpS5AQhapJgk6/fULTFeS+Ee
# kRH9FDGCAx4wggMaAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2ln
# bmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQDyLHeeRvkUFg5QtSFTT8FjAJ
# BgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B
# CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAj
# BgkqhkiG9w0BCQQxFgQUz7Ac7tehNVq8iVyyIwQvZspnoBAwDQYJKoZIhvcNAQEB
# BQAEggIAiBngpp7EtlJHDdvCS4ODKlC0FzNBieCOFjOPGyhcdA9JtvVziPUokGrH
# yn3OZ0XXh+JY0ypLT3LY1J44+yYvEkfs8CtcHVZvFOmqcGqp7bi88s+/ClZHEEsB
# phwHKHldhlRTgUA2pc4CwcauClhJ65Aked9sIionLwld9sQbtu66ET0rgHv9+8fL
# 2MNaOqCzfHLGG3TUhrVn/X5UGZ7kgrAHGI6BzaOFb+QxpwqWlwmPmkcYLIaphksW
# WvN5/DzbZ45FcxtwVI/xt5qFp391b3tQyoG5G9UJRG8igmwq0joIj2zogvdG6jJD
# UGpbIgrX89ALKNXMkSQCPkdnqv1b+RoMOl8ioJwi3VwUeck5vJshUZ/g+ciBbTCW
# 9x11bVb/bhwnasIpNilkFJTzi7RGrez50dqU/9tvNGP5afdKLUd6pw7r45BSnxiw
# okPV3aqlLakeTuPI0hs4e54+KlSZP7XVwGWrby8JNMkaNvlLKi0qVGIrlvPkTJd3
# kZZZhJOkIm3F7C3Ay2OEYt2vd0ECT6JbLSMpLs6Lbmly24ugimtDd08anIXeU1Ip
# eDkx/CE8azCDf3+G3hSyTJyZuwtQde82ALByHjMkm96+2a1IihpYgwd4oCGwdHxQ
# I6gZOzdyqjj7OyS5onjWCrLYjImYtiZYWb1IcjMaIJzUSxmxezk=
# SIG # End signature block