DifferentialMigration/DifferentialMigration.psm1

using module '../Common/Result'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath DifferentialProfile  | Join-Path -ChildPath DifferentialProfile)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Util | Join-Path -ChildPath MigrationUtil)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Preflight | Join-Path -ChildPath Preflight)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Common | Join-Path -ChildPath Common)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath CloudAccount | Join-Path -ChildPath CloudAccount)

function Start-RMInteractiveDM {
    param ()
    $OrganizationId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly
    $Name = "PowerShell - Differential Profile - " + [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
   
    $Migration, $Source = Read-RMFullMigrationId

    if ((Test-RMSourceHasRunningMigration -OrganizationId $OrganizationId -SourceId $Source.id)) {
        $UserMessage = "Currently there is a migration running for the source of the given migration ID, this differential migration might fail."
        Write-Warning $UserMessage
        $RMMigrationReturn.AddRMWarning([RMWarning]::new($UserMessage))
    }

    $ScheduledAt = Read-RMMigrationSchedule -IsDifferentialMigration $true

    $TransferMode = "run-once"

    if ("Continuous" -ieq $ScheduledAt) {
        $TransferMode = "continuous"
        $ScheduledAt = ""
    }

    $ShutdownSource = Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated " -DefaultValue $false

    $ShutdownTarget = Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue $false

    $RemoveRMSAgent = Read-RMBoolean -UserMessage "Remove RMS agent post migration" -DefaultValue $false

    $ReadValue = Read-RMPair -UserMessage "Enter one or more migration instructions in the format 'key=value', separated by commas" `
        -Separator "=" -DefaultValue "None"
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue

    $IgnoreValidationErrors = Read-RMBoolean -UserMessage "Ignore Validation Errors" -DefaultValue $false

    if (!('windows' -eq $Migration.os_type -or $Migration.source.discovered_features.features_list -contains 'linux_block_based')) {
        $UpdateMountsPoints = Read-RMBoolean -UserMessage "Update mounts points" -DefaultValue $false
    } else {
        $UpdateMountsPoints = $true
    }

    $SelectedMounts = @()
    if ($UpdateMountsPoints) {
        $UpdateIncludesAndExcludes = $true

        $SelectedMounts = $Migration.target.properties.selected_mounts.PSObject.properties.Name

        $TransferMethod = (Get-RMTransferMethod -Source $Source -SelectedMountPoints $SelectedMounts -IsInteractive $true)[0]
        if ('block-based' -eq  $TransferMethod) {
            $UpdateIncludesAndExcludes = $false
        }

        $MountPointsAsString = $SelectedMounts -join  ","
        Write-Output "Mount points to be migrated [$MountPointsAsString]"
        $SelectedMountPoints = @()
        if ($SelectedMounts.Count -gt 1) {
            $ExcludedMountPoints = Get-RMExcludedMountPoint -OSType $Migration.os_type -MountPoints $SelectedMounts -IsDifferentialMigration $true
            if ("" -ne $ExcludedMountPoints) {
                $ExcludedList = $ExcludedMountPoints.Split(",").Trim()
                $Results = Compare-Object -ReferenceObject $SelectedMounts -DifferenceObject $ExcludedList -IncludeEqual 
                foreach ($Result in $Results) {
                    if (!($Result.SideIndicator -contains "==")) {
                        $SelectedMountPoints += $Result.InputObject 
                    }
                }
            } 
        }

        if ($SelectedMountPoints.Count -eq 0) {
            $SelectedMountPoints = $SelectedMounts
        }
                
        if (!$UpdateIncludesAndExcludes) {
            $MountPoints = Update-RMDefaultMountPoints -UpdateSelectedMounts $SelectedMountPoints -Migration $Migration
        } else {
            foreach($MountPoint in $Migration.target.properties.selected_mounts.PSObject.properties) {
                $MountName = $MountPoint.name

                if ($SelectedMountPoints -contains $MountName) {

                    $Includes = @()
                    if ('linux' -eq $Migration.os_type) {
                        $OverwriteIncludes = Read-RMBoolean -UserMessage "Overwrite Includes for mount point '$MountName'" -DefaultValue $false
                        if ($OverwriteIncludes) {
                            $IncludesAsString = Read-RMString -UserMessage "For mount point '$MountName', update one or more include paths, separated by commas " `
                                -DefaultValue 'None' -ParameterName "Includes" -IsRequired $false
                            if ("" -ne $IncludesAsString) {
                                $Includes = $IncludesAsString -split ","
                            }
                        }
                    }
                   
                    $Excludes = @()
                    $DefaultExcludes = 'None'
                    if ("linux" -eq $Migration.os_type) {
                        $DefaultExcludes = '**/.snapshot'
                    }

                    $OverwriteExcludes = Read-RMBoolean -UserMessage "Overwrite Excludes for mount point '$MountName'" -DefaultValue $false
                    if ($OverwriteExcludes) {
                        $ExcludesAsString = Read-RMString -UserMessage "For mount point '$MountName', update one or more exclude paths, separated by commas"`
                        -DefaultValue $DefaultExcludes -ParameterName "Excludes" -IsRequired $false
                        if ("" -ne $ExcludesAsString) {
                            $Excludes =  $ExcludesAsString  -split ","
                        }
                    } elseif ("linux" -eq $Migration.os_type) {
                        $Excludes += '**/.snapshot'
                    }
 
                   $MountPoints += @{$MountName = @{"includes" = $Includes
                                                "excludes" =  $Excludes
                                                }
                                    }
                }
            }
        }
    } else {
        $MountPoints = Update-RMDefaultMountPoints -UpdateSelectedMounts $Migration.target.properties.selected_mounts.PSObject.properties.Name -Migration $Migration
    }

    $TargetProperties = Edit-RMMountPoints -TargetProperties $Migration.target.properties -MountPoints $MountPoints

    $UpdateSourceCredentials = Read-RMBoolean -UserMessage "Update Source Cresentials" -DefaultValue $false

    if ($UpdateSourceCredentials) {
       $SourceUserName = Read-RMString -UserMessage "Enter the username" -ParameterName "Username" -IsRequired $true

       $SourceUseSSHPrivateKey = Read-RMBoolean -UserMessage "Use SSH private key" -DefaultValue $false

       if ($SourceUseSSHPrivateKey) {
            $SourcePrivateKey = Read-RMString -UserMessage "Enter the private key" -ParameterName "Private Key" -IsRequired $true

            $SourcePassword = Read-RMSecureString -UserMessage "Enter the passphrase" -ParameterName "Passphrase" `
            -ConfirmMessage "Confirm the passphrase" -ConfirmParameterName "Confirm passphrase" -IsRequired $false 
       } else {
            $SourcePassword = Read-RMSecureString -UserMessage "Enter the password" -ParameterName "Password" `
            -ConfirmMessage "Confirm the password" -ConfirmParameterName "Confirm password" -IsRequired $true
       }

       $SourceDomain = Read-RMString -UserMessage "Enter the domain" -ParameterName "Domain" -IsRequired $false
    }

    $UpdateTargetInstance = Read-RMBoolean -UserMessage "Update target instance/VM cresentials" -DefaultValue $false

    if ($UpdateTargetInstance) {
        $TargetUserName = Read-RMString -UserMessage "Enter the username" -ParameterName "Username" -IsRequired $true

        $TargetUseSSHPrivateKey = Read-RMBoolean -UserMessage "Use SSH private key" -DefaultValue $false
 
        if ($TargetUseSSHPrivateKey) {
             $TargetPrivateKey = Read-RMString -UserMessage "Enter the private key" -ParameterName "Private Key" -IsRequired $true
 
             $TargetPassword = Read-RMSecureString -UserMessage "Enter the passphrase" -ParameterName "Passphrase" `
             -ConfirmMessage "Confirm the passphrase" -ConfirmParameterName "Confirm passphrase" -IsRequired $false 
        } else {
             $TargetPassword = Read-RMSecureString -UserMessage "Enter the password" -ParameterName "Password" `
             -ConfirmMessage "Confirm the password" -ConfirmParameterName "Confirm password" -IsRequired $true
        }
 
        $TargetDomain = Read-RMString -UserMessage "Enter the domain" -ParameterName "Domain" -IsRequired $false
    }

    $UpdateTargetIPAddress = Read-RMBoolean -UserMessage "Update target IP address" -DefaultValue $false

    $TargetIPAddress = $Migration.target.ip 
    if ($UpdateTargetIPAddress) {
       $TargetIPAddress = Read-RMIPAddress -UserMessage "Enter new target IP address" -ParameterName "Target IP Address" `
         -DefaultValue "$TargetIPAddress" -IsRequired $false
    }

    $HashArguments = @{
        Name = $Name
        ScheduledAt = $ScheduledAt
        OrganizationId = $OrganizationId
        ApplianceId = $Migration.cloud_appliance_id
        MigrationId = $Migration.id
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveTargetAgent = $RemoveRMSAgent
        MigrationInstructions = $MigrationInstructions
        TargetProperties = $TargetProperties
        IgnoreValidationErrors = $IgnoreValidationErrors
        SourceUsername = $SourceUserName
        SourcePrivateKey = $SourcePrivateKey
        SourcePassword = $SourcePassword
        SourceDomain = $SourceDomain
        SourceHost = $Migration.source_hostname
        TargetUsername = $TargetUsername
        TargetPrivateKey = $TargetPrivateKey
        TargetPassword = $TargetPassword
        TargetDomain = $TargetDomain
        TargetHost =  $Migration.target_hostname
        TargetIPAddress = $TargetIPAddress
        SourceId = $Migration.source_id
        TransferMethod = $TransferMethod
        TransferMode = $TransferMode
    }

    $Response = New-RMDifferentialProfile @HashArguments
    $ShouldExit = Start-RMDifferentialMigrationPreflight -DifferentialProfileId $Response.id -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    $IsScheduled = ![string]::IsNullOrWhiteSpace($ScheduledAt)

    $RMLoginResult = Get-Variable -Name "RMContext-UserLogin"
    $Uri = Get-Variable -Name "RMContext-ReactorURI"

    $Headers = @{
        Accept = "application/rm+json"
        "X-Auth-Token" = $RMLoginResult.Value.token
    }

    $Params = @{
        Method = "Post"
        Uri = $Uri.Value + "/differentialprofiles/" + $Response.id + "/migrations"
        Headers = $Headers
        ContentType = "application/json"
    }

    $MigrationResponse = Invoke-RMRestMethod -Params $Params
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Differential migration started successfully, migration ID"

}

function Start-RMNonInteractiveDM {
    param (
        [string] $MigrationId,

        [string] $ScheduledAt,

        [bool] $RunContinuous,

        [bool]  $ShutdownSource,

        [bool] $ShutdownTarget,

        [bool] $RemoveRMSAgent,

        [string[]] $MigrationInstruction,

        [bool] $IgnoreValidationError,

        [bool] $UpdateMountPoint,

        [hashtable] $UpdateSelectedMount,

        [bool] $UpdateSourceCredential,

        [string] $SourceUsername,

        [bool] $SourceUseSSHPrivateKey,

        [string] $SourcePrivateKey,

        [string] $SourcePassphrase,

        [string] $SourceConfirmPassphrase,

        [string] $SourcePassword,

        [string] $SourceConfirmPassword,

        [string] $SourceDomain,

        [bool] $UpdateTargetInstance,

        [string] $TargetUsername,

        [bool] $TargetUseSSHPrivateKey,

        [string] $TargetPrivateKey,

        [string] $TargetPassphrase,

        [string] $TargetConfirmPassphrase,

        [string] $TargetPassword,

        [string] $TargetConfirmPassword,

        [string] $TargetDomain,

        [string] $TargetIPAddress,

        [System.Object] $TransferMethod
    )

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()

    $Errors, $IsValidMigrationId = Confirm-RMDMParameter -UserParameter $PSBoundParameters
    if (!$IsValidMigrationId) {
        $RMMigrationReturn.SetReturnCode([RMReturn]::ERROR)
        $Errors | ForEach-Object -Process {$RMMigrationReturn.AddRMError([RMError]::new($_))}
        Out-RMUserParameterResult -ErrorMessage $Errors
        return $RMMigrationReturn
    }

    $OrganizationId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly

    $Migration = Get-RMMigrationByIdAndStatus -MigrationId $MigrationId

    $Source, $ErrorString = Confirm-RMSource -SourceId $Migration.source_id
    if ("" -ne $ErrorString) {
        $Errors += $ErrorString
    }

    $MigrationInstructionAsHashTable = @{}
    
    try {
        if (![string]::IsNullOrWhiteSpace($MigrationInstruction)) {
            $MigrationInstructionAsHashTable =  Get-RMStringArrayAsHashtable -InputItems $MigrationInstruction -ParameterName "MigrationInstruction"
        }
    } catch {
        $Errors += $PSItem.Exception.Message
    }

    $MigrationType = $Migration.migration_type
    $HeartbeatStatus = Test-RMApplianceHeartbeating -ApplianceId $Migration.cloud_appliance_id -AccountType $Migration.target_cloud_type
    if (!$HeartbeatStatus) {
        $Errors += "Migration appliance is not ready, cannot start differential migration"
    }

    if ("full" -ne $MigrationType) {
        $Errors += "Migration type for the given migration ID is '$MigrationType', differential migration can only be started for a full migration."
    } else {
        $MigrationList = Get-RMMigrationListBySourceId -OrganizationId $OrganizationId -SourceId $Source.id
        foreach ($Mig in $MigrationList) {
            if ("running" -eq $Mig.state) {
                $UserMessage = "Currently there is a migration running for the source of the given migration ID, this differential migration might fail."
                Write-Warning $UserMessage
                $RMMigrationReturn.AddRMWarning([RMWarning]::new($UserMessage))
                Break;
            }
        }
    }
    $TransferMode, $ScheduledAt = Get-RMTransferModeAndSchedule -RunContinuous $RunContinuous -ScheduledAt $ScheduledAt

    if ($UpdateMountPoint -or 'windows' -eq $Migration.os_type -or $Migration.source.discovered_features.features_list -contains 'linux_block_based') {
        $UpdateIncludesAndExcludes = $true

        $TransferMethod, $ErrorString = Get-RMTransferMethod -Source $Source -SelectedMountPoints $UpdateSelectedMount.Keys`
            -TransferMethod $TransferMethod

        if ("" -ne $ErrorString) {
            $Errors += $ErrorString
        }
       
        if ('block-based' -eq $TransferMethod) {
            $UpdateIncludesAndExcludes = $false
        }
       
        $MountPoints, $MountPointsErrors = Update-RMMountPoints -UpdateSelectedMounts $UpdateSelectedMount -UpdateIncludesAndExcludes $UpdateIncludesAndExcludes -Migration $Migration
        if ($MountPointsErrors -gt 0) {
            $Errors += $MountPointsErrors
        }      
    } else {
        $MountPoints = Update-RMDefaultMountPoints -UpdateSelectedMounts $Migration.target.properties.selected_mounts.PSObject.properties.Name -Migration $Migration
    }

    $TargetProperties = Edit-RMMountPoints -TargetProperties $Migration.target.properties -MountPoints $MountPoints

    if ([string]::IsNullOrWhiteSpace($TargetIPAddress)) {
        $TargetIPAddress = $Migration.target.ip
    }

    if ($Errors.Count -gt 0) {
        $RMMigrationReturn.SetReturnCode([RMReturn]::ERROR)
        $Errors | ForEach-Object -Process {$RMMigrationReturn.AddRMError([RMError]::new($_))}
        Out-RMUserParameterResult -ErrorMessage $Errors
        return $RMMigrationReturn
    }

    $Name = "PowerShell - Differential Profile - " + [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()

    $HashArguments = @{
        Name = $Name
        ScheduledAt = $ScheduledAt
        OrganizationId = $OrganizationId
        ApplianceId = $Migration.cloud_appliance_id
        MigrationId = $Migration.id
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveTargetAgent = $RemoveRMSAgent
        MigrationInstructions = $MigrationInstructionAsHashTable
        TargetProperties = $TargetProperties
        IgnoreValidationErrors = $IgnoreValidationError
        SourceUsername = $SourceUsername
        SourcePrivateKey = $SourcePrivateKey
        SourcePassword = $SourcePassword
        SourceDomain = $SourceDomain
        SourceHost = $Migration.source_hostname
        TargetUsername = $TargetUsername
        TargetPrivateKey = $TargetPrivateKey
        TargetPassword = $TargetPassword
        TargetDomain = $TargetDomain
        TargetHost = $Migration.target_hostname
        TargetIPAddress = $TargetIPAddress
        SourceId = $Migration.source_id
        TransferMethod = $TransferMethod
        TransferMode = $TransferMode
    }

    $Response = New-RMDifferentialProfile @HashArguments
    $ShouldExit = Start-RMDifferentialMigrationPreflight -DifferentialProfileId $Response.id `
        -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    $IsScheduled = ![string]::IsNullOrWhiteSpace($ScheduledAt)

    $RMLoginResult = Get-Variable -Name "RMContext-UserLogin"
    $Uri = Get-Variable -Name "RMContext-ReactorURI"

    $Headers = @{
        Accept = "application/rm+json"
        "X-Auth-Token" = $RMLoginResult.Value.token
    }

    $Params = @{
        Method = "Post"
        Uri = $Uri.Value + "/differentialprofiles/" + $Response.id + "/migrations"
        Headers = $Headers
        ContentType = "application/json"
    }

    $MigrationResponse = Invoke-RMRestMethod -Params $Params
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Differential migration started successfully, migration ID"
}

function Update-RMMountPoints {
    param(
        [hashtable] $UpdateSelectedMounts,
        [bool] $UpdateIncludesAndExcludes,
        [System.Object] $Migration
    )

    $MountPoints = @()
    $MountPointsErrors = @()
    $TargetMounts =  $Migration.target.properties.selected_mounts.PSObject.properties.Name

    if ('linux' -eq $Migration.os_type -and ![string]::IsNullOrWhiteSpace($UpdateSelectedMounts)) {
        $MountPointsErrors += Compare-RMMountPoints -TargetMounts $TargetMounts -SelectedMounts $UpdateSelectedMounts.Keys
    }

    # $UpdateSelectedMounts format example
    # @{"/" = @{"includes" = @("/value1")} ; "/boot" = @{"includes" = @("/value1","/value2") ; "excludes" = @("value1","value2")}}

    if ([string]::IsNullOrWhiteSpace($UpdateSelectedMounts)) {
        $MountPoints = Update-RMDefaultMountPoints -UpdateSelectedMounts $Migration.target.properties.selected_mounts.PSObject.properties.Name -Migration $Migration
    } elseif (!$UpdateIncludesAndExcludes) {
        $MountPoints = Update-RMDefaultMountPoints -UpdateSelectedMounts $UpdateSelectedMounts.Keys -Migration $Migration
    } else {
        foreach ($SelectedMount in $UpdateSelectedMounts.Keys) {
            $Includes = @()
            $Excludes = @()
    
            if ("windows" -eq $Migration.os_type) {
                $Excludes = $UpdateSelectedMounts[$SelectedMount]["excludes"]
            } else {
                if (![string]::IsNullOrWhiteSpace($UpdateSelectedMounts[$SelectedMount]["includes"])) {
                    $Includes = $UpdateSelectedMounts[$SelectedMount]["includes"]
                }
    
                if ([string]::IsNullOrWhiteSpace($UpdateSelectedMounts[$SelectedMount]["excludes"])) {
                    $Excludes = @('**shnapshot')
                } else {
                    $Excludes = $UpdateSelectedMounts[$SelectedMount]["excludes"]
                }
            }
    
            $MountPoints += @{$SelectedMount = @{"includes" =  $Includes
                                                "excludes" =  $Excludes 
                                                }
                            }
        }
    }

    return $MountPoints, $MountPointsErrors
}

function Test-RMApplianceHeartbeating {
    param(
        [string] $ApplianceId,
        [string] $AccountType
    )
    $HeartBeatingCloudAccounts, $NonHeartBeatingCloudAccounts = Get-RMCloudAccountsForCurrentProject -AccountType $AccountType
    foreach ($CloudAccount in $HeartBeatingCloudAccounts.Values) {
        if ($CloudAccount.appliance.id -eq $ApplianceId) {
            return $true
        }
    }

    return $false
}

function Update-RMDefaultMountPoints {
    param(
        [array] $UpdateSelectedMounts,
        [System.Object] $Migration
    )

    $MountPoints = @{}
    $Includes = @()
    $Excludes = @()
    
    if ("linux" -eq $Migration.os_type) {
        $Excludes = @('**/.snapshot')
    } 
    foreach ($MountPoint in $UpdateSelectedMounts) {
       
        $MountPoints += @{$MountPoint = @{"includes" = $Includes
                                          "excludes" = $Excludes
                                        }
                        }
    }

    return $MountPoints
}

function Compare-RMMountPoints {
    param(
        [array] $TargetMounts,
        [array] $SelectedMounts
    )

    $MountPointsErrors = @()
    $MountsNotExcludeList = @("/", "/bin", "/boot" , "/dev", "/etc", "/home", "/lib" , "/mnt", "/opt", "/proc", "/root", "/run", "/sbin", "/srv", "/tmp", "/usr", "/var")
    $Results = Compare-Object -ReferenceObject $TargetMounts -DifferenceObject $SelectedMounts -IncludeEqual

    if ($Results.SideIndicator -contains "<=" -or $Results.SideIndicator -contains "=>") {
        foreach ($Result in $Results) {
            $MountPoint = $Result.InputObject
            if ($Result.SideIndicator -eq "<=" -and $MountsNotExcludeList -contains $Result.InputObject) {
                $MountPointsErrors += "Mount points '$MountPoint' is required"
            } elseif ($Result.SideIndicator -eq "=>") {
                $MountPointsErrors +=  "Mount point '$MountPoint' does not exist on the target machine."
            }
        }
    }

    return $MountPointsErrors
}

function Confirm-RMDMParameter {
    param(
        [hashtable] $UserParameter
    )
    $Errors = @()
    $ErrorsCommon, $IsValidMigrationId = Confirm-RMVMBasedDMCommonParameter -UserParameter $UserParameter
    $Errors += $ErrorsCommon

    if ($UserParameter.ContainsKey("UpdateMountPoint") -and $UserParameter["UpdateMountPoint"]) {
        if (!$UserParameter.ContainsKey("UpdateSelectedMount") -or [string]::IsNullOrWhiteSpace($UserParameter["UpdateSelectedMount"])) {
            $Errors += "UpdateSelectedMount is required."
        }
    }

    if ($UserParameter.ContainsKey("UpdateSourceCredential") -and $UserParameter["UpdateSourceCredential"]) {
        if (!$UserParameter.ContainsKey("SourceUsername") -or [string]::IsNullOrWhiteSpace($UserParameter["SourceUsername"])) {
            $Errors += "SourceUsername is required."
        }

        if ($UserParameter.ContainsKey("SourceUseSSHPrivateKey") -and $UserParameter["SourceUseSSHPrivateKey"]) {
            if (!$UserParameter.ContainsKey("SourcePrivateKey") -or [string]::IsNullOrWhiteSpace($UserParameter["SourcePrivateKey"])) {
                $Errors += "SourcePrivateKey is required."
            }

            if ($UserParameter.ContainsKey("SourcePassphrase") -and ![string]::IsNullOrWhiteSpace($UserParameter["SourcePassphrase"])) {
                if (!$UserParameter.ContainsKey("SourceConfirmPassphrase") -or [string]::IsNullOrWhiteSpace($UserParameter["SourceConfirmPassphrase"])) {
                    $Errors += "SourceConfirmPassphrase is required"
                } elseif ($UserParameter["SourcePassphrase"] -ne $UserParameter["SourceConfirmPassphrase"]) {
                    $Errors += "SourcePassphrase and SourceConfirmPassphrase must match"
                }
            }
        } else  {
            if(!$UserParameter.ContainsKey("SourcePassword") -or [string]::IsNullOrWhiteSpace($UserParameter["SourcePassword"])) {
                $Errors += "SourcePassword is required"
            }
            
            if (!$UserParameter.ContainsKey("SourceConfirmPassword") -or [string]::IsNullOrWhiteSpace($UserParameter["SourceConfirmPassword"])) {
                $Errors += "SourceConfirmPassword is required"
            } elseif ($UserParameter["SourcePassword"] -ne $UserParameter["ConfirmPassword"]) {
                $Errors += "SourcePassword and ConfirmPassword must match"
            }
        }
    }
        

    if ($UserParameter.ContainsKey("UpdateTargetInstance") -and $UserParameter["UpdateTargetInstance"]) {
        if (!$UserParameter.ContainsKey("TargetUsername") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetUsername"])) {
            $Errors += "TargetUsername is required."
        }

        if ($UserParameter.ContainsKey("TargetUseSSHPrivateKey") -and $UserParameter["TargetUseSSHPrivateKey"]) {
            if (!$UserParameter.ContainsKey("TargetPrivateKey") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetPrivateKey"])) {
                $Errors += "TargetPrivateKey is required."
            }

            if ($UserParameter.ContainsKey("TargetPassphrase") -and ![string]::IsNullOrWhiteSpace($UserParameter["TargetPassphrase"])) {
                if (!$UserParameter.ContainsKey("TargetConfirmPassphrase") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetConfirmPassphrase"])) {
                    $Errors += "TargetConfirmPassphrase is required"
                } elseif ($UserParameter["TargetPassphrase"] -ne $UserParameter["TargetConfirmPassphrase"]) {
                    $Errors += "TargetPassphrase and TargetConfirmPassphrase must match"
                }
            }
        } else {
            if(!$UserParameter.ContainsKey("TargetPassword") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetPassword"])) {
                $Errors += "TargetPassword is required"
            }

            if (!$UserParameter.ContainsKey("TargetConfirmPassword") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetConfirmPassword"])) {
                $Errors += "TargetConfirmPassword is required"
            } elseif ($UserParameter["TargetPassword"] -ne $UserParameter["TargetConfirmPassword"]) {
                $Errors += "TargetPassword and TargetConfirmPassword must match"
            }
        }
    }

    if ($UserParameter.ContainsKey("TargetIPAddress") -and ![string]::IsNullOrWhiteSpace($UserParameter["TargetIPAddress"])) {
        $IsValidIPAddress = Confirm-RMIPAddress -IPAddress $UserParameter.TargetIPAddress
        if (!$IsValidIPAddress) {
            $Errors += "Invalid Target IP address."
        }
    }
       
    return $Errors, $IsValidMigrationId
    
}

function Read-RMFullMigrationId  {
    param (
        [bool] $VMBasedSourceRequired
    )
    $Errors = @()
    while ($true) {
        $MigrationId =  Read-RMUUID -UserMessage "Enter the full migration ID"  -ParameterName "Full migration ID" -IsRequired $true
        $Migration = Get-RMMigrationByIdAndStatus -MigrationId $MigrationId
        $HeartbeatStatus = Test-RMApplianceHeartbeating -ApplianceId $Migration.cloud_appliance_id -AccountType $Migration.target_cloud_type
        if (!$HeartbeatStatus) {
            $Errors += "Migration appliance is not ready, cannot start differential migration, please make sure that the migration appliance is ready for use and then try again."
        }

        $MigrationType = $Migration.migration_type
        if ("full" -ne $MigrationType) {
            $Errors += "Migration type for the given migration ID is '$MigrationType', differential migration can only be started for a full migration, please try again."
        }

        $Source, $ErrorString = Confirm-RMSource -SourceId $Migration.source_id
        if ("" -ne $ErrorString) {
            $Errors += $ErrorString
        }

        if ($VMBasedSourceRequired) {
            if ($null -ne $Source -and $Source.collection_type -ine "vm") {
                $IPAddress = $Source.host
                $Errors += "The source with IP address '$IPAddress' is not a VM based source, please use the cmdlet 'Start-RMOSBasedDifferentialMigration' to start a differential migration for this source."
            }    
        }

        $Errors | ForEach-Object {
            Write-RMError -Message $_
        }

        if ($Errors.count -gt 0) {
            continue
        }
        return $Migration, $Source
    }
}

function Get-RMTransferModeAndSchedule {
    param(
        [bool] $RunContinuous,
        [string] $ScheduledAt
    )

    $TransferMode = "run-once"
    if ($RunContinuous) {
        $ScheduledAt =  ""
        $TransferMode = "continuous"
    } elseif ([string]::IsNullOrWhiteSpace($ScheduledAt)) {
        $ScheduledAt =  ""
    } else {
        $ScheduledAt = Convert-RMDateTimeToUTC -InputDateTime $ScheduledAt
    }
    return $TransferMode, $ScheduledAt
}