@@ -331,6 +331,13 @@ function Set-KeeperNSFRecordAccess {
331331 . Parameter Role
332332 Access role for grant action: viewer (default), share-manager, content-manager,
333333 content-share-manager, full-manager.
334+
335+ . Parameter ExpireIn
336+ Optional. Share expiration period from now (e.g. 30d, 6mo, 1y, 24h, 30mi), integer minutes,
337+ or a TimeSpan. Same as Grant-KeeperRecordAccess.
338+
339+ . Parameter ExpireAt
340+ Optional. Absolute share expiration as ISO datetime (e.g. 2027-01-01T00:00:00Z).
334341#>
335342 [CmdletBinding ()]
336343 [Diagnostics.CodeAnalysis.SuppressMessageAttribute (" PSUseShouldProcessForStateChangingFunctions" , " " )]
@@ -347,7 +354,15 @@ function Set-KeeperNSFRecordAccess {
347354
348355 [Parameter ()]
349356 [ValidateSet (' viewer' , ' share-manager' , ' content-manager' , ' content-share-manager' , ' full-manager' )]
350- [string ] $Role = ' viewer'
357+ [string ] $Role = ' viewer' ,
358+
359+ [Alias (' expire-in' )]
360+ [Parameter ()]
361+ [System.Object ] $ExpireIn ,
362+
363+ [Alias (' expire-at' )]
364+ [Parameter ()]
365+ [string ] $ExpireAt
351366 )
352367
353368 try {
@@ -363,11 +378,27 @@ function Set-KeeperNSFRecordAccess {
363378 return
364379 }
365380
381+ $shareOptions = $null
382+ if ($Action -eq ' grant' -and ($ExpireIn -or $ExpireAt )) {
383+ try {
384+ $expirationDto = Get-ExpirationDate - ExpireIn $ExpireIn - ExpireAt $ExpireAt
385+ }
386+ catch {
387+ Write-Host " Error: $ ( $_.Exception.Message ) " - ForegroundColor Red
388+ return
389+ }
390+ $shareOptions = New-Object KeeperSecurity.Vault.SharedFolderRecordOptions
391+ $shareOptions.Expiration = $expirationDto
392+ }
393+
366394 foreach ($user in $Email ) {
367395 try {
368396 if ($Action -eq ' grant' ) {
369- [void ]$vault.ShareKeeperNSFRecord ($RecordUid , $user , $Role ).GetAwaiter().GetResult()
370- Write-Host " Granted '$Role ' access to '$user ' on record '$RecordUid '." - ForegroundColor Green
397+ [void ]$vault.ShareKeeperNSFRecord ($RecordUid , $user , $Role , $shareOptions ).GetAwaiter().GetResult()
398+ $expireMsg = if ($shareOptions -and $shareOptions.Expiration ) {
399+ " (expires $ ( $shareOptions.Expiration.LocalDateTime.ToString (' g' )) )"
400+ } else { ' ' }
401+ Write-Host " Granted '$Role ' access to '$user ' on record '$RecordUid '$expireMsg ." - ForegroundColor Green
371402 }
372403 else {
373404 [void ]$vault.UnshareKeeperNSFRecord ($RecordUid , $user ).GetAwaiter().GetResult()
@@ -881,21 +912,18 @@ function Remove-KeeperNSFRecord {
881912 begin {
882913 [KeeperSecurity.Vault.VaultOnline ]$vault = getVault
883914 $removals = New-Object ' System.Collections.Generic.List[KeeperSecurity.Vault.KeeperNSFRecordRemoval]'
884- $resolvedFolderUid = $null
915+ $folderHint = $null
885916
886917 if ($Folder ) {
887918 [KeeperSecurity.Vault.FolderNode ]$folderNode = $null
888919 if (-not $vault.TryResolveKeeperNSFFolder ($Folder , [ref ]$folderNode )) {
889920 Write-Error - Message " Keeper NSF folder `" $Folder `" was not found. Run Sync-Keeper or nsf-list first."
890921 return
891922 }
892- $resolvedFolderUid = $folderNode .FolderUid
923+ $folderHint = $Folder
893924 }
894- elseif ($Script :Context.CurrentFolder ) {
895- [KeeperSecurity.Vault.FolderNode ]$currentFolder = $null
896- if ($vault.TryResolveKeeperNSFFolder ($Script :Context.CurrentFolder , [ref ]$currentFolder )) {
897- $resolvedFolderUid = $currentFolder.FolderUid
898- }
925+ elseif ($Operation -ne ' owner-trash' -and $Script :Context.CurrentFolder ) {
926+ $folderHint = $Script :Context.CurrentFolder
899927 }
900928
901929 $op = switch ($Operation ) {
@@ -904,7 +932,7 @@ function Remove-KeeperNSFRecord {
904932 ' unlink' { [KeeperSecurity.Vault.KeeperNSFRecordRemoveOperation ]::Unlink }
905933 }
906934
907- if ($op -eq [KeeperSecurity.Vault.KeeperNSFRecordRemoveOperation ]::Unlink -and -not $resolvedFolderUid ) {
935+ if ($op -eq [KeeperSecurity.Vault.KeeperNSFRecordRemoveOperation ]::Unlink -and [ string ]::IsNullOrWhiteSpace( $folderHint ) ) {
908936 Write-Error - Message " Folder context is required for unlink. Use -Folder or cd into a Keeper NSF folder."
909937 return
910938 }
@@ -918,14 +946,15 @@ function Remove-KeeperNSFRecord {
918946 continue
919947 }
920948
921- $folderUid = $resolvedFolderUid
922- if (-not $folderUid -and $op -ne [KeeperSecurity.Vault.KeeperNSFRecordRemoveOperation ]::OwnerTrash) {
923- $folderUids = @ ($vault.GetKeeperNSFFoldersForRecord ($kdRecord.RecordUid ))
924- if ($folderUids.Count -eq 0 ) {
949+ [string ]$folderUid = $null
950+ if (-not $vault.TryResolveKeeperNSFRecordRemovalFolder ($kdRecord.RecordUid , $folderHint , $op , [ref ]$folderUid )) {
951+ if ($Folder ) {
952+ Write-Error - Message " Keeper NSF folder `" $Folder `" was not found. Run Sync-Keeper or nsf-list first."
953+ }
954+ else {
925955 Write-Error - Message " No folder context for record `" $name `" . Use -Folder or -Operation owner-trash."
926- continue
927956 }
928- $folderUid = $folderUids [ 0 ]
957+ continue
929958 }
930959
931960 $removal = New-Object KeeperSecurity.Vault.KeeperNSFRecordRemoval
@@ -946,6 +975,15 @@ function Remove-KeeperNSFRecord {
946975 $previewResult = $vault.RemoveKeeperNSFRecords ($removals , $true ).GetAwaiter().GetResult()
947976 Write-KeeperNSFRemoveImpact - Response $previewResult.PreviewResponse
948977
978+ $previewErrors = @ ($previewResult.PreviewResponse.Results | Where-Object {
979+ $_.Error -and -not [string ]::IsNullOrWhiteSpace($_.Error.Message )
980+ })
981+ if ($previewErrors.Count -gt 0 ) {
982+ Write-Host " "
983+ Write-Host " One or more records could not be previewed. Aborting." - ForegroundColor Yellow
984+ return
985+ }
986+
949987 try {
950988 [KeeperSecurity.Vault.VaultOnline ]::ValidateRemoveResponse($previewResult.PreviewResponse , $false )
951989 }
0 commit comments