Membership.ps1

#requires -Version 5.1

function Export-KeeperMembership {
    <#
    .Synopsis
    Download shared folder and team membership data to a JSON file

    .Parameter FileName
    Output JSON filename (default: shared_folder_membership.json)

    .Parameter Force
    If file exists, overwrite it. Otherwise, merge with existing data

    .Parameter FoldersOnly
    Download shared folders only, skip teams

    .Parameter ForceManageUsers
    Force enable 'manage users' permission for all users in shared folders

    .Parameter ForceManageRecords
    Force enable 'manage records' permission for all users in shared folders

    .Parameter RestrictManageUsers
    Force disable 'manage users' permission for all users in shared folders

    .Parameter RestrictManageRecords
    Force disable 'manage records' permission for all users in shared folders

    .Parameter SubFolderHandling
    Shared sub-folder handling: 'ignore' or 'flatten'

    .Parameter Source
    Membership source: keeper, lastpass, thycotic. Currently only keeper is supported.

    .Description
    Downloads shared folder and team membership information from your Keeper vault.
    This is useful for migration, backup, or analysis of access permissions.
    
    If the output file exists and -Force is not specified, the new data will be
    merged with the existing file content.
    
    Use ForceManageUsers/ForceManageRecords to grant permissions to all users.
    Use RestrictManageUsers/RestrictManageRecords to revoke permissions from all users.

    .Example
    Export-KeeperMembership
    Downloads membership to default file "shared_folder_membership.json"

    .Example
    Export-KeeperMembership -FileName "backup.json" -Force
    Downloads membership and overwrites existing backup.json

    .Example
    Export-KeeperMembership -FoldersOnly
    Downloads only shared folder membership, skipping teams

    .Example
    Export-KeeperMembership -FileName "membership.json" -ForceManageUsers -ForceManageRecords
    Downloads membership with 'manage users' and 'manage records' permissions enabled for all users

    .Example
    Export-KeeperMembership -FileName "restricted.json" -RestrictManageUsers
    Downloads membership with 'manage users' permission disabled for all users
#>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $false, Position = 0)]
        [string] $FileName = "shared_folder_membership.json",

        [Parameter(Mandatory = $false)]
        [switch] $Force,

        [Parameter(Mandatory = $false)]
        [switch] $FoldersOnly,

        [Parameter(Mandatory = $false)]
        [switch] $ForceManageUsers,

        [Parameter(Mandatory = $false)]
        [switch] $ForceManageRecords,

        [Parameter(Mandatory = $false)]
        [switch] $RestrictManageUsers,

        [Parameter(Mandatory = $false)]
        [switch] $RestrictManageRecords,

        [Parameter(Mandatory = $false)]
        [ValidateSet('ignore', 'flatten')]
        [string] $SubFolderHandling,

        [Parameter(Mandatory = $false)]
        [ValidateSet('keeper', 'lastpass', 'thycotic')]
        [string] $Source = 'keeper'
    )

    if ([string]::IsNullOrWhiteSpace($Source)) { $Source = 'keeper' }
    $sourceLower = $Source.ToLower()
    if ($sourceLower -ne 'keeper') {
        throw "Membership download source '$Source' is not supported. Valid values: keeper, lastpass, thycotic. Currently only keeper is implemented."
    }

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault

    Write-Host "Downloading shared folder membership from Keeper..."

    $downloadOptions = New-Object KeeperSecurity.Vault.DownloadMembershipOptions
    $downloadOptions.FoldersOnly = $FoldersOnly.IsPresent

    if ($ForceManageUsers -and $RestrictManageUsers) {
         throw "Cannot specify both -ForceManageUsers and -RestrictManageUsers" 
    }
    
    if ($ForceManageRecords -and $RestrictManageRecords) {
        throw "Cannot specify both -ForceManageRecords and -RestrictManageRecords" 
    }

    if ($ForceManageUsers.IsPresent) {
        $downloadOptions.ForceManageUsers = $true
    }

    if ($ForceManageRecords.IsPresent) {
        $downloadOptions.ForceManageRecords = $true
    }

    if ($RestrictManageUsers.IsPresent) {
        $downloadOptions.ForceManageUsers = $false
    }
    
    if ($RestrictManageRecords.IsPresent) {
        $downloadOptions.ForceManageRecords = $false
    }

    if ($SubFolderHandling) {
        $downloadOptions.SubFolderHandling = $SubFolderHandling
    }

    try {
        $downloadTask = [KeeperSecurity.Vault.KeeperMembershipDownload]::DownloadMembership(
            $vault,
            $downloadOptions
        )
        $downloadTask.Wait()
        $exportFile = $downloadTask.Result

        if (-not $FileName.EndsWith(".json", [StringComparison]::OrdinalIgnoreCase)) {
            $FileName += ".json"
        }

        $fileExists = Test-Path $FileName
        if ($fileExists -and $Force) {
            Write-Host "File `"$FileName`" will be overwritten (--force flag is set)."
        }
        
        $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FileName)
        
        $directory = [System.IO.Path]::GetDirectoryName($fullPath)
        if (-not [string]::IsNullOrEmpty($directory) -and -not (Test-Path $directory)) {
            New-Item -ItemType Directory -Path $directory -Force | Out-Null
        }

        if ($fileExists -and -not $Force) {
            Write-Host "Merging with existing file..."
            
            if (Test-Path $fullPath) {
                $existingJson = [System.IO.File]::ReadAllText($fullPath)
                $jOptions = New-Object System.Runtime.Serialization.Json.DataContractJsonSerializerSettings
                $jOptions.UseSimpleDictionaryFormat = $true
                $jOptions.EmitTypeInformation = [System.Runtime.Serialization.EmitTypeInformation]::Never
                
                $serializer = New-Object System.Runtime.Serialization.Json.DataContractJsonSerializer(
                    [KeeperSecurity.Commands.ExportFile], $jOptions)
                
                $ms = New-Object System.IO.MemoryStream(,[System.Text.Encoding]::UTF8.GetBytes($existingJson))
                try {
                    $existingExportFile = $serializer.ReadObject($ms)
                    
                    $mergedSharedFolders = New-Object 'System.Collections.Generic.List[KeeperSecurity.Commands.ExportSharedFolder]'
                    $newUids = New-Object 'System.Collections.Generic.HashSet[string]'
                    
                    if ($exportFile.SharedFolders) {
                        foreach ($sf in $exportFile.SharedFolders) {
                            $mergedSharedFolders.Add($sf)
                            $newUids.Add($sf.Uid) | Out-Null
                        }
                    }
                    
                    if ($existingExportFile.SharedFolders) {
                        foreach ($sf in $existingExportFile.SharedFolders) {
                            if (-not $newUids.Contains($sf.Uid)) {
                                $mergedSharedFolders.Add($sf)
                            }
                        }
                    }
                    
                    $mergedTeams = New-Object 'System.Collections.Generic.List[KeeperSecurity.Commands.ExportTeam]'
                    $newTeamUids = New-Object 'System.Collections.Generic.HashSet[string]'
                    
                    if ($exportFile.Teams) {
                        foreach ($team in $exportFile.Teams) {
                            $mergedTeams.Add($team)
                            $newTeamUids.Add($team.Uid) | Out-Null
                        }
                    }
                    
                    if ($existingExportFile.Teams) {
                        foreach ($team in $existingExportFile.Teams) {
                            if (-not $newTeamUids.Contains($team.Uid)) {
                                $mergedTeams.Add($team)
                            }
                        }
                    }
                    
                    $mergedExportFile = New-Object KeeperSecurity.Commands.ExportFile
                    if ($mergedSharedFolders.Count -gt 0) {
                        $mergedExportFile.SharedFolders = $mergedSharedFolders.ToArray()
                    }
                    if ($mergedTeams.Count -gt 0) {
                        $mergedExportFile.Teams = $mergedTeams.ToArray()
                    }
                    
                    $exportFile = $mergedExportFile
                }
                finally {
                    $ms.Dispose()
                }
            }
        }

        $jsonBytes = [KeeperSecurity.Utils.JsonUtils]::DumpJson($exportFile, $true)
        $jsonContent = [System.Text.Encoding]::UTF8.GetString($jsonBytes)
        [System.IO.File]::WriteAllText($fullPath, $jsonContent)
        
        Write-Debug "Downloaded membership to $fullPath"

        $sharedFolderCount = if ($exportFile.SharedFolders) { $exportFile.SharedFolders.Length } else { 0 }
        $teamCount = if ($exportFile.Teams) { $exportFile.Teams.Length } else { 0 }

        Write-Host ""
        Write-Host "Download Summary:"
        Write-Host " Shared Folders: $sharedFolderCount"
        if (-not $FoldersOnly) {
            Write-Host " Teams: $teamCount"
        }
        Write-Host " Output File: $fullPath"
        Write-Host ""
        Write-Host "Download membership completed successfully." -ForegroundColor Green
    }
    catch {
        Write-Error "Failed to download membership: $_"
        return
    }
}
New-Alias -Name kdwnmbs -Value Export-KeeperMembership

function Import-KeeperMembership {
    <#
    .Synopsis
    Load shared folder membership from a JSON file into Keeper

    .Parameter FileName
    Input JSON filename (default: shared_folder_membership.json)

    .Parameter FullSync
    Update and remove membership to match the file; otherwise only add/update

    .Description
    Reads shared folder membership from a JSON file (produced by Export-KeeperMembership/ same format as what is exported by Export-KeeperMembership)
    and applies it to the vault. Use -FullSync to also remove users/teams that are not in the file.

    .Example
    Import-KeeperMembership
    Loads membership from default file "shared_folder_membership.json"

    .Example
    Import-KeeperMembership -FileName "backup.json" -FullSync
    Loads membership from backup.json and removes any users/teams not in the file
#>


    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $false, Position = 0)]
        [string] $FileName = "shared_folder_membership.json",

        [Parameter(Mandatory = $false)]
        [switch] $FullSync
    )

    $DefaultFileName = "shared_folder_membership.json"
    $MaxFileSizeBytes = 50 * 1024 * 1024  # 50 MB

    if ([string]::IsNullOrWhiteSpace($FileName)) {
        $FileName = $DefaultFileName
    }

    $fullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FileName)
    if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) {
        Write-Error "Shared folder membership file `"$fullPath`" not found"
        return
    }

    $fileInfo = Get-Item -LiteralPath $fullPath
    if ($fileInfo.Length -gt $MaxFileSizeBytes) {
        $maxMB = [math]::Round($MaxFileSizeBytes / (1024 * 1024), 2)
        $sizeMB = [math]::Round($fileInfo.Length / (1024 * 1024), 2)
        Write-Error "File size ($sizeMB MB) exceeds maximum allowed size ($maxMB MB)"
        return
    }

    try {
        $jsonBytes = [System.IO.File]::ReadAllBytes($fullPath)
        $parseJson = [KeeperSecurity.Utils.JsonUtils].GetMethod("ParseJson", [Type[]]@([byte[]]))
        $importFile = $parseJson.MakeGenericMethod([KeeperSecurity.Commands.ImportFile]).Invoke($null, @(,$jsonBytes))
    }
    catch {
        Write-Error "Error reading membership file: $_"
        return
    }

    $sharedFolderCount = if ($importFile.SharedFolders) { $importFile.SharedFolders.Length } else { 0 }
    Write-Host "Processing $sharedFolderCount shared folder(s)..."

    $vault = getVault
    if (-not $vault) {
        Write-Error "Not connected to Keeper. Please login first."
        return
    }

    $applyOptions = New-Object KeeperSecurity.Vault.ApplyMembershipOptions
    $applyOptions.FullSync = $FullSync.IsPresent

    try {
        $summary = [KeeperSecurity.Vault.KeeperApplyMembership]::ApplyMembership($vault, $importFile, $applyOptions).GetAwaiter().GetResult()

        Write-Host ""
        if ($summary.TeamsAdded -gt 0)   { Write-Host "$($summary.TeamsAdded) team(s) added to shared folders" }
        if ($summary.UsersAdded -gt 0)  { Write-Host "$($summary.UsersAdded) user(s) added to shared folders" }
        if ($summary.TeamsUpdated -gt 0) { Write-Host "$($summary.TeamsUpdated) team(s) updated in shared folders" }
        if ($summary.UsersUpdated -gt 0) { Write-Host "$($summary.UsersUpdated) user(s) updated in shared folders" }
        if ($summary.TeamsRemoved -gt 0) { Write-Host "$($summary.TeamsRemoved) team(s) removed from shared folders" }
        if ($summary.UsersRemoved -gt 0) { Write-Host "$($summary.UsersRemoved) user(s) removed from shared folders" }

        $anyChanges = ($summary.TeamsAdded + $summary.UsersAdded + $summary.TeamsUpdated + $summary.UsersUpdated + $summary.TeamsRemoved + $summary.UsersRemoved) -gt 0
        if (-not $anyChanges) {
            Write-Host "No changes applied. All memberships are up to date."
        }
        Write-Host ""
        Write-Host "Apply membership completed successfully." -ForegroundColor Green
    }
    catch {
        Write-Error "Error applying membership: $_"
        return
    }
}
New-Alias -Name kapplymbs -Value Import-KeeperMembership
# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAYiHSo/cfM4ifG
# U5B2b4IfnI0ILSbNvHo8a1+mrklZdqCCITswggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQg
# MjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0Cit
# eLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS
# 1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZM
# swEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBC
# Mo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3
# /hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26j
# q22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5
# OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo
# 7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzU
# tw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwm
# KwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NP
# TLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/
# BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0j
# BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E
# PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkq
# hkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDK
# r2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvda
# qpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+
# lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7a
# brs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNS
# y0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeK
# iwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYb
# KyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8Q
# xAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVm
# zGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCn
# HwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4w
# gga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1
# c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0Zo
# dLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi
# 6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNg
# xVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiF
# cMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJ
# m/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvS
# GmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1
# ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9
# MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7
# Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bG
# RinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6
# X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAd
# BgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJx
# XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
# BwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln
# aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo
# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy
# bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL
# BQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxj
# aaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0
# hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0
# F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnT
# mpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKf
# ZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzE
# wlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbh
# OhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOX
# gpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EO
# LLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wG
# WqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWg
# AwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG
# EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0
# IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex
# MB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMx
# FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy
# NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZI
# hvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3
# zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8Tch
# TySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWj
# FDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2Uo
# yrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjP
# KHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KS
# uNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7w
# JNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vW
# doUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOg
# rY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K
# 096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCf
# gPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zy
# Me39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL
# TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG
# AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
# b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy
# dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j
# cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB
# CwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZ
# D9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/
# ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu
# +WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4o
# bEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2h
# ECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasn
# M9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol
# /DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgY
# xQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3oc
# CVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcB
# ZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCB0kwggUx
# oAMCAQICEAe0P3SLJmcoVNrErUyxTt0wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy
# dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB
# MTAeFw0yNTEyMzEwMDAwMDBaFw0yOTAxMDIyMzU5NTlaMIHRMRMwEQYLKwYBBAGC
# NzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMR0wGwYDVQQPDBRQ
# cml2YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQwNzk4NTELMAkGA1UEBhMC
# VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK
# ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5
# IEluYy4wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCUcNMoSVmxAi0a
# vG+StFJMNFFTUIOo3HdBZ+0gqA1XpNgUx11vB1vCZrvFsD9m5oA58tdp4gZN3LmQ
# aMvCl2ANUT7MilI02Hf1RWlygBzon6iE0GpU3lgRrwrk1dhtLpGsR6dbMKUUHprc
# vKpXk90/VN+vhzY1uik1tCTxkDCPu/AYJg7m9+tR2KqvMuYMaMLhii66eWUAGsBC
# h/uZxjkGoJF6qZ0DgFd7rW7VYljbfYSNPeZNGTDgB0J/wOsKl0mn612DTseIvAKt
# 4vra/FLFukyEyStnfQ8lWYDcLLCMCjNVrzGipmT5E2iyx7Y1RZCIpNwVogp3Ixbk
# Gbq5A/41YNOLLd4cFewyB2F037RevBCRsUODZEt1qBf7Jbu3DiYo1G+zTj9E0R1s
# FzyijcfdsTm6X5ble+yCJeGkX5XgsyPnZpyz/FX9Fr0N9pMPGWwW2PKyHEnSytXm
# 0Dxdq2P4mA4CBUxq7YoV26L2PF6QEh9BQdXTPcnLysUv7SI/a0ECAwEAAaOCAgIw
# ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBRG
# 4H6CH8pvNX632bsdnrda4MtJLDA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB
# BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC
# B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p
# bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT
# QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA
# A4ICAQA1Wlq0WzJa3N6DgjgBU7nagIJBab1prPARXZreX1MOv9VjnS5o0CrfQLr6
# z3bmWHw7xT8dt6bcSwRixqvPJtv4q8Rvo80O3eUMvMxQzqmi7z1zf+HG+/3G4F+2
# IYegvPc8Ui151XCV9rjA8tvFWRLRMX0ZRxY1zfT027HMw0iYL20z44+Cky//FAnL
# iRwoNDGiRkZiHbB9YOftPAYNMG3gm1z3zOW5RdfKPrqvMuijE+dfyLIAA6Immpzu
# FMH+Wgn8NnSlot9b4YKycaqqdjd7wXDjPub/oQ7VShuCSBWj+UNOTVh0vcZGackc
# H1DLVgwp2dcKlxJiQKtkHT/T6LloY6LTe6+8wkVkr8EAv1W+q/+M1a4Ao+ykFbIA
# 2LBEmA9qdgoLtenAYIiEg+48SjMPgyBbVPE3bhL1vIqjEIxYCfdmi6wx33oYX7HB
# +bJ7zitHw4GgtpfPV8y8QRZImKmeDOKyXjQPDmQM/Eglm/Ns0GzBkVXM8h6UI34b
# WZrHz9sbLSE20m5Svmxftvw5zju+I3WsmS/stNfWlOkwU0niUgwPHaz21kjXEA5A
# g+aqv26wodqZcnGOlChoWDvSJ8KKgdOFbeAYKAMp1NY7iWV315zpGH19RipCR1NH
# 0ND8iIubk3WGNf2rzEfqlOi3h2ywqVkU6AKXHdO5JV4otSKKEDGCBdkwggXVAgEB
# MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD
# VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI
# QTM4NCAyMDIxIENBMQIQB7Q/dIsmZyhU2sStTLFO3TANBglghkgBZQMEAgEFAKCB
# hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ
# BDEiBCBKMKvuY1XH1BXB6kTmr2MrtxeS1ULUlUa+XiuLVJJt9zANBgkqhkiG9w0B
# AQEFAASCAYBycE5A/nq6vmVqLq49pH/ie01Fq5qlAtJs4FZPlubgo1O96wZGo6IV
# GWxWGx4wH60DkSypJ5habv5pB7pEYC0SCOfJv8jK3zhAdJrJgYdvivAtHxgCrv41
# qgosOCr53DBQgV1z7wrYRvOr9AeVm1H5gbC8RYQpPq0IdBzx57gTucJzq1TOsbVP
# /LPhJmLk7I71DLAdRw6VwEmxg9nh6wkX6kSgBgN19vQOTntci7QS4peh07bLU/sk
# MMLuQccwTjoX5TCNz1/w/Lov7e3O06KG+wk5EnMpR9uwh2kV4W2pcKEdywMrvWNb
# AYUFNUnR+XVz2DBcvWEhjXz/gMtYVZqTHRdGMNwGwatFeji/cG8jbGADa5nBrVz3
# IaOK/T5vW7PKyv82K76q1TRPGjWoKH5XPjwFNmgDPXXDVGQa3hhkNNcmkqsewyiv
# L6Sii59fjCjOdIsTyTTATFt4XYDQpRL7Fk4gPG/IB9aWwN4lKsiYc98NgdTDz8wT
# Ww+4FSHIT+2hggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg
# Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN
# AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwMjI1MjIzODAwWjAv
# BgkqhkiG9w0BCQQxIgQgy746U/vq4pTdaG96Iw6XEmnmOg4vqj1+Tf34mTWLo3Yw
# DQYJKoZIhvcNAQEBBQAEggIAikQ4JRQZrxjRBfbqbsE03I/S3e7TFjaUEYPgXMXU
# GYSZu1Y3Tcngau6rleMN5emnhPXtWKgzeuLcrelabvnAogGH3fryiLWj3CqomtIT
# XxfdqOLm8Bqo5FxNVmtiEVjJFbsiFm62M4ry23gCelaft7oy9T15v3XSSHKh3bMf
# zfqM324TjBtQbdTVoHDrWU0G7GLiGQ1iqQ7OwEp3abO6q3LqwKXSh3G/fBlXoKlC
# iFjx9lBp+QZ39MNyi53Fez8T7/BinJuaWRVdX+HLiLoicHbhS6EBs1Cp61PBqrnT
# kkj1BCWdp88NNk3EuGe7FGFAfRzDNtgrPQ4sPRgOAUxXcx3WUQFVxjtOpCRuCx4o
# fGxtC7MSfJFiBrbrV/pzPU59MBiWo9pPEo0ATGJKQscvaNDu733WFi1V2+frvQPs
# Z16sqpN7J9JTXQ2L2YgbhkSwam3Bhz4GGX/+Lv60ny2MGSV7W3CJ/USA8imlNbYa
# Y1vNGR9ZL131y1jwdke5BZnPZXd/84QSSFksnZyoO2CxlbauLnp0AOG3WHFqobek
# OhwzR8+qCAOurEU0ykvIkQ4+ZXMUEhtm5l42ujBV7cK+ZcHo5tpsEuPNLAFzBGOh
# zUtqiAsTYudksjvdRdZpuhN3rimTYASmxLuqQlvAfzHL7gAqRfoffvB/CHTUFSC2
# pa0=
# SIG # End signature block