EnterpriseTeam.ps1
|
function New-KeeperEnterpriseTeam { <# .SYNOPSIS Create an enterprise team .DESCRIPTION Creates a new Keeper Enterprise Team under the specified parent node. .PARAMETER TeamName Team name (required) .PARAMETER ParentNode Parent node name or ID. Defaults to the enterprise root node. .PARAMETER RestrictView Disable password viewing/copying for team members. .PARAMETER RestrictEdit Disable record editing for team members. .PARAMETER RestrictShare Disable record re-sharing for team members. .EXAMPLE New-KeeperEnterpriseTeam -TeamName "Backend Team" Creates a team under the enterprise root node. .EXAMPLE New-KeeperEnterpriseTeam -TeamName "Support" -ParentNode 12345 -RestrictView Creates a team under node 12345 with restrict view enabled. .EXAMPLE keta "Frontend Team" -ParentNode "Engineering" -RestrictEdit -RestrictShare Creates a team using the keta alias with restrict edit and share enabled. #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)][string]$TeamName, [Parameter()][string] $ParentNode, [Parameter()][Switch] $RestrictView, [Parameter()][Switch] $RestrictEdit, [Parameter()][Switch] $RestrictShare ) [Enterprise]$enterprise = getEnterprise $team = New-Object Keepersecurity.Enterprise.EnterpriseTeam $team.Name = $TeamName [KeeperSecurity.Enterprise.EnterpriseNode] $parent = $null if ($ParentNode) { $parent = resolveSingleNode $ParentNode $team.ParentNodeId = $parent.Id } else { $team.ParentNodeId = $enterprise.enterpriseData.RootNode.Id } if ($RestrictView.IsPresent) { $team.RestrictView = $true } if ($RestrictEdit.IsPresent) { $team.RestrictEdit = $true } if ($RestrictShare.IsPresent) { $team.RestrictSharing = $true } $t = $enterprise.enterpriseData.CreateTeam($team).GetAwaiter().GetResult() $t } New-Alias -Name keta -Value New-KeeperEnterpriseTeam function Update-KeeperEnterpriseTeam { <# .SYNOPSIS Updates an existing enterprise team .DESCRIPTION Updates enterprise team properties including name, parent node, and restrict flags. Only specified parameters are changed; omitted parameters retain their current values. .PARAMETER Team Team UID or team name to update. .PARAMETER TeamName New team display name. .PARAMETER ParentNode Parent node name or ID to move the team under. .PARAMETER RestrictView Disable or enable password viewing/copying: ON or OFF. .PARAMETER RestrictEdit Disable or enable record editing: ON or OFF. .PARAMETER RestrictShare Disable or enable record re-sharing: ON or OFF. .EXAMPLE Update-KeeperEnterpriseTeam -Team "Developers" -RestrictEdit ON Enables restrict edit for the Developers team. .EXAMPLE Update-KeeperEnterpriseTeam -Team "1P7A8XZ9K3J9H" -RestrictShare OFF -RestrictView ON Updates restrict flags for a team identified by UID. .EXAMPLE Update-KeeperEnterpriseTeam -Team "Developers" -TeamName "Engineering" -ParentNode "IT" -RestrictShare OFF Renames the team, moves it to the IT node, and disables restrict share. .EXAMPLE kete "Developers" -RestrictEdit ON Updates a team using the kete alias. #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)][string] $Team, [Parameter()][string] $TeamName, [Parameter()][string] $ParentNode, [Parameter()][ValidateSet('ON', 'OFF')][string] $RestrictView, [Parameter()][ValidateSet('ON', 'OFF')][string] $RestrictEdit, [Parameter()][ValidateSet('ON', 'OFF')][string] $RestrictShare ) [Enterprise]$enterprise = getEnterprise $teamObject = resolveTeam $enterprise.enterpriseData $Team if (-not $teamObject) { return } if ($PSBoundParameters.ContainsKey('TeamName')) { if ([string]::IsNullOrWhiteSpace($TeamName)) { Write-Error "TeamName cannot be empty." -ErrorAction Stop } $teamObject.Name = $TeamName.Trim() } if ($PSBoundParameters.ContainsKey('ParentNode')) { if ([string]::IsNullOrWhiteSpace($ParentNode)) { Write-Error "ParentNode cannot be empty." -ErrorAction Stop } $parent = resolveSingleNode $ParentNode $teamObject.ParentNodeId = $parent.Id } if ($PSBoundParameters.ContainsKey('RestrictView')) { $teamObject.RestrictView = ($RestrictView -eq 'ON') } if ($PSBoundParameters.ContainsKey('RestrictEdit')) { $teamObject.RestrictEdit = ($RestrictEdit -eq 'ON') } if ($PSBoundParameters.ContainsKey('RestrictShare')) { $teamObject.RestrictSharing = ($RestrictShare -eq 'ON') } $updateFields = @('TeamName', 'ParentNode', 'RestrictView', 'RestrictEdit', 'RestrictShare') | Where-Object { $PSBoundParameters.ContainsKey($_) } if ($updateFields.Count -eq 0) { Write-Warning "No changes specified. Use -TeamName, -ParentNode, or restrict flags to update the team." return } try { $updated = $enterprise.enterpriseData.UpdateTeam($teamObject).GetAwaiter().GetResult() Write-Output "Team `"$($updated.Name)`" updated." return $updated } catch { Write-Error "Failed to update team `"$($teamObject.Name)`": $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Update-KeeperEnterpriseTeam -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter New-Alias -Name kete -Value Update-KeeperEnterpriseTeam function Remove-KeeperEnterpriseTeam { <# .SYNOPSIS Deletes an enterprise team .DESCRIPTION Permanently deletes an enterprise team by name or UID. This operation cannot be undone. Use -Force to skip the confirmation prompt. .PARAMETER Team Team UID or team name to delete. .PARAMETER Force Do not prompt for confirmation before deleting the team. .EXAMPLE Remove-KeeperEnterpriseTeam -Team "Old Team" Deletes the team after confirmation. .EXAMPLE Remove-KeeperEnterpriseTeam -Team "1P7A8XZ9K3J9H" -Force Deletes the team identified by UID without prompting for confirmation. .EXAMPLE ketdel "Old Team" -Force Deletes a team using the ketdel alias without confirmation. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)][string] $Team, [Parameter()][switch] $Force ) [Enterprise]$enterprise = getEnterprise $teamObject = resolveTeam $enterprise.enterpriseData $Team if (-not $teamObject) { return } $teamName = $teamObject.Name $teamUid = $teamObject.Uid if (-not $Force -and -not $PSCmdlet.ShouldProcess($teamName, "Delete Enterprise Team")) { return } try { $enterprise.enterpriseData.DeleteTeam($teamUid).GetAwaiter().GetResult() | Out-Null Write-Output "Team `"$teamName`" deleted successfully." } catch { Write-Error "Failed to delete team `"$teamName`": $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseTeam -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter New-Alias -Name ketdel -Value Remove-KeeperEnterpriseTeam function Get-KeeperEnterpriseTeamUser { <# .Synopsis Get a list of enterprise users for team #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Team ) [Enterprise]$enterprise = getEnterprise $enterpriseData = $enterprise.enterpriseData $uid = $null if ($Team -is [String]) { $uids = Get-EnterpriseTeam | Where-Object { $_.Uid -ceq $Team -or $_.Name -ieq $Team } | Select-Object -Property Uid if ($uids.Length -gt 1) { Write-Error -Message "Team name `"$Team`" is not unique. Use Team UID" -ErrorAction Stop } if ($null -ne $uids.Uid) { $uid = $uids.Uid } } elseif ($null -ne $Team.Uid) { $uid = $Team.Uid } if ($uid) { $team = $null if ($enterpriseData.TryGetTeam($uid, [ref]$team)) { foreach ($memberUserId in (Get-EnterpriseDistinctUserIdsForTeam -EnterpriseData $enterpriseData -TeamUid $uid)) { $user = $null if ($enterpriseData.TryGetUserById($memberUserId, [ref]$user)) { $user } } } else { Write-Error -Message "Team `"$uid`" not found" -ErrorAction Stop } } else { Write-Error -Message "Team `"$Team`" not found" -ErrorAction Stop } } New-Alias -Name ketu -Value Get-KeeperEnterpriseTeamUser Register-ArgumentCompleter -CommandName Get-KeeperEnterpriseTeamUser -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter function Add-KeeperEnterpriseTeamMember { <# .SYNOPSIS Adds existing enterprise users to a Keeper team. .DESCRIPTION Adds one or more users by email (-Emails) or by email / enterprise user ID. At least one of -Emails or -User must be specified. Use -HideSharedFolders (on/off) for team admin types. Inactive users are queued to the team. If the user is already on the team, a warning is shown and no duplicate membership is created. When -HideSharedFolders is also specified, the team admin type is updated for that existing member. .PARAMETER Team Team UID or Team Name. .PARAMETER Emails One or more user email addresses to add to the team. .PARAMETER Users One or more user email addresses or enterprise user IDs to add to the team. -User is an alias for this parameter. Use -Emails, -User, or both together. .PARAMETER HideSharedFolders on = Admin Only (no shared folders). off = Admin with shared folder access. Applies when adding active users. Not applied when queueing inactive users (set admin type after the user is active). .EXAMPLE Add-KeeperEnterpriseTeamMember -Team "Engineering" -Emails "alice@example.com", "bob@example.com" .EXAMPLE Add-KeeperEnterpriseTeamMember -Team "Engineering" -User "user@example.com" .EXAMPLE Add-KeeperEnterpriseTeamMember -Team "Engineering" -User "user@example.com" -HideSharedFolders on #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)][string] $Team, [Parameter()][string[]] $Emails, [Parameter()][Alias('User')][string[]] $Users, [ValidateSet('on', 'off')][string]$HideSharedFolders ) try { $userInputs = Get-EnterpriseTeamMemberInputs -EmailInputs $Emails -UserInputs $Users } catch { Write-Error $_.Exception.Message -ErrorAction Stop } [Enterprise]$enterprise = getEnterprise $teamTarget = Resolve-EnterpriseTeamTarget -Enterprise $enterprise -TeamInput $Team if (-not $teamTarget) { return } $processedUserIds = [System.Collections.Generic.HashSet[long]]::new() foreach ($userInput in @($userInputs)) { try { $userObject = resolveUser $enterprise.enterpriseData $userInput } catch { Write-Warning $_.Exception.Message continue } if (-not $processedUserIds.Add($userObject.Id)) { Write-Warning "User `"$($userObject.Email)`" was already specified; skipping duplicate." continue } try { $request = [EnterpriseTeamMembershipRequest]::new() $request.Enterprise = $enterprise $request.User = $userObject $request.TeamTarget = $teamTarget if ($PSBoundParameters.ContainsKey('HideSharedFolders')) { $request.HideSharedFolders = $HideSharedFolders } [void](Add-EnterpriseUserToTeamMembership -Request $request) } catch { Write-Warning "Failed to add user `"$($userObject.Email)`" to team `"$($teamTarget.Name)`": $($_.Exception.Message)" } } } Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseTeamMember -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseTeamMember -ParameterName Emails -ScriptBlock $Keeper_EnterpriseUserCompleter Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseTeamMember -ParameterName User -ScriptBlock $Keeper_EnterpriseUserCompleter Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseTeamMember -ParameterName Users -ScriptBlock $Keeper_EnterpriseUserCompleter function Remove-KeeperEnterpriseTeamMember { <# .SYNOPSIS Removes existing enterprise users from a Keeper team. .DESCRIPTION Removes one or more users by email (-Emails) or by email / enterprise user ID (-User or -Users). At least one of -Emails or -User must be specified. If the user is not on the team, a warning is shown and no remove request is sent. .PARAMETER Team Team UID or Team Name from which the users will be removed. .PARAMETER Emails One or more user email addresses to remove from the team. .PARAMETER Users One or more user email addresses or enterprise user IDs to remove from the team. -User is an alias for this parameter. Use -Emails, -User, or both together. .EXAMPLE Remove-KeeperEnterpriseTeamMember -Team "Engineering" -Emails "alice@example.com", "bob@example.com" .EXAMPLE Remove-KeeperEnterpriseTeamMember -Team "Engineering" -User "user@example.com" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)][string] $Team, [Parameter()][string[]] $Emails, [Parameter()][Alias('User')][string[]] $Users ) try { $userInputs = Get-EnterpriseTeamMemberInputs -EmailInputs $Emails -UserInputs $Users } catch { Write-Error $_.Exception.Message -ErrorAction Stop } [Enterprise]$enterprise = getEnterprise $teamTarget = Resolve-EnterpriseTeamTarget -Enterprise $enterprise -TeamInput $Team if (-not $teamTarget) { return } $teamUid = $teamTarget.Uid $emailList = [System.Collections.Generic.List[string]]::new() $processedUserIds = [System.Collections.Generic.HashSet[long]]::new() foreach ($userInput in @($userInputs)) { try { $userObject = resolveUser $enterprise.enterpriseData $userInput if (-not $processedUserIds.Add($userObject.Id)) { Write-Warning "User `"$($userObject.Email)`" was already specified; skipping duplicate." continue } if (-not (Test-EnterpriseUserOnTeam -EnterpriseData $enterprise.enterpriseData -TeamUid $teamUid -UserId $userObject.Id)) { Write-Warning "User `"$($userObject.Email)`" is not a member of team `"$($teamTarget.Name)`"." continue } [void]$emailList.Add($userObject.Email) } catch { Write-Warning $_.Exception.Message } } if ($emailList.Count -eq 0) { Write-Warning "No team members were removed." return } try { Invoke-EnterpriseRemoveUsersFromTeams -EnterpriseData $enterprise.enterpriseData -Emails $emailList.ToArray() -TeamUids @($teamUid) $enterprise.loader.Load().GetAwaiter().GetResult() | Out-Null Write-Output "Removed $($emailList.Count) user(s) from team '$($teamTarget.Name)'." } catch { Write-Warning "Failed to remove users from team '$($teamTarget.Name)': $($_.Exception.Message)" } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseTeamMember -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseTeamMember -ParameterName Emails -ScriptBlock $Keeper_EnterpriseUserCompleter Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseTeamMember -ParameterName User -ScriptBlock $Keeper_EnterpriseUserCompleter Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseTeamMember -ParameterName Users -ScriptBlock $Keeper_EnterpriseUserCompleter function Get-KeeperEnterpriseTeamRole { <# .SYNOPSIS Gets enterprise roles assigned to a team. .DESCRIPTION Read-only query matching enterprise-team role membership (used by -ar/-rr). Only approved teams are supported; queued-only teams return an empty list. .PARAMETER Team Team name, UID, or EnterpriseTeam object. .EXAMPLE Get-KeeperEnterpriseTeamRole -Team "Engineering" #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Team ) [Enterprise]$enterprise = getEnterprise $teamInput = if ($Team -is [string]) { $Team } elseif ($null -ne $Team.Uid) { [string]$Team.Uid } elseif ($Team.Name) { [string]$Team.Name } else { [string]$Team } $teamTarget = Resolve-EnterpriseTeamTarget -Enterprise $enterprise -TeamInput $teamInput if (-not $teamTarget) { return @() } if (-not $teamTarget.Team) { Write-Warning "Team `"$($teamTarget.Name)`" is queued only. Roles are listed after the team is approved." return @() } $roleIds = @($enterprise.roleData.GetRolesForTeam($teamTarget.Uid)) if ($roleIds.Count -eq 0) { return @() } $roles = foreach ($roleId in $roleIds) { $role = $null if ($enterprise.roleData.TryGetRole($roleId, [ref]$role)) { $role } } return @($roles | Sort-Object { $_.DisplayName }) } Register-ArgumentCompleter -CommandName Get-KeeperEnterpriseTeamRole -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter New-Alias -Name ketr -Value Get-KeeperEnterpriseTeamRole function Get-TeamMembersBatch { <# .SYNOPSIS Fetches team members in batches from the API. .DESCRIPTION Internal helper function that retrieves team member emails for multiple teams using parallel API calls in configurable batch sizes. #> [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory)][KeeperSecurity.Authentication.IAuthentication]$Auth, [Parameter(Mandatory)][array]$TeamUids, [int]$BatchSize = 20 ) if ($TeamUids.Count -eq 0) { return @{} } $results = @{} for ($i = 0; $i -lt $TeamUids.Count; $i += $BatchSize) { $batch = $TeamUids[$i..([Math]::Min($i + $BatchSize - 1, $TeamUids.Count - 1))] $tasks = @{} foreach ($uid in $batch) { try { $request = New-Object Enterprise.GetTeamMemberRequest $request.TeamUid = [Google.Protobuf.ByteString]::CopyFrom( [KeeperSecurity.Utils.CryptoUtils]::Base64UrlDecode($uid)) $tasks[$uid] = $Auth.ExecuteAuthRest( "vault/get_team_members", $request, [Enterprise.GetTeamMemberResponse] ) } catch { Write-Warning "Failed to create request for team $uid : $($_.Exception.Message)" $results[$uid] = [System.Collections.Generic.List[string]]::new() } } if ($tasks.Count -eq 0) { continue } try { [System.Threading.Tasks.Task]::WhenAll($tasks.Values).GetAwaiter().GetResult() | Out-Null } catch { Write-Warning "Some team member requests failed: $($_.Exception.Message)" } foreach ($uid in $tasks.Keys) { $task = $tasks[$uid] if ($task.IsCompletedSuccessfully) { $emails = [System.Collections.Generic.List[string]]::new() if ($task.Result.EnterpriseUser) { foreach ($u in $task.Result.EnterpriseUser) { $emails.Add($u.Email) } } $results[$uid] = $emails } else { $results[$uid] = [System.Collections.Generic.List[string]]::new() } } } return $results } function Get-KeeperEnterpriseTeams { <# .SYNOPSIS Lists all Keeper Enterprise teams. .DESCRIPTION Show details for all teams you have access to within your organization. .PARAMETER ShowMembers List team members from cache (fast, may be incomplete). Alias: -v .PARAMETER ShowAllMembers List team members, fetching from server if cache is empty (slower, complete). Alias: -vv .PARAMETER All Show all teams including those from managed companies (MSP admin). Alias: -a .PARAMETER Sort Sort teams by column: company, team_uid, name (default: company) .EXAMPLE Get-KeeperEnterpriseTeams # Default sort by company Get-KeeperEnterpriseTeams -Sort name # Sort by team name Get-KeeperEnterpriseTeams -Sort team_uid # Sort by team UID Get-KeeperEnterpriseTeams -v # Show members from cache (fast) Get-KeeperEnterpriseTeams -vv # Show all members (fetches from server if needed) Get-KeeperEnterpriseTeams -a # Include teams outside primary organization (MSP admin) Get-KeeperEnterpriseTeams -vv -a # All teams (including managed companies) with complete member list #> [CmdletBinding()] param ( [Parameter()][Alias('v')][Switch] $ShowMembers, [Parameter()][Alias('vv')][Switch] $ShowAllMembers, [Parameter()][Alias('a')][Switch] $All, [Parameter()][ValidateSet('company', 'team_uid', 'name')][string] $Sort = 'company' ) if (-not $Script:Context.Auth) { Write-Error "Not connected. Please run Connect-Keeper first." -ErrorAction Stop } $includeManagedCompanyTeams = $All.IsPresent $memberMode = if ($ShowAllMembers.IsPresent) { 'full' } elseif ($ShowMembers.IsPresent) { 'cache' } else { 'none' } $showMemberInfo = $memberMode -ne 'none' [Enterprise]$enterprise = $null if ($showMemberInfo) { try { $enterprise = getEnterprise } catch { Write-Warning "Could not load enterprise data for member info: $($_.Exception.Message)" $enterprise = $null } if (-not $enterprise -or -not $enterprise.enterpriseData) { Write-Warning "Member information will not be displayed." $showMemberInfo = $false } } $results = [System.Collections.ArrayList]::new() $teamByUid = @{} try { $request = New-Object Records.GetShareObjectsRequest $response = $Script:Context.Auth.ExecuteAuthRest( "vault/get_share_objects", $request, [Records.GetShareObjectsResponse] ).GetAwaiter().GetResult() if (-not $response) { Write-Warning "Empty response from API" return } $enterpriseNames = @{} if ($response.ShareEnterpriseNames) { foreach ($ent in $response.ShareEnterpriseNames) { $enterpriseNames[$ent.EnterpriseId] = $ent.Enterprisename } } $apiTeams = if ($response.ShareTeams) { @($response.ShareTeams) } else { @() } if ($includeManagedCompanyTeams -and $response.ShareMCTeams) { $apiTeams += @($response.ShareMCTeams) } $primaryEnterpriseId = $null try { $primaryEnterpriseId = $Script:Context.Auth.AuthContext.License.EnterpriseId } catch { $primaryEnterpriseId = $null } $hasNoValidEnterpriseId = ($null -eq $primaryEnterpriseId -or $primaryEnterpriseId -le 0) $hasShareTeams = ($response.ShareTeams -and $response.ShareTeams.Count -gt 0) if ($hasNoValidEnterpriseId -and $hasShareTeams) { $primaryEnterpriseId = $response.ShareTeams[0].EnterpriseId } if (-not $includeManagedCompanyTeams -and $null -ne $primaryEnterpriseId -and $primaryEnterpriseId -gt 0) { $apiTeams = @($apiTeams | Where-Object { $_.EnterpriseId -eq $primaryEnterpriseId }) } foreach ($team in $apiTeams) { $teamUid = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlEncode($team.TeamUid.ToByteArray()) if ($teamByUid.ContainsKey($teamUid)) { continue } $companyName = $enterpriseNames[$team.EnterpriseId] $members = [System.Collections.Generic.List[string]]::new() if ($showMemberInfo) { foreach ($userId in $enterprise.enterpriseData.GetUsersForTeam($teamUid)) { $user = $null if ($enterprise.enterpriseData.TryGetUserById($userId, [ref]$user)) { $members.Add($user.Email) } } } $teamByUid[$teamUid] = @{ Uid = $teamUid Name = $team.Teamname Company = $companyName Members = $members } } } catch { Write-Warning "Failed to fetch teams from API: $($_.Exception.Message)" return } $allTeams = @($teamByUid.Values) if ($memberMode -eq 'full' -and $showMemberInfo) { $teamsNeedToFetch = @($allTeams | Where-Object { $_.Members.Count -eq 0 } | ForEach-Object { $_.Uid }) if ($teamsNeedToFetch.Count -gt 0) { $fetchedMembers = Get-TeamMembersBatch -Auth $Script:Context.Auth -TeamUids $teamsNeedToFetch if ($fetchedMembers) { foreach ($team in $allTeams) { if ($team.Members.Count -eq 0 -and $fetchedMembers.ContainsKey($team.Uid)) { $team.Members = $fetchedMembers[$team.Uid] } } } } } $allTeams = @(switch ($Sort) { 'team_uid' { $allTeams | Sort-Object { if ($_.Uid) { $_.Uid.ToLower() } else { '' } } } 'name' { $allTeams | Sort-Object { if ($_.Name) { $_.Name.ToLower() } else { '' } } } default { $allTeams | Sort-Object { if ($_.Company) { $_.Company.ToLower() } else { '' } }, { if ($_.Name) { $_.Name.ToLower() } else { '' } } } }) $index = 0 foreach ($team in $allTeams) { $index++ $props = [ordered]@{ '#' = $index 'Company' = $team.Company 'Team UID' = $team.Uid 'Name' = $team.Name } if ($showMemberInfo) { $props['Member'] = if ($team.Members.Count -gt 0) { $team.Members[0] } else { '' } } [void]$results.Add([PSCustomObject]$props) if ($showMemberInfo) { for ($i = 1; $i -lt $team.Members.Count; $i++) { $memberRow = [ordered]@{ '#' = ''; 'Company' = ''; 'Team UID' = ''; 'Name' = ''; 'Member' = $team.Members[$i] } [void]$results.Add([PSCustomObject]$memberRow) } } } if ($results.Count -eq 0) { Write-Host "No teams found." return } Write-Host "`nFound $($allTeams.Count) team(s).`n" $results | Format-Table -AutoSize } New-Alias -Name list-team -Value Get-KeeperEnterpriseTeams # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBp4ICGgxrSpl5n # lIspdzrSocs1zy4aoPC8APLU2Ut936CCITswggWNMIIEdaADAgECAhAOmxiO+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 # BDEiBCBonOEohUSlJv41kfIcZvfjCVtt5eLjLjlzjbnNpbvc1DANBgkqhkiG9w0B # AQEFAASCAYBo933Ufs3OdO+w1Kl407frqLSMeMYZKfE9L92dP3ULHFPDyP2r9HOq # awbo1mZGBDZzMtB3fd243jhSO/RXnB7wHaGLWfRGzwp/+nppoWHdw25FC0lSFlJ5 # mMr1Q2xBzpGQV8GXgjFYOBe+c1rVF5J90hRIBvOG8O11lzTMNupvlvduZyz35cv8 # hnmUA1ijmhvjBSzmg/NYxUDwf8qfdaJLXyFDQesmDiN5wC5o0LlyebUZe0Xr36fp # JGanf+lKeEcQzvMAKFvcGdtJ/sEnXeAt3MiHuZ/DA4b8M+U3jFHLG44P8X8WagaP # s3Zxaoknd0Yn60OgpIgcMf+sQul12SLrG/hg1TtJ3/kNLGqOzsH0goIzHRNfh4e6 # F81/sfabaJjXBWKpMqwYPwYcHDWOcObGnVzQzT5fcsKTTdJelZLpZ8BmgAJbs10e # Il8k341Gneo/RlUxmF5tclTSQoo1jalYNP1bgG7mx/Ncy4ADY4ldJTtwDW+VvmdR # CH/sjwPvh4yhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwODE0MjExOTEzWjAv # BgkqhkiG9w0BCQQxIgQgur3Q2ZIO+viuZFKdjGi31WedQ/WHkIkiMJe+jetLQkgw # DQYJKoZIhvcNAQEBBQAEggIATToskwn+ozT2Ds1rJqolbsVjcJcfKbdYtJ0gzXk/ # QsziAlDIdzBGHmo6bjzzavCSxuA3i5AdHgA911cebk/S6taASpsLpipYooVLthdY # GNGMq7VFumnDFOttKQM+yzr2wPQgpBH+oeDu4ePqm0vNrk7rHlFfp1LiLEAWF6ur # tSsOsWS1EZzsa1JDFIfJj0D4yWARwXzKVB/YLIByuntvN6UJQA1IEtb0q3TQHQdg # KIHI6yIULvcD4B5mmlHJMQN2QbgB5PTF8AR1isO+bXcarENMHMfUy26bEec738+b # Bmjq9oNHI8Tw3UN4Uiy7EvubK6gd4zy1b3Cmfu09KbkE8mHfe4xJrZ5oNQm6Wqw3 # yYlhFrkFyaWkW1GdgxnGIcnSVz3ZAuo4Evd3XncqGsx5I/qm9cMaCnJIrm7TXzGu # gAoK6CasoN7vDqdhmxo7r778g2W/+hVbshfH4ps45fHNf4xCLqey+wZpVdZLfuud # 2bkiT/n/Y+nh/llCIqkfs0hM3lL+5Mf17/V2UqDnRpVzMMvQTBLerxuDve0CO1ay # c5McNRj2STb5BtszGtMYmgyIP/iisSno1hOszbDODfZvkZNDxXIRegq/YG+d7Z4v # QHv1H7YR1fQ7SmlIXd1yMNpAbgNlDbpg+lgD143VGYN+W40nbO/tPHcxWVewy7I/ # h3M= # SIG # End signature block |