EnterpriseNode.ps1

function New-KeeperEnterpriseNode {
    <#
    .SYNOPSIS
    Creates Enterprise Node

    .PARAMETER ParentNode
    Parent Node name or ID

    .PARAMETER NodeName
    Node name
    #>

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

    [Enterprise]$enterprise = getEnterprise

    [KeeperSecurity.Enterprise.EnterpriseNode] $parent = $null
    if ($ParentNode) {
        $parent = resolveSingleNode $ParentNode
    } else {
        $parent = $enterprise.enterpriseData.RootNode
    }

    $n = [KeeperSecurity.Enterprise.EnterpriseExtensions]::CreateNode($enterprise.enterpriseData, $NodeName, $parent).GetAwaiter().GetResult()
    Write-Information "Added node `"$($n.DisplayName)`""
}
New-Alias -Name kena -Value New-KeeperEnterpriseNode

function Edit-KeeperEnterpriseNode {
    <#
    .SYNOPSIS
    Updates an existing Enterprise Node

    .PARAMETER Node
    Node name or ID to update

    .PARAMETER NewNodeName
    New name for the node

    .PARAMETER ParentNode
    New parent Node name or ID (to move the node)

    .PARAMETER RestrictVisibility
    Enable node isolation (restricts visibility to users outside the node)

    .EXAMPLE
    Edit-KeeperEnterpriseNode -Node "OldName" -NewNodeName "NewName"
    Renames a node

    .EXAMPLE
    Edit-KeeperEnterpriseNode -Node "TestNode" -ParentNode "NewParent"
    Moves a node to a different parent

    .EXAMPLE
    kenu "SecureNode" -RestrictVisibility
    Enables node isolation for a node
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Node,
        [Parameter()][string] $NewNodeName,
        [Parameter()][string] $ParentNode,
        [Parameter()][Switch] $RestrictVisibility
    )

    [Enterprise]$enterprise = getEnterprise

    $nodeToUpdate = resolveSingleNode $Node
    if (-not $nodeToUpdate) {
        Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop
    }

    [KeeperSecurity.Enterprise.EnterpriseNode] $parent = $null
    if ($ParentNode) {
        $parent = resolveSingleNode $ParentNode
    } else {
        if ($nodeToUpdate.ParentNodeId -gt 0) {
            [KeeperSecurity.Enterprise.EnterpriseNode] $existingParent = $null
            if ($enterprise.enterpriseData.TryGetNode($nodeToUpdate.ParentNodeId, [ref]$existingParent)) {
                $parent = $existingParent
            } else {
                $parent = $enterprise.enterpriseData.RootNode
            }
        } else {
            $parent = $enterprise.enterpriseData.RootNode
        }
    }

    $hasChanges = $false
    if (-not [string]::IsNullOrEmpty($NewNodeName)) {
        $nodeToUpdate.DisplayName = $NewNodeName
        $hasChanges = $true
    }

    if ($hasChanges -or $ParentNode -or $RestrictVisibility.IsPresent) {
        if ($hasChanges -or $ParentNode) {
            try {
                [KeeperSecurity.Enterprise.EnterpriseExtensions]::UpdateNode($enterprise.enterpriseData, $nodeToUpdate, $parent).GetAwaiter().GetResult() | Out-Null
                Write-Output "Node `"$($nodeToUpdate.DisplayName)`" updated."
            }
            catch {
                Write-Error -Message "Failed to update node `"$($nodeToUpdate.DisplayName)`": $($_.Exception.Message)" -ErrorAction Stop
            }
        }

        if ($RestrictVisibility.IsPresent) {
            try {
                [KeeperSecurity.Enterprise.EnterpriseExtensions]::SetRestrictVisibility($enterprise.enterpriseData, $nodeToUpdate.Id).GetAwaiter().GetResult() | Out-Null
                $enterprise.loader.Load().GetAwaiter().GetResult() | Out-Null 
                
                $nodeToUpdate = resolveSingleNode $nodeToUpdate.Id
                $isolationStatus = if ($nodeToUpdate.RestrictVisibility) { 'ON' } else { 'OFF' }
                Write-Output "Node Isolation: $isolationStatus"
            }
            catch {
                Write-Error -Message "Failed to set node isolation for `"$($nodeToUpdate.DisplayName)`": $($_.Exception.Message)" -ErrorAction Stop
            }
        }
    } else {
        Write-Warning "No changes specified. Use -NewNodeName, -ParentNode, or -RestrictVisibility to update the node."
    }
}
New-Alias -Name kenu -Value Edit-KeeperEnterpriseNode

function Remove-KeeperEnterpriseNode {
    <#
    .SYNOPSIS
    Deletes an existing Enterprise Node

    .PARAMETER Node
    Node name or ID to delete

    .PARAMETER Force
    Skip confirmation prompt

    .DESCRIPTION
    Permanently deletes an enterprise node. The node must be empty (no users, teams, or sub-nodes) before it can be deleted.
    This operation cannot be undone.

    .EXAMPLE
    Remove-KeeperEnterpriseNode -Node "TestNode"
    Deletes a node with confirmation prompt

    .EXAMPLE
    Remove-KeeperEnterpriseNode -Node "TestNode" -Force
    Deletes a node without confirmation

    .EXAMPLE
    kend "OldNode" -Force
    Uses the alias to delete a node without confirmation
    #>

    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Node,
        [Parameter()][Switch] $Force
    )

    [Enterprise]$enterprise = getEnterprise

    $nodeToDelete = resolveSingleNode $Node
    if (-not $nodeToDelete) {
        Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop
    }

    if ($nodeToDelete.Id -eq $enterprise.enterpriseData.RootNode.Id) {
        Write-Error -Message "Cannot delete the root node" -ErrorAction Stop
    }

    $nodeName = $nodeToDelete.DisplayName
    
    if ($Force -or $PSCmdlet.ShouldProcess($nodeName, "Delete Enterprise Node")) {
        try {
            [KeeperSecurity.Enterprise.EnterpriseExtensions]::DeleteNode($enterprise.enterpriseData, $nodeToDelete.Id).GetAwaiter().GetResult() | Out-Null
            Write-Output "Node `"$nodeName`" deleted successfully."
        }
        catch {
            Write-Error -Message "Failed to delete node `"$nodeName`": $($_.Exception.Message)" -ErrorAction Stop
        }
    }
    else {
        Write-Output "Node deletion cancelled."
    }
}
New-Alias -Name kend -Value Remove-KeeperEnterpriseNode

function Set-KeeperEnterpriseNodeCustomInvitation {
    <#
    .SYNOPSIS
    Sets a custom invitation template for an Enterprise Node

    .PARAMETER Node
    Node name or ID

    .PARAMETER JsonFilePath
    Path to JSON file containing invitation template (subject, header, body, buttonLabel)

    .DESCRIPTION
    Sets a custom invitation template for an enterprise node from a JSON file.
    The JSON file should contain the following properties:
    - "subject" : Email subject line
    - "header" : Header text for the invitation
    - "body" : Body text for the invitation
    - "buttonLabel" : Label for the action button
    -Example:
    {
        "subject": "You're Invited to Join",
        "header": "Welcome to Our Portal",
        "body": "Click the button below to create your Keeper account and start protecting your passwords.",
        "buttonLabel": "Create Account"
    }

    .EXAMPLE
    Set-KeeperEnterpriseNodeCustomInvitation -Node "Sales" -JsonFilePath "C:\invitation.json"
    Sets custom invitation template for the Sales node

    .EXAMPLE
    Set-KeeperNodeCustomInvitation "Marketing" "C:\templates\marketing-invite.json"
    Uses the alias to set custom invitation template
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Node,
        [Parameter(Position = 1, Mandatory = $true)][string] $JsonFilePath
    )

    [Enterprise]$enterprise = getEnterprise

    $targetNode = resolveSingleNode $Node
    if (-not $targetNode) {
        Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop
    }

    if (-not (Test-Path -Path $JsonFilePath -PathType Leaf)) {
        Write-Error -Message "JSON file not found: $JsonFilePath" -ErrorAction Stop
    }

    try {
        [KeeperSecurity.Enterprise.EnterpriseExtensions]::SetEnterpriseCustomInvitation($enterprise.enterpriseData, $targetNode.Id, $JsonFilePath).GetAwaiter().GetResult() | Out-Null
        Write-Output "Custom invitation set for node `"$($targetNode.DisplayName)`""
    }
    catch {
        Write-Error -Message "Failed to set custom invitation for node `"$($targetNode.DisplayName)`": $($_.Exception.Message)" -ErrorAction Stop
    }
}
New-Alias -Name Set-KeeperNodeCustomInvitation -Value Set-KeeperEnterpriseNodeCustomInvitation

function Get-KeeperEnterpriseNodeCustomInvitation {
    <#
    .SYNOPSIS
    Gets the custom invitation template for an Enterprise Node

    .PARAMETER Node
    Node name or ID

    .DESCRIPTION
    Retrieves the custom invitation template configured for an enterprise node.
    Returns an object with Subject, Header, Body, and ButtonLabel properties.

    .EXAMPLE
    Get-KeeperEnterpriseNodeCustomInvitation -Node "Sales"
    Gets the custom invitation template for the Sales node

    .EXAMPLE
    Get-KeeperNodeCustomInvitation "Marketing"
    Uses the alias to get custom invitation template

    .EXAMPLE
    $invitation = Get-KeeperEnterpriseNodeCustomInvitation -Node "Sales"
    $invitation.Subject
    Retrieves and displays the invitation subject
    #>

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

    [Enterprise]$enterprise = getEnterprise

    $targetNode = resolveSingleNode $Node
    if (-not $targetNode) {
        Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop
    }

    try {
        $invitation = [KeeperSecurity.Enterprise.EnterpriseExtensions]::GetEnterpriseCustomInvitation($enterprise.enterpriseData, $targetNode.Id).GetAwaiter().GetResult()
        
        Write-Host "Custom invitation for node `"$($targetNode.DisplayName)`":"
        Write-Host "Subject: $($invitation.Subject)"
        Write-Host "Header: $($invitation.Header)"
        Write-Host "Body: $($invitation.Body)"
        Write-Host "Button Label: $($invitation.ButtonLabel)"
        
        return $invitation
    }
    catch {
        Write-Error -Message "Failed to get custom invitation for node `"$($targetNode.DisplayName)`": $($_.Exception.Message)" -ErrorAction Stop
    }
}
New-Alias -Name Get-KeeperNodeCustomInvitation -Value Get-KeeperEnterpriseNodeCustomInvitation

function Set-KeeperEnterpriseNodeCustomLogo {
    <#
    .SYNOPSIS
    Uploads a custom logo for an Enterprise Node

    .PARAMETER Node
    Node name or ID

    .PARAMETER LogoType
    Logo type ("vault" or "email")

    .PARAMETER LogoPath
    Path to the logo image file (JPEG, PNG, or GIF, max 500KB)

    .DESCRIPTION
    Uploads a custom logo for an enterprise node. The logo file must be:
    - Image format: JPEG, PNG, or GIF
    - Maximum size: 500 KB
    - Dimensions: Between 10x10 and 320x320 pixels

    The upload process includes validation, upload to cloud storage, and verification.

    .EXAMPLE
    Set-KeeperEnterpriseNodeCustomLogo -Node "Sales" -LogoType "enterprise" -LogoPath "C:\logo.png"
    Uploads an enterprise logo for the Sales node

    .EXAMPLE
    Set-KeeperNodeCustomLogo "Marketing" "email" "C:\email-logo.jpg"
    Uses the alias to upload an email logo for the Marketing node
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, Mandatory = $true)][string] $Node,
        [Parameter(Position = 1, Mandatory = $true)][string] $LogoType,
        [Parameter(Position = 2, Mandatory = $true)][string] $LogoPath
    )

    [Enterprise]$enterprise = getEnterprise

    $targetNode = resolveSingleNode $Node
    if (-not $targetNode) {
        Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop
    }

    if (-not (Test-Path -Path $LogoPath -PathType Leaf)) {
        Write-Error -Message "Logo file not found: $LogoPath" -ErrorAction Stop
    }

    try {
        $response = [KeeperSecurity.Enterprise.EnterpriseExtensions]::UploadEnterpriseCustomLogo($enterprise.enterpriseData, $targetNode.Id, $LogoType, $LogoPath).GetAwaiter().GetResult()
        Write-Output "Custom logo uploaded for node `"$($targetNode.DisplayName)`""
        return $response
    }
    catch {
        Write-Error -Message "Failed to upload custom logo for node `"$($targetNode.DisplayName)`": $($_.Exception.Message)" -ErrorAction Stop
    }
}
New-Alias -Name Set-KeeperNodeCustomLogo -Value Set-KeeperEnterpriseNodeCustomLogo

# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDH+169mZelmqUJ
# FN+wGVUdt+/VJv96IA1z+aUxvINJ5KCCITswggWNMIIEdaADAgECAhAOmxiO+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
# BDEiBCD363Nqb1KDELWJmUH5g9K37JAN6ktYGGzOZOhSxqdtjTANBgkqhkiG9w0B
# AQEFAASCAYB5sxriH0Arh7ZHP3wRZIsgz46EvC9akgrstkyPt2RT6lwEloxAkvLk
# SIT9hZYAXZaKbNW+SD5Hr2IDCNLFWHr5XvD/MdO5QOv/BZ9iBTt1zSQjpzgEkIAC
# CAdg1KXdyNJuisMcoiEzop2iy/zAPv4QNflwxpozBOWrZ5waxRBHzpvuGqKMuMnT
# 2HSoUo57xV71nTzj7iiAnMPYdpqN4g3hQhHpTRyOry0zU38amxHYiosEeyeDPU/D
# KZ22qPsBbwf/WEP1V6lwVdPvLhuuUjitO2LRqlL0fO5lmhVtuG9oVEWnXpz3fOIt
# +5l9aTT3+K+SyD1nT9hwaIXxubWeIxNybjVSz8xGRl2plyVm4EcKyKPmDNPLTarg
# FVibO1JB0hu8zmV6KWd6QBmFwZY+gB7pIItfHeisumTE5IuDM6fDqs8AkBQi52Pe
# udJdpHAeHaf8rWvxbAWev2+dZplrroj3+qiUfdsKvO8FDLdEI/GppOXCH6kx/7cw
# Sn/2JtgHQCGhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD
# ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg
# Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN
# AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwMTIzMjMzOTIxWjAv
# BgkqhkiG9w0BCQQxIgQgZB8A/Bv7aqqReNkYE/caSDXeLL1PLetjx93R+5gkf2Qw
# DQYJKoZIhvcNAQEBBQAEggIAUo8U3RFPt+44lBS0Ns2YV/iSVev3iXGJPD9Ft+f3
# WQVImuDO7U9ot2W1Ni4WrFiMWEQCkG4JG2oKKnxOnu58Jj8jjVwVYGl1PiSawQ/s
# mH6LYEOdOrrs/61zHmaU9diTODMAOdLGUDsFpNB53z8m8uW0jtVrXk6CZ2Q2t7vZ
# WjBjWGzxMAKjx1HI7vV0y4AW1Fjc/tSwF9m1TcilZEXmaUtw71Dn+6v6mb9j/5X3
# 9we0e+In/02nMdix4fORhxOsUjUCb/skvwXaXhXwxvc0W7CLwCCS2LExPzFJr1r5
# 60EhxuZ9FHtjodTpvbheXMyOgjDfjv7dbOEACELIUQfzcTsNxGGZcGOcuUuQRC6x
# /mIaPPRbGtHsmmRGhkRqlyK7TQeVVyGBPpHYSpmL4dZWbxK/BRGlCWVYLIQn+qWR
# X8aUrEwxH46tcvzPrb10HzgvDaowLBTAQb3K6nUakLF48dZmKCZgYyYAczjdUz6/
# Yhp5v2nYd1C6Jw0M3/fLw2d0a0XebX6/mPtL7HkYoYWcqswgzvXuqrQ7MSSJJ08N
# WHcOMOr+BTO92h4KA3E4RWEHQPWdAyS2tYSIVhdF9Y9nRhuaatygNbSftEsBlZ9p
# SAojgSdGHeaz9+jZhcHFCFvlpeBuTYs5xRyd99HRTj9+hZKxbGoIyH9Rd8tkouhW
# ADY=
# SIG # End signature block