Invoke-Async.ps1

#
# Invoke-Async.ps1
#
# Copyright 2019 Microsoft. All rights reserved.
#

<#
.Synopsis
   A means of running multiple instances of a cmdlet/function/scriptblock
.DESCRIPTION
   This function allows you to provide a cmdlet, function or script block with a set of data to allow multithreading.
.EXAMPLE
   $sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption}
   $servers = Get-Content servers.txt
   $rtn = Invoke-Async -Set $server -SetParam system -ScriptBlock $sb
.EXAMPLE
   $servers = Get-Content servers.txt
   $rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50
.INPUTS
 
.OUTPUTS
   Determined by the provided cmdlet, function or scriptblock.
.NOTES
    This can often times eat up a lot of memory due in part to how some cmdlets work. Test-Connection is a good example of this.
    Although it is not a good idea to manually run the garbage collector it might be needed in some cases and can be run like so:
    [gc]::Collect()
#>


function Invoke-Async{
param(
#The data group to process, such as server names.
[parameter(Mandatory=$true,ValueFromPipeLine=$true)]
[object[]]$Set,
#The parameter name that the set belongs to, such as Computername.
[parameter(Mandatory=$true)]
[string] $SetParam,
#The Cmdlet for Function you'd like to process with.
[parameter(Mandatory=$true, ParameterSetName='cmdlet')]
[string]$Cmdlet,
#The ScriptBlock you'd like to process with
[parameter(Mandatory=$true, ParameterSetName='ScriptBlock')]
[scriptblock]$ScriptBlock,
#any aditional parameters to be forwarded to the cmdlet/function/scriptblock
[hashtable]$Params,
#number of jobs to spin up, default being 10.
[int]$ThreadCount=10,
#return performance data
[switch]$Measure
)
Begin
{

    $Threads = @()
    $Length = $JobsLeft = $Set.Length

    $Count = 0
    if($Length -lt $ThreadCount){$ThreadCount=$Length}
    $timer = @(1..$ThreadCount  | ForEach-Object{$null})
    $Jobs = @(1..$ThreadCount  | ForEach-Object{$null})

    If($PSCmdlet.ParameterSetName -eq 'cmdlet')
    {
        $CmdType = (Get-Command $Cmdlet).CommandType
        if($CmdType -eq 'Alias')
        {
            $CmdType = (Get-Command (Get-Command $Cmdlet).ResolvedCommandName).CommandType
        }

        If($CmdType -eq 'Function')
        {
            $ScriptBlock = (Get-Item Function:\$Cmdlet).ScriptBlock
            1..$ThreadCount | ForEach-Object{ $Threads += [powershell]::Create().AddScript($ScriptBlock)}
        }
        ElseIf($CmdType -eq "Cmdlet")
        {
            1..$ThreadCount  | ForEach-Object{ $Threads += [powershell]::Create().AddCommand($Cmdlet)}
        }
    }
    Else
    {
        1..$ThreadCount | ForEach-Object{ $Threads += [powershell]::Create().AddScript($ScriptBlock)}
    }

    If($Params){$Threads | ForEach-Object{$_.AddParameters($Params) | Out-Null}}

}
Process
{
    while($JobsLeft)
    {
        for($idx = 0; $idx -le ($ThreadCount-1) ; $idx++)
        {
            $SetParamObj = $Threads[$idx].Commands.Commands[0].Parameters| Where-Object {$_.Name -eq $SetParam}

            If($Jobs[$idx].IsCompleted) #job ran ok, clear it out
            {
                $result = $null
                if($threads[$idx].InvocationStateInfo.State -eq "Failed")
                {
                    $result  = $Threads[$idx].InvocationStateInfo.Reason
                    Write-Error "Set Item: $($SetParamObj.Value) Exception: $result"
                }
                else
                {
                    $result = $Threads[$idx].EndInvoke($Jobs[$idx])
                }
                $ts = (New-TimeSpan -Start $timer[$idx] -End (Get-Date))
                if($Measure)
                {
                    new-object psobject -Property @{
                        TimeSpan = $ts
                        Output = $result
                        SetItem = $SetParamObj.Value}
                }
                else
                {
                    $result

                    if ($result.Orphaned -eq $true)
                    {
                        $script:totalAzureADOrphanedKeys += 1
                    }
                }
                $Jobs[$idx] = $null
                $JobsLeft-- #one less left
                write-verbose "Completed: $($SetParamObj.Value) in $ts"
                Update-GetAzureADWHfBKeysProgress
                write-progress -Activity "Processing Batch" -Status "$JobsLeft jobs left" -PercentComplete (($length-$jobsleft)/$length*100) -Id 2 -ParentId 1
            }
            If(($Count -lt $Length) -and ($Jobs[$idx] -eq $null)) #add job if there is more to process
            {
                write-verbose "starting: $($Set[$Count])"
                $timer[$idx] = get-date
                $Threads[$idx].Commands.Commands[0].Parameters.Remove($SetParamObj) | Out-Null #check for success?
                $Threads[$idx].AddParameter($SetParam,$Set[$Count]) | Out-Null
                $Jobs[$idx] = $Threads[$idx].BeginInvoke()
                $Count++
            }
        }

    }
}
End
{
    $Threads | ForEach-Object{$_.runspace.close();$_.Dispose()}
}
}

# SIG # Begin signature block
# MIIoDQYJKoZIhvcNAQcCoIIn/jCCJ/oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAorkUc/dpIlnJC
# ZchF/fNnGpDsVf7CZ/VNSC+uMly11qCCEXAwggiCMIIHaqADAgECAhM2AAAA6Gad
# G0hCpUxFAAEAAADoMA0GCSqGSIb3DQEBCwUAMEExEzARBgoJkiaJk/IsZAEZFgNH
# QkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFNRSBDUyBDQSAwMTAe
# Fw0xOTAzMjUxMzE3MTBaFw0yMDAzMjQxMzE3MTBaMC8xLTArBgNVBAMTJE1pY3Jv
# c29mdCBBenVyZSBEZXBlbmRlbmN5IENvZGUgU2lnbjCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBANzmuspUXRFHozpraZYcR8lLf8oaCK27bhl7agSI00Vc
# GZy2slaHQ8JZ+Bqiao7e2hpEVxeJ0s9lfIUe30ye3RwDa7oNloC8u8fa/W0QusGD
# T7p4c7wwbRJ5MrXowwB/nSgsOKtoCoJMpk6LDpRhgwhn4va4PjVrajBxrxN1eVIP
# Kw1GPkzh3qkLQ0SIhuiYUlZur4HSG6Mdkl7jYk1ifVtNupf+FO4Nflw+ZRsczZUg
# jo74fVEoBI9qzrZSkCg5wku1qGsTXrd9DtRgOQEEp96NruISfEb22ZZtpMrJg71H
# p/oAEqqlOBN5FIjzWi+mxznE1CNMBNFCbtsZOvHVSJMCAwEAAaOCBYMwggV/MCkG
# CSsGAQQBgjcVCgQcMBowDAYKKwYBBAGCN1sCATAKBggrBgEFBQcDAzA9BgkrBgEE
# AYI3FQcEMDAuBiYrBgEEAYI3FQiGkOMNhNW0eITxiz6Fm90Wzp0SgWCE2K5IhaaW
# JgIBZAIBDTCCAnYGCCsGAQUFBwEBBIICaDCCAmQwYgYIKwYBBQUHMAKGVmh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2lpbmZyYS9DZXJ0cy9CWTJQS0lDU0NBMDEu
# QU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MFIGCCsGAQUFBzAChkZo
# dHRwOi8vY3JsMS5hbWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUl
# MjBDUyUyMENBJTIwMDEoMSkuY3J0MFIGCCsGAQUFBzAChkZodHRwOi8vY3JsMi5h
# bWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUlMjBDUyUyMENBJTIw
# MDEoMSkuY3J0MFIGCCsGAQUFBzAChkZodHRwOi8vY3JsMy5hbWUuZ2JsL2FpYS9C
# WTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MFIG
# CCsGAQUFBzAChkZodHRwOi8vY3JsNC5hbWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEu
# QU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MIGtBggrBgEFBQcwAoaB
# oGxkYXA6Ly8vQ049QU1FJTIwQ1MlMjBDQSUyMDAxLENOPUFJQSxDTj1QdWJsaWMl
# MjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERD
# PUFNRSxEQz1HQkw/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRp
# ZmljYXRpb25BdXRob3JpdHkwHQYDVR0OBBYEFMhZoWfS7cerFd3KJYxV4M3+DOEi
# MA4GA1UdDwEB/wQEAwIHgDBQBgNVHREESTBHpEUwQzEpMCcGA1UECxMgTWljcm9z
# b2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xFjAUBgNVBAUTDTIzNjE2OSs0NTMz
# NzQwggHUBgNVHR8EggHLMIIBxzCCAcOgggG/oIIBu4Y8aHR0cDovL2NybC5taWNy
# b3NvZnQuY29tL3BraWluZnJhL0NSTC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMi5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMy5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsNC5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3JshoG6
# bGRhcDovLy9DTj1BTUUlMjBDUyUyMENBJTIwMDEsQ049QlkyUEtJQ1NDQTAxLENO
# PUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1D
# b25maWd1cmF0aW9uLERDPUFNRSxEQz1HQkw/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
# TGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MB8GA1Ud
# IwQYMBaAFBtmohn8m+ul2oSPGJjpEKTDe5K9MB8GA1UdJQQYMBYGCisGAQQBgjdb
# AgEGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQCVZn2MgvVx6Lix7aYfjSp6
# t9NYHifbtXl3VxQk20PyxzmNbXoXHQMtgWioOWP4SaQ0Jpw23TgpczqPCS/BWy0E
# t2iZ12XatAQRPqrsU4YKcTTExKEPa/gdOz63Okmi+5GQH78d53M5I39DJWzPja7d
# RdZgASuswapduSUZMAFJJOslCFtBYAuzrJDy+UH2k7NZQ4+ExtFuuwZZA16c1IbQ
# O9JwjGdYna9ZlNtWIc2RSDeNjX8St40y7hhxxrszktg13D1oGZX8+oeQBsPyzigY
# U+5JuPoUWGkFBUL8LKR6z3wddTWFZWrW5zfy2aQTxSYOpRYNcUmuAYfLBVhSNI/j
# MIII5jCCBs6gAwIBAgITHwAAABS0xR/G8oC+cQAAAAAAFDANBgkqhkiG9w0BAQsF
# ADA8MRMwEQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRAw
# DgYDVQQDEwdhbWVyb290MB4XDTE2MDkxNTIxMzMwM1oXDTIxMDkxNTIxNDMwM1ow
# QTETMBEGCgmSJomT8ixkARkWA0dCTDETMBEGCgmSJomT8ixkARkWA0FNRTEVMBMG
# A1UEAxMMQU1FIENTIENBIDAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEA1VeBAtb5+tD3G4C53TfNJNxmYfzhiXKtKQzSGxuav660bTS1VEeDDjSnFhsm
# nlb6GkPCeYmCJwWgZGs+3oWJ8yad3//VoP99bXG8azzTJmT2PFM1yKxUXUJgi7I9
# y3C4ll/ATfBwbGGRXD+2PdkdlVpxKWzeNEPVwbCtxWjUhHr6Ecy9R6O23j+2/RSZ
# SgfzYctDzDWhNf0PvGPflm31PSk4+ozca337/Ozu0+naDKg5i/zFHhfSJZkq5dPP
# G6C8wDrdiwHh6G5IGrMd2QXnmvEfjtpPqE+G8MeWbszaWxlxEjQJQC6PBwn+8Qt4
# Vqlc0am3Z3fBw8kzRunOs8Mn/wIDAQABo4IE2jCCBNYwEAYJKwYBBAGCNxUBBAMC
# AQEwIwYJKwYBBAGCNxUCBBYEFJH8M85CnvaT5uJ9VNcIGLu413FlMB0GA1UdDgQW
# BBQbZqIZ/JvrpdqEjxiY6RCkw3uSvTCCAQQGA1UdJQSB/DCB+QYHKwYBBQIDBQYI
# KwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3FAIBBgkrBgEEAYI3FQYGCisGAQQB
# gjcKAwwGCSsGAQQBgjcVBgYIKwYBBQUHAwkGCCsGAQUFCAICBgorBgEEAYI3QAEB
# BgsrBgEEAYI3CgMEAQYKKwYBBAGCNwoDBAYJKwYBBAGCNxUFBgorBgEEAYI3FAIC
# BgorBgEEAYI3FAIDBggrBgEFBQcDAwYKKwYBBAGCN1sBAQYKKwYBBAGCN1sCAQYK
# KwYBBAGCN1sDAQYKKwYBBAGCN1sFAQYKKwYBBAGCN1sEAQYKKwYBBAGCN1sEAjAZ
# BgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwEgYDVR0TAQH/
# BAgwBgEB/wIBADAfBgNVHSMEGDAWgBQpXlFeZK40ueusnA2njHUB0QkLKDCCAWgG
# A1UdHwSCAV8wggFbMIIBV6CCAVOgggFPhiNodHRwOi8vY3JsMS5hbWUuZ2JsL2Ny
# bC9hbWVyb290LmNybIYxaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJh
# L2NybC9hbWVyb290LmNybIYjaHR0cDovL2NybDIuYW1lLmdibC9jcmwvYW1lcm9v
# dC5jcmyGI2h0dHA6Ly9jcmwzLmFtZS5nYmwvY3JsL2FtZXJvb3QuY3JshoGqbGRh
# cDovLy9DTj1hbWVyb290LENOPUFNRVJPT1QsQ049Q0RQLENOPVB1YmxpYyUyMEtl
# eSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9QU1F
# LERDPUdCTD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xh
# c3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwggGrBggrBgEFBQcBAQSCAZ0wggGZMDcG
# CCsGAQUFBzAChitodHRwOi8vY3JsMS5hbWUuZ2JsL2FpYS9BTUVST09UX2FtZXJv
# b3QuY3J0MEcGCCsGAQUFBzAChjtodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp
# aW5mcmEvY2VydHMvQU1FUk9PVF9hbWVyb290LmNydDA3BggrBgEFBQcwAoYraHR0
# cDovL2NybDIuYW1lLmdibC9haWEvQU1FUk9PVF9hbWVyb290LmNydDA3BggrBgEF
# BQcwAoYraHR0cDovL2NybDMuYW1lLmdibC9haWEvQU1FUk9PVF9hbWVyb290LmNy
# dDCBogYIKwYBBQUHMAKGgZVsZGFwOi8vL0NOPWFtZXJvb3QsQ049QUlBLENOPVB1
# YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRp
# b24sREM9QU1FLERDPUdCTD9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9
# Y2VydGlmaWNhdGlvbkF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAKLdKhpqP
# H6QBaM3CAOqQi8oA4WQeZLW3QOXNmWm7UA018DQEa1yTqEQbuD5OlR1Wu/F289Dm
# XNTdsZM4GTKEaZehIiVaMoLvEJtu5h6CTyfWqPetNyOJqR1sGqod0Xwn5/G/zcTY
# Sxn5K3N8KdlcDrZAIyfq3yaEJYHGnA9eJ/f1RrfbJgeo/RAhICctOONwfpsBXcgi
# TuTmlD/k0DqogvzJgPq9GOkIyX/dxk7IkPzX/n484s0zHR4IKU58U3G1oPSQmZ5O
# HAvgHaEASkdN5E20HyJv5zN7du+QY08fI+VIci6pagLfXHYaTX3ZJ/MUM9XU+oU5
# y4qMLzTj1JIG0LVfuHK8yoB7h2inyTe7bn6h2G8NxZ02aKZ0xa+n/JnoXKNsaVPG
# 1SoTuItMsXV5pQtIShsBqnXqFjY3bJMlMhIofMcjiuOwRCW+prZ+PoYvE2P+ML7g
# s3L65GZ9BdKF3fSW3TvmpOujPQ23rzSle9WGxFJ02fNbaF9C7bG44uDzMoZU4P+u
# vQaB7KE4OMqAvYYfFy1tv1dpVIN/qhx0H/9oNiOJpuZZ39ZibLt9DXbsq5qwyHmd
# JXaisxwB53wJshUjc1i76xqFPUNGb8EZQ3aFKl2w9B47vfBi+nU3sN0tpnLPtew4
# LHWq4LBD5uiNZVBOYosZ6BKhSlk1+Y/0y1IxghXzMIIV7wIBATBYMEExEzARBgoJ
# kiaJk/IsZAEZFgNHQkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFN
# RSBDUyBDQSAwMQITNgAAAOhmnRtIQqVMRQABAAAA6DANBglghkgBZQMEAgEFAKCB
# rjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYK
# KwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg/FOobTCBMj7//87cUHrL+8u2ksbE
# HM8EHifDhAmwy6wwQgYKKwYBBAGCNwIBDDE0MDKgFIASAE0AaQBjAHIAbwBzAG8A
# ZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTANBgkqhkiG9w0BAQEFAASC
# AQBN+5sDSt/Ps3xup7axW13J1EXxTNWEnO83lF7EjdQ0XjhGOjbg7HKc3Wfg+jb9
# MKxkC5VSN/KTeyNFw7EkhiPp3qkrpzCMhEl1lrwZZFeKop1Sk058LBr5ZQfUxOW6
# shycdO3lx0OWbLa3M/muzzqk1MIj0l9lXDkHXQV346dMT04d3takv7R6ckpJmEjy
# Ig6RbsashAhQ0G8N6ekYTIzTbHZbxbwZHidAc3p8sj9QWkA0ZbUcXgquio72k+Y2
# PM0Mg8mczeVpA5xd3r5EmVONiPGCTOtyeuf66rskx4ta8ikdAINQvQAW3KoSmX1f
# v0Lez22zttz/lEBMXfg3kikLoYITuzCCE7cGCisGAQQBgjcDAwExghOnMIITowYJ
# KoZIhvcNAQcCoIITlDCCE5ACAQMxDzANBglghkgBZQMEAgEFADCCAVgGCyqGSIb3
# DQEJEAEEoIIBRwSCAUMwggE/AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIB
# BQAEIDhyb0eYcXc4e5WYz4fX28RvQC86Wd1REAkcMApLIQiDAgZd5tGdD3QYEzIw
# MTkxMjA0MDgyNDM4Ljc4N1owBwIBAYACAfSggdSkgdEwgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3MjhE
# LUM0NUYtRjlFQjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCDyMwggT1MIID3aADAgECAhMzAAABBAkBdQhYhy0pAAAAAAEEMA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE5MDkwNjIw
# NDExOFoXDTIwMTIwNDIwNDExOFowgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0
# byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3MjhELUM0NUYtRjlFQjEl
# MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZI
# hvcNAQEBBQADggEPADCCAQoCggEBAMgtB6ARuhhmlpThYPwWgmtO2oNVTTZyHgYQ
# Bc3GH/J1w6bhgTcgpNiZnGZe2kv1Abyg7ABSP6ekgpRhWpByx5gOeOxpllPXkCxp
# iMlKFFx++Rnxg0N1YFN2aAsVj9GRMWc3R6hPKtgFMHXULPxji3fu6DTgjfOi2pih
# 5r/O+cp1Oi8KvdT+8p5JlROk1/85nsTggE80CudP/NhuiIrSvmDNKVmOMF3afUWU
# swVP6v6t9cGjSWG3GMGNZe8FB3VVOL+pNtCbRV83qhQtkyIyA8HvGaciAfrXZi/Q
# D5C/vK7XcvoeHbizh7j5lXUD3PiH0ffqHvMp58lsU/Ajpqr5ZGcCAwEAAaOCARsw
# ggEXMB0GA1UdDgQWBBSY1V7fwkQaDhcBi/GZ08MisOia6jAfBgNVHSMEGDAWgBTV
# YzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3
# LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqG
# SIb3DQEBCwUAA4IBAQA9FdSzd2l8NAMX17RFeWLhOqnOAgyXIjH8tdW1yA94Zdzy
# n8NeukcjyIL7/Pkj8R7KEtEUL0cfRnds6KITaPBXxlosz1i+kMhfd6d4kSgnPWm0
# qoA14fqxJUM6P5fZfWRGUrtkNJha6N8Id1Ciuyibq7K03EnTLgli3EX1LXlzBOyy
# yjM3hDGVxgPk9D7Bw5ikgVju+Yql+tXjjgG/oFw+WJvwBN7YunaRV06JKZwsYGPs
# OYA1qyc8VXBoyeKGFKhI2oThT/P7IM3hCxLNc4fix3sLaKe4NZta0rjdssY8Kz+Z
# 4sr8T9daXSFa7kUpKVw5277+0QFCc6bkrHjlKB/lMIIGcTCCBFmgAwIBAgIKYQmB
# KgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX77Xxo
# SyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM1024
# OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHPk0yS
# wcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpC
# TUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw6ZnN
# POcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHiMBAG
# CSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZ
# BgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
# BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8E
# TzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9k
# dWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBM
# MEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRz
# L01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGSMIGP
# BgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0A
# TABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0w
# DQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+zwXi
# qf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKKdsxA
# QEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/UveYFl
# 2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4zu2Jf
# mttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHimbdLh
# nPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlXdqJx
# qgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/n
# MQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJ
# KlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdCosnP
# GUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42neV8HR
# 3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nfj950
# iEkSoYIDsTCCApkCAQEwgf6hgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3MjhELUM0NUYtRjlF
# QjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIlCgEBMAkG
# BSsOAwIaBQADFQCzRh5/R0jzKEyIVLZzGHgW3BUKfaCB3jCB26SB2DCB1TELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9z
# b2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNVBAsTHm5DaXBoZXIgTlRT
# IEVTTjo0REU5LTBDNUUtM0UwOTErMCkGA1UEAxMiTWljcm9zb2Z0IFRpbWUgU291
# cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAOGRUBkwIhgPMjAxOTEy
# MDQwNTIwMjVaGA8yMDE5MTIwNTA1MjAyNVoweDA+BgorBgEEAYRZCgQBMTAwLjAK
# AgUA4ZFQGQIBADALAgEAAgMCteoCAf8wBwIBAAICF3swCgIFAOGSoZkCAQAwNgYK
# KwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAaAKMAgCAQACAxbjYKEKMAgCAQAC
# AwehIDANBgkqhkiG9w0BAQUFAAOCAQEAJZ27rvTBd0fpu9zNtEfufvIBCFSRMXlt
# /NvcBnLfEOTBx3/Rf6YLO3DKHSXISZ5aEnBPk97Frll71El0bBnZ7AKGX0EYKJ4k
# ZBLn5CTbQTCUvH8vswKYg9sEXZ2aSDzXZTd0Xnwq1RUtlrWBX1dIVgDwotb81V/l
# FeziqCSVMuzBg0RIXGRZrSk8u1bLgDpPqL9iezDaA8al90E/FQBgubpRjBR/Qttp
# bvl1viZ8LV6IalVYpcKHBEgYYSI1NyC4euFS/lMZxuWolH+PWY6/WlGGo9zg8lHR
# +4BOhtgJUXjOqi0S5o6kaAcQ9C+XrUGgie/NHF2Vk0jWNtyJDjGWkzGCAvUwggLx
# AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABBAkBdQhY
# hy0pAAAAAAEEMA0GCWCGSAFlAwQCAQUAoIIBMjAaBgkqhkiG9w0BCQMxDQYLKoZI
# hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIGau3p32kRjPTNLqAiVWdijXjvRc63dG
# jLx1rxeWbllnMIHiBgsqhkiG9w0BCRACDDGB0jCBzzCBzDCBsQQUs0Yef0dI8yhM
# iFS2cxh4FtwVCn0wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MAITMwAAAQQJAXUIWIctKQAAAAABBDAWBBR4K4K+Z4Ae8/LBAKOh1eoMHO1p5jAN
# BgkqhkiG9w0BAQsFAASCAQB5H/KPiaBTNZsg5IkduuXpXhutu7M6zZa/c3GgFo7X
# VK5Op2SmlwQL4uiBXeOA04DEgblEwgQphtHrmS4OpUC5nxNvQ77AAYDl6roDhpOd
# 6SevBGqVT3cf0Cmblco3aLHzO7BlgxcqlSuB9ss2d8kh4/Q7W2vv6Uw1pPqAZ2Yz
# m6lh6NZEdd+x+P+X8HLboMZETFKLsi+cxHjFcZ/KxJB3ZmbocH0ulEVlFeXwf8Yc
# 7aYTTKTnaxLh5sKqcwD+6HIH0y+KanY3zaSX09RGGnmWhID7C8qRzyRomu6WXJ6+
# aFY7IM0DIltigli9IUIepFnOsz2aDVuDZ5QFX+YtQOo5
# SIG # End signature block