ReportCommands/ShareReport.ps1
|
#requires -Version 5.1 function Script:Get-ShareReportFolderPath { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter()] [string] $FolderUid ) if ([string]::IsNullOrEmpty($FolderUid)) { return '' } $parts = [System.Collections.Generic.List[string]]::new() $visited = [System.Collections.Generic.HashSet[string]]::new() $current = $FolderUid while (-not [string]::IsNullOrEmpty($current) -and $visited.Add($current)) { [KeeperSecurity.Vault.FolderNode] $folder = $null if (-not $Vault.TryGetFolder($current, [ref]$folder)) { break } $parts.Add($folder.Name) $current = $folder.ParentUid } $parts.Reverse() return ($parts -join '\') } function Script:Get-ShareReportRecordShares { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [string[]] $RecordUids ) $batchSize = 100 $allShares = [System.Collections.Generic.List[KeeperSecurity.Vault.RecordSharePermissions]]::new() for ($i = 0; $i -lt $RecordUids.Count; $i += $batchSize) { $end = [Math]::Min($i + $batchSize, $RecordUids.Count) $batch = [System.Collections.Generic.List[string]]::new() for ($j = $i; $j -lt $end; $j++) { $batch.Add($RecordUids[$j]) } try { $shares = $Vault.GetSharesForRecords([System.Collections.Generic.IEnumerable[string]]$batch).GetAwaiter().GetResult() foreach ($s in $shares) { $allShares.Add($s) } } catch { Write-Warning "Failed to retrieve record shares: $($_.Exception.Message)" } } return $allShares } function Script:Get-ShareReportTeamMembers { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault ) $teamMembers = @{} try { $enterprise = getEnterprise } catch { Write-Warning "Enterprise data is not available for team expansion. Details: $($_.Exception.Message)" return $teamMembers } if ($null -eq $enterprise -or $null -eq $enterprise.enterpriseData) { Write-Warning 'Enterprise data is not available for team expansion.' return $teamMembers } $enterpriseData = $enterprise.enterpriseData foreach ($team in $Vault.Teams) { $teamUid = $team.TeamUid $members = [System.Collections.Generic.List[string]]::new() foreach ($userId in $enterpriseData.GetUsersForTeam($teamUid)) { $user = $null if ($enterpriseData.TryGetUserById($userId, [ref]$user)) { $members.Add($user.Email) } } $teamMembers[$teamUid] = $members } return $teamMembers } function Script:Get-ShareReportAuditEvents { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Authentication.IAuthentication] $Auth, [Parameter(Mandatory = $true)] [string] $RecordUid, [Parameter(Mandatory = $true)] [ref] $AramEnabled ) $rq = New-Object KeeperSecurity.Enterprise.AuditLogCommands.GetAuditEventReportsCommand $rq.ReportType = 'raw' $rq.Limit = 1000 $rq.Order = 'descending' $filter = New-Object KeeperSecurity.Enterprise.AuditLogCommands.ReportFilter $filter.EventTypes = @('share', 'record_share_outside_user', 'remove_share', 'folder_add_team', 'folder_remove_team', 'folder_add_record', 'folder_remove_record') $filter.RecordUid = @($RecordUid) $rq.Filter = $filter try { $response = $Auth.ExecuteAuthCommand( $rq, [KeeperSecurity.Enterprise.AuditLogCommands.GetAuditEventReportsResponse], $true ).GetAwaiter().GetResult() $rs = [KeeperSecurity.Enterprise.AuditLogCommands.GetAuditEventReportsResponse]$response } catch [KeeperSecurity.Authentication.KeeperApiException] { $apiEx = $_.Exception if ($apiEx.Code -eq 'not_an_enterprise_user' -or $apiEx.Code -eq 'access_denied') { $AramEnabled.Value = $false } return @() } catch { return @() } if (-not $rs.Events -or $rs.Events.Count -eq 0) { return @() } $latestRecordEvents = [ordered]@{} $latestFolderEvents = [ordered]@{} foreach ($evt in $rs.Events) { $eventType = if ($evt.ContainsKey('audit_event_type')) { $evt['audit_event_type'].ToString() } else { '' } $recUid = if ($evt.ContainsKey('record_uid')) { $evt['record_uid'].ToString() } else { '' } $created = Get-ShareReportCreatedUnixSeconds -AuditEvent $evt if ($evt.ContainsKey('to_username')) { $toUser = $evt['to_username'].ToString() $key = "${recUid}-${toUser}" if ([string]::IsNullOrEmpty($recUid) -or [string]::IsNullOrEmpty($toUser)) { continue } $existing = $latestRecordEvents[$key] if ($null -eq $existing -or $created -gt $existing.Created) { $latestRecordEvents[$key] = [PSCustomObject]@{ Created = $created Event = $evt } } } elseif ($evt.ContainsKey('shared_folder_uid')) { $sfUid = $evt['shared_folder_uid'].ToString() $key = "${recUid}-${sfUid}" if ([string]::IsNullOrEmpty($recUid) -or [string]::IsNullOrEmpty($sfUid)) { continue } $existing = $latestFolderEvents[$key] if ($null -eq $existing -or $created -gt $existing.Created) { $latestFolderEvents[$key] = [PSCustomObject]@{ Created = $created Event = $evt } } } } $activeEvents = [System.Collections.Generic.List[object]]::new() foreach ($entry in $latestRecordEvents.Values) { $eventType = if ($entry.Event.ContainsKey('audit_event_type')) { $entry.Event['audit_event_type'].ToString() } else { '' } if ($eventType -in 'share', 'record_share_outside_user') { $activeEvents.Add($entry.Event) | Out-Null } } foreach ($entry in $latestFolderEvents.Values) { $eventType = if ($entry.Event.ContainsKey('audit_event_type')) { $entry.Event['audit_event_type'].ToString() } else { '' } if ($eventType -eq 'folder_add_record') { $activeEvents.Add($entry.Event) | Out-Null } } return $activeEvents } function Script:Get-ShareReportCreatedUnixSeconds { param($AuditEvent) if ($null -eq $AuditEvent -or -not $AuditEvent.ContainsKey('created') -or $null -eq $AuditEvent['created']) { return 0L } $raw = $AuditEvent['created'].ToString() $epoch = 0L if (-not [long]::TryParse($raw, [ref]$epoch)) { return 0L } $maxReasonableSeconds = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + (10 * 365 * 24 * 60 * 60) if ($epoch -le $maxReasonableSeconds) { return $epoch } if ($epoch -le ($maxReasonableSeconds * 100)) { return [long]($epoch / 100) } return [long]($epoch / 1000) } function Script:Get-ShareDateForUser { param($ActivityList, [string] $Username) if (-not $ActivityList -or $ActivityList.Count -eq 0) { return '' } $matchesUserName = $ActivityList | Where-Object { $_.ContainsKey('to_username') -and $_['to_username'].ToString() -eq $Username } if (-not $matchesUserName -or $matchesUserName.Count -eq 0) { return '' } $activity = $null $createdSec = 0L foreach ($m in $matchesUserName) { $sec = Get-ShareReportCreatedUnixSeconds -AuditEvent $m if ($sec -gt $createdSec) { $createdSec = $sec $activity = $m } } if ($createdSec -le 0) { return '' } if ($createdSec -lt 946684800) { return '' } $dt = [DateTimeOffset]::FromUnixTimeSeconds($createdSec).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss zzz') $eventType = if ($activity.ContainsKey('audit_event_type')) { $activity['audit_event_type'].ToString() } else { '' } if ($eventType -eq 'record_share_outside_user') { return "(externally shared on $dt)" } return "(shared on $dt)" } function Script:Get-ShareDateForFolder { param($ActivityList, [string] $SharedFolderUid) if (-not $ActivityList -or $ActivityList.Count -eq 0) { return '' } $matchesFolder = $ActivityList | Where-Object { $_.ContainsKey('audit_event_type') -and $_['audit_event_type'].ToString() -match 'folder' -and $_.ContainsKey('shared_folder_uid') -and $_['shared_folder_uid'].ToString() -eq $SharedFolderUid } if (-not $matchesFolder -or $matchesFolder.Count -eq 0) { return '' } $createdSec = 0L foreach ($m in $matchesFolder) { $sec = Get-ShareReportCreatedUnixSeconds -AuditEvent $m if ($sec -gt $createdSec) { $createdSec = $sec } } if ($createdSec -le 0) { return '' } if ($createdSec -lt 946684800) { return '' } $dt = [DateTimeOffset]::FromUnixTimeSeconds($createdSec).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss zzz') return "(shared on $dt)" } function Script:Get-ShareReportRecordDate { param($ActivityList) if (-not $ActivityList -or $ActivityList.Count -eq 0) { return '' } $createdSec = 0L foreach ($activity in $ActivityList) { if (-not $activity.ContainsKey('audit_event_type')) { continue } $eventType = $activity['audit_event_type'].ToString() if ($eventType -notin @('share', 'record_share_outside_user', 'folder_add_record')) { continue } $sec = Get-ShareReportCreatedUnixSeconds -AuditEvent $activity if ($sec -le 0) { continue } if ($createdSec -eq 0 -or $sec -lt $createdSec) { $createdSec = $sec } } if ($createdSec -le 0) { return '' } return [DateTimeOffset]::FromUnixTimeSeconds($createdSec).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss UTC') } function Script:Get-PermissionsText { param([bool] $CanEdit, [bool] $CanShare) if (-not $CanEdit -and -not $CanShare) { return 'Read Only' } $parts = @() if ($CanShare) { $parts += 'Share' } if ($CanEdit) { $parts += 'Edit' } return 'Can ' + ($parts -join ' & ') } function Script:Get-SharedFolderPermissionsText { param([bool] $ManageRecords, [bool] $ManageUsers) if (-not $ManageRecords -and -not $ManageUsers) { return 'No User Permissions' } $parts = @() if ($ManageUsers) { $parts += 'Manage Users' } if ($ManageRecords) { $parts += 'Manage Records' } return 'Can ' + ($parts -join ' & ') } function Script:Write-ShareReportOutput { param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]] $Rows, [Parameter()] [string] $Title, [Parameter(Mandatory = $true)] [ValidateSet('table', 'json', 'csv')] [string] $Format, [Parameter()] [string] $Output ) if (-not $Rows -or $Rows.Count -eq 0) { Write-Warning 'No data to display.' return } switch ($Format) { 'json' { $jsonText = $Rows | ConvertTo-Json -Depth 5 if ($Output) { Set-Content -Path $Output -Value $jsonText -Encoding utf8 Write-Host "Output written to $Output" return } return $jsonText } 'csv' { $csvText = $Rows | ConvertTo-Csv -NoTypeInformation if ($Output) { Set-Content -Path $Output -Value $csvText -Encoding utf8 Write-Host "Output written to $Output" return } return $csvText } default { if ($Output) { $tableText = @($Rows | Format-Table -Property * -AutoSize | Out-String -Width 8192) $content = @() if ($Title) { $content += $Title; $content += '' } $content += $tableText Set-Content -Path $Output -Value $content -Encoding utf8 Write-Host "Output written to $Output" return } if ($Title) { Write-Host '' Write-Host $Title } $Rows | Format-Table -Property * -AutoSize | Out-String -Width 8192 } } } function Script:Build-RecordToFolderIndex { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault ) $index = @{} foreach ($fn in $Vault.Folders) { foreach ($recUid in $fn.Records) { if (-not $index.ContainsKey($recUid)) { $index[$recUid] = [System.Collections.Generic.List[string]]::new() } $index[$recUid].Add($fn.FolderUid) } } return $index } function Script:Add-ShareReportSetValue { param( [Parameter(Mandatory = $true)] [hashtable] $Map, [Parameter(Mandatory = $true)] [string] $Key, [Parameter(Mandatory = $true)] [string] $Value ) if ([string]::IsNullOrEmpty($Key) -or [string]::IsNullOrEmpty($Value)) { return } if (-not $Map.ContainsKey($Key)) { $Map[$Key] = [System.Collections.Generic.HashSet[string]]::new() } [void]$Map[$Key].Add($Value) } function Script:Build-ShareReportSharedFolderCache { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]] $AllShares ) $sfMembershipCache = @{} foreach ($shareInfo in $AllShares) { if (-not $shareInfo.SharedFolderPermissions) { continue } foreach ($sfp in $shareInfo.SharedFolderPermissions) { if ($sfMembershipCache.ContainsKey($sfp.SharedFolderUid)) { continue } [KeeperSecurity.Vault.SharedFolder] $sf = $null if ($Vault.TryGetSharedFolder($sfp.SharedFolderUid, [ref]$sf)) { $sfMembershipCache[$sfp.SharedFolderUid] = $sf } } } return $sfMembershipCache } function Script:Build-ShareReportRecordOwnerMap { param( [Parameter(Mandatory = $true)] [string[]] $RecordUids, [Parameter(Mandatory = $true)] [hashtable] $SharesMap, [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [string] $CurrentUser ) $recordOwnerMap = @{} foreach ($uid in $RecordUids) { $shareInfo = $SharesMap[$uid] $ownerName = '' if ($shareInfo -and $shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if ($up.Owner) { $ownerName = $up.Username break } } } [KeeperSecurity.Vault.KeeperRecord] $rec = $null if (-not $ownerName -and $Vault.TryGetKeeperRecord($uid, [ref]$rec) -and $rec.Owner) { $ownerName = $CurrentUser } $recordOwnerMap[$uid] = $ownerName } return $recordOwnerMap } function Script:Get-ShareReportFolderPathText { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter(Mandatory = $true)] [hashtable] $RecordToFolderIndex, [Parameter(Mandatory = $true)] [string] $RecordUid, [Parameter()] [System.Collections.Generic.HashSet[string]] $FilterFolderUids ) $folderPaths = [System.Collections.Generic.List[string]]::new() $folderUids = $RecordToFolderIndex[$RecordUid] if (-not $folderUids) { return '' } foreach ($fUid in $folderUids) { if ($FilterFolderUids -and -not $FilterFolderUids.Contains($fUid)) { continue } $folderPath = Get-ShareReportFolderPath -Vault $Vault -FolderUid $fUid if ($folderPath) { $folderPaths.Add($folderPath) } } return ($folderPaths -join "`n") } function Script:Add-ShareReportSharedRecordRow { param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [System.Collections.Generic.List[PSCustomObject]] $Table, [Parameter(Mandatory = $true)] [string] $RecordUid, [Parameter(Mandatory = $true)] [string] $Title, [Parameter(Mandatory = $true)] [string] $ShareType, [Parameter(Mandatory = $true)] [string] $SharedTo, [Parameter(Mandatory = $true)] [string] $Permissions, [Parameter(Mandatory = $true)] [string] $FolderPath, [Parameter(Mandatory = $true)] [bool] $IncludeOwner, [Parameter()] [string] $Owner ) $row = [ordered]@{ 'Record UID' = $RecordUid 'Title' = $Title 'Share Type' = $ShareType 'Shared To' = $SharedTo 'Permissions' = $Permissions 'Folder Path' = $FolderPath } if ($IncludeOwner) { $row.Insert(0, 'Owner', $Owner) } $Table.Add([PSCustomObject]$row) } function Script:Write-ShareReportFolders { param( [KeeperSecurity.Vault.VaultOnline] $Vault, [hashtable] $TeamMembers, [bool] $ShowTeamUsers, [string] $Format, [string] $Output ) $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($sf in $Vault.SharedFolders) { $folderPath = Get-ShareReportFolderPath -Vault $Vault -FolderUid $sf.Uid if (-not $folderPath) { $folderPath = $sf.Name } foreach ($perm in $sf.UsersPermissions) { $permText = Get-SharedFolderPermissionsText -ManageRecords $perm.ManageRecords -ManageUsers $perm.ManageUsers $isTeam = $perm.UserType -eq [KeeperSecurity.Vault.UserType]::Team if ($isTeam -and $ShowTeamUsers) { $table.Add([PSCustomObject][ordered]@{ 'Folder UID' = $sf.Uid 'Folder Name' = $sf.Name 'Shared To' = "(Team) $($perm.Name)" 'Permissions' = $permText 'Folder Path' = $folderPath }) $members = $TeamMembers[$perm.Uid] if ($members) { foreach ($member in $members) { $table.Add([PSCustomObject][ordered]@{ 'Folder UID' = '' 'Folder Name' = '' 'Shared To' = " $([char]0x2514) $member" 'Permissions' = '' 'Folder Path' = '' }) } } } else { $displayName = if ($isTeam) { "(Team) $($perm.Name)" } else { $perm.Name } $table.Add([PSCustomObject][ordered]@{ 'Folder UID' = $sf.Uid 'Folder Name' = $sf.Name 'Shared To' = $displayName 'Permissions' = $permText 'Folder Path' = $folderPath }) } } } Write-ShareReportOutput -Rows $table -Title 'Shared Folders' -Format $Format -Output $Output } function Script:Write-ShareReportRecordDetail { param( [string[]] $RecordUids, [hashtable] $SharesMap, [hashtable] $RecordTitleMap, [hashtable] $SfMembershipCache ) foreach ($uid in $RecordUids) { $shareInfo = $SharesMap[$uid] $title = $RecordTitleMap[$uid] Write-Host '' Write-Host ('{0,20} {1}' -f 'Record UID:', $uid) Write-Host ('{0,20} {1}' -f 'Title:', $title) $i = 0 if ($shareInfo -and $shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if ($up.Owner) { continue } $label = if ($i -eq 0) { 'Shared with:' } else { '' } $permText = Get-PermissionsText -CanEdit $up.CanEdit -CanShare $up.CanShare $target = $up.Username if ($up.AwaitingApproval) { $target += ' (pending)' } Write-Host ('{0,20} {1} => {2}' -f $label, $target, $permText) $i++ } } if ($shareInfo -and $shareInfo.SharedFolderPermissions) { foreach ($sfp in $shareInfo.SharedFolderPermissions) { $sf = $SfMembershipCache[$sfp.SharedFolderUid] $sfName = if ($sf) { $sf.Name } else { $sfp.SharedFolderUid } $label = if ($i -eq 0) { 'Shared with:' } else { '' } Write-Host ('{0,20} via Shared Folder: {1}' -f $label, $sfName) $i++ } } Write-Host '' } } function Script:Write-ShareReportUserSharedFolders { param( [hashtable] $SfShares, [hashtable] $SfMembershipCache, [KeeperSecurity.Vault.VaultOnline] $Vault, [System.Collections.Generic.HashSet[string]] $UserFilter, [string] $Format, [string] $Output ) $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($user in $SfShares.Keys) { if (-not $UserFilter.Contains($user)) { continue } foreach ($sfUid in $SfShares[$user]) { $sf = $SfMembershipCache[$sfUid] if (-not $sf) { [KeeperSecurity.Vault.SharedFolder] $sfObj = $null if ($Vault.TryGetSharedFolder($sfUid, [ref]$sfObj)) { $sf = $sfObj } } $sfName = if ($sf) { $sf.Name } else { '' } $table.Add([PSCustomObject][ordered]@{ 'Username' = $user 'Shared Folder UID' = $sfUid 'Name' = $sfName }) } } Write-ShareReportOutput -Rows $table -Title 'Shared Folders by User' -Format $Format -Output $Output } function Script:Build-ShareReportFolderShares { param( [Parameter(Mandatory = $true)] [KeeperSecurity.Vault.VaultOnline] $Vault, [Parameter()] [hashtable] $TeamMembers, [Parameter()] [bool] $ShowTeamUsers ) $sfShares = @{} foreach ($sf in $Vault.SharedFolders) { foreach ($perm in $sf.UsersPermissions) { $target = $perm.Name Add-ShareReportSetValue -Map $sfShares -Key $target -Value $sf.Uid $isTeam = $perm.UserType -eq [KeeperSecurity.Vault.UserType]::Team if ($isTeam -and $ShowTeamUsers) { $members = $TeamMembers[$perm.Uid] if ($members) { foreach ($member in $members) { Add-ShareReportSetValue -Map $sfShares -Key $member -Value $sf.Uid } } } } } return $sfShares } function Script:Write-ShareReportUserRecords { param( [hashtable] $RecordShares, [hashtable] $RecordOwnerMap, [hashtable] $RecordTitleMap, [System.Collections.Generic.HashSet[string]] $UserFilter, [string] $Format, [string] $Output ) $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($user in $RecordShares.Keys) { if (-not $UserFilter.Contains($user)) { continue } foreach ($uid in $RecordShares[$user]) { $table.Add([PSCustomObject][ordered]@{ 'Username' = $user 'Record Owner' = $RecordOwnerMap[$uid] 'Record UID' = $uid 'Record Title' = $RecordTitleMap[$uid] }) } } Write-ShareReportOutput -Rows $table -Title 'Shared Records by User' -Format $Format -Output $Output } function Script:Write-ShareReportOwner { param( [System.Collections.Generic.List[string]] $RecordUids, [hashtable] $SharesMap, [hashtable] $RecordOwnerMap, [hashtable] $RecordTitleMap, [hashtable] $SfMembershipCache, [hashtable] $RecordToFolderIndex, [KeeperSecurity.Vault.VaultOnline] $Vault, [hashtable] $TeamMembers, [bool] $ShowTeamUsers, [bool] $IncludeShareDate, [bool] $DetailedView, $UserFilter, [string] $Format, [string] $Output ) $aramEnabled = $true $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($uid in $RecordUids) { $shareInfo = $SharesMap[$uid] if (-not $shareInfo) { continue } $recOwner = $RecordOwnerMap[$uid] $title = $RecordTitleMap[$uid] $folderPathStr = Get-ShareReportFolderPathText -Vault $Vault -RecordToFolderIndex $RecordToFolderIndex -RecordUid $uid $shareEvents = @() if ($IncludeShareDate -and $aramEnabled) { $shareEvents = Get-ShareReportAuditEvents -Auth $Vault.Auth -RecordUid $uid -AramEnabled ([ref]$aramEnabled) } $recordShareDate = '' if ($IncludeShareDate -and $shareEvents.Count -gt 0) { $recordShareDate = Get-ShareReportRecordDate -ActivityList $shareEvents } $shareTargets = [System.Collections.Generic.List[string]]::new() $permissionsList = [System.Collections.Generic.List[object]]::new() if ($shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if (-not $up.Owner) { $permissionsList.Add($up) } } } if ($UserFilter) { $filtered = [System.Collections.Generic.List[object]]::new() foreach ($p in $permissionsList) { if ($UserFilter.Contains($p.Username)) { $filtered.Add($p) } } $permissionsList = $filtered } if (-not $DetailedView) { $shareWith = $permissionsList.Count if ($shareInfo.SharedFolderPermissions) { foreach ($sfp in $shareInfo.SharedFolderPermissions) { $sf = $SfMembershipCache[$sfp.SharedFolderUid] if ($sf) { $shareWith += $sf.UsersPermissions.Count } } } $shareInfoText = $shareWith.ToString() } else { if ($ShowTeamUsers -and $recOwner) { $shareTargets.Add("$recOwner => Owner") } foreach ($up in $permissionsList) { $permText = Get-PermissionsText -CanEdit $up.CanEdit -CanShare $up.CanShare $dateSuffix = '' if ($IncludeShareDate) { $dateStr = '' if ($shareEvents.Count -gt 0) { $dateStr = Get-ShareDateForUser -ActivityList $shareEvents -Username $up.Username } if ($dateStr) { $dateSuffix = $dateStr } else { $dateSuffix = '(share date unavailable)' } } $shareTargets.Add("$($up.Username) => $permText$(if ($dateSuffix) { " $dateSuffix" } else { '' })") if ($up.Expiration) { $expDt = $up.Expiration.Value.LocalDateTime $shareTargets.Add("`t(expires on $expDt)") } } if ($shareInfo.SharedFolderPermissions) { foreach ($sfp in $shareInfo.SharedFolderPermissions) { $sf = $SfMembershipCache[$sfp.SharedFolderUid] if (-not $sf) { continue } foreach ($sfUser in $sf.UsersPermissions) { if ($sfUser.Name -eq $recOwner) { continue } if ($UserFilter -and -not $UserFilter.Contains($sfUser.Name)) { continue } $isTeam = $sfUser.UserType -eq [KeeperSecurity.Vault.UserType]::Team $displayName = if ($isTeam -and $ShowTeamUsers) { "(Team) $($sfUser.Name)" } else { $sfUser.Name } $permText = Get-PermissionsText -CanEdit $sfUser.ManageRecords -CanShare $sfUser.ManageUsers $dateSuffix = '' if ($IncludeShareDate) { $dateStr = '' if ($shareEvents.Count -gt 0) { if (-not $isTeam) { $dateStr = Get-ShareDateForUser -ActivityList $shareEvents -Username $sfUser.Name } else { $dateStr = Get-ShareDateForFolder -ActivityList $shareEvents -SharedFolderUid $sfp.SharedFolderUid } } if ($dateStr) { $dateSuffix = $dateStr } else { $dateSuffix = '(share date unavailable)' } } $shareTargets.Add("$displayName => $permText$(if ($dateSuffix) { " $dateSuffix" } else { '' })") if ($isTeam -and $ShowTeamUsers) { $members = $TeamMembers[$sfUser.Uid] if ($members) { foreach ($member in $members) { $shareTargets.Add("(Team User) $member => $permText") } } } } } } $shareInfoText = $shareTargets -join "`n" } $row = [ordered]@{ 'Record Owner' = $recOwner 'Record UID' = $uid 'Record Title' = $title 'Shared With' = $shareInfoText 'Folder Path' = $folderPathStr } if ($IncludeShareDate) { $row['Share Date'] = $recordShareDate } $table.Add([PSCustomObject]$row) } Write-ShareReportOutput -Rows $table -Title 'Record Share Report (Owner)' -Format $Format -Output $Output } function Script:Write-ShareReportSummary { param( [hashtable] $RecordShares, [hashtable] $SfShares, [string] $CurrentUser, $UserFilter, [string] $Format, [string] $Output ) $RecordShares.Remove($CurrentUser) $SfShares.Remove($CurrentUser) $allTargets = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($key in $RecordShares.Keys) { [void]$allTargets.Add($key) } foreach ($key in $SfShares.Keys) { [void]$allTargets.Add($key) } if ($UserFilter) { $allTargets = [System.Collections.Generic.HashSet[string]]::new( ($allTargets | Where-Object { $UserFilter.Contains($_) }), [System.StringComparer]::OrdinalIgnoreCase ) } $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($target in ($allTargets | Sort-Object)) { $recCount = if ($RecordShares.ContainsKey($target)) { $RecordShares[$target].Count } else { $null } $sfCount = if ($SfShares.ContainsKey($target)) { $SfShares[$target].Count } else { $null } $table.Add([PSCustomObject][ordered]@{ 'Shared To' = $target 'Records' = $recCount 'Shared Folders' = $sfCount }) } Write-ShareReportOutput -Rows $table -Title 'Share Report Summary' -Format $Format -Output $Output } function Get-KeeperShareReport { <# .SYNOPSIS Show a report of shared records and shared folders. .DESCRIPTION Generates a report of records and folders shared both with and by the current user. Supports multiple modes: summary, per-record detail, per-user filter, shared folders listing, and owner report with optional share-date information. Use -Verbose or -ShowTeamUsers to show detailed share permissions per target. .PARAMETER Format Output format: table, json, or csv. Default is table. .PARAMETER Output Path to write the report to a file. .PARAMETER Record Record name(s) or UID(s) to show share information for. .PARAMETER Email User email(s) or team name(s) to filter the report by. .PARAMETER Owner Show record ownership information in the report. .PARAMETER ShareDate Include the date when each record was shared. Requires enterprise admin with report permissions. Only applies to the owner report (-Owner). .PARAMETER SharedFolders Display shared folder detail instead of records. Used with -Email. .PARAMETER Folders Limit the report to shared folders only (excludes shared records). .PARAMETER ShowTeamUsers Expand team shares to show individual team members. Requires enterprise admin. .EXAMPLE Get-KeeperShareReport Display a summary of all shares grouped by share target. .EXAMPLE Get-KeeperShareReport -Record "5R7Ued8#JctulYbBLwM$" Display share info for a specific record. .EXAMPLE Get-KeeperShareReport -Format csv -Output share_report.csv Export share report as CSV. .EXAMPLE Get-KeeperShareReport -Email "john.doe@gmail.com" -Owner -ShareDate -Verbose Show records shared with a user including owner and share dates. .EXAMPLE Get-KeeperShareReport -Folders Display a list of shared folders with their access permissions. .EXAMPLE Get-KeeperShareReport -Folders -ShowTeamUsers Display shared folders with team memberships expanded to individual users. #> [CmdletBinding()] Param ( [Parameter()] [ValidateSet('table', 'json', 'csv')] [string] $Format = 'table', [Parameter()] [string] $Output, [Parameter()] [string[]] $Record, [Parameter()] [string[]] $Email, [Parameter()] [switch] $Owner, [Parameter()] [switch] $ShareDate, [Parameter()] [switch] $SharedFolders, [Parameter()] [switch] $Folders, [Parameter()] [switch] $ShowTeamUsers ) try { [KeeperSecurity.Vault.VaultOnline]$vault = getVault } catch { Write-Error "Unable to load Keeper vault context. Run Connect-Keeper and Sync-Keeper first. Details: $($_.Exception.Message)" -ErrorAction Stop } $currentUser = $vault.Auth.Username $verbose = $PSCmdlet.MyInvocation.BoundParameters.ContainsKey('Verbose') -or $ShowTeamUsers.IsPresent $teamMembers = @{} if ($ShowTeamUsers.IsPresent) { $teamMembers = Get-ShareReportTeamMembers -Vault $vault } if ($Folders.IsPresent) { Write-ShareReportFolders -Vault $vault -TeamMembers $teamMembers -ShowTeamUsers $ShowTeamUsers.IsPresent ` -Format $Format -Output $Output return } $sharedRecordUids = [System.Collections.Generic.List[string]]::new() $recordTitleMap = @{} if ($Record) { foreach ($r in $Record) { [KeeperSecurity.Vault.KeeperRecord] $rec = $null if ($vault.TryGetKeeperRecord($r, [ref]$rec)) { $sharedRecordUids.Add($rec.Uid) $recordTitleMap[$rec.Uid] = $rec.Title } else { $found = $false foreach ($kr in $vault.KeeperRecords) { if ($kr.Title -eq $r) { $sharedRecordUids.Add($kr.Uid) $recordTitleMap[$kr.Uid] = $kr.Title $found = $true break } } if (-not $found) { Write-Warning "Cannot find a Keeper record: $r" } } } } else { foreach ($kr in $vault.KeeperRecords) { if ($kr.Shared) { $sharedRecordUids.Add($kr.Uid) $recordTitleMap[$kr.Uid] = $kr.Title } } } if ($sharedRecordUids.Count -eq 0 -and -not ($Email -and $SharedFolders.IsPresent)) { Write-Warning 'No shared records found.' return } $allShares = Get-ShareReportRecordShares -Vault $vault -RecordUids $sharedRecordUids.ToArray() $sharesMap = @{} foreach ($s in $allShares) { $sharesMap[$s.RecordUid] = $s } $sfMembershipCache = Build-ShareReportSharedFolderCache -Vault $vault -AllShares $allShares $recordOwnerMap = Build-ShareReportRecordOwnerMap -RecordUids $sharedRecordUids.ToArray() ` -SharesMap $sharesMap -Vault $vault -CurrentUser $currentUser if ($Record) { Write-ShareReportRecordDetail -RecordUids $sharedRecordUids.ToArray() -SharesMap $sharesMap ` -RecordTitleMap $recordTitleMap -SfMembershipCache $sfMembershipCache return } $userFilter = $null if ($Email) { $userFilter = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($e in $Email) { [void]$userFilter.Add($e) } } if ($Owner.IsPresent) { $recordToFolderIndex = Build-RecordToFolderIndex -Vault $vault Write-ShareReportOwner -RecordUids $sharedRecordUids -SharesMap $sharesMap ` -RecordOwnerMap $recordOwnerMap -RecordTitleMap $recordTitleMap ` -SfMembershipCache $sfMembershipCache -RecordToFolderIndex $recordToFolderIndex ` -Vault $vault -TeamMembers $teamMembers -ShowTeamUsers $ShowTeamUsers.IsPresent ` -IncludeShareDate $ShareDate.IsPresent -DetailedView $verbose ` -UserFilter $userFilter -Format $Format -Output $Output return } $recordShares = @{} foreach ($uid in $sharedRecordUids) { $shareInfo = $sharesMap[$uid] if (-not $shareInfo) { continue } if ($shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if ($up.Owner) { continue } Add-ShareReportSetValue -Map $recordShares -Key $up.Username -Value $uid } } if ($shareInfo.SharedFolderPermissions) { foreach ($sfp in $shareInfo.SharedFolderPermissions) { $sf = $sfMembershipCache[$sfp.SharedFolderUid] if (-not $sf) { continue } foreach ($perm in $sf.UsersPermissions) { $isTeam = $perm.UserType -eq [KeeperSecurity.Vault.UserType]::Team Add-ShareReportSetValue -Map $recordShares -Key $perm.Name -Value $uid if ($isTeam -and $ShowTeamUsers.IsPresent) { $members = $teamMembers[$perm.Uid] if ($members) { foreach ($member in $members) { Add-ShareReportSetValue -Map $recordShares -Key $member -Value $uid } } } } } } } $sfShares = Build-ShareReportFolderShares -Vault $vault -TeamMembers $teamMembers -ShowTeamUsers $ShowTeamUsers.IsPresent if ($Email -and $SharedFolders.IsPresent) { Write-ShareReportUserSharedFolders -SfShares $sfShares -SfMembershipCache $sfMembershipCache ` -Vault $vault -UserFilter $userFilter -Format $Format -Output $Output return } if ($Email) { Write-ShareReportUserRecords -RecordShares $recordShares -RecordOwnerMap $recordOwnerMap ` -RecordTitleMap $recordTitleMap -UserFilter $userFilter -Format $Format -Output $Output return } Write-ShareReportSummary -RecordShares $recordShares -SfShares $sfShares ` -CurrentUser $currentUser -UserFilter $userFilter -Format $Format -Output $Output } function Get-KeeperSharedRecordsReport { <# .SYNOPSIS Report shared records for the logged-in user. .DESCRIPTION Generates a report of all shared records showing the share type, recipient, and permissions per row. the share type (Direct Share, Share Folder, Share Team Folder), permissions, and folder path. By default only owned shared records are included. Use -AllRecords to include non-owned records. Mirrors the Python Commander 'ksrr' command. Alias: ksrr .PARAMETER ShowTeamUsers Expand team shares to show individual team members. Requires enterprise admin. .PARAMETER AllRecords Include all shared records in the vault, not just records owned by the current user. .PARAMETER Folder Optional folder path(s) or UID(s) to scope the report to records within those folders. .PARAMETER Format Output format: table (default), json, or csv. .PARAMETER Output Path to write the report to a file. .EXAMPLE Get-KeeperSharedRecordsReport Report all owned shared records in table format. .EXAMPLE Get-KeeperSharedRecordsReport -AllRecords Report all shared records in the vault (including non-owned). .EXAMPLE Get-KeeperSharedRecordsReport -ShowTeamUsers Report with team shares expanded to individual team members. .EXAMPLE Get-KeeperSharedRecordsReport -Format csv -Output "shared_records.csv" Export the shared records report to a CSV file. .EXAMPLE Get-KeeperSharedRecordsReport -Folder "Shared\Projects" Report shared records within a specific folder. #> [CmdletBinding()] Param ( [Parameter()] [Alias('tu')] [switch] $ShowTeamUsers, [Parameter()] [switch] $AllRecords, [Parameter()] [string[]] $Folder, [Parameter()] [ValidateSet('table', 'json', 'csv')] [string] $Format = 'table', [Parameter()] [string] $Output ) try { [KeeperSecurity.Vault.VaultOnline]$vault = getVault } catch { Write-Error "Unable to load Keeper vault context. Run Connect-Keeper and Sync-Keeper first. Details: $($_.Exception.Message)" -ErrorAction Stop } $currentUser = $vault.Auth.Username $allowedVersions = if ($AllRecords.IsPresent) { @(0, 1, 2, 3, 5, 6) } else { @(2, 3) } $teamMembers = @{} if ($ShowTeamUsers.IsPresent) { $teamMembers = Get-ShareReportTeamMembers -Vault $vault } $records = @{} $filterFolderUids = $null if ($Folder -and $Folder.Count -gt 0) { $filterFolderUids = [System.Collections.Generic.HashSet[string]]::new() foreach ($folderName in $Folder) { $folderFound = $false foreach ($fn in $vault.Folders) { $folderPath = Get-ShareReportFolderPath -Vault $vault -FolderUid $fn.FolderUid if ($fn.FolderUid -eq $folderName -or $fn.Name -eq $folderName -or $folderPath -eq $folderName) { [void]$filterFolderUids.Add($fn.FolderUid) $folderFound = $true foreach ($recUid in $fn.Records) { [KeeperSecurity.Vault.KeeperRecord] $rec = $null if (-not $vault.TryGetKeeperRecord($recUid, [ref]$rec)) { continue } if (-not $rec.Shared) { continue } if ($rec.Version -notin $allowedVersions) { continue } $records[$rec.Uid] = $rec } } } if (-not $folderFound) { Write-Warning "Folder '$folderName' could not be found." } } } else { foreach ($kr in $vault.KeeperRecords) { if (-not $kr.Shared) { continue } if ($kr.Version -notin $allowedVersions) { continue } $records[$kr.Uid] = $kr } } if (-not $AllRecords.IsPresent) { $ownedOnly = @{} foreach ($entry in $records.GetEnumerator()) { if ($entry.Value.Owner) { $ownedOnly[$entry.Key] = $entry.Value } } $records = $ownedOnly } if ($records.Count -eq 0) { Write-Warning 'No shared records found.' return } $recordUids = [string[]]@($records.Keys) $allShares = Get-ShareReportRecordShares -Vault $vault -RecordUids $recordUids $sharesMap = @{} foreach ($s in $allShares) { $sharesMap[$s.RecordUid] = $s } $sfMembershipCache = Build-ShareReportSharedFolderCache -Vault $vault -AllShares $allShares $recordToFolderIndex = Build-RecordToFolderIndex -Vault $vault $table = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($recordUid in $recordUids) { $rec = $records[$recordUid] $shareInfo = $sharesMap[$recordUid] if (-not $shareInfo) { continue } $recTitle = $rec.Title if ($Format -eq 'table' -and $recTitle.Length -gt 40) { $recTitle = $recTitle.Substring(0, 38) + '...' } $owner = $null if ($shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if ($up.Owner) { $owner = $up.Username; break } } } if (-not $owner -and $rec.Owner) { $owner = $currentUser } $folderPathStr = Get-ShareReportFolderPathText -Vault $vault -RecordToFolderIndex $recordToFolderIndex ` -RecordUid $recordUid -FilterFolderUids $filterFolderUids if ($shareInfo.UserPermissions) { foreach ($up in $shareInfo.UserPermissions) { if ($up.Owner) { continue } if (-not $AllRecords.IsPresent -and $up.Username -eq $currentUser) { continue } $permText = Get-PermissionsText -CanEdit $up.CanEdit -CanShare $up.CanShare Add-ShareReportSharedRecordRow -Table $table -RecordUid $recordUid -Title $recTitle ` -ShareType 'Direct Share' -SharedTo $up.Username -Permissions $permText ` -FolderPath $folderPathStr -IncludeOwner $AllRecords.IsPresent -Owner $owner } } if ($shareInfo.SharedFolderPermissions) { foreach ($sfp in $shareInfo.SharedFolderPermissions) { $sf = $sfMembershipCache[$sfp.SharedFolderUid] if (-not $sf) { $permText = Get-PermissionsText -CanEdit $sfp.CanEdit -CanShare $sfp.CanShare Add-ShareReportSharedRecordRow -Table $table -RecordUid $recordUid -Title $recTitle ` -ShareType 'Share Folder' -SharedTo '***' -Permissions $permText ` -FolderPath $sfp.SharedFolderUid -IncludeOwner $AllRecords.IsPresent -Owner $owner continue } $sfFolderPath = Get-ShareReportFolderPath -Vault $vault -FolderUid $sf.Uid if (-not $sfFolderPath) { $sfFolderPath = $sf.Name } foreach ($sfUser in $sf.UsersPermissions) { $isTeam = $sfUser.UserType -eq [KeeperSecurity.Vault.UserType]::Team $permText = Get-PermissionsText -CanEdit $sfp.CanEdit -CanShare $sfp.CanShare if ($isTeam) { if ($ShowTeamUsers.IsPresent -and $teamMembers.ContainsKey($sfUser.Uid)) { foreach ($member in $teamMembers[$sfUser.Uid]) { Add-ShareReportSharedRecordRow -Table $table -RecordUid $recordUid -Title $recTitle ` -ShareType 'Share Team Folder' -SharedTo "($($sfUser.Name)) $member" ` -Permissions $permText -FolderPath $sfFolderPath ` -IncludeOwner $AllRecords.IsPresent -Owner $owner } } else { Add-ShareReportSharedRecordRow -Table $table -RecordUid $recordUid -Title $recTitle ` -ShareType 'Share Team Folder' -SharedTo $sfUser.Name -Permissions $permText ` -FolderPath $sfFolderPath -IncludeOwner $AllRecords.IsPresent -Owner $owner } } else { if (-not $AllRecords.IsPresent -and $sfUser.Name -eq $currentUser) { continue } Add-ShareReportSharedRecordRow -Table $table -RecordUid $recordUid -Title $recTitle ` -ShareType 'Share Folder' -SharedTo $sfUser.Name -Permissions $permText ` -FolderPath $sfFolderPath -IncludeOwner $AllRecords.IsPresent -Owner $owner } } } } } Write-ShareReportOutput -Rows $table -Title 'Shared Records Report' -Format $Format -Output $Output } New-Alias -Name ksrr -Value Get-KeeperSharedRecordsReport # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCACf7iKj8qevFIS # YVUpgz38L5p+a8f1Jw4HLCozuGhoeqCCITswggWNMIIEdaADAgECAhAOmxiO+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 # BDEiBCC8z7gQ7OzCglg0Wd2fQ3j3ARfdA+9YjmIMz4+anca/JzANBgkqhkiG9w0B # AQEFAASCAYAV6dyhwacU9cRLRpSQAU/dlQFGBgVlwDF3c1rRwPaqYXM7OU+v1iR7 # DN7ExwpgeSaKebzjUs9Ah59UwPUivk3n8VRwfHtLLgYZS4nnaKBk9NdQu6qeA5MP # 8VB9inB3/uzs3XWq2fX495WFpmcKiYIluh9JPRN7WlFyNYWXaVEgOrb1OEP0oJxg # BA8IjJtS0gCQUn2XWfDPsRuXUclhn3u+qnZCFV0eko6gnVMRAfsFqgmHTs3iT49j # Z9BzqVvwjgRqmjdMYYjYB/tiET2j+PMotIRyTaeNRKsqilp6kv9Un7OHKQaw9lDC # mqzBqG+bbDeza89wxCfzrS5ggruArSBJAThVvh2UfS+G86En1OpK4G7dQjYLS1eO # bI3OHE+OMeT3uZQUu61y2F3FVJNi5md+WHtpPxRl4DtbxgrYYqDq3hBiHDg+Toj6 # gR5wD9k/yvXvJC7F8IhPhOG6Kjkg8KwSJbuV1o3KQLusL/sKVXQW4Zl9HNw7TGsz # pTSyc/f7pC6hggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNjI0MTgwNDQxWjAv # BgkqhkiG9w0BCQQxIgQgPDhslGajfzgOmaZ1NeM8OspgMQhvN6vqPBZjhyhkRZUw # DQYJKoZIhvcNAQEBBQAEggIAXHfPXgDc6atLa0i5bsNOBd+MyMd37wXFMX8rrBNf # CKoB7PwzSajBPUxJ6a+SfsRPi/nlpyy+6QAiUE+8+KBJN2DHwWvDglIUT60ftDc0 # QoLkhVl2UI29cho26Z8/Y8YN4quOrGpnUUiGHt2n/5rxXi/6pxJbSHQbj1WRtKkS # LcSlCLjqaSGseXH+o5VTNmMHDoZFhGxPRE7KKVrjgOZp4IknnEWeREcmZhinjSMk # xMnMOeiMDa3oCI+rfa8t2BC1jHvT4O0TkrjzSylJHs2crVTlgwaEYe5bEQ1ZTf96 # Smwp6x/rneYJW2FqnZqIrcl4nNwCBHsJbpLvUxtXyXptvqPs0qBBlmZAeiBXKQTA # HzgCetVzAgfe6QbH++pfIqicgWCEqspSOku164dwrMYz8s7WrOKkGxNAkepcn6II # c9J0LaND5Z0HOeG6rar5IJZWzqCTlH2HTRkFcy56nL8+IqYNHwzQRYdodZHOSsag # b3V3swjE6BJOYFwS1Qs8IniBCHiyscuzluzPaSpANMYEt/vy/R1f8m80SHaIrkFY # UzRDVkRjpxYiMxZefkF6fujjdyxquJd54AQjkA3x0exmvvlWdQy3QNiDlmFJxh8z # u+Oj3yNnrj57L5C5r9e36yJazdo/zBvlmzxeOzp+1Xp9Yv2xXbyDPQRl+ve8fD8j # sqM= # SIG # End signature block |