EnterpriseRole.ps1
|
function Get-EnterpriseRole { <# .SYNOPSIS Get a list of enterprise roles .PARAMETER Role Role Name or ID #> [CmdletBinding()] [Enterprise]$enterprise = getEnterprise return $enterprise.roleData.Roles } function Get-KeeperEnterpriseRole { <# .SYNOPSIS Get a list of enterprise roles .PARAMETER Name Role Name or ID (exact match). Returns the single matching role. .PARAMETER Filter Search filter applied across all role properties (case-insensitive regex match). .PARAMETER Format Output format: table (default) or json. .PARAMETER Output File path to export results when Format is 'json'. Ignored for 'table' format. .EXAMPLE Get-KeeperEnterpriseRole Lists all enterprise roles in table format. .EXAMPLE Get-KeeperEnterpriseRole -Name "AdminRole" Returns the enterprise role named "AdminRole". .EXAMPLE Get-KeeperEnterpriseRole -Filter "manager" Returns all enterprise roles whose properties match "manager". .EXAMPLE Get-KeeperEnterpriseRole -Format json -Output "roles.json" Exports all enterprise roles to a JSON file. #> [CmdletBinding()] Param ( [Parameter()][string] $Name, [Parameter()][string] $Filter, [Parameter()][ValidateSet('table', 'json')][string] $Format = 'table', [Parameter()][string] $Output ) if ($Name) { $Name = $Name.Trim() } if ($Filter) { $Filter = $Filter.Trim() } $roles = Get-EnterpriseRole if (-not $roles) { Write-Warning "No enterprise roles found." return @() } if ($Name) { $roles = $roles | Where-Object { ($_.DisplayName -eq $Name) -or ($_.Id.ToString() -eq $Name) } } if ($Filter) { $filterLower = $Filter.ToLower() $roles = $roles | Where-Object { $text = ($_.PSObject.Properties.Value | ForEach-Object { "$_" }) -join ' ' $text -match [regex]::Escape($filterLower) } } $result = @($roles) if ($result.Count -eq 0 -and ($Name -or $Filter)) { Write-Host "No matching enterprise roles found." -ForegroundColor Yellow return @() } if ($Format -eq 'json') { $json = $result | ConvertTo-Json -Depth 5 if ($Output) { Set-Content -Path $Output -Value $json -Encoding utf8 Write-Host "Results exported to: $Output" -ForegroundColor Green } else { return $json } } else { return $result } } New-Alias -Name ker -Value Get-KeeperEnterpriseRole function Get-KeeperEnterpriseRoleUsers { <# .SYNOPSIS Get a list of enterprise users for a role .PARAMETER Role Role Name or ID #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role ) [Enterprise]$enterprise = getEnterprise $enterpriseData = $enterprise.enterpriseData $roleData = $enterprise.roleData $roleId = $null if ($Role -is [String]) { $ids = @(Get-KeeperEnterpriseRole | Where-Object { $_.Id -eq $Role -or $_.DisplayName -ieq $Role } | Select-Object -Property Id) if ($ids.Length -gt 1) { Write-Error -Message "Role name `"$Role`" is not unique. Use Role ID" -ErrorAction Stop } if ($ids.Length -eq 1 -and $null -ne $ids[0].Id) { $roleId = $ids[0].Id } } elseif ($Role -is [long]) { $ids = @(Get-KeeperEnterpriseRole | Where-Object { $_.Id -ceq $Role } | Select-Object -Property Id -First 1) if ($ids.Length -eq 1 -and $null -ne $ids[0].Id) { $roleId = $ids[0].Id } } elseif ($null -ne $Role.Id) { $roleId = $Role.Id } if ($roleId) { $erole = $null if ($roleData.TryGetRole($roleId, [ref]$erole)) { foreach ($userId in $roleData.GetUsersForRole($erole.Id)) { $user = $null if ($enterpriseData.TryGetUserById($userId, [ref]$user)) { Write-Output $user } } } else { Write-Error -Message "Role `"$roleId`" not found" -ErrorAction Stop } } else { Write-Error -Message "Role `"$Role`" not found" -ErrorAction Stop } } New-Alias -Name keru -Value Get-KeeperEnterpriseRoleUsers Register-ArgumentCompleter -CommandName Get-KeeperEnterpriseRoleUsers -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter function Get-KeeperEnterpriseRoleTeams { <# .SYNOPSIS Get a list of enterprise teams for a role .PARAMETER Role Role Name or ID #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role ) [Enterprise]$enterprise = getEnterprise $enterpriseData = $enterprise.enterpriseData $roleData = $enterprise.roleData $roleId = $null if ($Role -is [String]) { $ids = @(Get-KeeperEnterpriseRole | Where-Object { $_.Id -eq $Role -or $_.DisplayName -ieq $Role } | Select-Object -Property Id) if ($ids.Length -gt 1) { Write-Error -Message "Role name `"$Role`" is not unique. Use Role ID" -ErrorAction Stop } if ($ids.Length -eq 1 -and $null -ne $ids[0].Id) { $roleId = $ids[0].Id } } elseif ($Role -is [long]) { $ids = @(Get-KeeperEnterpriseRole | Where-Object { $_.Id -ceq $Role } | Select-Object -Property Id -First 1) if ($ids.Length -eq 1 -and $null -ne $ids[0].Id) { $roleId = $ids[0].Id } } elseif ($null -ne $Role.Id) { $roleId = $Role.Id } if ($roleId) { $erole = $null if ($roleData.TryGetRole($roleId, [ref]$erole)) { foreach ($teamUid in $roleData.GetTeamsForRole($erole.Id)) { $team = $null if ($enterpriseData.TryGetTeam($teamUid, [ref]$team)) { Write-Output $team } } } else { Write-Error -Message "Role `"$roleId`" not found" -ErrorAction Stop } } else { Write-Error -Message "Role `"$Role`" not found" -ErrorAction Stop } } New-Alias -Name kert -Value Get-KeeperEnterpriseRoleTeams Register-ArgumentCompleter -CommandName Get-KeeperEnterpriseRoleTeams -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter function Get-KeeperEnterpriseAdminRole { <# .SYNOPSIS Get a list of Administrator Permissions .PARAMETER Pattern Role search pattern #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $false)]$Pattern ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roles = $null if ($Pattern -is [String]) { $roles = Get-EnterpriseRole | Where-Object { $_.Id -eq $Pattern -or $_.DisplayName -match $Pattern } } elseif ($Pattern -is [long]) { $roles = Get-EnterpriseRole | Where-Object { $_.Id -eq $Pattern } } elseif ($null -ne $Pattern.Id) { $roles = $Pattern } else { $roles = Get-EnterpriseRole } if ($null -ne $roles -and $roles.Length -gt 0 ) { $roles = $roles | Sort-Object -Property DisplayName foreach ($role in $roles) { if ($null -ne $role.Id) { foreach ($rp in $roleData.GetRolePermissions($role.Id)) { $rp } } } } else { Write-Error -Message "Role `"$Role`" not found" -ErrorAction Stop } } New-Alias -Name kerap -Value Get-KeeperEnterpriseAdminRole function Set-KeeperEnterpriseRole { <# .SYNOPSIS Updates Enterprise Role properties .DESCRIPTION Updates properties of an existing Enterprise Role, such as setting it as default for new users. .PARAMETER Role Role Name or ID, or EnterpriseRole object .PARAMETER NewUserInherit Set role as default for new users in the node. If specified, this will update the NewUserInherit property. .PARAMETER VisibleBelow Set role visibility to subnodes. If specified, this will update the VisibleBelow property. .PARAMETER NewDisplayName New role display name. If specified, this will update the role name. .EXAMPLE Set-KeeperEnterpriseRole -Role "MyRole" -NewUserInherit $true Sets the role "MyRole" as the default role for new users .EXAMPLE Set-KeeperEnterpriseRole -Role 123456789 -NewUserInherit $false Removes the role with ID 123456789 as the default role for new users .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Set-KeeperEnterpriseRole -NewUserInherit $true Sets the role using pipeline input #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter()][bool]$NewUserInherit, [Parameter()][bool]$VisibleBelow, [Parameter()][string]$NewDisplayName ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $updateParams = @{} if ($PSBoundParameters.ContainsKey('NewUserInherit')) { $updateParams['newUserInherit'] = $NewUserInherit } if ($PSBoundParameters.ContainsKey('VisibleBelow')) { $updateParams['visibleBelow'] = $VisibleBelow } if ($PSBoundParameters.ContainsKey('NewDisplayName')) { $updateParams['displayName'] = $NewDisplayName } if ($updateParams.Count -eq 0) { Write-Warning "No properties specified to update. Use -NewUserInherit, -VisibleBelow, or -NewDisplayName parameters." return } $roleName = $roleObject.DisplayName if ($PSCmdlet.ShouldProcess($roleName, "Update Enterprise Role")) { try { $updatedRole = $roleData.UpdateRole( $roleObject, $updateParams['newUserInherit'], $updateParams['visibleBelow'], $updateParams['displayName'] ).GetAwaiter().GetResult() if ($updatedRole) { Write-Output "Role `"$($updatedRole.DisplayName)`" updated successfully" $updatedRole } } catch { Write-Error "Failed to update role `"$roleName`": $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Set-KeeperEnterpriseRole -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name kers -Value Set-KeeperEnterpriseRole function Grant-KeeperEnterpriseRoleToUser { <# .SYNOPSIS Adds a user to an Enterprise Role .DESCRIPTION Assigns an existing enterprise user to an enterprise role. .PARAMETER Role Role Name, ID, or EnterpriseRole object .PARAMETER User User email, ID, or EnterpriseUser object .EXAMPLE Grant-KeeperEnterpriseRoleToUser -Role "MyRole" -User "user@example.com" Adds the user to the role .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Grant-KeeperEnterpriseRoleToUser -User "user@example.com" Adds the user using pipeline input for the role #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)]$User ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $enterpriseData = $enterprise.enterpriseData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $userObject = resolveUser $enterpriseData $User if (-not $userObject) { return } $roleName = $roleObject.DisplayName $userEmail = $userObject.Email if ($PSCmdlet.ShouldProcess("User `"$userEmail`" to Role `"$roleName`"", "Add")) { try { $roleData.AddUserToRole($roleObject, $userObject).GetAwaiter().GetResult() | Out-Null Write-Output "User `"$userEmail`" added to role `"$roleName`"" } catch { Write-Error "Failed to add user `"$userEmail`" to role `"$roleName`": $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Grant-KeeperEnterpriseRoleToUser -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter Register-ArgumentCompleter -CommandName Grant-KeeperEnterpriseRoleToUser -ParameterName User -ScriptBlock $Keeper_ActiveUserCompleter New-Alias -Name kerua -Value Grant-KeeperEnterpriseRoleToUser function Revoke-KeeperEnterpriseRoleFromUser { <# .SYNOPSIS Removes a user from an Enterprise Role .DESCRIPTION Removes an enterprise user from an enterprise role. .PARAMETER Role Role Name, ID, or EnterpriseRole object .PARAMETER User User email, ID, or EnterpriseUser object .EXAMPLE Revoke-KeeperEnterpriseRoleFromUser -Role "MyRole" -User "user@example.com" Removes the user from the role .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Revoke-KeeperEnterpriseRoleFromUser -User "user@example.com" Removes the user using pipeline input for the role #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)]$User ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $enterpriseData = $enterprise.enterpriseData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $userObject = resolveUser $enterpriseData $User if (-not $userObject) { return } $roleName = $roleObject.DisplayName $userEmail = $userObject.Email if ($PSCmdlet.ShouldProcess("User `"$userEmail`" from Role `"$roleName`"", "Remove")) { try { $roleData.RemoveUserFromRole($roleObject, $userObject).GetAwaiter().GetResult() | Out-Null Write-Output "User `"$userEmail`" removed from role `"$roleName`"" } catch { Write-Error "Failed to remove user `"$userEmail`" from role `"$roleName`": $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Revoke-KeeperEnterpriseRoleFromUser -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter Register-ArgumentCompleter -CommandName Revoke-KeeperEnterpriseRoleFromUser -ParameterName User -ScriptBlock $Keeper_ActiveUserCompleter New-Alias -Name kerur -Value Revoke-KeeperEnterpriseRoleFromUser function Grant-KeeperEnterpriseRoleToTeam { <# .SYNOPSIS Adds a team to an Enterprise Role .DESCRIPTION Assigns an existing enterprise team to an enterprise role. .PARAMETER Role Role Name, ID, or EnterpriseRole object .PARAMETER Team Team UID, Name, or EnterpriseTeam object .EXAMPLE Grant-KeeperEnterpriseRoleToTeam -Role "MyRole" -Team "Engineering" Adds the team to the role .EXAMPLE Grant-KeeperEnterpriseRoleToTeam -Role "MyRole" -Team "1P7A8XZ9K3J9H" Adds the team using Team UID .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Grant-KeeperEnterpriseRoleToTeam -Team "Engineering" Adds the team using pipeline input for the role #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)]$Team ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $enterpriseData = $enterprise.enterpriseData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $teamObject = resolveTeam $enterpriseData $Team if (-not $teamObject) { return } $roleName = $roleObject.DisplayName $teamName = $teamObject.Name if ($PSCmdlet.ShouldProcess("Team `"$teamName`" to Role `"$roleName`"", "Add")) { try { $roleData.AddTeamToRole($roleObject, $teamObject).GetAwaiter().GetResult() | Out-Null Write-Output "Team `"$teamName`" added to role `"$roleName`"" } catch { Write-Error "Failed to add team `"$teamName`" to role `"$roleName`": $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Grant-KeeperEnterpriseRoleToTeam -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter Register-ArgumentCompleter -CommandName Grant-KeeperEnterpriseRoleToTeam -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter New-Alias -Name kerta -Value Grant-KeeperEnterpriseRoleToTeam function Revoke-KeeperEnterpriseRoleFromTeam { <# .SYNOPSIS Removes a team from an Enterprise Role .DESCRIPTION Removes an enterprise team from an enterprise role. .PARAMETER Role Role Name, ID, or EnterpriseRole object .PARAMETER Team Team UID, Name, or EnterpriseTeam object .EXAMPLE Revoke-KeeperEnterpriseRoleFromTeam -Role "MyRole" -Team "Engineering" Removes the team from the role .EXAMPLE Revoke-KeeperEnterpriseRoleFromTeam -Role "MyRole" -Team "1P7A8XZ9K3J9H" Removes the team using Team UID .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Revoke-KeeperEnterpriseRoleFromTeam -Team "Engineering" Removes the team using pipeline input for the role #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)]$Team ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $enterpriseData = $enterprise.enterpriseData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $teamObject = resolveTeam $enterpriseData $Team if (-not $teamObject) { return } $roleName = $roleObject.DisplayName $teamName = $teamObject.Name if ($PSCmdlet.ShouldProcess("Team `"$teamName`" from Role `"$roleName`"", "Remove")) { try { $roleData.RemoveTeamFromRole($roleObject, $teamObject).GetAwaiter().GetResult() | Out-Null Write-Output "Team `"$teamName`" removed from role `"$roleName`"" } catch { Write-Error "Failed to remove team `"$teamName`" from role `"$roleName`": $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Revoke-KeeperEnterpriseRoleFromTeam -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter Register-ArgumentCompleter -CommandName Revoke-KeeperEnterpriseRoleFromTeam -ParameterName Team -ScriptBlock $Keeper_TeamNameCompleter New-Alias -Name kertr -Value Revoke-KeeperEnterpriseRoleFromTeam function New-KeeperEnterpriseRole { <# .SYNOPSIS Create new enterprise role in the Keeper Enterprise. .DESCRIPTION Creates new enterprise role with optional settings for parent node, new user inheritance, visibility, and enforcements. .PARAMETER Role Role Name of the new role. .PARAMETER ParentNode Parent node name or ID. If not specified, the role will be created in the root node. .PARAMETER NewUser Assign this role to new users. Valid values: 'ON', 'OFF'. Default is 'OFF'. .PARAMETER VisibleBelow Make role visible to all subnodes. Valid values: 'ON', 'OFF'. Default is 'OFF'. .PARAMETER Enforcement Sets role enforcement in KEY:VALUE format. Can be repeated multiple times. .PARAMETER Force Do not prompt for confirmation when a role with the same name already exists. .EXAMPLE New-KeeperEnterpriseRole -Role "Manager" Creates a role named "Manager" in the root node. .EXAMPLE New-KeeperEnterpriseRole -Role "Manager", "Employee" -ParentNode "Sales" -NewUser "ON" Creates two roles "Manager" and "Employee" in the "Sales" node, assigned to new users. .EXAMPLE New-KeeperEnterpriseRole -Role "Admin" -ParentNode 123456789 -VisibleBelow "ON" -Enforcement "logout_timer_desktop:3600" Creates an "Admin" role in node 123456789, visible to subnodes, with a logout timer enforcement. .EXAMPLE New-KeeperEnterpriseRole -Role "TestRole" -Force Creates a role even if one with the same name already exists, without prompting. #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $false)] [string[]]$Role, [Parameter()][string]$ParentNode, [Parameter()][ValidateSet('ON', 'OFF')][string]$NewUser = 'OFF', [Parameter()][ValidateSet('ON', 'OFF')][string]$VisibleBelow = 'OFF', [Parameter()][string[]]$Enforcement, [Parameter()][switch]$Force ) [Enterprise]$enterprise = getEnterprise $enterpriseData = $enterprise.enterpriseData $roleData = $enterprise.roleData $auth = $enterprise.loader.Auth $nodeId = $null if (-not [string]::IsNullOrWhiteSpace($ParentNode)) { $ParentNode = $ParentNode.Trim() $parsedId = 0 if ([long]::TryParse($ParentNode, [ref]$parsedId)) { $node = $null if ($enterpriseData.TryGetNode($parsedId, [ref]$node)) { $nodeId = $parsedId } } if (-not $nodeId) { $nodes = @($enterpriseData.Nodes | Where-Object { $_.DisplayName -ieq $ParentNode }) if ($nodes.Count -eq 1) { $nodeId = $nodes[0].Id } elseif ($nodes.Count -eq 0) { Write-Error "Node `"$ParentNode`" not found" -ErrorAction Stop return } else { Write-Error "More than one node with name `"$ParentNode`" are found. Use Node ID." -ErrorAction Stop return } } } else { $nodeId = $enterpriseData.RootNode.Id } $newUserInherit = $NewUser -eq 'ON' $visibleBelowBool = $VisibleBelow -eq 'ON' $uniqueRoles = $Role | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique if ($uniqueRoles.Count -ne ($Role | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count) { Write-Warning "Duplicate role names detected in input. Only unique names will be processed." } $Role = $uniqueRoles $allRoles = Get-EnterpriseRole if ($Enforcement -and $Enforcement.Count -gt 0) { foreach ($enf in $Enforcement) { if (-not [string]::IsNullOrWhiteSpace($enf) -and $enf -notmatch '^[^:]+:.+$') { Write-Warning "Enforcement `"$enf`" does not match KEY:VALUE format. It will be skipped." } } } $createdRoles = @() foreach ($roleName in $Role) { if ([string]::IsNullOrWhiteSpace($roleName)) { Write-Warning "Skipping empty role name" continue } $roleName = $roleName.Trim() if ($roleName.Length -gt 255) { Write-Error "Role name `"$roleName`" exceeds maximum length of 255 characters" -ErrorAction Continue continue } $existingRoles = $allRoles | Where-Object { $_.DisplayName -ieq $roleName } if ($existingRoles.Count -gt 0) { if (-not $Force) { $confirmation = Read-Host "Role with name `"$roleName`" already exists. Do you want to create a new one? (Yes/No)" if ($confirmation -notmatch '^[Yy]([Ee][Ss])?$') { Write-Output "Skipping role `"$roleName`"" continue } } else { Write-Verbose "Role `"$roleName`" already exists, but Force is set. Creating anyway." } } if ($PSCmdlet.ShouldProcess($roleName, "Create Enterprise Role")) { try { $createdRole = $roleData.CreateRole($roleName, $nodeId, $newUserInherit).GetAwaiter().GetResult() if (-not $createdRole) { Write-Error "Failed to create role `"$roleName`"" -ErrorAction Continue continue } Write-Output "Role `"$roleName`" created successfully (ID: $($createdRole.Id))" if ($visibleBelowBool) { try { $updatedRole = $roleData.UpdateRole($createdRole, $null, $true, $null).GetAwaiter().GetResult() if ($updatedRole) { Write-Verbose "Role `"$roleName`" set to visible below subnodes" } } catch { Write-Warning "Failed to set VisibleBelow for role `"$roleName`": $($_.Exception.Message)" } } if ($Enforcement -and $Enforcement.Count -gt 0) { foreach ($enf in $Enforcement) { if ([string]::IsNullOrWhiteSpace($enf)) { continue } $parts = $enf -split ':', 2 if ($parts.Count -ne 2) { Write-Warning "Invalid enforcement format `"$enf`". Expected KEY:VALUE format. Skipping." continue } $enforcementKey = $parts[0].Trim() $enforcementValue = $parts[1].Trim() if ([string]::IsNullOrWhiteSpace($enforcementKey) -or [string]::IsNullOrWhiteSpace($enforcementValue)) { Write-Warning "Invalid enforcement format `"$enf`". Key and Value cannot be empty. Skipping." continue } try { $enfCmd = New-Object KeeperSecurity.Commands.RoleEnforcementAddCommand $enfCmd.RoleId = $createdRole.Id $enfCmd.Enforcement = $enforcementKey $enfCmd.Value = $enforcementValue [KeeperSecurity.Authentication.AuthExtensions]::ExecuteAuthCommand($auth, $enfCmd).GetAwaiter().GetResult() | Out-Null Write-Verbose "Added enforcement `"$enforcementKey`" = `"$enforcementValue`" to role `"$roleName`"" } catch { Write-Warning "Failed to add enforcement `"$enf`" to role `"$roleName`": $($_.Exception.Message)" } } } try { $enterprise.loader.Load().GetAwaiter().GetResult() | Out-Null } catch { Write-Warning "Failed to reload enterprise data after creating role `"$roleName`": $($_.Exception.Message)" } $finalRole = $null if ($roleData.TryGetRole($createdRole.Id, [ref]$finalRole)) { $createdRoles += $finalRole } else { Write-Warning "Role `"$roleName`" was created (ID: $($createdRole.Id)) but could not be retrieved after reload. The role may still exist." $createdRoles += $createdRole } } catch { Write-Error "Failed to create role `"$roleName`": $($_.Exception.Message)" ` -ErrorAction Continue ` -ErrorId "RoleCreationFailed" ` -Category InvalidOperation continue } } } return $createdRoles } Register-ArgumentCompleter -CommandName New-KeeperEnterpriseRole -ParameterName ParentNode -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $result = @() [Enterprise]$enterprise = $Script:Context.Enterprise if (-not $enterprise) { return $null } if ($wordToComplete) { $to_complete = $wordToComplete + '*' } else { $to_complete = '*' } foreach ($node in $enterprise.enterpriseData.Nodes) { if ($node.DisplayName -like $to_complete) { $nodeName = $node.DisplayName if ($nodeName -match '[\s'']') { $nodeName = $nodeName -replace '''', '''''' $nodeName = "'${nodeName}'" } $result += $nodeName } } if ($result.Count -gt 0) { return $result } else { return $null } } New-Alias -Name keradd -Value New-KeeperEnterpriseRole function Remove-KeeperEnterpriseRole { <# .SYNOPSIS Delete an enterprise role .DESCRIPTION Removes an enterprise role from the Keeper Enterprise. This operation cannot be undone. .PARAMETER Role Role Name, ID, or EnterpriseRole object to delete .PARAMETER Force Do not prompt for confirmation before deleting the role .EXAMPLE Remove-KeeperEnterpriseRole -Role "MyRole" Deletes the role named "MyRole" after confirmation .EXAMPLE Remove-KeeperEnterpriseRole -Role "MyRole" -Force Deletes the role named "MyRole" without prompting for confirmation .EXAMPLE Get-EnterpriseRole | Where-Object { $_.DisplayName -eq "MyRole" } | Remove-KeeperEnterpriseRole Deletes a role using pipeline input #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')] Param ( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]$Role, [Parameter()][switch]$Force ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $roleName = $roleObject.DisplayName $roleId = $roleObject.Id if (-not $Force -and -not $PSCmdlet.ShouldProcess($roleName, "Delete Enterprise Role")) { return } try { $roleData.DeleteRole($roleObject).GetAwaiter().GetResult() | Out-Null Write-Output "Role `"$roleName`" (ID: $roleId) deleted successfully" } catch { Write-Error "Failed to delete role `"$roleName`": $($_.Exception.Message)" ` -ErrorAction Stop ` -ErrorId "RoleDeletionFailed" ` -Category InvalidOperation } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseRole -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name kerdel -Value Remove-KeeperEnterpriseRole function Add-KeeperEnterpriseRoleManagedNode { <# .SYNOPSIS Adds a managed node to an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Node Node name or ID to add as a managed node .PARAMETER Cascade Cascade node management to subnodes .DESCRIPTION Adds a node as a managed node to an enterprise role. This allows the role to manage the specified node and optionally cascade management to subnodes. .EXAMPLE Add-KeeperEnterpriseRoleManagedNode -Role "AdminRole" -Node "Sales" Adds the Sales node as a managed node to the AdminRole .EXAMPLE Add-KeeperEnterpriseRoleManagedNode -Role 123456789 -Node 987654321 -Cascade Adds node 987654321 as a managed node to role 123456789 with cascade enabled #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string]$Node, [Parameter()][bool]$Cascade = $false ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $targetNode = resolveSingleNode $Node if (-not $targetNode) { Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop } try { $roleData.RoleManagedNodeAdd($roleObject, $targetNode, ($Cascade -eq $true)).GetAwaiter().GetResult() | Out-Null $nodeDisplayName = if ([string]::IsNullOrEmpty($targetNode.DisplayName)) { $targetNode.Id.ToString() } else { $targetNode.DisplayName } Write-Output "Managed node `"$nodeDisplayName`" added to role `"$($roleObject.DisplayName)`" successfully." } catch { Write-Error -Message "Failed to add managed node: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseRoleManagedNode -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Add-KeeperRoleManagedNode -Value Add-KeeperEnterpriseRoleManagedNode function Update-KeeperEnterpriseRoleManagedNode { <# .SYNOPSIS Updates a managed node configuration for an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Node Node name or ID of the managed node to update .PARAMETER Cascade Cascade node management to subnodes .DESCRIPTION Updates the cascade setting for a managed node in an enterprise role. .EXAMPLE Update-KeeperEnterpriseRoleManagedNode -Role "AdminRole" -Node "Sales" -Cascade Updates the Sales managed node in AdminRole to enable cascade #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string]$Node, [Parameter()][bool]$Cascade = $false ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $targetNode = resolveSingleNode $Node if (-not $targetNode) { Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop } try { $roleData.RoleManagedNodeUpdate($roleObject, $targetNode, ($Cascade -eq $true)).GetAwaiter().GetResult() | Out-Null $nodeDisplayName = if ([string]::IsNullOrEmpty($targetNode.DisplayName)) { $targetNode.Id.ToString() } else { $targetNode.DisplayName } Write-Output "Managed node `"$nodeDisplayName`" updated for role `"$($roleObject.DisplayName)`" successfully." } catch { Write-Error -Message "Failed to update managed node: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Update-KeeperEnterpriseRoleManagedNode -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Update-KeeperRoleManagedNode -Value Update-KeeperEnterpriseRoleManagedNode function Remove-KeeperEnterpriseRoleManagedNode { <# .SYNOPSIS Removes a managed node from an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Node Node name or ID of the managed node to remove .DESCRIPTION Removes a node from the managed nodes list of an enterprise role. .EXAMPLE Remove-KeeperEnterpriseRoleManagedNode -Role "AdminRole" -Node "Sales" Removes the Sales node from the managed nodes of AdminRole #> [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string]$Node ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $targetNode = resolveSingleNode $Node if (-not $targetNode) { Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop } $nodeDisplayName = if ([string]::IsNullOrEmpty($targetNode.DisplayName)) { $targetNode.Id.ToString() } else { $targetNode.DisplayName } if ($PSCmdlet.ShouldProcess("Managed node `"$nodeDisplayName`" from role `"$($roleObject.DisplayName)`"", "Remove")) { try { $roleData.RoleManagedNodeRemove($roleObject, $targetNode).GetAwaiter().GetResult() | Out-Null Write-Output "Managed node `"$nodeDisplayName`" deleted from role `"$($roleObject.DisplayName)`" successfully." } catch { Write-Error -Message "Failed to remove managed node: $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseRoleManagedNode -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Remove-KeeperRoleManagedNode -Value Remove-KeeperEnterpriseRoleManagedNode function Add-KeeperEnterpriseRolePrivilege { <# .SYNOPSIS Adds privileges to a managed node for an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Node Node name or ID of the managed node .PARAMETER Privilege One or more privilege names to add. Valid values: MANAGE_NODES, MANAGE_USER, MANAGE_LICENCES, MANAGE_ROLES, MANAGE_TEAMS, TRANSFER_ACCOUNT, RUN_REPORTS, VIEW_TREE, MANAGE_BRIDGE, MANAGE_COMPANIES, SHARING_ADMINISTRATOR, APPROVE_DEVICE, MANAGE_RECORD_TYPES, RUN_COMPLIANCE_REPORTS .DESCRIPTION Adds privileges to a managed node for an enterprise role. The node must already be a managed node for the role. .EXAMPLE Add-KeeperEnterpriseRolePrivilege -Role "AdminRole" -Node "Sales" -Privilege "MANAGE_USERS", "MANAGE_TEAMS" Adds MANAGE_USERS and MANAGE_TEAMS privileges to the Sales managed node for AdminRole .EXAMPLE Add-KeeperEnterpriseRolePrivilege -Role 123456789 -Node "Sales" -Privilege "RUN_REPORTS" Adds RUN_REPORTS privilege using role ID #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string]$Node, [Parameter(Position = 2, Mandatory = $true)][string[]]$Privilege ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $targetNode = resolveSingleNode $Node if (-not $targetNode) { Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop } $managedNodes = $roleData.GetManagedNodes() | Where-Object { $_.RoleId -eq $roleObject.Id -and $_.ManagedNodeId -eq $targetNode.Id } if ($managedNodes.Count -eq 0) { $nodeDisplayName = if ([string]::IsNullOrEmpty($targetNode.DisplayName)) { $targetNode.Id.ToString() } else { $targetNode.DisplayName } Write-Error -Message "Role `"$($roleObject.DisplayName)`" does not have node `"$nodeDisplayName`" as a managed node. Use Add-KeeperEnterpriseRoleManagedNode first." -ErrorAction Stop } $privilegeList = New-Object System.Collections.Generic.List[KeeperSecurity.Enterprise.RoleManagedNodePrivilege] $invalidPrivileges = @() foreach ($priv in $Privilege) { $privTrimmed = ($priv.Trim() -replace '\s+', '_').ToUpperInvariant() try { $parsedPriv = [Enum]::Parse([KeeperSecurity.Enterprise.RoleManagedNodePrivilege], $privTrimmed, $true) $privilegeList.Add($parsedPriv) } catch { $invalidPrivileges += $priv.Trim() } } if ($invalidPrivileges.Count -gt 0) { $validValues = [System.Enum]::GetNames([KeeperSecurity.Enterprise.RoleManagedNodePrivilege]) -join ", " Write-Error -Message "Invalid privileges: $($invalidPrivileges -join ', '). Valid values: $validValues" -ErrorAction Stop } if ($privilegeList.Count -eq 0) { Write-Error -Message "No valid privileges specified." -ErrorAction Stop } try { $responses = $roleData.RoleManagedNodePrivilegeAddBatch($roleObject, $targetNode, $privilegeList).GetAwaiter().GetResult() for ($i = 0; $i -lt $responses.Count; $i++) { $response = $responses[$i] $privilege = $privilegeList[$i] if ($response.IsSuccess) { Write-Output "Command: $($response.command), Privilege: $privilege, Result: $($response.result)" } else { Write-Output "Command: $($response.command), Privilege: $privilege, Result: $($response.result), Code: $($response.resultCode), Message: $($response.message)" } } } catch { Write-Error -Message "Failed to add privileges: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseRolePrivilege -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Add-KeeperRolePrivilege -Value Add-KeeperEnterpriseRolePrivilege function Remove-KeeperEnterpriseRolePrivilege { <# .SYNOPSIS Removes privileges from a managed node for an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Node Node name or ID of the managed node .PARAMETER Privilege One or more privilege names to remove. Valid values: MANAGE_NODES, MANAGE_USER, MANAGE_LICENCES, MANAGE_ROLES, MANAGE_TEAMS, TRANSFER_ACCOUNT, RUN_REPORTS, VIEW_TREE, MANAGE_BRIDGE, MANAGE_COMPANIES, SHARING_ADMINISTRATOR, APPROVE_DEVICE, MANAGE_RECORD_TYPES, RUN_COMPLIANCE_REPORTS .DESCRIPTION Removes privileges from a managed node for an enterprise role. .EXAMPLE Remove-KeeperEnterpriseRolePrivilege -Role "AdminRole" -Node "Sales" -Privilege "MANAGE_USERS" Removes MANAGE_USERS privilege from the Sales managed node for AdminRole #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string]$Node, [Parameter(Position = 2, Mandatory = $true)][string[]]$Privilege ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } $targetNode = resolveSingleNode $Node if (-not $targetNode) { Write-Error -Message "Node `"$Node`" not found" -ErrorAction Stop } $managedNodes = $roleData.GetManagedNodes() | Where-Object { $_.RoleId -eq $roleObject.Id -and $_.ManagedNodeId -eq $targetNode.Id } if ($managedNodes.Count -eq 0) { $nodeDisplayName = if ([string]::IsNullOrEmpty($targetNode.DisplayName)) { $targetNode.Id.ToString() } else { $targetNode.DisplayName } Write-Error -Message "Role `"$($roleObject.DisplayName)`" does not have node `"$nodeDisplayName`" as a managed node. Use Add-KeeperEnterpriseRoleManagedNode first." -ErrorAction Stop } $privilegeList = New-Object System.Collections.Generic.List[KeeperSecurity.Enterprise.RoleManagedNodePrivilege] $invalidPrivileges = @() foreach ($priv in $Privilege) { $privTrimmed = ($priv.Trim() -replace '\s+', '_').ToUpperInvariant() try { $parsedPriv = [Enum]::Parse([KeeperSecurity.Enterprise.RoleManagedNodePrivilege], $privTrimmed, $true) $privilegeList.Add($parsedPriv) } catch { $invalidPrivileges += $priv.Trim() } } if ($invalidPrivileges.Count -gt 0) { $validValues = [System.Enum]::GetNames([KeeperSecurity.Enterprise.RoleManagedNodePrivilege]) -join ", " Write-Error -Message "Invalid privileges: $($invalidPrivileges -join ', '). Valid values: $validValues" -ErrorAction Stop } if ($privilegeList.Count -eq 0) { Write-Error -Message "No valid privileges specified." -ErrorAction Stop } try { $responses = $roleData.RoleManagedNodePrivilegeRemoveBatch($roleObject, $targetNode, $privilegeList).GetAwaiter().GetResult() for ($i = 0; $i -lt $responses.Count; $i++) { $response = $responses[$i] $privilege = $privilegeList[$i] if ($response.IsSuccess) { Write-Output "Command: $($response.command), Privilege: $privilege, Result: $($response.result)" } else { Write-Output "Command: $($response.command), Privilege: $privilege, Result: $($response.result), Code: $($response.resultCode), Message: $($response.message)" } } } catch { Write-Error -Message "Failed to remove privileges: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseRolePrivilege -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Remove-KeeperRolePrivilege -Value Remove-KeeperEnterpriseRolePrivilege function Add-KeeperEnterpriseRoleEnforcement { <# .SYNOPSIS Adds enforcements to an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Enforcement Enforcement(s) in KEY=value format. Can be semicolon or comma separated. Multiple enforcements can be provided as an array. .DESCRIPTION Adds enforcement policies to an enterprise role. Enforcements are specified in KEY=value format. Multiple enforcements can be provided separated by semicolons or commas, or as an array. .EXAMPLE Add-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB=3600" Adds a two-factor authentication duration enforcement .EXAMPLE Add-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB=3600;MASTER_PASSWORD_MINIMUM_LENGTH=12" Adds multiple enforcements separated by semicolons #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string[]]$Enforcement ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } if ($null -eq $Enforcement -or $Enforcement.Count -eq 0) { Write-Error -Message "Enforcement parameter is required. Format: KEY=value;KEY2=value2 (semicolon or comma separated)." -ErrorAction Stop } $enforcementDict = New-Object 'System.Collections.Generic.Dictionary[KeeperSecurity.Enterprise.RoleEnforcementPolicies,string]' $enforcementKeys = New-Object System.Collections.Generic.List[KeeperSecurity.Enterprise.RoleEnforcementPolicies] $invalidEnforcements = @() foreach ($item in $Enforcement) { # since we are using the same separator for both semicolon and comma $parts = $item -split '[;,]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($part in $parts) { $trimmedPart = $part.Trim() if ([string]::IsNullOrWhiteSpace($trimmedPart)) { continue } $separatorIndex = $trimmedPart.IndexOf('=') if ($separatorIndex -lt 0) { $separatorIndex = $trimmedPart.IndexOf(':') } $key = $null $value = $null if ($separatorIndex -gt 0) { $key = $trimmedPart.Substring(0, $separatorIndex).Trim() $value = $trimmedPart.Substring($separatorIndex + 1).Trim() } else { $key = $trimmedPart } $parsedKey = ConvertTo-RoleEnforcementPolicy $key if ($null -ne $parsedKey) { $enforcementDict[$parsedKey] = $value $enforcementKeys.Add($parsedKey) } else { $invalidEnforcements += $key } } } if ($invalidEnforcements.Count -gt 0) { $validValues = [System.Enum]::GetNames([KeeperSecurity.Enterprise.RoleEnforcementPolicies]) -join ", " Write-Error -Message "Invalid enforcements: $($invalidEnforcements -join ', '). Valid values: $validValues" -ErrorAction Stop } if ($enforcementKeys.Count -eq 0) { Write-Error -Message "No valid enforcements specified." -ErrorAction Stop } try { $responses = $roleData.RoleEnforcementAddBatch($roleObject, $enforcementDict).GetAwaiter().GetResult() for ($i = 0; $i -lt $responses.Count; $i++) { $response = $responses[$i] $enforcementKey = $enforcementKeys[$i] if ($response.IsSuccess) { $value = if ($enforcementDict.ContainsKey($enforcementKey) -and $enforcementDict[$enforcementKey]) { "=$($enforcementDict[$enforcementKey])" } else { "" } Write-Output "Command: $($response.command), Enforcement: $enforcementKey$value, Result: $($response.result)" } else { Write-Output "Command: $($response.command), Enforcement: $enforcementKey, Result: $($response.result), Code: $($response.resultCode), Message: $($response.message)" } } } catch { Write-Error -Message "Failed to add enforcements: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Add-KeeperEnterpriseRoleEnforcement -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Add-KeeperRoleEnforcement -Value Add-KeeperEnterpriseRoleEnforcement function Update-KeeperEnterpriseRoleEnforcement { <# .SYNOPSIS Updates enforcements for an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Enforcement Enforcement(s) in KEY=value format. Can be semicolon or comma separated. Multiple enforcements can be provided as an array. .DESCRIPTION Updates enforcement policies for an enterprise role. Enforcements are specified in KEY=value format. .EXAMPLE Update-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB=7200" Updates the two-factor authentication duration enforcement .EXAMPLE Update-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB=7200,MASTER_PASSWORD_MINIMUM_LENGTH=16" Updates multiple enforcements separated by commas #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string[]]$Enforcement ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } if ($null -eq $Enforcement -or $Enforcement.Count -eq 0) { Write-Error -Message "Enforcement parameter is required. Format: KEY=value;KEY2=value2 (semicolon or comma separated)." -ErrorAction Stop } $enforcementDict = New-Object 'System.Collections.Generic.Dictionary[KeeperSecurity.Enterprise.RoleEnforcementPolicies,string]' $enforcementKeys = New-Object System.Collections.Generic.List[KeeperSecurity.Enterprise.RoleEnforcementPolicies] $invalidEnforcements = @() foreach ($item in $Enforcement) { $parts = $item -split '[;,]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($part in $parts) { $trimmedPart = $part.Trim() if ([string]::IsNullOrWhiteSpace($trimmedPart)) { continue } $separatorIndex = $trimmedPart.IndexOf('=') if ($separatorIndex -lt 0) { $separatorIndex = $trimmedPart.IndexOf(':') } $key = $null $value = $null if ($separatorIndex -gt 0) { $key = $trimmedPart.Substring(0, $separatorIndex).Trim() $value = $trimmedPart.Substring($separatorIndex + 1).Trim() } else { $key = $trimmedPart } $parsedKey = ConvertTo-RoleEnforcementPolicy $key if ($null -ne $parsedKey) { $enforcementDict[$parsedKey] = $value $enforcementKeys.Add($parsedKey) } else { $invalidEnforcements += $key } } } if ($invalidEnforcements.Count -gt 0) { $validValues = [System.Enum]::GetNames([KeeperSecurity.Enterprise.RoleEnforcementPolicies]) -join ", " Write-Error -Message "Invalid enforcements: $($invalidEnforcements -join ', '). Valid values: $validValues" -ErrorAction Stop } if ($enforcementKeys.Count -eq 0) { Write-Error -Message "No valid enforcements specified." -ErrorAction Stop } try { $responses = $roleData.RoleEnforcementUpdateBatch($roleObject, $enforcementDict).GetAwaiter().GetResult() for ($i = 0; $i -lt $responses.Count; $i++) { $response = $responses[$i] $enforcementKey = $enforcementKeys[$i] if ($response.IsSuccess) { $value = if ($enforcementDict.ContainsKey($enforcementKey) -and $enforcementDict[$enforcementKey]) { "=$($enforcementDict[$enforcementKey])" } else { "" } Write-Output "Command: $($response.command), Enforcement: $enforcementKey$value, Result: $($response.result)" } else { Write-Output "Command: $($response.command), Enforcement: $enforcementKey, Result: $($response.result), Code: $($response.resultCode), Message: $($response.message)" } } } catch { Write-Error -Message "Failed to update enforcements: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Update-KeeperEnterpriseRoleEnforcement -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Update-KeeperRoleEnforcement -Value Update-KeeperEnterpriseRoleEnforcement function Remove-KeeperEnterpriseRoleEnforcement { <# .SYNOPSIS Removes enforcements from an Enterprise Role .PARAMETER Role Role Name or ID .PARAMETER Enforcement Enforcement key(s) to remove. Can be semicolon or comma separated. For remove operations, use KEY only (no value). .DESCRIPTION Removes enforcement policies from an enterprise role. Only the enforcement key is required (no value). .EXAMPLE Remove-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB" Removes the TWO_FACTOR_DURATION_WEB enforcement .EXAMPLE Remove-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB;MASTER_PASSWORD_MINIMUM_LENGTH" Removes multiple enforcements separated by semicolons .EXAMPLE Remove-KeeperEnterpriseRoleEnforcement -Role "AdminRole" -Enforcement "TWO_FACTOR_DURATION_WEB,MASTER_PASSWORD_MINIMUM_LENGTH" Removes multiple enforcements separated by commas #> [CmdletBinding()] Param ( [Parameter(Position = 0, Mandatory = $true)]$Role, [Parameter(Position = 1, Mandatory = $true)][string[]]$Enforcement ) [Enterprise]$enterprise = getEnterprise $roleData = $enterprise.roleData $roleObject = resolveRole $roleData $Role if (-not $roleObject) { return } if ($null -eq $Enforcement -or $Enforcement.Count -eq 0) { Write-Error -Message "Enforcement parameter is required. Format: KEY (for remove operations, use KEY only)." -ErrorAction Stop } $enforcementKeys = New-Object System.Collections.Generic.List[KeeperSecurity.Enterprise.RoleEnforcementPolicies] $invalidEnforcements = @() foreach ($item in $Enforcement) { $parts = $item -split '[;,]' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } foreach ($part in $parts) { $trimmedPart = $part.Trim() if ([string]::IsNullOrWhiteSpace($trimmedPart)) { continue } $separatorIndex = $trimmedPart.IndexOf('=') if ($separatorIndex -lt 0) { $separatorIndex = $trimmedPart.IndexOf(':') } $key = if ($separatorIndex -gt 0) { $trimmedPart.Substring(0, $separatorIndex).Trim() } else { $trimmedPart } $parsedKey = ConvertTo-RoleEnforcementPolicy $key if ($null -ne $parsedKey) { $enforcementKeys.Add($parsedKey) } else { $invalidEnforcements += $key } } } if ($invalidEnforcements.Count -gt 0) { $validValues = [System.Enum]::GetNames([KeeperSecurity.Enterprise.RoleEnforcementPolicies]) -join ", " Write-Error -Message "Invalid enforcements: $($invalidEnforcements -join ', '). Valid values: $validValues" -ErrorAction Stop } if ($enforcementKeys.Count -eq 0) { Write-Error -Message "No valid enforcements specified." -ErrorAction Stop } try { $responses = $roleData.RoleEnforcementRemoveBatch($roleObject, $enforcementKeys).GetAwaiter().GetResult() for ($i = 0; $i -lt $responses.Count; $i++) { $response = $responses[$i] $enforcementKey = $enforcementKeys[$i] if ($response.IsSuccess) { Write-Output "Command: $($response.command), Enforcement: $enforcementKey, Result: $($response.result)" } else { Write-Output "Command: $($response.command), Enforcement: $enforcementKey, Result: $($response.result), Code: $($response.resultCode), Message: $($response.message)" } } } catch { Write-Error -Message "Failed to remove enforcements: $($_.Exception.Message)" -ErrorAction Stop } } Register-ArgumentCompleter -CommandName Remove-KeeperEnterpriseRoleEnforcement -ParameterName Role -ScriptBlock $Keeper_RoleNameCompleter New-Alias -Name Remove-KeeperRoleEnforcement -Value Remove-KeeperEnterpriseRoleEnforcement function Copy-KeeperEnterpriseRole { <# .SYNOPSIS Copies an enterprise role to another node with enforcements, users, and teams. .DESCRIPTION Creates a new role on the target node with the same NewUserInherit and VisibleBelow as the source role, copies all enforcements, and optionally copies users and teams from the source role to the new role. .PARAMETER SourceRole Role name, ID, or EnterpriseRole object to copy from. .PARAMETER TargetNode Target node name or ID where the new role will be created. .PARAMETER NewRoleName Display name for the new role. .PARAMETER CopyUsers Copy users from the source role to the new role. Default is $true. .PARAMETER CopyTeams Copy teams from the source role to the new role. Default is $true. .PARAMETER Force Reload enterprise data before running. .EXAMPLE Copy-KeeperEnterpriseRole -SourceRole "Test-App" -TargetNode "dev" -NewRoleName "second dev" Creates a new role "second dev" on node "dev" with enforcements, users, and teams from "Test-App". .EXAMPLE Copy-KeeperEnterpriseRole -SourceRole "AdminRole" -TargetNode 123456789 -NewRoleName "AdminRole-Copy" -CopyUsers $false Copies only enforcements and teams (no users) to the new role. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] Param ( [Parameter(Position = 0, Mandatory = $true)]$SourceRole, [Parameter(Position = 1, Mandatory = $true)][string]$TargetNode, [Parameter(Position = 2, Mandatory = $true)][string]$NewRoleName, [Parameter()][bool]$CopyUsers = $true, [Parameter()][bool]$CopyTeams = $true, [Parameter()][switch]$Force ) if ($Force) { Sync-KeeperEnterprise | Out-Null } [Enterprise]$enterprise = getEnterprise $enterpriseData = $enterprise.enterpriseData $roleData = $enterprise.roleData $sourceRoleObject = resolveRole $roleData $SourceRole if (-not $sourceRoleObject) { return } $targetNodeObject = resolveSingleNode $TargetNode if (-not $targetNodeObject) { return } $nodeId = $targetNodeObject.Id $newRoleNameTrimmed = $NewRoleName.Trim() $sourceName = $sourceRoleObject.DisplayName if ($PSCmdlet.ShouldProcess("Role `"$newRoleNameTrimmed`" on node `"$($targetNodeObject.DisplayName)`"", "Copy from `"$sourceName`"")) { try { $newRole = $roleData.CreateRole($newRoleNameTrimmed, $nodeId, $sourceRoleObject.NewUserInherit).GetAwaiter().GetResult() if (-not $newRole) { Write-Error "Failed to create role `"$newRoleNameTrimmed`"" -ErrorAction Stop return } if ($newRole.VisibleBelow -ne $sourceRoleObject.VisibleBelow) { try { $updated = $roleData.UpdateRole($newRole, $null, $sourceRoleObject.VisibleBelow, $null).GetAwaiter().GetResult() if ($updated) { $newRole = $updated } } catch { Write-Warning "Failed to set VisibleBelow for role `"$newRoleNameTrimmed`": $($_.Exception.Message)" } } $sourceEnforcements = @($roleData.GetEnforcementsForRole($sourceRoleObject.Id)) if ($sourceEnforcements.Count -gt 0) { $enforcementDict = New-Object 'System.Collections.Generic.Dictionary[KeeperSecurity.Enterprise.RoleEnforcementPolicies,string]' foreach ($re in $sourceEnforcements) { $enforcementTypeStr = $re.EnforcementType if ([string]::IsNullOrWhiteSpace($enforcementTypeStr)) { continue } $normalized = $enforcementTypeStr -replace '_', '' $parsed = $null if ([System.Enum]::TryParse([KeeperSecurity.Enterprise.RoleEnforcementPolicies], $normalized, $true, [ref]$parsed)) { $enforcementDict[$parsed] = if ($re.Value) { $re.Value } else { '' } } } if ($enforcementDict.Count -gt 0) { $roleData.RoleEnforcementAddBatch($newRole, $enforcementDict).GetAwaiter().GetResult() | Out-Null } } $usersCopied = 0 if ($CopyUsers) { $sourceUserIds = @($roleData.GetUsersForRole($sourceRoleObject.Id)) foreach ($userId in $sourceUserIds) { $user = $null if ($enterpriseData.TryGetUserById($userId, [ref]$user)) { try { $roleData.AddUserToRole($newRole, $user).GetAwaiter().GetResult() | Out-Null $usersCopied++ } catch { Write-Warning "Could not add user `"$($user.Email)`" to new role: $($_.Exception.Message)" } } } } $teamsCopied = 0 if ($CopyTeams) { $sourceTeamUids = @($roleData.GetTeamsForRole($sourceRoleObject.Id)) foreach ($teamUid in $sourceTeamUids) { $team = $null if ($enterpriseData.TryGetTeam($teamUid, [ref]$team)) { try { $roleData.AddTeamToRole($newRole, $team).GetAwaiter().GetResult() | Out-Null $teamsCopied++ } catch { Write-Warning "Could not add team `"$($team.Name)`" to new role: $($_.Exception.Message)" } } } } try { $enterprise.loader.Load().GetAwaiter().GetResult() | Out-Null } catch { Write-Warning "Failed to reload enterprise data: $($_.Exception.Message)" } $msg = "Role `"$newRoleNameTrimmed`" created with enforcements from `"$sourceName`"" if ($usersCopied -gt 0 -or $teamsCopied -gt 0) { $msg += " ($usersCopied user(s), $teamsCopied team(s) copied)" } $msg += "." Write-Output $msg } catch { Write-Error "Copy role failed: $($_.Exception.Message)" -ErrorAction Stop } } } Register-ArgumentCompleter -CommandName Copy-KeeperEnterpriseRole -ParameterName SourceRole -ScriptBlock $Keeper_RoleNameCompleter Register-ArgumentCompleter -CommandName Copy-KeeperEnterpriseRole -ParameterName TargetNode -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $result = @() [Enterprise]$enterprise = $Script:Context.Enterprise if (-not $enterprise) { return $null } $to_complete = if ($wordToComplete) { $wordToComplete + '*' } else { '*' } foreach ($node in $enterprise.enterpriseData.Nodes) { if ($node.DisplayName -like $to_complete) { $nodeName = $node.DisplayName if ($nodeName -match '[\s'']') { $nodeName = $nodeName -replace '''', ''''''; $nodeName = "'${nodeName}'" } $result += $nodeName } } if ($result.Count -gt 0) { return $result } return $null } New-Alias -Name kercopy -Value Copy-KeeperEnterpriseRole # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBxkoHDt1qYbhJh # bbfKPNscx5tq1GmBt6TwVtiZYbThe6CCITswggWNMIIEdaADAgECAhAOmxiO+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 # BDEiBCDk55YwMZ9Ct6Wnz7plsbs77v1n03d4byVOMJGj9KUr9DANBgkqhkiG9w0B # AQEFAASCAYA6n6SVJqDwJKyegvRshEtaH0M7U6tqw2aEdFY4dRx9fMHy7u7OuhEC # W+DRqvaVCt/AzCIpnJXw9LLoXb3Y+zbNBRJKfotKDe7eOP4+jW8iB1e7Mi3Mlmxl # sIHjlwifC/5qOjTCoaw0p+uOT/tfrk5aDp7KDLP8dBR1DuXW639tgwguy/NQtTPo # ojWX54Vye0QST4t2z/TsZIKfFlxgimXUizpi4WBbxhBkxuseH7L3YM/7Hy9tSd3I # fU93rXCgt/AshfVXRK0qfbR/Llpm9Pb5icn3W6ePuqtSWp897KUSuQ5ytu6Z9OqS # 8ws/xa+i4qOW2+stPOijtRRBYKUx2XfCWYZ3EDw/6vXSxhA5ayfCSDLA+avdEjHE # m6Mfagj77fSnC36uxxMI9huBmkbVjrOM+NuEKCBXWnFW8TtbmxBHj2ZDudA6QDe+ # O75+97JQXMhr7E+fx00/ylGaZ49NsklvTnnuBd96xS0jPBwcvyytz/yeXtgo0QCt # g0APAM6or/2hggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNTI2MDQwNjQwWjAv # BgkqhkiG9w0BCQQxIgQgwl42IqIozEyqgpV57/a/L4YeZNDItdqT8JCsV1ZEcA0w # DQYJKoZIhvcNAQEBBQAEggIAAP+xxO0rWyG/u5rDtFCFzvBGAt1TErqnEU/gheu1 # V6NuEevOMJydvCx6Cx96ytqzOhKFYUX8zmHmtc7OSJvNPa3RcZ1cF7YZJJbCHpRZ # eiolncuDIztl9mdlvXo9C6HVjbHef9eRV0CXd4H8UhVy1Q6M3Jci6pMHqMNYCIFX # 4219bOkw/20ybSgr/2nH9WIuNMyxqiE23AEsRXrE9QMhuC+tcOtx9Y9Pg8Pdn2tV # THOStOq7Qr3FXI/E2VaQKh5tIMFvoZljahA5rJrPdadHiIhJisMlddBshdQ6rYxB # 2iFnvFJ2gbkYc+Ntn8/cqzkeHNq3iBvukjdjRJnaWBSnNoWweCy9u/RHkEFBuDIC # V3rJkizFoePBQ2WC5re7u5ckbExCFqWo7NpW+mRmitM1R0HHWbQSjdKsrneTDY9D # t1RORGewdqbgv0lzvZ3fVyA+F9KzpsMtqz7szv9yENMj9/aJqvTx//whe4ciyDQT # nkVkwLJGdqX8uA29yF479hnHmKh4cSwsIiKRPOO/B0JH5PMV6bwjNqTNh+eQLs0m # svsR47DDzdYLUZB4+S7B/nrZvfSzzhl/x0NisDwri2agQgsyYrxVncOJC+eeXFdr # w3k0jloK2oqFpxZYUXMqQh6lY92h8XhOPdSWeStkywjajfjvc4OVugKNMKmt/ELc # GKM= # SIG # End signature block |