DRJones.psm1
|
function Assert-ARKElevated { <# .SYNOPSIS Throws if the current session is not elevated. .DESCRIPTION Throws a terminating error naming the operation when the current session is not running as Administrator. .PARAMETER Operation The operation that requires elevation, used in the error message. .EXAMPLE Assert-ARKElevated -Operation 'CAVaultManager RestoreDB' Throws: CAVaultManager RestoreDB must run in an elevated session. Run PowerShell as Administrator. #> [CmdletBinding()] param( [Parameter( Mandatory = $true )] [ValidateNotNullOrEmpty()] [string]$Operation ) if (-not (Test-ARKElevated)) { throw "$Operation must run in an elevated session. Run PowerShell as Administrator." } } function ConvertTo-ARKArgument { <# .SYNOPSIS Converts values into an argument string for a recovery utility command line. .DESCRIPTION Joins values with spaces, wrapping a value in double quotes only when it is empty or contains whitespace or double quotes, so switches such as /RestoreSafe are passed as typed. In a quoted value, embedded double quotes are escaped, and trailing backslashes are doubled so a folder path such as 'C:\Vault Keys\' is not read as an escaped closing quote when the utility parses its command line. .PARAMETER Value The values to convert, in command line order. .EXAMPLE ConvertTo-ARKArgument -Value 'C:\Vault\Data\*', 'D:\Restored', 'C:\Vault Keys\' Returns: C:\Vault\Data\* D:\Restored "C:\Vault Keys\\" #> [CmdletBinding()] [OutputType('System.String')] param( [Parameter( Mandatory = $true, ValueFromPipeline = $true )] [AllowEmptyString()] [string[]]$Value ) begin { $Arguments = [System.Collections.Generic.List[string]]::new() } process { foreach ($Item in $Value) { if ($Item -eq '' -or $Item -match '[\s"]') { $Item = ($Item -replace '(\\*)"', '$1$1\"') -replace '(\\+)$', '$1$1' $Item = '"{0}"' -f $Item } $Arguments.Add($Item) } } end { $Arguments -join ' ' } } function Get-ARKBackupArgument { <# .SYNOPSIS Builds the leading PARestore and PAReplicate arguments: Vault file, user logon and backup pool. .DESCRIPTION Returns the arguments common to every PARestore and PAReplicate operation, in command line order: <VaultFile> <User>/<Password> | /LogonFromFile <LogonFile>, then /BackupPoolName <BackupPoolName>. VaultFile defaults to Vault.ini, which the utility reads from its own folder. Relative paths provided for VaultFile and LogonFile are resolved against the current PowerShell location. .PARAMETER VaultFile The Vault parameter file. .PARAMETER Credential The Vault user and password. .PARAMETER LogonFile The user credentials file. .PARAMETER BackupPoolName The backup pool name. .EXAMPLE Get-ARKBackupArgument -LogonFile C:\Creds\Operator.cred Returns: Vault.ini, /LogonFromFile, C:\Creds\Operator.cred #> [CmdletBinding()] [OutputType('System.String')] param( [Parameter( Mandatory = $false )] [string]$VaultFile, [Parameter( Mandatory = $true, ParameterSetName = 'Credential' )] [pscredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = 'LogonFile' )] [string]$LogonFile, [Parameter( Mandatory = $false )] [string]$BackupPoolName ) if ($VaultFile) { $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($VaultFile) } else { 'Vault.ini' } switch ($PSCmdlet.ParameterSetName) { 'Credential' { '{0}/{1}' -f $Credential.UserName, $Credential.GetNetworkCredential().Password } 'LogonFile' { '/LogonFromFile' $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($LogonFile) } } if ($BackupPoolName) { '/BackupPoolName' $BackupPoolName } } function Get-ARKUtilityPath { <# .SYNOPSIS Gets the path to a CyberArk utility. .DESCRIPTION Returns the path to the utility executable in the first folder set with Set-ARKConfiguration -UtilityPath that contains it. Throws if no utility folder is set, or if the utility is not found in any of them. .PARAMETER Utility The utility to locate: Recover, SafeRecover, PARestore, PAReplicate or CAVaultManager. .EXAMPLE Get-ARKUtilityPath -Utility PAReplicate Returns: C:\Program Files (x86)\PrivateArk\Replicate\PAReplicate.exe #> [CmdletBinding()] [OutputType('System.String')] param( [Parameter( Mandatory = $true )] [ValidateSet('Recover', 'SafeRecover', 'PARestore', 'PAReplicate', 'CAVaultManager')] [string]$Utility ) $UtilityPath = @($Script:ARKConfig.UtilityPath | Where-Object { -not [string]::IsNullOrEmpty($_) }) if ($UtilityPath.Count -eq 0) { throw "Recovery utility folder not set. Run Set-ARKConfiguration -UtilityPath <folder containing $Utility.exe>" } $FilePath = @( $UtilityPath | ForEach-Object { Join-Path -Path $_ -ChildPath "$Utility.exe" } | Where-Object { Test-Path -Path $_ -PathType Leaf } )[0] if (-not $FilePath) { throw "$Utility.exe not found in $($UtilityPath -join ', '). Run Set-ARKConfiguration -UtilityPath <folder containing $Utility.exe>" } $FilePath } function Invoke-ARKUtility { <# .SYNOPSIS Runs a CyberArk recovery utility. .DESCRIPTION Runs Recover.exe, SafeRecover.exe, PARestore.exe, PAReplicate.exe or CAVaultManager.exe from the first folder set with Set-ARKConfiguration -UtilityPath that contains it, passing the supplied arguments in order. The utility runs with its own folder as the working directory. A non-zero exit code, or a PARestore cancellation (PARST032E) after a confirmation prompt, is reported as an error; otherwise the exit code and output are returned. An exit code of 0xC0000135 (STATUS_DLL_NOT_FOUND) is reported as a missing DLL. Known PARestore errors (ITATS633E, ITATS004E, ITATS654E, a missing original Safe, CASCF034E) include a hint to the likely cause. .PARAMETER Utility The utility to run: Recover, SafeRecover, PARestore, PAReplicate or CAVaultManager. .PARAMETER ArgumentList The utility arguments, in command line order. .PARAMETER SensitiveValue Values, such as passwords, to mask in debug output and error records. .PARAMETER Interactive Runs the utility in the current console without redirecting its input or output, so the user sees and answers its prompts. The output is not captured. .EXAMPLE Invoke-ARKUtility -Utility SafeRecover -ArgumentList 'Research', 'C:\Research', 'C:\MasterKey' Runs: SafeRecover.exe Research C:\Research C:\MasterKey #> [CmdletBinding()] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $true )] [ValidateSet('Recover', 'SafeRecover', 'PARestore', 'PAReplicate', 'CAVaultManager')] [string]$Utility, [Parameter( Mandatory = $true )] [ValidateNotNullOrEmpty()] [string[]]$ArgumentList, [Parameter( Mandatory = $false )] [string[]]$SensitiveValue, [Parameter( Mandatory = $false )] [switch]$Interactive ) #STATUS_DLL_NOT_FOUND (0xC0000135) $DllNotFound = -1073741515 $FilePath = Get-ARKUtilityPath -Utility $Utility $WorkingDirectory = Split-Path -Path $FilePath -Parent $Arguments = ConvertTo-ARKArgument -Value $ArgumentList $MaskedArguments = ConvertTo-ARKArgument -Value @( foreach ($Argument in $ArgumentList) { foreach ($Value in $SensitiveValue | Where-Object { $_ }) { $Argument = $Argument.Replace($Value, '********') } $Argument } ) Write-Debug "Command: $FilePath $MaskedArguments" $Process = [System.Diagnostics.Process]::new() try { $Process.StartInfo.FileName = $FilePath $Process.StartInfo.Arguments = $Arguments $Process.StartInfo.WorkingDirectory = $WorkingDirectory $Process.StartInfo.RedirectStandardInput = -not $Interactive $Process.StartInfo.RedirectStandardOutput = -not $Interactive $Process.StartInfo.RedirectStandardError = -not $Interactive $Process.StartInfo.UseShellExecute = $false $Process.StartInfo.CreateNoWindow = -not $Interactive $Result = Start-ARKUtilityProcess -Process $Process } finally { $Process.Dispose() } $Cancelled = [regex]::Match([string]$Result.StdOut, 'PARST032E.*') if ($Result.ExitCode -ne 0 -or $Cancelled.Success) { $Message = if ($Result.ExitCode -eq $DllNotFound) { "$Utility.exe could not start: a required DLL was not found. Copy the DLLs supplied with $Utility.exe in the Vault Server folder to $WorkingDirectory, and install the Visual C++ redistributable it requires." } elseif ($Cancelled.Success) { $Cancelled.Value } else { @(@($Result.StdErr, $Result.StdOut) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })[0] } if (-not $Message) { $Message = "$Utility.exe exited with code $($Result.ExitCode)" } $Message = $Message.Trim() $Hints = [ordered]@{ 'ITATS633E' = 'Check that the backup files are in the SafesDirectory set in tsparm.ini, that the Metadata folder holds every metadata file, and that BackupPoolName matches the pool used by PAReplicate. With LoadOnly, the files must already be in the Restored Safes folder on the Vault server.' 'ITATS654E' = 'The target Safe must not exist: restore into a new Safe name.' 'ITATS004E' = 'Check the password or credentials file. A user created to run PARestore or PAReplicate must have the CABACKUP user type.' 'corresponding safe does not exis|Code: 929\b' = 'The Safe being restored must exist in the Vault, with the restoring user as an owner. If it was deleted, create an empty Safe with the same name and its original owners.' 'CASCF034E' = 'The credentials file is restricted to another OS user, executable path or machine. A file restricted to SYSTEM works only from a scheduled task, such as one registered by Register-ARKBackupTask.' } foreach ($Pattern in $Hints.Keys) { if ($Message -match $Pattern) { $Message = "$Message`n$($Hints[$Pattern])" } } Write-Error -Message $Message -ErrorId "$Utility.ExitCode.$($Result.ExitCode)" -Category InvalidResult -TargetObject $MaskedArguments } else { [PSCustomObject]@{ PSTypeName = 'DRJones.UtilityResult' Utility = $Utility ExitCode = $Result.ExitCode StdOut = $Result.StdOut StdErr = $Result.StdErr } } } function Read-ARKIniFile { <# .SYNOPSIS Reads a CyberArk parameter file. .DESCRIPTION Reads a parameter file, such as DBParm.ini or Vault.ini, returning its lines, its encoding, and the parameters found in it. Files with a UTF-8 or Unicode byte order mark are read with that encoding; other files are read byte for byte, so that unchanged lines are written back unaltered. Lines starting with ; or # are comments. .PARAMETER Path The parameter file. .EXAMPLE Read-ARKIniFile -Path 'C:\Program Files (x86)\PrivateArk\Server\Conf\DBParm.ini' Returns the lines, encoding and parameters of DBParm.ini. #> [CmdletBinding()] param( [Parameter( Mandatory = $true )] [ValidateNotNullOrEmpty()] [string]$Path ) $Bytes = [System.IO.File]::ReadAllBytes($Path) $Encoding = switch ($true) { { $Bytes.Length -ge 3 -and $Bytes[0] -eq 0xEF -and $Bytes[1] -eq 0xBB -and $Bytes[2] -eq 0xBF } { [System.Text.UTF8Encoding]::new($true); break } { $Bytes.Length -ge 2 -and $Bytes[0] -eq 0xFF -and $Bytes[1] -eq 0xFE } { [System.Text.UnicodeEncoding]::new($false, $true); break } default { [System.Text.Encoding]::GetEncoding(28591) } } $Preamble = $Encoding.GetPreamble().Length $Lines = $Encoding.GetString($Bytes, $Preamble, $Bytes.Length - $Preamble) -split '\r?\n' $Section = $null $Parameters = for ($Index = 0; $Index -lt $Lines.Count; $Index++) { if ($Lines[$Index] -match '^\s*\[(?<Section>[^\]]+)\]\s*$') { $Section = $Matches['Section'].Trim() } elseif ($Lines[$Index] -match '^(?<Prefix>\s*(?<Name>[^\s=;#\[][^=]*?)\s*=\s*)(?<Value>.*?)\s*$') { [PSCustomObject]@{ Index = $Index Section = $Section Name = $Matches['Name'] Value = $Matches['Value'] Prefix = $Matches['Prefix'] } } } [PSCustomObject]@{ Encoding = $Encoding Lines = [System.Collections.Generic.List[string]]$Lines Parameters = @($Parameters) Sections = @($Lines | Where-Object { $_ -match '^\s*\[[^\]]+\]\s*$' }).Count } } function Start-ARKUtilityProcess { <# .SYNOPSIS Starts a recovery utility process. .DESCRIPTION Starts the process described by a System.Diagnostics.Process object prepared by Invoke-ARKUtility, waits for it to exit, and returns its exit code and output. When input and output are redirected, standard input is closed as soon as the process starts, so a utility prompting for input reads end of input rather than waiting indefinitely. Otherwise the process runs in the current console and its output is not captured. .PARAMETER Process System.Diagnostics.Process object containing the utility path and arguments. .EXAMPLE Start-ARKUtilityProcess -Process $Process Starts the process and returns an object with ExitCode, StdOut and StdErr properties. #> [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'ShouldProcess handling is in the calling public function')] [OutputType('System.Management.Automation.PSCustomObject')] param( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [System.Diagnostics.Process]$Process ) process { $null = $Process.Start() $StdOut = $null $StdErr = $null if ($Process.StartInfo.RedirectStandardInput) { $Process.StandardInput.Close() #Read output streams before waiting, otherwise a full output buffer blocks the process from exiting. $StdOut = $Process.StandardOutput.ReadToEnd() $StdErr = $Process.StandardError.ReadToEnd() } $Process.WaitForExit() [PSCustomObject]@{ ExitCode = $Process.ExitCode StdOut = $StdOut StdErr = $StdErr } } } function Test-ARKElevated { <# .SYNOPSIS Tests whether the current session is elevated. .DESCRIPTION Returns true when the current Windows identity is running as a member of the local Administrators group. .EXAMPLE Test-ARKElevated Returns true in an elevated session. #> [CmdletBinding()] [OutputType('System.Boolean')] param( ) $Principal = [Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent()) $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # .ExternalHelp DRJones-help.xml function Backup-ARKVault { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'LogonFile')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $true, ParameterSetName = 'Credential' )] [pscredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = 'LogonFile' )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$LogonFile, [Parameter( Mandatory = $false )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$VaultFile, [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName, [Parameter( Mandatory = $false )] [switch]$Incremental ) $LogonParameters = @{ } foreach ($Key in 'VaultFile', 'Credential', 'LogonFile', 'BackupPoolName') { if ($PSBoundParameters.ContainsKey($Key)) { $LogonParameters[$Key] = $PSBoundParameters[$Key] } } $ArgumentList = @( Get-ARKBackupArgument @LogonParameters if (-not $Incremental) { '/FullBackup' } ) $BackupType = if ($Incremental) { 'incremental' } else { 'full' } if ($PSCmdlet.ShouldProcess('Vault', "Run $BackupType backup")) { Invoke-ARKUtility -Utility PAReplicate -ArgumentList $ArgumentList -SensitiveValue $( if ($Credential) { $Credential.GetNetworkCredential().Password } ) } } # .ExternalHelp DRJones-help.xml function Get-ARKConfiguration { [CmdletBinding()] [OutputType('System.Management.Automation.PSCustomObject')] param( ) $Script:ARKConfig | Select-Object -Property UtilityPath, KeyPath } # .ExternalHelp DRJones-help.xml function Get-ARKVaultDatabaseReport { [CmdletBinding()] [OutputType('DRJones.UtilityResult')] param( ) Assert-ARKElevated -Operation 'CAVaultManager DiagnoseDBReport' Invoke-ARKUtility -Utility CAVaultManager -ArgumentList 'DiagnoseDBReport' } # .ExternalHelp DRJones-help.xml function Get-ARKVaultParameter { [CmdletBinding()] [OutputType('DRJones.VaultParameter')] param( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [Alias('FullName')] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$Path, [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]]$Name = '*', [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$Section ) process { $FullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $Ini = Read-ARKIniFile -Path $FullPath foreach ($Parameter in $Ini.Parameters) { if ($Section -and $Parameter.Section -ne $Section) { continue } if (-not ($Name | Where-Object { $Parameter.Name -like $_ })) { continue } [PSCustomObject]@{ PSTypeName = 'DRJones.VaultParameter' Section = $Parameter.Section Name = $Parameter.Name Value = $Parameter.Value Path = $FullPath } } } } # .ExternalHelp DRJones-help.xml function Register-ARKBackupTask { [CmdletBinding(SupportsShouldProcess)] [OutputType('Microsoft.Management.Infrastructure.CimInstance#MSFT_ScheduledTask')] param( [Parameter( Mandatory = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$LogonFile, [Parameter( Mandatory = $false )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$VaultFile, [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName, [Parameter( Mandatory = $false )] [datetime]$At = '01:00', [Parameter( Mandatory = $false )] [System.DayOfWeek]$FullBackupDay = 'Sunday', [Parameter( Mandatory = $false )] [ValidatePattern('^\\(.+\\)?$')] [string]$TaskPath = '\DRJones\' ) Assert-ARKElevated -Operation 'Registering a SYSTEM scheduled task' $FilePath = Get-ARKUtilityPath -Utility PAReplicate $LogonParameters = @{ } foreach ($Key in 'VaultFile', 'LogonFile', 'BackupPoolName') { if ($PSBoundParameters.ContainsKey($Key)) { $LogonParameters[$Key] = $PSBoundParameters[$Key] } } $ArgumentList = @(Get-ARKBackupArgument @LogonParameters) $Principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\SYSTEM' -LogonType ServiceAccount -RunLevel Highest $Settings = New-ScheduledTaskSettingsSet -StartWhenAvailable $Tasks = @( @{ TaskName = 'PAReplicate Incremental Backup' Argument = ConvertTo-ARKArgument -Value $ArgumentList Days = [System.DayOfWeek].GetEnumValues() | Where-Object { $_ -ne $FullBackupDay } } @{ TaskName = 'PAReplicate Full Backup' Argument = ConvertTo-ARKArgument -Value ($ArgumentList + '/FullBackup') Days = $FullBackupDay } ) foreach ($Task in $Tasks) { if ($PSCmdlet.ShouldProcess("$TaskPath$($Task.TaskName)", 'Register scheduled task')) { $Action = New-ScheduledTaskAction -Execute $FilePath -Argument $Task.Argument -WorkingDirectory (Split-Path -Path $FilePath -Parent) $Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek $Task.Days -At $At #Local time, so the task keeps its time of day across daylight saving changes $Trigger.StartBoundary = $At.ToString('s') Register-ScheduledTask -TaskName $Task.TaskName -TaskPath $TaskPath -Action $Action -Trigger $Trigger ` -Principal $Principal -Settings $Settings -Description 'CyberArk Vault backup, registered by DRJones.' -Force } } } # .ExternalHelp DRJones-help.xml function Repair-ARKVaultBackup { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName ) Assert-ARKElevated -Operation 'CAVaultManager RecoverBackupFiles' $ArgumentList = @( 'RecoverBackupFiles' if ($BackupPoolName) { '/BackupPoolName' $BackupPoolName } ) $Target = if ($BackupPoolName) { "Backup pool $BackupPoolName" } else { 'Restored Safes backup files' } if ($PSCmdlet.ShouldProcess($Target, 'Recover and re-encrypt with a new backup key')) { Invoke-ARKUtility -Utility CAVaultManager -ArgumentList $ArgumentList } } # .ExternalHelp DRJones-help.xml function Restore-ARKFile { [CmdletBinding(SupportsShouldProcess)] [OutputType('DRJones.RecoveryResult')] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [Alias('FullName')] [ValidateNotNullOrEmpty()] [string]$Path, [Parameter( Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$OutputPath, [Parameter( Mandatory = $false, Position = 2, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Container })] [string]$KeyPath = $Script:ARKConfig.KeyPath, [Parameter( Mandatory = $false, Position = 3, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$SafeRecoveryKeyPath ) process { if ([string]::IsNullOrEmpty($KeyPath)) { throw 'Recovery key folder not specified. Provide -KeyPath, or set a default with Set-ARKConfiguration -KeyPath' } $InFile = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) $OutFolder = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutputPath) $ArgumentList = @( $InFile $OutFolder $PSCmdlet.GetUnresolvedProviderPathFromPSPath($KeyPath) ) if ($PSBoundParameters.ContainsKey('SafeRecoveryKeyPath')) { $ArgumentList += $PSCmdlet.GetUnresolvedProviderPathFromPSPath($SafeRecoveryKeyPath) } if ($PSCmdlet.ShouldProcess($OutFolder, "Recover $InFile")) { if (-not (Test-Path -Path $OutFolder -PathType Container)) { $null = New-Item -Path $OutFolder -ItemType Directory -Force } $Result = Invoke-ARKUtility -Utility Recover -ArgumentList $ArgumentList if ($Result) { [PSCustomObject]@{ PSTypeName = 'DRJones.RecoveryResult' Source = $InFile OutputPath = $OutFolder Utility = $Result.Utility ExitCode = $Result.ExitCode Output = $Result.StdOut } } } } } # .ExternalHelp DRJones-help.xml function Restore-ARKRecordingSafe { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Credential')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [Alias('SafeName')] [ValidateNotNullOrEmpty()] [string]$Safe, [Parameter( Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$TargetSafe, [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { $_ -notmatch '\\' })] [string[]]$Owners, [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateRange(1, 90)] [int]$Retention, [Parameter( Mandatory = $true, ParameterSetName = 'Credential' )] [pscredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = 'LogonFile' )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$LogonFile, [Parameter( Mandatory = $false )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$VaultFile, [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName ) process { $LogonParameters = @{ } foreach ($Key in 'VaultFile', 'Credential', 'LogonFile', 'BackupPoolName') { if ($PSBoundParameters.ContainsKey($Key)) { $LogonParameters[$Key] = $PSBoundParameters[$Key] } } $ArgumentList = @( Get-ARKBackupArgument @LogonParameters '/RestoreRecordings' '/RestoreSafe' $Safe '/TargetSafe' $TargetSafe if ($PSBoundParameters.ContainsKey('Owners')) { '/Owners' $Owners -join '\' } if ($PSBoundParameters.ContainsKey('Retention')) { '/Retention' $Retention } ) if ($PSCmdlet.ShouldProcess($TargetSafe, "Restore recordings Safe $Safe")) { Invoke-ARKUtility -Utility PARestore -ArgumentList $ArgumentList -Interactive -SensitiveValue $( if ($Credential) { $Credential.GetNetworkCredential().Password } ) } } } # .ExternalHelp DRJones-help.xml function Restore-ARKSafe { [CmdletBinding(SupportsShouldProcess)] [OutputType('DRJones.RecoveryResult')] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [Alias('SafeName')] [ValidateNotNullOrEmpty()] [string]$Safe, [Parameter( Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$OutputPath, [Parameter( Mandatory = $false, Position = 2, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Container })] [string]$KeyPath = $Script:ARKConfig.KeyPath ) begin { Assert-ARKElevated -Operation 'SafeRecover' } process { if ([string]::IsNullOrEmpty($KeyPath)) { throw 'Recovery key folder not specified. Provide -KeyPath, or set a default with Set-ARKConfiguration -KeyPath' } $OutFolder = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($OutputPath) $ArgumentList = @( $Safe $OutFolder $PSCmdlet.GetUnresolvedProviderPathFromPSPath($KeyPath) ) if ($PSCmdlet.ShouldProcess($OutFolder, "Recover Safe $Safe")) { if ((Get-Service -Name 'PrivateArk Server' -ErrorAction Ignore).Status -eq 'Running') { throw 'The PrivateArk Server service is running. Stop it before recovering a Safe; SafeRecover.exe cannot run while the Vault is running' } if (-not (Test-Path -Path $OutFolder -PathType Container)) { $null = New-Item -Path $OutFolder -ItemType Directory -Force } $Result = Invoke-ARKUtility -Utility SafeRecover -ArgumentList $ArgumentList if ($Result) { [PSCustomObject]@{ PSTypeName = 'DRJones.RecoveryResult' Source = $Safe OutputPath = $OutFolder Utility = $Result.Utility ExitCode = $Result.ExitCode Output = $Result.StdOut } } } } } # .ExternalHelp DRJones-help.xml function Restore-ARKVault { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'Credential')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $true, ParameterSetName = 'Credential' )] [pscredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = 'LogonFile' )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$LogonFile, [Parameter( Mandatory = $false )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$VaultFile, [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName, [Parameter( Mandatory = $false )] [switch]$FromRestored ) Assert-ARKElevated -Operation 'PARestore /FullVaultRestore' $LogonParameters = @{ } foreach ($Key in 'VaultFile', 'Credential', 'LogonFile', 'BackupPoolName') { if ($PSBoundParameters.ContainsKey($Key)) { $LogonParameters[$Key] = $PSBoundParameters[$Key] } } $ArgumentList = @( Get-ARKBackupArgument @LogonParameters '/FullVaultRestore' if ($FromRestored) { '/FromRestored' } ) if ($PSCmdlet.ShouldProcess('Vault Restored Safes folder', 'Upload full Vault backup')) { Invoke-ARKUtility -Utility PARestore -ArgumentList $ArgumentList -SensitiveValue $( if ($Credential) { $Credential.GetNetworkCredential().Password } ) } } # .ExternalHelp DRJones-help.xml function Restore-ARKVaultDatabase { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $false )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName, [Parameter( Mandatory = $false )] [switch]$NoSynchronize ) Assert-ARKElevated -Operation 'CAVaultManager RestoreDB' $ArgumentList = @( 'RestoreDB' if ($BackupPoolName) { '/BackupPoolName' $BackupPoolName } if ($NoSynchronize) { '/NoSynchronize' } '/Force' ) if ($PSCmdlet.ShouldProcess('Vault database', 'Restore from Restored Safes folder')) { Invoke-ARKUtility -Utility CAVaultManager -ArgumentList $ArgumentList } } # .ExternalHelp DRJones-help.xml function Restore-ARKVaultSafe { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Credential')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true )] [Alias('SafeName')] [ValidateNotNullOrEmpty()] [string]$Safe, [Parameter( Mandatory = $true, Position = 1, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$TargetSafe, [Parameter( Mandatory = $true, ParameterSetName = 'Credential' )] [pscredential]$Credential, [Parameter( Mandatory = $true, ParameterSetName = 'LogonFile' )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$LogonFile, [Parameter( Mandatory = $false )] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$VaultFile, [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [string]$BackupPoolName, [Parameter( Mandatory = $false )] [switch]$DataOnly, [Parameter( Mandatory = $false )] [switch]$MetadataOnly, [Parameter( Mandatory = $false )] [switch]$LoadOnly ) process { if ($DataOnly -and $MetadataOnly) { throw 'DataOnly and MetadataOnly cannot be used together' } $LogonParameters = @{ } foreach ($Key in 'VaultFile', 'Credential', 'LogonFile', 'BackupPoolName') { if ($PSBoundParameters.ContainsKey($Key)) { $LogonParameters[$Key] = $PSBoundParameters[$Key] } } $ArgumentList = @( Get-ARKBackupArgument @LogonParameters if ($LoadOnly) { '/LoadOnly' } '/RestoreSafe' $Safe '/TargetSafe' $TargetSafe if ($DataOnly) { '/DataOnly' } if ($MetadataOnly) { '/MetadataOnly' } ) if ($PSCmdlet.ShouldProcess($TargetSafe, "Restore Safe $Safe")) { $Result = Invoke-ARKUtility -Utility PARestore -ArgumentList $ArgumentList -SensitiveValue $( if ($Credential) { $Credential.GetNetworkCredential().Password } ) #ITATS658W: a backed-up owner not matched in the Vault is removed from the target Safe $Result.StdOut -split '\r?\n' | Where-Object { $_ -match 'ITATS658W' } | ForEach-Object { Write-Warning $_.Trim() } $Result } } } # .ExternalHelp DRJones-help.xml function Set-ARKConfiguration { [CmdletBinding(SupportsShouldProcess)] param( [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Container })] [string[]]$UtilityPath, [Parameter( Mandatory = $false, ValueFromPipelineByPropertyName = $true )] [ValidateScript( { Test-Path -Path $_ -PathType Container })] [string]$KeyPath ) process { if ($PSCmdlet.ShouldProcess($Script:ARKConfigFile, 'Save DRJones Configuration')) { switch ($PSBoundParameters.Keys) { 'UtilityPath' { $Script:ARKConfig.UtilityPath = [string[]]@($UtilityPath | ForEach-Object { $PSCmdlet.GetUnresolvedProviderPathFromPSPath($_) }) } 'KeyPath' { $Script:ARKConfig.KeyPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($KeyPath) } } $Script:ARKConfig | Export-Clixml -Path $Script:ARKConfigFile -Force } } } # .ExternalHelp DRJones-help.xml function Set-ARKVaultParameter { [CmdletBinding(SupportsShouldProcess)] param( [Parameter( Mandatory = $true, ValueFromPipelineByPropertyName = $true )] [Alias('FullName')] [ValidateScript( { Test-Path -Path $_ -PathType Leaf })] [string]$Path, [Parameter( Mandatory = $true, Position = 0 )] [ValidatePattern('^[\w.-]+$')] [string]$Name, [Parameter( Mandatory = $true, Position = 1 )] [AllowEmptyString()] [ValidatePattern('^[^\r\n]*$')] [string]$Value, [Parameter( Mandatory = $false )] [ValidatePattern('^[^\[\]\r\n]+$')] [string]$Section ) process { $FullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $Ini = Read-ARKIniFile -Path $FullPath $Lines = $Ini.Lines $Existing = @($Ini.Parameters | Where-Object { $_.Name -eq $Name }) $End = $Lines.Count if ($Lines[$End - 1] -eq '') { $End-- } if ($Existing.Count -gt 0) { foreach ($Parameter in $Existing) { $Lines[$Parameter.Index] = $Parameter.Prefix + $Value } } elseif ($Section) { $Header = -1 for ($Index = 0; $Index -lt $End; $Index++) { if ($Lines[$Index] -match "^\s*\[\s*$([regex]::Escape($Section))\s*\]\s*$") { $Header = $Index; break } } if ($Header -lt 0) { $Lines.InsertRange($End, [string[]]@("[$Section]", "$Name=$Value")) } else { $Next = $End for ($Index = $Header + 1; $Index -lt $End; $Index++) { if ($Lines[$Index] -match '^\s*\[[^\]]+\]\s*$') { $Next = $Index; break } } $Insert = $Next while ($Insert -gt $Header + 1 -and [string]::IsNullOrWhiteSpace($Lines[$Insert - 1])) { $Insert-- } $Lines.Insert($Insert, "$Name=$Value") } } elseif ($Ini.Sections -gt 0) { throw "$Name not found in $FullPath. Specify -Section to add it." } else { $Lines.Insert($End, "$Name=$Value") } if ($PSCmdlet.ShouldProcess($FullPath, "Set $Name=$Value")) { $Bytes = [byte[]]($Ini.Encoding.GetPreamble() + $Ini.Encoding.GetBytes($Lines -join "`r`n")) [System.IO.File]::WriteAllBytes($FullPath, $Bytes) } } } # .ExternalHelp DRJones-help.xml function Sync-ARKVaultDatabase { [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType('DRJones.UtilityResult')] param( [Parameter( Mandatory = $false, Position = 0 )] [ValidateNotNullOrEmpty()] [string]$SafePattern, [Parameter( Mandatory = $false )] [switch]$FilesSyncOnly, [Parameter( Mandatory = $false )] [switch]$QuotaSyncOnly, [Parameter( Mandatory = $false )] [switch]$Update ) $ArgumentList = @( 'SynchronizeDB' if ($SafePattern) { '/SafePattern' $SafePattern } if ($FilesSyncOnly) { '/FilesSyncOnly' } if ($QuotaSyncOnly) { '/QuotaSyncOnly' } if ($Update) { '/Update' } '/Force' ) $Target = if ($SafePattern) { "Safes matching $SafePattern" } else { 'All Safes' } if ((-not $Update) -or $PSCmdlet.ShouldProcess($Target, 'Synchronize Vault database with restored files')) { Invoke-ARKUtility -Utility CAVaultManager -ArgumentList $ArgumentList } } # Module scope code below the Loader region is appended to the built module after the function definitions. # Script scope configuration object, persisted by Set-ARKConfiguration $Script:ARKConfigFile = Join-Path $HOME 'DRJonesConfiguration.xml' $Script:ARKConfig = [PSCustomObject]@{ UtilityPath = $null KeyPath = $null } if (Test-Path -Path $Script:ARKConfigFile -PathType Leaf) { Write-Verbose "Importing Settings: $Script:ARKConfigFile" $Script:ARKConfig = Import-Clixml -Path $Script:ARKConfigFile } |