RecordCommands.ps1
#requires -Version 5.1 function Get-KeeperRecord { <# .Synopsis Get Keeper Records .Parameter Uid Record UID .Parameter Filter Return matching records only #> [CmdletBinding()] [OutputType([KeeperSecurity.Vault.KeeperRecord[]])] Param ( [string] $Uid, [string] $Filter ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault if ($Uid) { [KeeperSecurity.Vault.KeeperRecord] $record = $null if ($vault.TryGetKeeperRecord($uid, [ref]$record)) { $record } } else { foreach ($record in $vault.KeeperRecords) { if ($Filter) { $match = $($record.Uid, $record.TypeName, $record.Title, $record.Notes) | Select-String $Filter | Select-Object -First 1 if (-not $match) { continue } } $record } } } New-Alias -Name kr -Value Get-KeeperRecord function Copy-KeeperToClipboard { <# .Synopsis Copy record password to clipboard or output .Parameter Record Record UID or any object containing property Uid .Parameter Field Record field to copy to clipboard. Record password is default. .Parameter Output Password output destination. Clipboard is default. Use "Stdout" for scripting #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $Record, [string] [ValidateSet('Login' , 'Password', 'URL')] $Field = 'Password', [string] [ValidateSet('Clipboard' , 'Stdout')] $Output = 'Clipboard' ) Process { if ($Record -is [Array]) { if ($Record.Count -ne 1) { Write-Error -Message 'Only one record is expected' return } $Record = $Record[0] } [KeeperSecurity.Vault.VaultOnline]$vault = getVault $uid = $null if ($Record -is [String]) { $uid = $Record } elseif ($null -ne $Record.Uid) { $uid = $Record.Uid } $found = $false if ($uid) { [KeeperSecurity.Vault.KeeperRecord] $rec = $null if (-not $vault.TryGetKeeperRecord($uid, [ref]$rec)) { $entries = Get-KeeperChildItem -Filter $uid -ObjectType Record if ($entries.Uid) { $vault.TryGetRecord($entries[0].Uid, [ref]$rec) | Out-Null } } if ($rec) { $found = $true $value = '' if ($rec -is [KeeperSecurity.Vault.PasswordRecord]) { switch ($Field) { 'Login' { $value = $rec.Login } 'Password' { $value = $rec.Password } 'URL' { $value = $rec.Link } } } elseif ($rec -is [KeeperSecurity.Vault.TypedRecord]) { $fieldType = '' switch ($Field) { 'Login' { $fieldType = 'login' } 'Password' { $fieldType = 'password' } 'URL' { $fieldType = 'url' } } if ($fieldType) { $recordField = $rec.Fields | Where-Object FieldName -eq $fieldType | Select-Object -First 1 if (-not $recordField) { $recordField = $rec.Custom | Where-Object FieldName -eq $fieldType | Select-Object -First 1 } if ($recordField) { $value = $recordField.ObjectValue } } } if ($value) { if ($Output -eq 'Stdout') { $value } else { if ([System.Threading.Thread]::CurrentThread.GetApartmentState() -eq [System.Threading.ApartmentState]::MTA) { powershell -sta "Set-Clipboard -Value '$value'" } else { Set-Clipboard -Value $value } Write-Output "Copied to clipboard: $Field for $($rec.Title)" } if ($Field -eq 'Password') { $vault.AuditLogRecordCopyPassword($rec.Uid) } } else { Write-Output "Record $($rec.Title) has no $Field" } } } if (-not $found) { Write-Error -Message "Cannot find a Keeper record: $Record" } } } New-Alias -Name kcc -Value Copy-KeeperToClipboard function Get-KeeperPasswordVisible { <# .Synopsis Show/hide secret fields #> if ($Script:PasswordVisible) { $true } else { $false } } function Set-KeeperPasswordVisible { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] Param ([switch] $Visible) $Script:PasswordVisible = $Visible.IsPresent } function Show-TwoFactorCode { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $Records ) Begin { [KeeperSecurity.Vault.VaultOnline]$vault = getVault $totps = @() } Process { foreach ($r in $Records) { $uid = $null if ($r -is [String]) { $uid = $r } elseif ($null -ne $r.Uid) { $uid = $r.Uid } if ($uid) { [KeeperSecurity.Vault.KeeperRecord] $rec = $null if ($vault.TryGetKeeperRecord($uid, [ref]$rec)) { if ($rec -is [KeeperSecurity.Vault.PasswordRecord]) { if ($rec.ExtraFields) { foreach ($ef in $rec.ExtraFields) { if ($ef.FieldType -eq 'totp') { $totps += [PSCustomObject]@{ RecordUid = $rec.Uid Title = $rec.Title TotpData = $ef.Custom['data'] } } } } } elseif ($rec -is [KeeperSecurity.Vault.TypedRecord]) { $recordTypeField = New-Object KeeperSecurity.Vault.RecordTypeField 'oneTimeCode', $null [KeeperSecurity.Vault.ITypedField]$recordField = $null if ([KeeperSecurity.Vault.VaultDataExtensions]::FindTypedField($rec, $recordTypeField, [ref]$recordField)) { $data = $recordField.Value if ($data) { $totps += [PSCustomObject]@{ RecordUid = $rec.Uid Title = $rec.Title TotpData = $data } } } } } } } } End { $output = @() foreach ($totp in $totps) { [Tuple[string, int, int]]$code = [KeeperSecurity.Utils.CryptoUtils]::GetTotpCode($totp.TotpData) if ($code) { $output += [PSCustomObject]@{ PSTypeName = 'TOTP.Codes' RecordTitle = $totp.Title TOTPCode = $code.Item1 Elapsed = $code.Item2 Left = $code.Item3 - $code.Item2 } } } $output | Format-Table } } New-Alias -Name 2fa -Value Show-TwoFactorCode $Keeper_RecordTypeNameCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $result = @() [KeeperSecurity.Vault.VaultOnline]$vault = $Script:Context.Vault if ($vault) { $toComplete = $wordToComplete + '*' foreach ($rt in $vault.RecordTypes) { if ($rt.Name -like $toComplete) { $result += $rt.Name } } } if ($result.Count -gt 0) { return $result } else { return $null } } function Add-KeeperRecord { <# .Synopsis Creates or Modifies a Keeper record in the current folder. .Parameter Uid Record UID. If provided the existing record to be updated. Otherwise record is added. .Parameter RecordType Record Type (if account supports record types). .Parameter Title Record Title. Mandatory field for added record. .Parameter Notes Record Notes. .Parameter GeneratePassword Generate random password. .Parameter Fields A list of record Fields. Record field format NAME=VALUE Predefined fields are login Login Name password Password url Web Address Any other name is added to Custom Fields Example: login=username password=userpassword "Database Server=value1" #> [CmdletBinding(DefaultParameterSetName = 'add')] Param ( [Parameter()] [switch] $GeneratePassword, [Parameter(ParameterSetName = 'add')] [string] $RecordType, [Parameter(ParameterSetName = 'add')] [string] $Folder, [Parameter(ParameterSetName = 'edit', Mandatory = $True)] [string] $Uid, [Parameter(ParameterSetName = 'add', Mandatory = $True)] [string] $Title, [Parameter()] $Notes, [Parameter(ValueFromRemainingArguments = $true)] $Extra ) Begin { [KeeperSecurity.Vault.VaultOnline]$vault = getVault [KeeperSecurity.Vault.KeeperRecord]$record = $null $fields = @{} $fieldName = $null foreach ($var in $Extra) { if ($var -match '^-') { $fieldName = $var.Substring(1) if ($var -match ':$') { $fieldName = $fieldName.Substring(0, $fieldName.Length - 1) } } elseif ($null -ne $fieldName) { $fields[$fieldName] = $var $fieldName = $null } else { if ($var -match '^([^=]+)=(.*)?') { $n = $Matches[1].Trim() $v = $Matches[2].Trim() if ($n -and $v) { $fields[$n] = $v } } } } } Process { if ($Uid) { if (-not $vault.TryGetKeeperRecord($Uid, [ref]$record)) { $objs = Get-KeeperChildItem -ObjectType Record | Where-Object Name -eq $Uid if ($objs.Length -gt 1) { $vault.TryGetKeeperRecord($objs[0].Uid, [ref]$record) } } if (-not $record) { Write-Error -Message "Record `"$Uid`" not found" return } } else { if (-not $RecordType -or $RecordType -eq 'legacy') { $record = New-Object KeeperSecurity.Vault.PasswordRecord } else { $record = New-Object KeeperSecurity.Vault.TypedRecord $RecordType [KeeperSecurity.Utils.RecordTypesUtils]::AdjustTypedRecord($vault, $record) } $record.Title = $Title } if ($Notes -is [string]) { if ($Notes.Length -gt 0 -and $Notes[0] -eq '+') { $Notes = $record.Notes + "`n" + $Notes.Substring(1) } elseif ($Notes -eq '-') { $Notes = '' } $record.Notes = $Notes } if ($GeneratePassword.IsPresent) { $fields['password'] = [Keepersecurity.Utils.CryptoUtils]::GenerateUid() } foreach ($fieldName in $fields.Keys) { $fieldValue = $fields[$fieldName] $fieldLabel = '' if ($fieldName -match '^([^.]+)(\..+)?$') { if ($Matches[1] -and $Matches[2]) { $fieldName = $Matches[1].Trim() $fieldLabel = $Matches[2].Trim().Substring(1) } } if ($fieldName -match '^\$') { $fieldName = $fieldName.Substring(1).Trim() } if ($record -is [KeeperSecurity.Vault.PasswordRecord]) { switch ($fieldName) { 'login' { $record.Login = $fieldValue } 'password' { $record.Password = $fieldValue } 'url' { $record.Link = $fieldValue } Default { if ($fieldLabel) { if ($fieldName -eq 'text') { $fieldName = $fieldLabel } else { $fieldName = "${fieldName}:${fieldLabel}" } } if ($fieldValue) { $record.SetCustomField($fieldName, $fieldValue) | Out-Null } else { $record.DeleteCustomField($fieldName) | Out-Null } } } } elseif ($record -is [KeeperSecurity.Vault.TypedRecord]) { if (-not $fieldLabel) { [KeeperSecurity.Vault.RecordField]$recordField = $null if (-not [KeeperSecurity.Vault.RecordTypesConstants]::TryGetRecordField($fieldName, [ref]$recordField)) { $fieldLabel = $fieldName $fieldName = 'text' } } $recordTypeField = New-Object KeeperSecurity.Vault.RecordTypeField $fieldName, $fieldLabel [KeeperSecurity.Vault.ITypedField]$typedField = $null if ([KeeperSecurity.Vault.VaultDataExtensions]::FindTypedField($record, $recordTypeField, [ref]$typedField)) { } else { if ($fieldValue) { $typedField = [KeeperSecurity.Vault.VaultDataExtensions]::CreateTypedField($fieldName, $fieldLabel) if ($typedField) { $record.Custom.Add($typedField) } } } if ($typedField) { if ($fieldValue) { $typedField.ObjectValue = $fieldValue } else { $typedField.DeleteValueAt(0) } } } } } End { if ($record.Uid) { $task = $vault.UpdateRecord($record) } else { $folderUid = $Script:Context.CurrentFolder if ($Folder) { $folderNode = resolveFolderNode $vault $Folder $folderUid = $folderNode.FolderUid } $task = $vault.CreateRecord($record, $folderUid) } $task.GetAwaiter().GetResult() } } New-Alias -Name kadd -Value Add-KeeperRecord Register-ArgumentCompleter -CommandName Add-KeeperRecord -ParameterName Folder -ScriptBlock $Keeper_FolderPathRecordCompleter Register-ArgumentCompleter -CommandName Add-KeeperRecord -ParameterName RecordType -ScriptBlock $Keeper_RecordTypeNameCompleter function Remove-KeeperRecord { <# .Synopsis Removes Keeper record. .Parameter Name Folder name or Folder UID #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(DefaultParameterSetName = 'Default')] Param ( [Parameter(Position = 0, Mandatory = $true)][string] $Name ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault $folderUid = $null $recordUid = $null [KeeperSecurity.Vault.KeeperRecord] $record = $null if ($vault.TryGetKeeperRecord($Name, [ref]$record)) { $recordUid = $record.Uid if (-not $vault.RootFolder.Records.Contains($recordUid)) { foreach ($f in $vault.Folders) { if ($f.Records.Contains($recordUid)) { $folderUid = $f.FolderUid break } } } } if (-not $recordUid) { $objs = Get-KeeperChildItem -ObjectType Record | Where-Object Name -eq $Name if (-not $objs) { Write-Error -Message "Record `"$Name`" does not exist" return } if ($objs.Length -gt 1) { Write-Error -Message "There are more than one records with name `"$Name`". Use Record UID do delete the correct one." return } $recordUid = $objs[0].Uid $folderUid = $Script:Context.CurrentFolder } $recordPath = New-Object KeeperSecurity.Vault.RecordPath $recordPath.RecordUid = $recordUid $recordPath.FolderUid = $folderUid $task = $vault.DeleteRecords(@($recordPath)) $task.GetAwaiter().GetResult() | Out-Null } New-Alias -Name kdel -Value Remove-KeeperRecord function Move-RecordToFolder { <# .Synopsis Moves records to Folder. .Parameter Record Record UID, Path or any object containing property Uid. .Parameter Folder Folder Name, Path, or UID #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)]$Records, [Parameter(Position = 0, Mandatory = $true)][string]$Folder, [Parameter()][switch]$Link ) Begin { [KeeperSecurity.Vault.VaultOnline]$vault = getVault $folderNode = resolveFolderNode $vault $Folder $sourceRecords = @() } Process { foreach ($r in $Records) { if ($null -ne $r.Uid) { $r = $r.Uid } [KeeperSecurity.Vault.FolderNode]$folder = $null [KeeperSecurity.Vault.KeeperRecord]$record = $null if ($vault.TryGetKeeperRecord($r, [ref]$record)) { if ($record -is [KeeperSecurity.Vault.PasswordRecord] -or $record -is [KeeperSecurity.Vault.TypedRecord]) { if ($folderNode.FolderUid -and $vault.RootFolder.Records.Contains($record.Uid)) { $folder = $vault.RootFolder } else { foreach ($fol in $vault.Folders) { if ($fol.FolderUid -eq $folderNode.FolderUid) { continue } if ($fol.Records.Contains($record.Uid)) { $folder = $fol break } } } } else { Write-Error "`$r`" record type is not supported." -ErrorAction Stop } } else { [KeeperSecurity.Vault.FolderNode]$fol = $null if (-not $vault.TryGetFolder($Script:Context.CurrentFolder, [ref]$fol)) { $fol = $vault.RootFolder } $comps = splitKeeperPath $r $folder, $rest = parseKeeperPath $comps $vault $fol if (-not $rest) { Write-Error "`"$r`" should be a record" -ErrorAction Stop } [KeeperSecurity.Vault.KeeperRecord]$rec = $null foreach ($recordUid in $folder.Records) { if ($vault.TryGetKeeperRecord($recordUid, [ref]$rec)) { if ($rec.Title -eq $rest) { if ($rec -is [KeeperSecurity.Vault.PasswordRecord] -or $rec -is [KeeperSecurity.Vault.TypedRecord]) { $record = $rec break } } } } } if (-not $record -or -not $folder) { Write-Error "Record `"$r`" cannot be found" -ErrorAction Stop } $rp = New-Object KeeperSecurity.Vault.RecordPath $rp.RecordUid = $record.Uid $rp.FolderUid = $folder.FolderUid $sourceRecords += $rp } } End { if (-not $sourceRecords) { Write-Error "There are no records to move" -ErrorAction Stop } $vault.MoveRecords($sourceRecords, $folderNode.FolderUid, $Link.IsPresent).GetAwaiter().GetResult() | Out-Null $vault.ScheduleSyncDown([System.TimeSpan]::FromSeconds(0)).GetAwaiter().GetResult() | Out-Null } } New-Alias -Name kmv -Value Move-RecordToFolder Register-ArgumentCompleter -CommandName Move-RecordToFolder -ParameterName Folder -ScriptBlock $Keeper_FolderPathRecordCompleter function Get-KeeperRecordType { <# .Synopsis Get Record Type Information .Parameter Record Record UID, Path or any object containing property Uid. .Parameter Folder Folder Name, Path, or UID #> [CmdletBinding()] Param ( [switch] $ShowFields, [Parameter(Position = 0, Mandatory = $false)][string] $Name ) [KeeperSecurity.Vault.VaultOnline]$vault = getVault if ($ShowFields.IsPresent) { [KeeperSecurity.Vault.RecordTypesConstants]::RecordFields | Where-Object { -not $Name -or $_.Name -eq $Name } | Sort-Object Name } else { $vault.RecordTypes | Where-Object { -not $Name -or $_.Name -eq $Name } | Sort-Object Name } } New-Alias -Name krti -Value Get-KeeperRecordType function resolveFolderNode { Param ([KeeperSecurity.Vault.VaultOnline]$vault, $path) [KeeperSecurity.Vault.FolderNode]$folder = $null if (-not $vault.TryGetFolder($path, [ref]$folder)) { if (-not $vault.TryGetFolder($Script:Context.CurrentFolder, [ref]$folder)) { $folder = $vault.RootFolder } $comps = splitKeeperPath $path $folder, $rest = parseKeeperPath $comps $vault $folder if ($rest) { Write-Error "Folder $path not found" -ErrorAction Stop } } $folder } |