ozo-ad-windows-enumerate-directory-users.ps1

#Requires -Modules ActiveDirectory,ImportExcel,@{ModuleName="OZO";ModuleVersion="1.6.0"},OZOFiles,OZOLogger -Version 5.1

<#PSScriptInfo
    .VERSION 1.0.0
    .GUID 7465a5f8-752f-4b68-a91c-b681bc639e81
    .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-Windows-Enumrate-Directory-Users/blob/main/LICENSE
    .PROJECTURI https://github.com/onezeroone-dev/OZO-AD-Windows-Enumrate-Directory-Users
    .ICONURI
    .EXTERNALMODULEDEPENDENCIES ActiveDirectory,ImportExcel
    .REQUIREDSCRIPTS
    .EXTERNALSCRIPTDEPENDENCIES
    .RELEASENOTES https://github.com/onezeroone-dev/OZO-AD-Windows-Enumrate-Directory-Users/blob/main/CHANGELOG.md
#>


<#
    .SYNOPSIS
    See description.
    .DESCRIPTION
    Enumerates the directories and files in a given path and produces an Excel report containing users, email addresses, groups, and last modified child date.
    .PARAMETER OutDir
    Directory for the Excel report. Defaults to the current directory.
    .PARAMETER Path
    The path to inspect. Defaults to the current directory.
    .LINK
    https://github.com/onezeroone-dev/OZO-AD-Windows-Enumrate-Directory-Users/blob/main/README.md
#>


# PARAMETERS
[CmdletBinding(SupportsShouldProcess = $true)] Param (
    [Parameter(Mandatory=$false,HelpMessage="Path for the Excel report")][String]$OutDir = (Get-Location),
    [Parameter(Mandatory=$false,HelpMessage="Path to inspect")][String]$Path = (Get-Location)
)

# CLASSES
Class OZOMain {
    # PROPERTIES: Booleans, Strings
    [Boolean] $Validates = $true
    [String]  $excelPath = $null
    [String]  $outDir    = $null
    [String]  $Path      = $null
    # PROPERTIES: PSCustomObjects
    [PSCustomObject] $ozoLogger = $null
    # PROPERTIES: Lists
    [System.Collections.Generic.List[PSCustomObject]] $childItems = @()
    # METHODS
    # Constructor method
    OZOMain($OutDir,$Path) {
        # Set properties
        $this.outDir = $OutDir
        $this.Path   = $Path
        # Create a logger
        $this.ozoLogger = (New-OZOLogger)
        # Log a process start message
        $this.ozoLogger.Write("Starting process.","Information")
        # Determine if the configuation is valid
        If (($this.ValidateEnvironment()) -eq $true) {
            # Call GetItems to generate OZOItem objects
            $this.GetChildItems()
        }
        # Report
        $this.Report()
        # Log a process end message
        $this.ozoLogger.Write(("Process complete."),"Information")
    }
    # Environment validation method
    Hidden [Boolean] ValidateEnvironment() {
        # Control variable
        [Boolean] $Return = $true
        # Determine if the outDir exists
        If ([Boolean](Test-Path -Path $this.outDir) -eq $true) {
            # Output directory exists; set the Excel path
            $this.excelPath = (Join-Path -Path $this.outDir -ChildPath ((Get-OZO8601Date -Time) + "-ozo-ad-windows-directory-users.xlsx"))
        } Else {
            # Output directory does not exist; report
            $this.ozoLogger.Write(("Output directory is invalid or inaccessible."),"Error")
            $Return = $false
        }
        # Return
        return $Return
    }
    # GetItems method
    Hidden [Void] GetChildItems() {
        # Iterate through the child items
        ForEach ($childItem in (Get-ChildItem -Path $this.Path)) {
            # Add an OZOitem object to the Items list
            $this.childItems.Add(([OZOChildItem]::new($childItem)))
        }
    }
    # Report method
    Hidden [Void] Report() {
        # Determine if any ChildItems were processed
        If ($this.ChildItems.Count -gt 0) {
            # At least one ChildItem was processed; produce the Item Detail sheet
            $this.childItems | Select-Object -Property @{Name="Name";Expression={$_.childItem.Name}},
            @{Name="Path";Expression={$_.ozoDirectorySummary.Path}},
            @{Name="Last Modified";Expression={$_.ozoDirectorySummary.newestChildWriteTime}},
            @{Name="Total size (GB)";Expression={($_.ozoDirectorySummary.totalSize / 1073741824)}},
            @{Name="Long paths";Expression={[Boolean]$_.ozoDirectorySummary.longPaths}},
            @{Name="Problem paths";Expression={[Boolean]$_.ozoDirectorySummary.problemPaths}},
            @{Name="Messages";Expression={$_.Messages -Join "; "}} | Export-Excel -WorksheetName "Item Detail" -Path $this.excelPath
            # Produce the sheet detailing all unique users found for all child items
            $this.childItems.adObjects | Where-Object {$null -ne $_.adUser} | Select-Object -Unique -Property @{Name="Last Name";Expression={$_.adUser.Surname}},
            @{Name="First Name";Expression={$_.adUser.GivenName}},
            @{Name="Account";Expression={$_.adUser.SamAccountName}},
            @{Name="Email Address";Expression={$_.adUser.EmailAddress}} | Sort-Object -Property "Last Name","First Name","Account" | Export-Excel -WorksheetName "Users" -Path $this.excelPath
            # Produce the sheet detailing all unique groups found for all items
            $this.childItems.adObjects | Where-Object {$null -ne $_.adGroup} | Select-Object -Unique -Property @{Name="Group Name";Expression={$_.adGroup.Name}} | Export-Excel -WorksheetName "Groups" -Path $this.excelPath
            # Determine if there are any invalid SIDs
            If (($this.childItems.adObjects | Where-Object {$_.Validates -eq $false}).Count -gt 0) {
                # Found invalid SIDs; produce the sheet detailing all unique invalid SIDs
                $this.childItems.adObjects | Where-Object {$_.Validates -eq $false} | Select-Object -Unique -Property @{Name="SID";Expression={$_.objectSID}},@{Name="Messasges";Expression={$_.Messages -Join "; "}} | Export-Excel -WorksheetName "Invalid Objects" -Path $this.excelPath
            }
            # Determine if there are any long paths
            If (($this.childItems.ozoDirectorySummary.longPaths).Count -gt 0) {
                # Found long paths on one or more objects; produce sheet detailing all long paths
                $this.childItems.ozoDirectorySummary.longPaths | Select-Object -Property @{Name="Path";Expression={$_.FullName}},@{Name="Path Length";Expression={$_.FullName.Length}} | Export-Excel -WorksheetName "Long Paths" -Path $this.excelPath
            }
            # Determine if there are any problem paths
            If (($this.childItems.ozoDirectorySummary.problemPaths).Count -gt 0) {
                # Found problem paths on one or more objects; produce sheet detailing all problem paths
                $this.childItems.ozoDirectorySummary.problemPaths | Select-Object -Property @{Name="Path";Expression={$_.FullName}},@{Name="Error Message";Expression={$_.ErrorMessage}} | Export-Excel -WorksheetName "Problem Paths" -Path $this.excelPath
            }
        } Else {
            # No ChildItems were processed
            $this.ozoLogger.Write("No child items were processed.","Warning")
        }
    }
}

Class OZOChildItem {
    # PROPERTIES: FileSystemInfo
    [System.IO.FileSystemInfo] $childItem = $null
    # PROPERTIES: PSCustomObjects
    [PSCustomObject] $ozoDirectorySummary = $null
    # PROPERTIES: PSCustomObject Lists
    [System.Collections.Generic.List[PSCustomObject]] $adObjects = @()
    # PROPERTIES: String Lists
    [System.Collections.Generic.List[String]]         $Messages  = @()
    # METHODS
    # Constructor method
    OZOChildItem($ChildItem) {
        # Set properties
        $this.childItem = $ChildItem
        # Get directory summary
        $this.ozoDirectorySummary = (Get-OZODirectorySummary -Path $this.childItem.FullName)
        # Iterate through the SIDs in the directory summary
        ForEach ($objectSID in $this.ozoDirectorySummary.objectSIDs) {
            # Determine if identity reference is a a domain ID
            If ($objectSID -Like "S-1-5-21*") {
                # objectSID is a domain ID; add an OZOADObject to the adObjects list
                $this.adObjects.Add(([OZOADObject]::new($objectSID)))
            }
        }
        # Iterate through the valid adObjects that contain an AD group
        ForEach ($adObject in ($this.adObjects | Where-Object {$_.Validates -eq $true -And $null -ne $_.adGroup})) {
            # Iterate through the members of the group, recusrively
            ForEach ($groupMember in (Get-ADGroupMember -Identity $adObject.objectSID -Recursive)) {
                # Try to get the SID for this user
                Try {
                    $userSID = (Get-ADUser -Ideneity $groupMember -ErrorAction Stop).SID
                    # Success; determine if adObjects does not already contain this SID
                    If ($this.adObjects.objectSID -NotContains $userSID) {
                        # adObjects does not already contain an object with this SID; add it
                        $this.adObjects.Add(([OZOADObject]::new($userSID)))
                    }
                } Catch {
                    # Failure
                    $this.Messages.Add(("While processing the " + $adObject.adGroup.Name + " [recursive] group members, unable to get SID for user with DN " + $groupMember + ". Error message is: " + $_))
                }
            }
        }
    }
}

Class OZOADObject {
    # PROPERTIES: Booleans, Strings
    [Boolean] $Validates = $true
    [String]  $objectSID = $null
    # PROPERTIES: PSCustomObjects
    [PSCustomObject] $adGroup  = $null
    [PSCustomObject] $adObject = $null
    [PSCustomObject] $adUser   = $null
    # PROPERTIES: String Lists
    [System.Collections.Generic.List[String]] $Messages = @()
    # METHODS
    # Constructor method
    OZOADObject($ObjectSID) {
        # Set properties
        $this.objectSID = $ObjectSID
        # Try to get the AD object
        Try {
            $this.adObject = (Get-ADObject -Filter {objectSid -eq $this.objectSID} -ErrorAction Stop)
            # Success; switch on objectClass
            Switch ($this.adObject.objectClass) {
                "user" {
                    $this.Validates = $this.GetADUser()
                }
                "group" {
                    $this.Validates = $this.GetADGroup()
                }
                default {
                    $this.Messages.Add(("ObjectClass " + $this.adObject.objectClass + " is not handled"))
                    $this.Validates -eq $false
                }
            }
        } Catch {
            # Failure
            $this.Messages.Add(("Unable to get AD object. Error message is: " + $_))
        }
    }
    # Get AD User method
    Hidden [Boolean] GetADUser() {
        # Control variable
        [Boolean] $Return = $true
        # Determine that the object is valid and not null and class is User
        If ($this.Validates -eq $true -And $null -ne $this.adObject -And $this.adObject.objectClass -eq "user") {
            # Valid, not null, and user class; try to get the AD user
            Try {
                $this.adUser = (Get-ADUser -Identity $this.objectSID -Properties SamAccountName,GivenName,Surname,EmailAddress -ErrorAction Stop)
                # Success; determine if user is disabled
                If ($this.adUser.Enabled -eq $false) {
                    # User is disabled
                    $this.Messages.Add("User is disabled")
                    $Return = $false
                }
            } Catch {
                # Failure
                $this.Messages.Add(("Unable to get AD user object. Error message is: " + $_))
                $Return = $false
            }
            # Make sure group is null
            $this.adGroup = $null
        }
        # Return
        return $Return
    }
    # Get AD Group method
    Hidden [Boolean] GetADGroup() {
        # Control variable
        [Boolean] $Return = $true
        # Determine that the object is valid and not null and class is Group
        If ($this.Validates -eq $true -And $null -ne $this.adObject -And $this.adObject.objectClass -eq "group") {
            # Valid, not null, and group class; try to get the group
            Try {
                $this.adGroup = (Get-ADGroup -Identity $this.objectSID -ErrorAction Stop)
                # Success
            } Catch {
                # Failure
                $this.Messages.Add(("Unable to get AD group object. Error message is: " + $_))
                $Return = $false
            }
            # Make sure user is null
            $this.adUser = $null
        }
        # Return
        return $Return
    }
}

# Create a Main object
[OZOMain]::new($OutDir,$Path) | Out-Null

# SIG # Begin signature block
# MIIfcQYJKoZIhvcNAQcCoIIfYjCCH14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCARlBjK0tlQ2jAB
# DRDor8fogYuWuNVSPI5hkN+CKdb9HaCCDPgwggZyMIIEWqADAgECAghkM1HTxzif
# 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
# 9w0BCQQxIgQgf770Vpd7OuJneXDOWkWO6jvHEZDcGfXqfAOjuIrcBtMwDQYJKoZI
# hvcNAQEBBQAEggGAZBBaEtX4260b71c0BQdvQhDZAsaAYZw0vR/wRkPq0+IdUDiD
# umTdRwsiUiFokJpK0qQ8RYO+7idsF0KQPkZo5Yat6qC33PetgCZF7U00wtBZWLsY
# 1toYJREZq8LybY5vCx02nWI9YUxT+v7bgHQBYNwC7T8ZmZGGnyigR3q0hU29d661
# IALLRZAGvvLpVpKlHiyy5yc/cK+tIyJ8pdFsdc2X4sd+uLKc2/m/tA2qJ33MPbe/
# QekTfRFSgthh/Q4t9SOtJCzXcOS7s9wu7H0BP7uc1ihLBslYq0K/ryTx/UbyIBIq
# 6Ht7mV7DSeO+oo8Y+UXodIp+Dp/o9uYP0ts9aKioFHY31jBkVLyzqmkjwST33HfI
# kdAnRPSuo2WXyyygWesCVIN/1cdVeNOP0KfNyTMwyGPAIFVnJ3+7uxWZqnLKuODX
# HF8Dz652+zgiXoQrYGW+kZ81XmT10XlWLtKdd5CXOVHAiKATipTT2q7fmRJ7IFMN
# fs/wS8nTdtKnYlhDoYIPFTCCDxEGCisGAQQBgjcDAwExgg8BMIIO/QYJKoZIhvcN
# AQcCoIIO7jCCDuoCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm
# MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCArPJCh2s4tzI2v
# SFTSjBihmZPvxKmu1mc1gsR6NH0UqgIIN1mw0DG4X8gYDzIwMjUwNTIwMTgxMTI4
# 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
# CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA1MjAxODExMjhaMCgGCSqG
# SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ
# BDEiBCCPQ9eqiNP2Bl2QcczoM7Qugl027BTxaoa0PnjD3bLBPTCByQYLKoZIhvcN
# AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29
# iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV
# BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t
# IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9
# xzAKBggqhkjOPQQDAgRGMEQCICzilBuaNR/eETKSCe5DnZEiISGeyGFJ0VraNa0p
# XhovAiASVDwxUjYaUqPsNTb+mCios977c1KJmSVpG6CT/Oxz1Q==
# SIG # End signature block