diff --git a/PowerCommander/AuthCommands.ps1 b/PowerCommander/AuthCommands.ps1 index 2ebc13a..d1b5b03 100644 --- a/PowerCommander/AuthCommands.ps1 +++ b/PowerCommander/AuthCommands.ps1 @@ -477,7 +477,8 @@ function Disconnect-Keeper { $Script:Context.AvailableTeams = $null $Script:Context.AvailableUsers = $null - + + $Script:Context.ManagedCompanyId = 0 $Script:Context.Enterprise = $null $vault = $Script:Context.Vault @@ -515,3 +516,120 @@ function Sync-Keeper { } } New-Alias -Name ks -Value Sync-Keeper + +function Get-KeeperInformation { + <# + .Synopsis + Prints account license information + #> + + $vault = getVault + [KeeperSecurity.Authentication.IAuthentication]$auth = $vault.Auth + + [KeeperSecurity.Authentication.AccountLicense]$license = $auth.AuthContext.License + switch ($license.AccountType) { + 0 { $accountType = $license.ProductTypeName } + 1 { $accountType = 'Family Plan'} + 2 { $accountType = 'Enterprise' } + Default { $accountType = $license.ProductTypeName } + } + $accountType = 'Enterprise' + [PSCustomObject]@{ + PSTypeName = "KeeperSecurity.License.Info" + User = $auth.Username + Server = $auth.Endpoint.Server + Admin = $auth.AuthContext.IsEnterpriseAdmin + AccountType = $accountType + RenewalDate = $license.ExpirationDate + StorageCapacity = [int] [Math]::Truncate($license.BytesTotal / (1024 * 1024 * 1024)) + StorageUsage = [int] [Math]::Truncate($license.BytesUsed * 100 / $license.BytesTotal) + StorageExpires = $license.StorageExpirationDate + } + + if ($license.AccountType -eq 2) { + $enterprise = getEnterprise + if ($enterprise) { + $enterpriseLicense = $enterprise.enterpriseData.EnterpriseLicense + $productTypeId = $enterpriseLicense.ProductTypeId + if ($productTypeId -in @(2, 5)) { + $tier = $enterpriseLicense.Tier + if ($tier -eq 1) { + $plan = 'Enterprise' + } else { + $plan = 'Business' + } + } + elseif ($productTypeId -in @(9, 10)) { + $distributor = $enterpriseLicense.Distributor + if ($distributor -eq $true) { + $plan = 'Distributor' + } else { + $plan = 'Managed MSP' + } + } + elseif ($productTypeId -in @(11, 12)) { + $plan = 'Keeper MSP' + } + elseif ($productTypeId -eq 8) { + $tier = $enterpriseLicense.Tier + if ($tier -eq 1) { + $plan = 'Enterprise' + } else { + $plan = 'Business' + } + $plan = "MC $plan" + } else { + $plan = 'Unknown' + } + if ($productTypeId -in @(5, 10, 12)) { + $plan = "$plan Trial" + } + + $enterpriseInfo = [PSCustomObject]@{ + PSTypeName = "KeeperSecurity.License.EnterpriseInfo" + LicenseType = 'Enterprise' + EnterpriseName = $enterprise.loader.EnterpriseName + BasePlan = $plan + } + if ($enterpriseLicense.Paid) { + $expiration = $enterpriseLicense.Expiration + if ($expiration -gt 0) { + $exp = [KeeperSecurity.Utils.DateTimeOffsetExtensions]::FromUnixTimeMilliseconds($expiration) + $expDate = $exp.ToString('d') + Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'Expires' -Value $expDate + } + + switch ($enterpriseLicense.filePlanTypeId) { + -1 { $filePlan = 'No Storage' } + 0 { $filePlan = 'Trial' } + 1 { $filePlan = '1GB' } + 2 { $filePlan = '10GB' } + 3 { $filePlan = '50GB' } + 4 { $filePlan = '100GB' } + 5 { $filePlan = '250GB' } + 6 { $filePlan = '500GB' } + 7 { $filePlan = '1TB' } + 8 { $filePlan = '10TB' } + Default { $filePlan = '???' } + } + Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'StorageCapacity' -Value $filePlan + + $numberOfSeats = $enterpriseLicense.NumberOfSeats + if ($numberOfSeats -gt 0) { + Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'TotalUsers' -Value $numberOfSeats + } + $seatsAllocated = $enterpriseLicense.SeatsAllocated + if ($seatsAllocated -gt 0) { + Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'ActiveUsers' -Value $seatsAllocated + } + $seatsPending = $enterpriseLicense.SeatsPending + if ($seatsAllocated -gt 0) { + Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'InvitedUsers' -Value $SeatsPending + } + + } + $enterpriseInfo + } + } +} +New-Alias -Name kwhoami -Value Get-KeeperInformation diff --git a/PowerCommander/Enterprise.ps1 b/PowerCommander/Enterprise.ps1 index e8a9ebd..00d1978 100644 --- a/PowerCommander/Enterprise.ps1 +++ b/PowerCommander/Enterprise.ps1 @@ -19,7 +19,35 @@ function getEnterprise { $enterprise.loader = New-Object KeeperSecurity.Enterprise.EnterpriseLoader($auth, $plugins) $enterprise.loader.Load().GetAwaiter().GetResult() | Out-Null + if ($enterprise.enterpriseData.EnterpriseLicense.licenseStatus.StartsWith("msp")) { + $enterprise.ManagedCompanies = @{} + } + $Script:Context.Enterprise = $enterprise + $Script:Context.ManagedCompanyId = 0 + } + + if ($Script:Context.ManagedCompanyId -gt 0) { + if ($null -ne $enterprise.ManagedCompanies) { + $enterpriseMc = $enterprise.ManagedCompanies[$Script:Context.ManagedCompanyId] + if ($null -eq $enterpriseMc) { + $authMc = New-Object KeeperSecurity.Enterprise.ManagedCompanyAuth + $authMc.LoginToManagedCompany($Script:Context.Enterprise.loader, $Script:Context.ManagedCompanyId).GetAwaiter().GetResult() | Out-Null + + $enterpriseMc = New-Object Enterprise + $enterpriseMc.enterpriseData = New-Object KeeperSecurity.Enterprise.EnterpriseData + $enterpriseMc.roleData = New-Object KeeperSecurity.Enterprise.RoleData + + [KeeperSecurity.Enterprise.EnterpriseDataPlugin[]] $plugins = $enterpriseMc.enterpriseData, $enterprise.roleData + + $enterpriseMc.loader = New-Object KeeperSecurity.Enterprise.EnterpriseLoader($authMc, $plugins) + $enterpriseMc.loader.Load().GetAwaiter().GetResult() | Out-Null + $enterprise.ManagedCompanies[$Script:Context.ManagedCompanyId] = $enterpriseMc + } + $enterprise = $enterpriseMc + } else { + $Script:Context.ManagedCompanyId = 0 + } } return $enterprise @@ -408,363 +436,3 @@ function Get-KeeperEnterpriseNode { } New-Alias -Name ken -Value Get-KeeperEnterpriseNode -function Get-KeeperManagedCompany { - <# - .Synopsis - Get a list of managed companies - .Parameter Filter - Managed Company ID or Name - #> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $false)][string] $Filter - ) - - [Enterprise]$enterprise = getMspEnterprise - if ($Name) { - $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseId -eq $Filter) -or ($_.EnterpriseName -like $Filter + '*') } - } - else { - $enterprise.mspData.ManagedCompanies - } -} -New-Alias -Name kmc -Value Get-KeeperManagedCompany - -$Keeper_MspAddonName = { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) - - $result = @() - $msp_addons = @('enterprise_breach_watch', 'compliance_report', 'enterprise_audit_and_reporting', 'msp_service_and_support', 'secrets_manager', 'connection_manager', 'chat') - - $toComplete = $wordToComplete += '*' - foreach ($addon in $msp_addons) { - if ($addon -like $toComplete) { - $result += $addon - } - } - if ($result.Count -gt 0) { - return $result - } - else { - return $null - } -} - -function New-KeeperManagedCompany { - <# - .Synopsis - Adds new Managed Company - .Parameter Name - Managed Company Name - .Parameter PlanId - Managed Company Plan - .Parameter MaximumSeats - Maximum Number of Seats - .Parameter Storage - Storage Plan - .Parameter Addons - Addons - .Parameter Node - Node Name or ID - #> - [CmdletBinding(SupportsShouldProcess=$true)] - Param ( - [Parameter(Mandatory = $true, Position = 0)][string] $Name, - [Parameter(Mandatory = $true)][ValidateSet('business', 'businessPlus', 'enterprise', 'enterprisePlus')][string] $PlanId, - [Parameter(Mandatory = $true)][int] $MaximumSeats, - [Parameter(Mandatory = $false)][ValidateSet('100GB', '1TB', '10TB')][string] $Storage, - [Parameter(Mandatory = $false)][string[]] $Addons, - [Parameter(Mandatory = $false)][string] $Node - ) - - [Enterprise]$enterprise = getMspEnterprise - - $options = New-Object KeeperSecurity.Enterprise.ManagedCompanyOptions - $options.Name = $Name - $options.ProductId = $PlanId - $options.NumberOfSeats = $MaximumSeats - if ($Node) { - $n = findEnterpriseNode $Node - if ($n) { - $options.NodeId = $n.Id - } - else { - Write-Error -Message "Node ${Node} not found" -ErrorAction Stop - } - } - else { - $options.NodeId = $enterprise.enterpriseData.RootNode.Id - } - switch ($Storage) { - '100GB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan100GB } - '1TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan1TB } - '10TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan10TB } - } - if ($Addons) { - $aons = @() - foreach ($addon in $Addons) { - $parts = $addon -split ':' - $addonOption = New-Object KeeperSecurity.Enterprise.ManagedCompanyAddonOptions - $addonOption.Addon = $parts[0] - if ($parts.Length -gt 1) { - $addonOption.NumberOfSeats = $parts[1] -as [int] - } - $aons += $addonOption - } - $options.Addons = $aons - } - - - if ($PSCmdlet.ShouldProcess($Name, "Creating Managed Company")) { - return $enterprise.mspData.CreateManagedCompany($options).GetAwaiter().GetResult() - } -} -New-Alias -Name kamc -Value New-KeeperManagedCompany -Register-ArgumentCompleter -CommandName New-KeeperManagedCompany -ParameterName Addons -ScriptBlock $Keeper_MspAddonName - -function Remove-KeeperManagedCompany { - <# - .Synopsis - Removes Managed Company - .Parameter Name - Managed Company Id or Name - #> - [CmdletBinding(SupportsShouldProcess=$true)] - Param ( - [Parameter(Position = 0, Mandatory = $true)][string] $Name - ) - - [Enterprise]$enterprise = getMspEnterprise - $mc = findManagedCompany $Name - if (-not $mc) { - Write-Error -Message "Managed Company ${Name} not found" -ErrorAction Stop - } - - if ($PSCmdlet.ShouldProcess($mc.EnterpriseName, "Removing Managed Company")) { - $enterprise.mspData.RemoveManagedCompany($mc.EnterpriseId).GetAwaiter().GetResult() | Out-Null - Write-Information "Removed Managed Company `"$($mc.EnterpriseName)`" ID: $($mc.EnterpriseId)" - } -} -New-Alias -Name krmc -Value Remove-KeeperManagedCompany - -function Edit-KeeperManagedCompany { - <# - .Synopsis - Removes Managed Company - .Parameter Name - Managed Company New Name - .Parameter PlanId - Managed Company Plan - .Parameter MaximumSeats - Maximum Number of Seats - .Parameter Storage - Storage Plan - .Parameter Addons - Addons - .Parameter Node - Node Name or ID - .Parameter Id - Managed Company Name or Id - #> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $false)][string] $Name, - [Parameter(Mandatory = $false)][ValidateSet('business', 'businessPlus', 'enterprise', 'enterprisePlus')][string] $PlanId, - [Parameter(Mandatory = $false)][int] $MaximumSeats, - [Parameter(Mandatory = $false)][ValidateSet('100GB', '1TB', '10TB')][string] $Storage, - [Parameter(Mandatory = $false)][string[]] $Addons, - [Parameter(Mandatory = $false)][string] $Node, - [Parameter(Position = 0, Mandatory = $true)][string] $Id - ) - - [Enterprise]$enterprise = getMspEnterprise - $mc = findManagedCompany $Id - if (-not $mc) { - Write-Error -Message "Managed Company ${Id} not found" -ErrorAction Stop - } - - $options = New-Object KeeperSecurity.Enterprise.ManagedCompanyOptions - if ($Name) { - $options.Name = $Name - } - if ($PlanId) { - $options.ProductId = $PlanId - } - if ($MaximumSeats) { - $options.NumberOfSeats = $MaximumSeats - } - switch ($Storage) { - '100GB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan100GB } - '1TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan1TB } - '10TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan10TB } - } - if ($Addons) { - $aons = @() - foreach ($addon in $Addons) { - $parts = $addon -split ':' - $addonOption = New-Object KeeperSecurity.Enterprise.ManagedCompanyAddonOptions - $addonOption.Addon = $parts[0] - if ($parts.Length -gt 1) { - $addonOption.NumberOfSeats = $parts[1] -as [int] - } - $aons += $addonOption - } - $options.Addons = $aons - } - if ($Node) { - $n = findEnterpriseNode $Node - if ($n) { - $options.NodeId = $n.Id - } - else { - Write-Error -Message "Node ${Node} not found" -ErrorAction Stop - } - } - else { - $options.NodeId = $enterprise.enterpriseData.RootNode.Id - } - $enterprise.mspData.UpdateManagedCompany($mc.EnterpriseId, $options).GetAwaiter().GetResult() -} -New-Alias -Name kemc -Value Edit-KeeperManagedCompany -Register-ArgumentCompleter -CommandName Edit-KeeperManagedCompany -ParameterName Addons -ScriptBlock $Keeper_MspAddonName - -class MspDailySnapshotAddon { - [string]$Addon - [int]$Units -} -class MspDailySnapshotRecord { - [System.DateTime]$Date - [int]$McEnterpriseId - [int]$LicenseCount - [string]$ProductPlan - [string]$FilePlan - [MspDailySnapshotAddon[]]$Addons -} - -function Get-MspBillingReport { - <# - .Synopsis - Runs MSP Billing Report - .Parameter Month - Report Month 1-12 - .Parameter Year - Report Year 20xx - #> - [CmdletBinding()] - Param ( - [Parameter(Mandatory = $false)][int] $Month, - [Parameter(Mandatory = $false)][int] $Year - ) - - $dt = Get-Date - if (0 -eq $Year) { - $Year = $dt.Year - } - if (0 -eq $Month) { - $Month = $dt.Month - 1 - if ($Month -le 0) { - $Year -= 1 - $Month = 12 - } - } - - $auth = [KeeperSecurity.Authentication.IAuthentication] $auth = $Script:Context.Auth - - $url = [KeeperSecurity.Authentication.AuthExtensions]::GetBiUrl($auth, 'mapping/addons') - $rq = New-Object BI.MappingAddonsRequest - $rs = $auth.ExecuteAuthRest($url, $rq, [BI.MappingAddonsResponse]).GetAwaiter().GetResult() - $filePlans = @{ - 4 = '100GB' - 7 = '1TB' - 8 = '10TB' - } - foreach ($fp in $rs.FilePlans) { - $filePlans[$fp.Id] = $fp.Name - } - $addons = @{} - foreach ($aon in $rs.Addons) { - $addons[$aon.Id] = $aon.Name - } - - $url = [KeeperSecurity.Authentication.AuthExtensions]::GetBiUrl($auth, 'reporting/daily_snapshot') - $rq = New-Object BI.ReportingDailySnapshotRequest - $rq.Month = $Month - $rq.Year = $Year - - $rs = $auth.ExecuteAuthRest($url, $rq, [BI.ReportingDailySnapshotResponse]).GetAwaiter().GetResult() - foreach ($rec in $rs.Records) { - $r = New-Object MspDailySnapshotRecord - $r.Date = [KeeperSecurity.Utils.DateTimeOffsetExtensions]::FromUnixTimeMilliseconds($rec.date).Date - $r.McEnterpriseId = $rec.mcEnterpriseId - $r.LicenseCount = $rec.maxLicenseCount - switch ($rec.MaxBasePlanId) { - 1 { $r.ProductPlan = 'business' } - 2 { $r.ProductPlan = 'businessPlus' } - 10 { $r.ProductPlan = 'enterprise' } - 11 { $r.ProductPlan = 'enterprisePlus' } - default { $r.ProductPlan = "Plan #$($r.rec)" } - } - if ($rec.maxFilePlanTypeId) { - $r.FilePlan = $filePlans[$rec.maxFilePlanTypeId] - if (-not $r.FilePlan) { - $r.FilePlan = "Storage Plan #$($rec.maxFilePlanTypeId)" - } - } - - foreach ($addon in $rec.addons) { - if ($addon.maxAddonId) { - $a = New-Object MspDailySnapshotAddon - $a.Addon = $addons[$addon.maxAddonId] - if (-not $a.Addon) { - $a.Addon = "Addon # $($addon.maxAddonId)" - } - $a.Units = $addon.units - $r.Addons += $a - } - } - $r - } -} - -function Script:Get-KeeperNodeName { - Param ( - [long]$nodeId - ) - $enterprise = getEnterprise - [KeeperSecurity.Enterprise.EnterpriseNode]$node = $null - if ($enterprise.enterpriseData.TryGetNode($nodeId, [ref]$node)) { - if ($node.ParentNodeId -gt 0) { - return $node.DisplayName - } - else { - return $enterprise.loader.EnterpriseName - } - } -} - -function findManagedCompany { - Param ( - [string]$mc - ) - $enterprise = getMspEnterprise - $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseId -eq $mc) -or ($_.EnterpriseName -eq $mc) } | Select-Object -First 1 -} - -function findEnterpriseNode { - Param ( - [string]$node - ) - $enterprise = getEnterprise - if ($node -eq $enterprise.loader.EnterpriseName) { - return $enterprise.enterpriseData.RootNode - } - $enterprise.enterpriseData.Nodes | Where-Object { ($_.Id -eq $node) -or ($_.DisplayName -eq $node) } | Select-Object -First 1 -} - -function getMspEnterprise { - [Enterprise]$enterprise = getEnterprise - if ($enterprise.enterpriseData.EnterpriseLicense -and $enterprise.enterpriseData.EnterpriseLicense.LicenseStatus -like "msp*") { - return $enterprise - } - Write-Error -Message "Not a MSP (Managed Service Provider)" -ErrorAction Stop -} diff --git a/PowerCommander/ManagedCompany.ps1 b/PowerCommander/ManagedCompany.ps1 new file mode 100644 index 0000000..f8df33a --- /dev/null +++ b/PowerCommander/ManagedCompany.ps1 @@ -0,0 +1,414 @@ +function Switch-KeeperMC { + <# + .Synopsis + Switch to managed company + + .Parameter Name + Managed Company ID or Name + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true, Position = 0)][string] $Name + ) + + [Enterprise]$enterprise = getMspEnterprise + + $mc = $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseId -eq $Name) } + if ($mc.Length -eq 0) { + $mc = $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseName -like $Name + '*') } + } + + if ($mc.Length -eq 0) { + Write-Error -Message "Managed Company`"$Name`" not found" -ErrorAction Stop + } + elseif ($mc.Length -gt 1) { + Write-Error -Message "Managed Company`"$Name`" is not unique. Use Company ID." -ErrorAction Stop + } + + $Script:Context.ManagedCompanyId = $mc.EnterpriseId + Sync-KeeperEnterprise + + Write-Information "Switched to MC `"$($mc.EnterpriseName)`"" +} +New-Alias -Name switch-to-mc -Value Switch-KeeperMC + +function Switch-KeeperMSP { + <# + .Synopsis + Switch to MSP + #> + [CmdletBinding()] + + [Enterprise]$enterprise = getMspEnterprise + + $Script:Context.ManagedCompanyId = 0 + Sync-KeeperEnterprise + + Write-Information "Switched to MSP" +} +New-Alias -Name switch-to-msp -Value Switch-KeeperMSP + + +function Get-KeeperManagedCompany { + <# + .Synopsis + Get a list of managed companies + .Parameter Filter + Managed Company ID or Name + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false)][string] $Filter + ) + + [Enterprise]$enterprise = getMspEnterprise + if ($Name) { + $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseId -eq $Filter) -or ($_.EnterpriseName -like $Filter + '*') } + } + else { + $enterprise.mspData.ManagedCompanies + } +} +New-Alias -Name kmc -Value Get-KeeperManagedCompany + +$Keeper_MspAddonName = { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + $result = @() + $msp_addons = @('enterprise_breach_watch', 'compliance_report', 'enterprise_audit_and_reporting', 'msp_service_and_support', 'secrets_manager', 'connection_manager', 'chat') + + $toComplete = $wordToComplete += '*' + foreach ($addon in $msp_addons) { + if ($addon -like $toComplete) { + $result += $addon + } + } + if ($result.Count -gt 0) { + return $result + } + else { + return $null + } +} + +function New-KeeperManagedCompany { + <# + .Synopsis + Adds new Managed Company + .Parameter Name + Managed Company Name + .Parameter PlanId + Managed Company Plan + .Parameter MaximumSeats + Maximum Number of Seats + .Parameter Storage + Storage Plan + .Parameter Addons + Addons + .Parameter Node + Node Name or ID + #> + [CmdletBinding(SupportsShouldProcess=$true)] + Param ( + [Parameter(Mandatory = $true, Position = 0)][string] $Name, + [Parameter(Mandatory = $true)][ValidateSet('business', 'businessPlus', 'enterprise', 'enterprisePlus')][string] $PlanId, + [Parameter(Mandatory = $true)][int] $MaximumSeats, + [Parameter(Mandatory = $false)][ValidateSet('100GB', '1TB', '10TB')][string] $Storage, + [Parameter(Mandatory = $false)][string[]] $Addons, + [Parameter(Mandatory = $false)][string] $Node + ) + + [Enterprise]$enterprise = getMspEnterprise + + $options = New-Object KeeperSecurity.Enterprise.ManagedCompanyOptions + $options.Name = $Name + $options.ProductId = $PlanId + $options.NumberOfSeats = $MaximumSeats + if ($Node) { + $n = findEnterpriseNode $Node + if ($n) { + $options.NodeId = $n.Id + } + else { + Write-Error -Message "Node ${Node} not found" -ErrorAction Stop + } + } + else { + $options.NodeId = $enterprise.enterpriseData.RootNode.Id + } + switch ($Storage) { + '100GB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan100GB } + '1TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan1TB } + '10TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan10TB } + } + if ($Addons) { + $aons = @() + foreach ($addon in $Addons) { + $parts = $addon -split ':' + $addonOption = New-Object KeeperSecurity.Enterprise.ManagedCompanyAddonOptions + $addonOption.Addon = $parts[0] + if ($parts.Length -gt 1) { + $addonOption.NumberOfSeats = $parts[1] -as [int] + } + $aons += $addonOption + } + $options.Addons = $aons + } + + + if ($PSCmdlet.ShouldProcess($Name, "Creating Managed Company")) { + return $enterprise.mspData.CreateManagedCompany($options).GetAwaiter().GetResult() + } +} +New-Alias -Name kamc -Value New-KeeperManagedCompany +Register-ArgumentCompleter -CommandName New-KeeperManagedCompany -ParameterName Addons -ScriptBlock $Keeper_MspAddonName + +function Remove-KeeperManagedCompany { + <# + .Synopsis + Removes Managed Company + .Parameter Name + Managed Company Id or Name + #> + [CmdletBinding(SupportsShouldProcess=$true)] + Param ( + [Parameter(Position = 0, Mandatory = $true)][string] $Name + ) + + [Enterprise]$enterprise = getMspEnterprise + $mc = findManagedCompany $Name + if (-not $mc) { + Write-Error -Message "Managed Company ${Name} not found" -ErrorAction Stop + } + + if ($PSCmdlet.ShouldProcess($mc.EnterpriseName, "Removing Managed Company")) { + $enterprise.mspData.RemoveManagedCompany($mc.EnterpriseId).GetAwaiter().GetResult() | Out-Null + Write-Information "Removed Managed Company `"$($mc.EnterpriseName)`" ID: $($mc.EnterpriseId)" + } +} +New-Alias -Name krmc -Value Remove-KeeperManagedCompany + +function Edit-KeeperManagedCompany { + <# + .Synopsis + Removes Managed Company + .Parameter Name + Managed Company New Name + .Parameter PlanId + Managed Company Plan + .Parameter MaximumSeats + Maximum Number of Seats + .Parameter Storage + Storage Plan + .Parameter Addons + Addons + .Parameter Node + Node Name or ID + .Parameter Id + Managed Company Name or Id + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false)][string] $Name, + [Parameter(Mandatory = $false)][ValidateSet('business', 'businessPlus', 'enterprise', 'enterprisePlus')][string] $PlanId, + [Parameter(Mandatory = $false)][int] $MaximumSeats, + [Parameter(Mandatory = $false)][ValidateSet('100GB', '1TB', '10TB')][string] $Storage, + [Parameter(Mandatory = $false)][string[]] $Addons, + [Parameter(Mandatory = $false)][string] $Node, + [Parameter(Position = 0, Mandatory = $true)][string] $Id + ) + + [Enterprise]$enterprise = getMspEnterprise + $mc = findManagedCompany $Id + if (-not $mc) { + Write-Error -Message "Managed Company ${Id} not found" -ErrorAction Stop + } + + $options = New-Object KeeperSecurity.Enterprise.ManagedCompanyOptions + if ($Name) { + $options.Name = $Name + } + if ($PlanId) { + $options.ProductId = $PlanId + } + if ($MaximumSeats) { + $options.NumberOfSeats = $MaximumSeats + } + switch ($Storage) { + '100GB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan100GB } + '1TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan1TB } + '10TB' { $options.FilePlanType = [KeeperSecurity.Enterprise.ManagedCompanyConstants]::StoragePlan10TB } + } + if ($Addons) { + $aons = @() + foreach ($addon in $Addons) { + $parts = $addon -split ':' + $addonOption = New-Object KeeperSecurity.Enterprise.ManagedCompanyAddonOptions + $addonOption.Addon = $parts[0] + if ($parts.Length -gt 1) { + $addonOption.NumberOfSeats = $parts[1] -as [int] + } + $aons += $addonOption + } + $options.Addons = $aons + } + if ($Node) { + $n = findEnterpriseNode $Node + if ($n) { + $options.NodeId = $n.Id + } + else { + Write-Error -Message "Node ${Node} not found" -ErrorAction Stop + } + } + else { + $options.NodeId = $enterprise.enterpriseData.RootNode.Id + } + $enterprise.mspData.UpdateManagedCompany($mc.EnterpriseId, $options).GetAwaiter().GetResult() +} +New-Alias -Name kemc -Value Edit-KeeperManagedCompany +Register-ArgumentCompleter -CommandName Edit-KeeperManagedCompany -ParameterName Addons -ScriptBlock $Keeper_MspAddonName + +class MspDailySnapshotAddon { + [string]$Addon + [int]$Units +} +class MspDailySnapshotRecord { + [System.DateTime]$Date + [int]$McEnterpriseId + [int]$LicenseCount + [string]$ProductPlan + [string]$FilePlan + [MspDailySnapshotAddon[]]$Addons +} + +function Get-MspBillingReport { + <# + .Synopsis + Runs MSP Billing Report + .Parameter Month + Report Month 1-12 + .Parameter Year + Report Year 20xx + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $false)][int] $Month, + [Parameter(Mandatory = $false)][int] $Year + ) + + $dt = Get-Date + if (0 -eq $Year) { + $Year = $dt.Year + } + if (0 -eq $Month) { + $Month = $dt.Month - 1 + if ($Month -le 0) { + $Year -= 1 + $Month = 12 + } + } + + $auth = [KeeperSecurity.Authentication.IAuthentication] $auth = $Script:Context.Auth + + $url = [KeeperSecurity.Authentication.AuthExtensions]::GetBiUrl($auth, 'mapping/addons') + $rq = New-Object BI.MappingAddonsRequest + $rs = $auth.ExecuteAuthRest($url, $rq, [BI.MappingAddonsResponse]).GetAwaiter().GetResult() + $filePlans = @{ + 4 = '100GB' + 7 = '1TB' + 8 = '10TB' + } + foreach ($fp in $rs.FilePlans) { + $filePlans[$fp.Id] = $fp.Name + } + $addons = @{} + foreach ($aon in $rs.Addons) { + $addons[$aon.Id] = $aon.Name + } + + $url = [KeeperSecurity.Authentication.AuthExtensions]::GetBiUrl($auth, 'reporting/daily_snapshot') + $rq = New-Object BI.ReportingDailySnapshotRequest + $rq.Month = $Month + $rq.Year = $Year + + $rs = $auth.ExecuteAuthRest($url, $rq, [BI.ReportingDailySnapshotResponse]).GetAwaiter().GetResult() + foreach ($rec in $rs.Records) { + $r = New-Object MspDailySnapshotRecord + $r.Date = [KeeperSecurity.Utils.DateTimeOffsetExtensions]::FromUnixTimeMilliseconds($rec.date).Date + $r.McEnterpriseId = $rec.mcEnterpriseId + $r.LicenseCount = $rec.maxLicenseCount + switch ($rec.MaxBasePlanId) { + 1 { $r.ProductPlan = 'business' } + 2 { $r.ProductPlan = 'businessPlus' } + 10 { $r.ProductPlan = 'enterprise' } + 11 { $r.ProductPlan = 'enterprisePlus' } + default { $r.ProductPlan = "Plan #$($r.rec)" } + } + if ($rec.maxFilePlanTypeId) { + $r.FilePlan = $filePlans[$rec.maxFilePlanTypeId] + if (-not $r.FilePlan) { + $r.FilePlan = "Storage Plan #$($rec.maxFilePlanTypeId)" + } + } + + foreach ($addon in $rec.addons) { + if ($addon.maxAddonId) { + $a = New-Object MspDailySnapshotAddon + $a.Addon = $addons[$addon.maxAddonId] + if (-not $a.Addon) { + $a.Addon = "Addon # $($addon.maxAddonId)" + } + $a.Units = $addon.units + $r.Addons += $a + } + } + $r + } +} + +function Script:Get-KeeperNodeName { + Param ( + [long]$nodeId + ) + $enterprise = getEnterprise + [KeeperSecurity.Enterprise.EnterpriseNode]$node = $null + if ($enterprise.enterpriseData.TryGetNode($nodeId, [ref]$node)) { + if ($node.ParentNodeId -gt 0) { + return $node.DisplayName + } + else { + return $enterprise.loader.EnterpriseName + } + } +} + +function findManagedCompany { + Param ( + [string]$mc + ) + $enterprise = getMspEnterprise + $enterprise.mspData.ManagedCompanies | Where-Object { ($_.EnterpriseId -eq $mc) -or ($_.EnterpriseName -eq $mc) } | Select-Object -First 1 +} + +function findEnterpriseNode { + Param ( + [string]$node + ) + $enterprise = getEnterprise + if ($node -eq $enterprise.loader.EnterpriseName) { + return $enterprise.enterpriseData.RootNode + } + $enterprise.enterpriseData.Nodes | Where-Object { ($_.Id -eq $node) -or ($_.DisplayName -eq $node) } | Select-Object -First 1 +} + +function getMspEnterprise { + [Enterprise] $enterprise = $Script:Context.Enterprise + if (-not $enterprise) { + $enterprise = getEnterprise + } + if ($enterprise.enterpriseData.EnterpriseLicense -and $enterprise.enterpriseData.EnterpriseLicense.LicenseStatus -like "msp*") { + return $enterprise + } + Write-Error -Message "Not a MSP (Managed Service Provider)" -ErrorAction Stop +} diff --git a/PowerCommander/PowerCommander.psd1 b/PowerCommander/PowerCommander.psd1 index ee7a32b..29b47e2 100644 --- a/PowerCommander/PowerCommander.psd1 +++ b/PowerCommander/PowerCommander.psd1 @@ -11,7 +11,7 @@ RootModule = 'PowerCommander.psm1' # Version number of this module. - ModuleVersion = '0.9.12' + ModuleVersion = '0.9.13' # Supported PSEditions CompatiblePSEditions = @('Desktop') @@ -68,7 +68,7 @@ # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess NestedModules = @('AuthCommands.ps1', 'VaultCommands.ps1', 'RecordCommands.ps1', 'SharedFolderCommands.ps1', - 'FolderCommands.ps1', 'Enterprise.ps1', 'Sharing.ps1', 'SecretsManager.ps1', 'AttachmentCommands.ps1') + 'FolderCommands.ps1', 'Enterprise.ps1', 'ManagedCompany.ps1', 'Sharing.ps1', 'SecretsManager.ps1', 'AttachmentCommands.ps1') # Functions to export from this module FunctionsToExport = @('Connect-Keeper', 'Sync-Keeper', 'Disconnect-Keeper', 'Get-KeeperLocation', 'Set-KeeperLocation', @@ -78,7 +78,7 @@ 'Get-KeeperEnterpriseUser', 'Get-KeeperEnterpriseTeam', 'Sync-KeeperEnterprise', 'Get-KeeperEnterpriseNode', 'Get-KeeperNodeName', 'Lock-KeeperEnterpriseUser', 'Unlock-KeeperEnterpriseUser', 'Move-KeeperEnterpriseUser', 'Remove-KeeperEnterpriseUser', 'Get-KeeperManagedCompany', 'New-KeeperManagedCompany', 'Remove-KeeperManagedCompany', 'Edit-KeeperManagedCompany', 'Get-MspBillingReport', - 'Get-KeeperEnterpriseTeamUser', + 'Switch-KeeperMC', 'Switch-KeeperMSP', 'Get-KeeperEnterpriseTeamUser', 'Get-KeeperInformation', # 'Test-Keeper', 'Show-KeeperRecordShare', 'Grant-KeeperRecordAccess', 'Revoke-KeeperRecordAccess', 'Grant-KeeperSharedFolderAccess', 'Revoke-KeeperSharedFolderAccess', 'Get-KeeperAvailableTeam', 'Move-KeeperRecordOwnership', 'Get-KeeperSecretManagerApp', @@ -95,8 +95,9 @@ # Aliases to export from this module AliasesToExport = @('kc', 'ks', 'kq', 'kpwd', 'kcd', 'kdir', 'ko', 'kr', 'ksf', 'kcc', '2fa', 'kadd', 'kdel', 'kmv', 'kmkdir', 'krmdir', 'krti', - 'ked', 'keu', 'ken', 'ket', 'ketu', 'kmc', 'kamc', 'krmc', 'kemc', 'msp-license', 'lock-user', 'unlock-user', 'transfer-user', - 'delete-user', 'kshrsh', 'kshr', 'kushr', 'kshf', 'kushf', 'kat', 'ktr', 'kotsr', 'kotsg', 'kotsn', + 'ked', 'keu', 'ken', 'ket', 'ketu', 'kmc', 'kamc', 'krmc', 'kemc', 'msp-license', 'switch-to-mc', 'switch-to-msp', + 'lock-user', 'unlock-user', 'transfer-user', 'delete-user', 'kshrsh', 'kshr', 'kushr', 'kshf', 'kushf', + 'kat', 'ktr', 'kotsr', 'kotsg', 'kotsn', 'kwhoami', 'ksm', 'ksm-create', 'ksm-share', 'ksm-unshare', 'ksm-addclient', 'ksm-rmclient', 'kda') # List of all modules packaged with this module @@ -112,7 +113,7 @@ LicenseUri = 'https://github.com/Keeper-Security/keeper-sdk-dotnet/blob/master/LICENSE' ProjectUri = 'https://github.com/Keeper-Security/keeper-sdk-dotnet' IconUri = 'https://keeper-email-images.s3.amazonaws.com/common/powershell.png' - ReleaseNotes = "Update KeeperSDK library" + ReleaseNotes = 'Switch to Managed Company' } } diff --git a/PowerCommander/PowerCommander.psm1 b/PowerCommander/PowerCommander.psm1 index 72e5e69..f191577 100644 --- a/PowerCommander/PowerCommander.psm1 +++ b/PowerCommander/PowerCommander.psm1 @@ -5,6 +5,7 @@ Class Enterprise { [KeeperSecurity.Enterprise.EnterpriseData] $enterpriseData [KeeperSecurity.Enterprise.RoleData] $roleData [KeeperSecurity.Enterprise.ManagedCompanyData] $mspData + [hashtable] $ManagedCompanies = $null } class KeeperContext { @@ -12,14 +13,15 @@ class KeeperContext { [KeeperSecurity.Vault.VaultOnline] $Vault = $null [string] $CurrentFolder = '' [Enterprise] $Enterprise = $null + [Int32] $ManagedCompanyId = 0 $AvailableTeams = $null $AvailableUsers = $null } New-Variable -Name Context -Option Constant -Scope 'Script' -Value (New-Object KeeperContext) -Export-ModuleMember -Function Connect-Keeper, Sync-Keeper, Disconnect-Keeper -Export-ModuleMember -Alias kc, ks, kq +Export-ModuleMember -Function Connect-Keeper, Sync-Keeper, Disconnect-Keeper, Get-KeeperInformation +Export-ModuleMember -Alias kc, ks, kq, kwhoami Export-ModuleMember -Function Get-KeeperLocation, Set-KeeperLocation, Get-KeeperChildItem, Get-KeeperObject @@ -43,8 +45,8 @@ Get-KeeperEnterpriseTeamUser Export-ModuleMember -Alias ked, keu, ket, ketu, ken, lock-user, unlock-user, transfer-user, delete-user Export-ModuleMember -Function Get-KeeperManagedCompany, New-KeeperManagedCompany, Remove-KeeperManagedCompany, -Edit-KeeperManagedCompany, Get-MspBillingReport -Export-ModuleMember -Alias kmc, kamc, krmc, kemc +Edit-KeeperManagedCompany, Get-MspBillingReport, Switch-KeeperMC, Switch-KeeperMSP +Export-ModuleMember -Alias kmc, kamc, krmc, kemc, switch-to-mc, switch-to-msp Export-ModuleMember -Function Show-KeeperRecordShare, Grant-KeeperRecordAccess, Revoke-KeeperRecordAccess, Grant-KeeperSharedFolderAccess, Revoke-KeeperSharedFolderAccess, Get-KeeperAvailableTeam, Move-KeeperRecordOwnership, diff --git a/PowerCommander/README.md b/PowerCommander/README.md index 263ff66..3bdf19d 100644 --- a/PowerCommander/README.md +++ b/PowerCommander/README.md @@ -31,6 +31,7 @@ To run the PowerCommander module from the source copy PowerCommander\ directory | Copy-KeeperFileAttachment | kda | Download file attachments | Copy-KeeperFileAttachmentToStream | | Download file attachement to stream | Copy-FileToKeeperRecord | | Upload file attachment to a record +| Get-KeeperInformation | kwhoami | Print account license information ### Sharing Cmdlets | Cmdlet name | Alias | Description @@ -59,6 +60,8 @@ To run the PowerCommander module from the source copy PowerCommander\ directory | Move-KeeperEnterpriseUser |transfer-user| Transfer user account to another user | Remove-KeeperEnterpriseUser | delete-user | Delete Enterprise User | Get-KeeperMspLicenses | msp-license | Return MSP licenses +| Switch-KeeperMC |switch-to-mc | Switch to Managed Company +| Switch-KeeperMSP |switch-to-msp| Switch back to MSP | Get-KeeperManagedCompany | kmc | Enumerate all enterprise managed companies | New-KeeperManagedCompany | kamc | Create Managed Company | Remove-KeeperManagedCompany | krmc | Remove Managed Company