NsfFolderCommands.ps1

#requires -Version 5.1

function New-KeeperNSFFolder {
    <#
    .Synopsis
    Creates a new Keeper NSF folder.

    .Description
    Creates a new folder in Keeper NSF using the v3 API.

    .Parameter Name
    Name of the folder to create.

    .Parameter ParentFolderUid
    UID of the parent folder. If omitted, the folder is created at root level.

    .Parameter Color
    Optional color for the folder.

    .Parameter NoInheritPermissions
    If specified, the folder will not inherit permissions from its parent.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, Mandatory = $true)]
        [string] $Name,

        [Parameter()]
        [string] $ParentFolderUid,

        [Parameter()]
        [string] $Color,

        [Parameter()]
        [switch] $NoInheritPermissions
    )

    try{
        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    } catch {
        Write-Host "Error getting vault: $($_.Exception.Message)" -ForegroundColor Red
        return
    }

    $inheritPermissions = -not $NoInheritPermissions.IsPresent

    try {
        $folderUid = $vault.CreateKeeperNSFFolder($Name, $ParentFolderUid, $Color, $inheritPermissions).GetAwaiter().GetResult()
        Write-Host "Folder '$Name' created successfully (UID: $folderUid)." -ForegroundColor Green
        return $folderUid
    }
    catch {
        Write-Host "Error creating folder: $($_.Exception.Message)" -ForegroundColor Red
    }
}

New-Alias -Name nsf-mkdir -Value New-KeeperNSFFolder

function Set-KeeperNSFFolderAccess {
    <#
    .Synopsis
    Grant or revoke user access to a Keeper NSF folder.

    .Description
    Changes the sharing permissions of a Keeper NSF folder using the v3 API.
    Supports granting access with a specified role, or revoking access entirely.

    .Parameter FolderUid
    UID of the folder to share.

    .Parameter Action
    Action to perform: 'grant' (default) or 'remove'.

    .Parameter Email
    One or more user email addresses to grant/revoke access.

    .Parameter Role
    Access role for grant action: viewer (default), share-manager, content-manager,
    content-share-manager, full-manager.

    .Parameter ExpireIn
    Optional. Share expiration period from now (e.g. 30d, 6mo, 1y, 24h, 30mi), integer minutes,
    or a TimeSpan. Same as Grant-KeeperRecordAccess.

    .Parameter ExpireAt
    Optional. Absolute share expiration as ISO datetime (e.g. 2027-01-01T00:00:00Z).
#>

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

        [Parameter()]
        [ValidateSet('grant', 'remove')]
        [string] $Action = 'grant',

        [Parameter(Mandatory = $true)]
        [string[]] $Email,

        [Parameter()]
        [ValidateSet('viewer', 'share-manager', 'content-manager', 'content-share-manager', 'full-manager')]
        [string] $Role = 'viewer',

        [Alias('expire-in')]
        [Parameter()]
        [System.Object] $ExpireIn,

        [Alias('expire-at')]
        [Parameter()]
        [string] $ExpireAt
    )

    try {
        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    } catch {
        Write-Host "Error getting vault: $($_.Exception.Message)" -ForegroundColor Red
        return
    }

    [KeeperSecurity.Vault.FolderNode]$tmpFolder = $null
    if (-not $vault.TryGetKeeperNSFFolder($FolderUid, [ref]$tmpFolder)) {
        Write-Host "Error: NSF folder '$FolderUid' not found." -ForegroundColor Red
        return
    }

    $shareOptions = $null
    if ($Action -eq 'grant' -and ($ExpireIn -or $ExpireAt)) {
        try {
            $expirationDto = Get-ExpirationDate -ExpireIn $ExpireIn -ExpireAt $ExpireAt
        }
        catch {
            Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
            return
        }
        $shareOptions = New-Object KeeperSecurity.Vault.SharedFolderUserOptions
        $shareOptions.Expiration = $expirationDto
    }

    foreach ($user in $Email) {
        try {
            if ($Action -eq 'grant') {
                [void]$vault.GrantKeeperNSFFolderAccess($FolderUid, $user, $Role, $shareOptions).GetAwaiter().GetResult()
                $expireMsg = if ($shareOptions -and $shareOptions.Expiration) {
                    " (expires $($shareOptions.Expiration.LocalDateTime.ToString('g')))"
                } else { '' }
                Write-Host "Granted '$Role' access to '$user' on folder '$FolderUid'$expireMsg." -ForegroundColor Green
            }
            else {
                [void]$vault.RevokeKeeperNSFFolderAccess($FolderUid, $user).GetAwaiter().GetResult()
                Write-Host "Revoked access for '$user' from folder '$FolderUid'." -ForegroundColor Green
            }
        }
        catch {
            Write-Host "Error ${Action}ing access for '$user': $($_.Exception.Message)" -ForegroundColor Red
        }
    }
}

New-Alias -Name nsf-share-folder -Value Set-KeeperNSFFolderAccess

function Write-KeeperNSFRemoveImpact {
    param(
        [Folder.V3.Remove.RemoveResponse]$Response,
        [string] $ItemLabel = 'Record'
    )

    if ($Response.ErrorMessage) {
        Write-Host "Error: $($Response.ErrorMessage)" -ForegroundColor Red
    }

    foreach ($result in $Response.Results) {
        $recordUid = if ($result.ItemUid.Length -gt 0) {
            [KeeperSecurity.Utils.CryptoUtils]::Base64UrlEncode($result.ItemUid.ToByteArray())
        } else { '(unknown)' }

        $folderUid = if ($result.FolderUid.Length -gt 0) {
            [KeeperSecurity.Utils.CryptoUtils]::Base64UrlEncode($result.FolderUid.ToByteArray())
        } else { '' }

        Write-Host ""
        Write-Host "${ItemLabel}: $recordUid" -ForegroundColor Cyan
        if ($folderUid) {
            Write-Host " Folder context: $folderUid"
        }
        Write-Host " Status: $($result.Status)"

        if ($result.Error -and $result.Error.Message) {
            Write-Host " Error: $($result.Error.Message)" -ForegroundColor Red
        }

        if ($result.Impact) {
            $impact = $result.Impact
            Write-Host " Impact:"
            Write-Host " Folders: $($impact.FoldersCount)"
            Write-Host " Records: $($impact.RecordsCount)"
            Write-Host " Affected users: $($impact.AffectedUsersCount)"
            Write-Host " Affected teams: $($impact.AffectedTeamsCount)"
            if ($impact.RecordInfo) {
                Write-Host " Other locations: $($impact.RecordInfo.LocationsCount)"
            }
            foreach ($warning in $impact.Warnings) {
                Write-Host " Warning: $warning" -ForegroundColor Yellow
            }
        }
    }
}
function Set-KeeperNSFFolder {
    <#
    .Synopsis
    Renames or recolors a Keeper NSF folder (Keeper NSF v3 API).

    .Parameter Folder
    Folder UID or name.

    .Parameter Name
    New folder name.

    .Parameter Color
    Optional folder color, or "none" to clear.
#>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
    Param(
        [Parameter(Position = 0, Mandatory = $true)]
        [string] $Folder,

        [Alias('n')]
        [Parameter(ParameterSetName = 'Default')]
        [string] $Name,

        [ValidateSet('none', 'red', 'orange', 'yellow', 'green', 'blue', 'gray', 'grey')]
        [string] $Color
    )

    if (-not $Name -and -not $PSBoundParameters.ContainsKey('Color')) {
        Write-Error -Message "Specify -Name and/or -Color to update the folder."
        return
    }
    
    try {
        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
    } catch {
        Write-Error -Message "Error getting vault: $($_.Exception.Message)"
        return
    }

    [KeeperSecurity.Vault.FolderNode]$folderNode = $null
    if (-not $vault.TryResolveKeeperNSFFolder($Folder, [ref]$folderNode)) {
        Write-Error -Message "Keeper NSF folder `"$Folder`" was not found. Run Sync-Keeper or nsf-list first."
        return
    }

    $target = "$($folderNode.Name) ($($folderNode.FolderUid))"
    if (-not $PSCmdlet.ShouldProcess($target, "Update Keeper NSF folder")) {
        return
    }

    $nameArg = if ($PSBoundParameters.ContainsKey('Name')) { $Name } else { [NullString]::Value }
    $colorArg = if ($PSBoundParameters.ContainsKey('Color')) { $Color } else { [NullString]::Value }

    try {
        $result = $vault.UpdateKeeperNSFFolder($folderNode.FolderUid, $nameArg, $colorArg).GetAwaiter().GetResult()
        [KeeperSecurity.Vault.VaultOnline]::ValidateFolderModifyResult($result)
    }
    catch {
        Write-Error -Message $_.Exception.Message
        return
    }

    $vault.SyncDown($false).GetAwaiter().GetResult() | Out-Null
    Write-Host "Keeper NSF folder updated." -ForegroundColor Green
}
New-Alias -Name nsf-rndir -Value Set-KeeperNSFFolder

function Remove-KeeperNSFFolder {
    <#
    .Synopsis
    Removes one or more Keeper NSF folders (Keeper NSF v3 API).

    .Parameter Folder
    One or more folder UIDs or names.

    .Parameter Operation
    folder-trash (default, recoverable) or delete-permanent (irreversible).

    .Parameter Force
    Skip confirmation after preview.

    .Parameter DryRun
    Preview only; do not remove folders.
#>

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

        [Alias('o')]
        [ValidateSet('folder-trash', 'delete-permanent')]
        [string] $Operation = 'folder-trash',

        [Alias('f')]
        [switch] $Force,

        [switch] $DryRun
    )

    begin {
        [KeeperSecurity.Vault.VaultOnline]$vault = getVault
        $removals = New-Object 'System.Collections.Generic.List[KeeperSecurity.Vault.KeeperNSFFolderRemoval]'

        $op = switch ($Operation) {
            'folder-trash' { [KeeperSecurity.Vault.KeeperNSFFolderRemoveOperation]::FolderTrash }
            'delete-permanent' { [KeeperSecurity.Vault.KeeperNSFFolderRemoveOperation]::DeletePermanent }
        }
    }

    process {
        foreach ($name in $Folder) {
            [KeeperSecurity.Vault.FolderNode]$folderNode = $null
            if (-not $vault.TryResolveKeeperNSFFolder($name, [ref]$folderNode)) {
                Write-Error -Message "Keeper NSF folder `"$name`" was not found. Run Sync-Keeper or nsf-list first."
                continue
            }

            $removal = New-Object KeeperSecurity.Vault.KeeperNSFFolderRemoval
            $removal.FolderUid = $folderNode.FolderUid
            $removal.Operation = $op
            $removals.Add($removal)
        }
    }

    end {
        if ($removals.Count -eq 0) {
            return
        }

        if ($Operation -eq 'delete-permanent' -and -not $Force -and -not $DryRun) {
            Write-Host ""
            Write-Host "*** WARNING ***" -ForegroundColor Red
            Write-Host " delete-permanent is IRREVERSIBLE."
            Write-Host " All sub-folders and records inside will be permanently destroyed."
        }

        Write-Host ""
        Write-Host "=== Keeper NSF Folder Remove Preview ===" -ForegroundColor Cyan
        $previewResult = $vault.RemoveKeeperNSFFolders($removals, $true).GetAwaiter().GetResult()
        Write-KeeperNSFRemoveImpact -Response $previewResult.PreviewResponse -ItemLabel 'Folder'

        try {
            [KeeperSecurity.Vault.VaultOnline]::ValidateRemoveResponse($previewResult.PreviewResponse, $false)
        }
        catch {
            Write-Error -Message $_.Exception.Message
            return
        }

        if ($DryRun) {
            Write-Host ""
            Write-Host "Dry run: no folders were removed." -ForegroundColor DarkYellow
            return
        }

        if (-not $Force) {
            $prompt = if ($Operation -eq 'delete-permanent') {
                "Are you sure you want to permanently delete the folder(s) above? This action cannot be undone. (yes/No)"
            } else {
                "Are you sure you want to remove the folder(s) above? (yes/No)"
            }
            $confirmation = Read-Host $prompt
            if ($confirmation -notmatch '^(y|yes)$') {
                Write-Host "Remove operation cancelled"
                return
            }
        }

        if ($previewResult.PreviewResponse.ConfirmationToken.IsEmpty) {
            Write-Error -Message "Preview did not return a confirmation token."
            return
        }

        Write-Host ""
        Write-Host "Removing folders..." -ForegroundColor Cyan
        $confirmResult = $vault.RemoveKeeperNSFFolders($removals, $false).GetAwaiter().GetResult()
        if (-not $confirmResult.Confirmed) {
            Write-Error -Message "Folder removal was not confirmed by the server."
            return
        }

        $vault.SyncDown($false).GetAwaiter().GetResult() | Out-Null
        Write-Host ""
        Write-Host "Keeper NSF folder removal completed." -ForegroundColor Green
    }
}
New-Alias -Name nsf-rmdir -Value Remove-KeeperNSFFolder

# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC0fc3To1aTccUI
# ii1Unh57/3OvCM28iPOASvfmefOgm6CCITswggWNMIIEdaADAgECAhAOmxiO+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
# BDEiBCB4SwrunVkqw4WAY+axbKxYWG8pvY8x8E8c6uows5+yZDANBgkqhkiG9w0B
# AQEFAASCAYB/uxv+sQbtPkHQDzxSijncDik3LqVzwMpSSstc+eBn3WihmF91t/CP
# jVBMkdM+fT3RiYAGZIA4bIRqD+ZvNL6gIPZROmeMJ/+TYOGFEyLoUwtD1FVaATS4
# S74P9C/e3sXBkcVHT9MR/4KcsG7mZsnrI88ROy2sByOKAEExdzCr5XjNmcyJULT1
# UWxh1+Zo2BXVj4lWvouOS9q6scrbLO32d0PfOdczxPTy0hgPBEg1M1B70FMQw7Be
# QV8KdGbS8Fa2A3Bg1Eg9F59FOyKlhCs1cmZ0cBiVGiJEQDCUw/bdI4vhjoLkYXCd
# f4EvtpZ8TSYhRCh5TwE2e3xFtuPM/jnzGgSV7HxlPhQnqoRLI2iJTmcQLBrHHTxC
# A4qhsqdkmT/TcRX9tVO+FKcuyB3NjHC/minKn+7RTcjytpQzHOoO6A98VRaOHE4Y
# uyPwaY1wDatWUS7qL6Kb4NXYLWuO/+U6rfmA7JfA9HhAMsvuxYoOkc4K1KsR68TV
# GQ1SVQbDzgWhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg
# Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN
# AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNjEzMDA1NjMxWjAv
# BgkqhkiG9w0BCQQxIgQgC7KKJ1twToMDv7TUaBeOK9rYwdrue3i8iCqjTwsPgi0w
# DQYJKoZIhvcNAQEBBQAEggIANTdL2s+BUzg61lYmhPQJFOWqtN9QeKZvHupP2qEH
# bM23tMS2Ftqy90MZJ/swaAZvwhCrTbKHLyUrhc4yxwFzDr7otrV2YJO4HIlwpwT+
# aIxhvzlIO8EFIf5tjl57cB5Hni3udob1+NRx1FI4zFxPKBjFtbv71cgAYKMVhW+6
# lVII5qvpg3yucZHme0ST7B3JGTXnDHGdHqp5Mwhx80r9/+lZQsloCpd3n3ZHVgvp
# daOQBrqXRlXxvnCRVkbOVCQnsJzoJa200zzZhsbsqCssH94oO0Ijlu/KSEjXTeAl
# ua9RWKEc+LBBmbvs03ZX9wT0LijY1UiJ6PkmUG6OvkD8T7k/aag2gGGpJzSESqAP
# P6agKtHsIs55UIO8Q8QJtRdm5TXLe4X31RZs+LzvSXtUP+NbNpEBuqfQ/2SHesKE
# ETfK+fmM04viqgpGbWcFc/sjinXwXZPgQaBeKu6gBObce1VtHJhBW6tgSWzTdFIe
# 8hkn/7hSuyAMU5B3CCRj5+K296K0MdUwD8wz3nHNUKYjom/HCN8Z47QQX5hN6w4j
# IR9K5W6tFsQ6oB8CIRwC3ymTwUlTqntdRTYTdTIdM+yv4xBvP5XJDa+JAeLXw6ET
# Ic/kBY7ksc6ssfqGaXxLWuQu52m1DpOmLiqXycaRQ6XoF9nD+tjpvB1YFxe3om2j
# 1CM=
# SIG # End signature block