FolderCommands.ps1

#requires -Version 5.1

class KeeperFolderListItem {
    [string]$FolderUid
    [string]$Name
    [string]$FolderType
    [string]$Category
    [string]$ParentUid
    [string]$SharedFolderUid
    [int]$SubfolderCount
    [int]$RecordCount
    [string]$Path
}

function Add-KeeperFolder {
    <#
    .Synopsis
    Creates a Keeper folder.

    .Parameter Name
    Folder name

    .Parameter ParentFolderUid
    Parent Folder UID. Use current folder if omitted

    .Parameter Shared
    Create a shared folder

    .Parameter CanEdit
    Anyone can edit records by default

    .Parameter CanShare
    Anyone can share records by default

    .Parameter ManageUsers
    Anyone can manage users by default

    .Parameter ManageRecords
    Anyone can manage records by default

#>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Name,
        [Parameter()][string] $ParentFolderUid,
        [Parameter()][switch] $Shared,
        [Parameter()][switch] $CanEdit,
        [Parameter()][switch] $CanShare,
        [Parameter()][switch] $ManageUsers,
        [Parameter()][switch] $ManageRecords
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault

    $objs = Get-KeeperChildItem -ObjectType Folder | Where-Object Name -eq $Name
    if ($objs.Length -gt 0 ) {
        Write-Error -Message "Folder `"$Name`" already exists" -ErrorAction Stop
    }

    $parentUid = $Script:Context.CurrentFolder
    if ($ParentFolderUid) {
        $folder = resolveKeeperFolder -Identifier $ParentFolderUid -Vault $vault -SupportPaths
        $parentUid = $folder.FolderUid
    }

    $options = $null
    if ($Shared.IsPresent) {
        $options = New-Object KeeperSecurity.Vault.SharedFolderOptions
        if ($CanEdit.IsPresent) {
            $options.CanEdit = $true
        }
        if ($CanShare.IsPresent) {
            $options.CanShare = $true
        }
        if ($ManageUsers.IsPresent) {
            $options.ManageUsers = $true
        }
        if ($ManageRecords.IsPresent) {
            $options.ManageRecords = $true
        }

    }
    $vault.CreateFolder($Name, $parentUid, $options).GetAwaiter().GetResult()
}
New-Alias -Name kmkdir -Value Add-KeeperFolder

function Remove-KeeperFolder {
    <#
    .Synopsis
    Delete Keeper folder.

    .Parameter Name
    Folder name or Folder UID
#>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Name
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    
    $folder = resolveKeeperFolder -Identifier $Name -Vault $vault
    
    $vault.DeleteFolder($folder.FolderUid).GetAwaiter().GetResult() | Out-Null
}
New-Alias -Name krmdir -Value Remove-KeeperFolder

function tryResolveKeeperFolder {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)][string] $Identifier,
        [Parameter(Mandatory = $true)][KeeperSecurity.Vault.VaultOnline] $Vault,
        [Parameter()][switch] $SupportPaths
    )

    [KeeperSecurity.Vault.FolderNode]$folder = $null

    if ($Vault.TryGetFolder($Identifier, [ref]$folder)) {
        return $folder
    }

    if ($SupportPaths.IsPresent) {
        [KeeperSecurity.Vault.FolderNode]$currentDir = $null
        if (-not $Vault.TryGetFolder($Script:Context.CurrentFolder, [ref]$currentDir)) {
            $currentDir = $Vault.RootFolder
        }

        $components = splitKeeperPath $Identifier
        $rs = parseKeeperPath $components $Vault $currentDir
        if ($rs -is [array] -and -not $rs[1]) {
            return $rs[0]
        }
    }

    $objs = @(Get-KeeperChildItem -ObjectType Folder -Recursive | Where-Object Name -eq $Identifier)
    if ($objs.Count -eq 0) {
        return $null
    }
    if ($objs.Count -gt 1) {
        Write-Error -Message "There are more than one folders with name `"$Identifier`". Use Folder UID$(if ($SupportPaths.IsPresent) { ' or full path' }) to specify the correct one using UID" -ErrorAction Stop
    }

    $folderUid = $objs[0].Uid
    if ($Vault.TryGetFolder($folderUid, [ref]$folder)) {
        return $folder
    }

    return $null
}

function resolveKeeperFolder {
    <#
    .Synopsis
    Internal helper function to resolve a classic folder by UID, name, or path.
    Throws if the identifier is a nested shared folder (use NSF cmdlets) or not found.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)][string] $Identifier,
        [Parameter(Mandatory = $true)][KeeperSecurity.Vault.VaultOnline] $Vault,
        [Parameter()][switch] $SupportPaths
    )

    $folder = tryResolveKeeperFolder -Identifier $Identifier -Vault $Vault -SupportPaths:$SupportPaths
    if ($folder) {
        return $folder
    }

    $nsfFolder = resolveKeeperNSFFolder -Identifier $Identifier -Vault $Vault
    if ($nsfFolder) {
        Write-Error -Message "The specified identifier `"$Identifier`" corresponds to a Nested Shared Folder. Use Set-KeeperNSFFolder (nsf-rndir) to edit Nested Shared Folders." -ErrorAction Stop
    }

    Write-Error -Message "Folder `"$Identifier`" not found or not accessible" -ErrorAction Stop
}

function Get-KeeperFolders {
    <#
    .Synopsis
    List all folders in the Keeper vault.

    .Description
    Returns a list of all folders in the Keeper vault with their details including
    UID, Name, Type (UserFolder/SharedFolder), Parent folder, and counts of subfolders and records.
    
    .Parameter Filter
    Filter folders by name (supports wildcards: * and ?)

    .Parameter Type
    Filter by folder type: 'User', 'Shared', or 'All' (default: All)

    .Parameter IncludeRoot
    Include the root folder in the results

    .Parameter Verbose
    Show detailed information including full paths
    
    .Parameter AsObject
    Return the folders as objects instead of displaying formatted information

    .Parameter ClassicOnly
    Exclude nested shared (NSF) folders from KeeperDrive sync.

    .Example
    Get-KeeperFolders
    Lists all folders in the vault

    .Example
    Get-KeeperFolders -Filter "Engineering*"
    Lists all folders whose names start with "Engineering"

    .Example
    Get-KeeperFolders -Type Shared
    Lists only shared folders

    .Example
    Get-KeeperFolders -Verbose
    Lists all folders with detailed information including full paths
#>


    [CmdletBinding()]
    Param (
        [Parameter(Position = 0)][string] $Filter,
        [Parameter()][ValidateSet('All', 'User', 'Shared')]
        [string] $Type = 'All',
        [Parameter()][switch] $IncludeRoot,
        [Parameter()][switch] $AsObject,
        [Parameter()][switch] $ClassicOnly
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault

    $folders = [System.Collections.Generic.List[KeeperFolderListItem]]::new()

    if (-not $ClassicOnly.IsPresent -and $Type -eq 'All') {
        foreach ($nsfFolder in $vault.KeeperNSFFolderNodes) {
            if ($Filter -and -not ($nsfFolder.Name -like $Filter)) { continue }
            $item = [KeeperFolderListItem]::new()
            $item.FolderUid       = $nsfFolder.FolderUid
            $item.Name            = $nsfFolder.Name
            $item.FolderType      = 'folder'
            $item.Category        = 'Nested Shared Folder'
            $item.ParentUid       = $nsfFolder.ParentUid
            $item.SharedFolderUid = $null
            $item.SubfolderCount  = $nsfFolder.Subfolders.Count
            $item.RecordCount     = $nsfFolder.Records.Count
            $item.Path            = $null
            $folders.Add($item) | Out-Null
        }
    }

    foreach ($folder in $vault.Folders) {
        if ([string]::IsNullOrEmpty($folder.FolderUid) -and -not $IncludeRoot.IsPresent) {
            continue
        }
        
        if ($Type -ne 'All') {
            $isShared = $folder.FolderType -eq [KeeperSecurity.Vault.FolderType]::SharedFolder -or 
                        $folder.FolderType -eq [KeeperSecurity.Vault.FolderType]::SharedFolderFolder
            
            if ($Type -eq 'Shared' -and -not $isShared) {
                continue
            }
            if ($Type -eq 'User' -and $isShared) {
                continue
            }
        }
        
        if ($Filter) {
            if (-not ($folder.Name -like $Filter)) {
                continue
            }
        }
        
        $folderInfo = [KeeperFolderListItem]::new()
        $folderInfo.FolderUid       = $folder.FolderUid
        $folderInfo.Name            = $folder.Name
        $folderInfo.FolderType      = $folder.FolderType.ToString()
        $folderInfo.Category        = 'Classic'
        $folderInfo.ParentUid       = $folder.ParentUid
        $folderInfo.SharedFolderUid = $folder.SharedFolderUid
        $folderInfo.SubfolderCount  = $folder.Subfolders.Count
        $folderInfo.RecordCount     = $folder.Records.Count
        $folderInfo.Path            = if ($PSCmdlet.MyInvocation.BoundParameters['Verbose']) {
            getVaultFolderPath $vault $folder.FolderUid
        } else {
            $null
        }

        $folders.Add($folderInfo) | Out-Null
    }

    $folders = @($folders | Sort-Object Name)
    
    if ($folders.Count -eq 0) {
        Write-Host "No folders found matching criteria."
        return
    }
    
    if ($AsObject.IsPresent) {
        return $folders
    }

    Write-Host ""
    Write-Host "Found $($folders.Count) folder(s)" -ForegroundColor Green
    Write-Host ""
    
    if ($PSCmdlet.MyInvocation.BoundParameters['Verbose']) {
        $folders | Format-Table -Property @(
            @{Label='UID'; Expression={$_.FolderUid}; Width=25},
            @{Label='Name'; Expression={$_.Name}; Width=35},
            @{Label='Type'; Expression={$_.FolderType}; Width=20},
            @{Label='Category'; Expression={$_.Category}; Width=22},
            @{Label='Subfolders'; Expression={$_.SubfolderCount}; Width=10; Align='Right'},
            @{Label='Records'; Expression={$_.RecordCount}; Width=8; Align='Right'},
            @{Label='Path'; Expression={$_.Path}}
        ) -AutoSize
    } else {
        $folders | Format-Table -Property @(
            @{Label='UID'; Expression={$_.FolderUid}; Width=25},
            @{Label='Name'; Expression={$_.Name}; Width=35},
            @{Label='Type'; Expression={$_.FolderType}; Width=20},
            @{Label='Category'; Expression={$_.Category}; Width=22},
            @{Label='Subfolders'; Expression={$_.SubfolderCount}; Width=10; Align='Right'},
            @{Label='Records'; Expression={$_.RecordCount}; Width=8; Align='Right'}
        ) -AutoSize
    }


}
New-Alias -Name kfolders -Value Get-KeeperFolders

function Get-KeeperFolder {
    <#
    .Synopsis
    Get detailed information about a Keeper folder.

    .Parameter Uid
    Folder UID, Name, or Path

    .Parameter AsObject
    Return the folder object instead of displaying formatted information
#>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Uid,
        [Parameter()][switch] $AsObject
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault

    $folder = tryResolveKeeperFolder -Identifier $Uid -Vault $vault -SupportPaths
    if (-not $folder) {
        $nsfFolder = resolveKeeperNSFFolder -Identifier $Uid -Vault $vault
        if ($nsfFolder) {
            if ($AsObject.IsPresent) { return $nsfFolder }
            Get-KeeperNSFRecord -Uid $Uid
            return
        }
        Write-Error -Message "Folder `"$Uid`" not found or not accessible" -ErrorAction Stop
    }

    if ($AsObject.IsPresent) {
        return $folder
    }

    Write-Host ""
    Write-Host (" {0,-20}: {1}" -f "Folder UID", $folder.FolderUid)
    
    if ($folder.ParentUid) {
        Write-Host (" {0,-20}: {1}" -f "Parent Folder UID", $folder.ParentUid)
    }
    
    Write-Host (" {0,-20}: {1}" -f "Folder Type", $folder.FolderType)
    Write-Host (" {0,-20}: {1}" -f "Name", $folder.Name)
    
    if ($folder.SharedFolderUid) {
        Write-Host (" {0,-20}: {1}" -f "Shared Folder UID", $folder.SharedFolderUid)
    }
    
    $path = getVaultFolderPath $vault $folder.FolderUid
    Write-Host (" {0,-20}: {1}" -f "Full Path", $path)
    
    $subfolderCount = $folder.Subfolders.Count
    $recordCount = $folder.Records.Count
    
    Write-Host (" {0,-20}: {1}" -f "Subfolders", $subfolderCount)
    Write-Host (" {0,-20}: {1}" -f "Records", $recordCount)
    
    Write-Host ""
}
New-Alias -Name kgetfolder -Value Get-KeeperFolder

function Edit-KeeperFolder {
    <#
    .Synopsis
    Edits a Keeper folder.

    .Parameter Uid
    Folder UID or Name
    
    .Parameter Name
    Folder new name

    .Parameter CanEdit
    Anyone can edit records by default (Shared Folder only)

    .Parameter CanShare
    Anyone can share records by default (Shared Folder only)

    .Parameter ManageUsers
    Anyone can manage users by default (Shared Folder only)

    .Parameter ManageRecords
    Anyone can manage records by default (Shared Folder only)
 
#>


    [CmdletBinding(DefaultParameterSetName = 'Default')]
    Param (
        [Parameter(Position=0, Mandatory = $true)] [string] $Uid,
        [Parameter()][string] $Name,
        [Parameter()][switch] $CanEdit,
        [Parameter()][switch] $CanShare,
        [Parameter()][switch] $ManageUsers,
        [Parameter()][switch] $ManageRecords
    )

    [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    
    $folder = resolveKeeperFolder -Identifier $Uid -Vault $vault

    $options = $null
    if ($CanEdit.IsPresent -or $CanShare.IsPresent -or $ManageUsers.IsPresent -or $ManageRecords.IsPresent) {
        $options = New-Object KeeperSecurity.Vault.SharedFolderOptions
        if ($CanEdit.IsPresent) {
            $options.CanEdit = $true
        }
        if ($CanShare.IsPresent) {
            $options.CanShare = $true
        }
        if ($ManageUsers.IsPresent) {
            $options.ManageUsers = $true
        }
        if ($ManageRecords.IsPresent) {
            $options.ManageRecords = $true
        }
    }
    
    $vault.UpdateFolder($folder.FolderUid, $Name, $options).GetAwaiter().GetResult()
}

# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAtX6SUlqFXkdUT
# I8FcET6C+UMBo+tQKg5P1Y4EVPLtMKCCITswggWNMIIEdaADAgECAhAOmxiO+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
# oAMCAQICEAHdzU+FVN9jCMv0HhHagNUwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy
# dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB
# MTAeFw0yNjA2MDUwMDAwMDBaFw0yNzA2MDQyMzU5NTlaMIHRMRMwEQYLKwYBBAGC
# NzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMR0wGwYDVQQPDBRQ
# cml2YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQwNzk4NTELMAkGA1UEBhMC
# VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK
# ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5
# IEluYy4wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCb4DRTV0sNQsa1
# 0YRh+bliabmLOVYr6S0+BSVvRJAN3SHP6x52i1Dkpki5xVDIH06ZnnsToVrgvTv+
# QxGwsn9SAPHEZ/PIJRFxbMR4ShDaptYyL4f0u4k/3HwRzIleWE4mTUonYH8BdgLw
# /F53B7wa7VTDHtxXltYTibEOwJxYCOi4Zr2FYQhjw14/CHcqS3FSMs6YYU2T56+g
# w819hQM3K0YlwTNOFoIm1v7/ZZZiJGH8uGDsvy1makh1Xyyo/wN8EbQ1nbslmePT
# roPm9w7WqiP/yiq+CZHiuTk9JK5bEgkWG3ns+v25cI251WidJx3SU7IZnX0OTd6/
# ZdKhprD5Gcfy5GBbJdcYw2WycQRW0PT5BEt55xRE0heufkpDaTUN6RdOuJdXbkl0
# hV91IZIuhueEMCk3h5mDTlU5gImxqj0R/TbAxjSSGTKCeuYFkQIRqytSabdrZZ48
# kW5hOIZMVDY1f4kpPJa8UeEvDZXT3vrtj36aSJrwez2uh4FMNlkCAwEAAaOCAgIw
# ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBT1
# SmCYU/7Yrz1fX66Ur5nSzlSYOzA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB
# BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC
# B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p
# bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT
# QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA
# A4ICAQBcavcUHNFEg872HDRq2+hRlnvaghCXv7X/6h9HSzjAQP3rt95BZty3ASqi
# 2MYyGQLGdDl4DToe/WhajtEOBOYa83agW6tBvrfcKRrDrwJOMPTbwNYvn+GuiL4T
# CKzXaytWiJJbrc5odc7Ecat2ZvJylpPmNainr4Q0LzzH23Gea/Mm/hIJTN4IGgrH
# hrXiTIIW/ZUzrY6g8b3RZB4BA497n43wNdSqP+C3ntFw6NiGB4Z25SW4YntIxYPv
# Kf37OVhF0xqxLC1sK/XxgK0EGQ6iaj8Ncpr2C5vSNZqfW2MndxOA1W67pgDpg83k
# UWG+/YJeGhqOTF82/0kIzQXeI/lIqbnL/IJAJqSm/ROSpsGUKVbzk03cpTD55ZQX
# WjM0fLirypBqY05T8gnh1L0fSwxr/SwJZ8OddivgyK1YOMn02nnsEG5kxBt9cMX4
# JCYABhypmAVDRvyYifEVdoFWv2gAXXW+PPRvlNa6E4aMCZrVcoKHiyeMAXOi1IC9
# mHvC2+foTSMFueq3AdnYfeKnZnAiKXKRhXcdHbQYcR2A7AIzIcqahPYr4FNEgb/E
# /y/kypAkf0rMHlYl1kNqLs2Nv1UnMEHYT5YmDVLO63+1Trcw4zTZ70zuqIqeID/d
# nbOlgtyG6DSRCL7f0E7kP18f4RoX5i1PkfeO4VJHsAuCeNG1qjGCBdkwggXVAgEB
# MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD
# VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI
# QTM4NCAyMDIxIENBMQIQAd3NT4VU32MIy/QeEdqA1TANBglghkgBZQMEAgEFAKCB
# hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ
# BDEiBCC3kJBixaltKsAR3mkiGYrXnNCmZcKBfsgkOdC0B4fEtDANBgkqhkiG9w0B
# AQEFAASCAYCT2XB3zWrr2kXWwp68jW7dWEnAGkb2yrVWFzhn/z9ETDZswjBQmzyy
# iBrxW0E/QmQDqbn5gJw90ol5cSFAeWFXA1Bi6J9KNEdFGxk9yWlQ4w5LJWFZ3G4A
# ATtaPHNMzC+Cg+R4jtjYJTe+z3hqkEvbYzXR4DaRugYXv6Kix8nQNQ7JM1yoUnQx
# 3ShoxWdGfGgxTOKkI2HwxAz5YWpBzzu9gRqhj2CtogH8PebrLXDNl/UilNOdJjOa
# hKNVc6C4y9XUlhk3+SOZBv+f0FPay9gxgmURvS8WR2urVsVy8o6JaGiflJL3+aga
# lXPwRqSViG/y+JWJsFPXte3IMBIvVSlzJA3MOMvNAbwLIg6t+EwALETHBq+IHse0
# srGx9LkTMxYpkrlrctxK64/MSJSQ2T6C+kK6bsDGbp6P0RdY2qs7NhJZG5vbxJ7Z
# jc7XPk8NGYmmh2HIk9V/HJkSo5f8JnStnO+MKNA0r6nlPE/DQdwRdv1CrH14tMxb
# JxSqqZ9UelmhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg
# Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN
# AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNjEzMDA1NjI5WjAv
# BgkqhkiG9w0BCQQxIgQg59eRx7lvkSd5fxiZm0HZl4MNtkxuvem2n15hZyHfEoAw
# DQYJKoZIhvcNAQEBBQAEggIAYKXlIb2ZeUO3kf3Dul5jwd7zv8AmNLn5XbNH3go7
# 1AowFWDy9wDkncqEh6vSk3IPGwqod9X0A1QfiB4W8D4MZNR3wftUJGTrBB8kLfgx
# GdDkLPiU+wwWE/FrUkZKoOrCGGYHnd3bUkbq/3ssVT4kg2r200TLG50JJZ2F6hrj
# ZxP6Y18oMa5QPUWEsmx9oJ7jkvoayjOP71oX2FxGVWNvPyW6UrUGtKkEhlPzy/11
# 2eCauXKxtnKrQE6u7pBmd8VrDyr/qPVcB8WsQ8HQ2jirNaD6EfKDUyOOqRJ6mrbz
# MZV8EUi55gMY3btg/mH2DruBkiKye68qjIQI2DH8hrNoU0v+VqKvhFt6wFzNvgDM
# Shn8a12I5P0oDm/3vEhkuMlIVrGW8sqTze5bJQFktY9rVA7tz72JFNvzNiPuCNmp
# jvXpDyGTRDzRFx76YHcwoKbeBYxLnFHVySdhdejTx9W6x9HxC5thADYFKQxrnXB5
# 8H/N+sPynhEgJYs48eBgEwYeowxvuA4lnY5Hd+Msl1yWxxkrjnsFO3X+jdxOMNzE
# KA+sGxNYnM+VCtjXbyh7Rv7Xja9JqFLMsSxI57SUHTF/UtoLxzbCmTjlukOmwN2L
# HSDYNswLT3K1SSvEq1/hw9ptrKFgsauRjSBHU4e1qARZoS9qt8V8wqtynts2bocS
# OZU=
# SIG # End signature block