DifferentialMigration/vSphere/VMBasedDifferentialMigration.psm1

using module '../../Common/Result'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath DifferentialMigration)

function Start-RMInteractiveVSphereVMBasedDifferentialMigration {
    param()

    $UserInput = @{}
    $OrganizationId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly
    $Migration, $Source = Read-RMFullMigrationId -VMBasedSourceRequired $true
    $UserInput.Add("Migration", $Migration)
    $UserInput.Add("Source", $Source)
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    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 = ""
    } else {
        $UpgradeTools = Read-RMBoolean -UserMessage "Do you want to upgrade the VM tools on the target VM" -DefaultValue "false"
        $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"
        $UserInput.Add("UpgradeTools", $UpgradeTools)
        $UserInput.Add("ShutdownSource", $ShutdownSource)
        $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    }
    $UserInput.Add("ScheduledAt", $ScheduledAt)
    $UserInput.Add("TransferMode", $TransferMode)

    $FinalizeMigration = Read-RMBoolean -UserMessage "Remove snapshot(s) from the Target VM in preparation for a cutover" -DefaultValue "false"
    $UserInput.Add("FinalizeMigration", $FinalizeMigration)

    $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
    $UserInput.Add("MigrationInstruction", $MigrationInstructions)

    $IgnoreValidationErrors = Read-RMBoolean -UserMessage "Ignore validation errors" -DefaultValue "false"
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationErrors)

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

    $IsScheduled = ![string]::IsNullOrWhiteSpace($ScheduledAt)
    $MigrationResponse = Invoke-RMDifferentialMigrationPost -DifferentialProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Differential migration started successfully, migration ID"
}

function Start-RMNonInteractiveVsphereVMBasedDifferentialMigration {
    param (
        [System.Guid] $MigrationId,
        [string] $ScheduledAt,
        [bool] $RunContinuous,
        [bool] $UpgradeTools,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $FinalizeMigration,
        [string[]] $MigrationInstruction,
        [bool] $IgnoreValidationErrors
    )
    $UserInput = @{}
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    $Errors, $IsValidMigrationId = Confirm-RMVMBasedDMCommonParameter -UserParameter $PSBoundParameters
    if (!$IsValidMigrationId) {
        $RMMigrationReturn.SetReturnCode([RMReturn]::ERROR)
        $Errors | ForEach-Object -Process {$RMMigrationReturn.AddRMError([RMError]::new($_))}
        Out-RMUserParameterResult -ErrorMessage $Errors
        return $RMMigrationReturn
    }

    $Migration = Get-RMMigrationByIdAndStatus -MigrationId $MigrationId
    $UserInput.Add("Migration", $Migration)

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

    $ErrorsInMigrationObject, $Warnings = Confirm-RMVMBasedDMParameterWithMigrationAndSource -UserParameter $PSBoundParameters `
        -Migration $Migration -Source $Source
    $Errors += $ErrorsInMigrationObject
    
    $TransferMode, $ScheduledAt = Get-RMTransferModeAndSchedule -RunContinuous $RunContinuous -ScheduledAt $ScheduledAt
    $MigrationInstructionAsHashTable = @{}
    try {
        if (![string]::IsNullOrWhiteSpace($MigrationInstruction)) {
            $MigrationInstructionAsHashTable =  Get-RMStringArrayAsHashtable -InputItems $MigrationInstruction -ParameterName "MigrationInstruction"
        }
    } catch {
        $Errors += $PSItem.Exception.Message
    }

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

    $UserInput.Add("TransferMode", $TransferMode)
    $UserInput.Add("ScheduledAt", $ScheduledAt)
    $UserInput.Add("UpgradeTools", $UpgradeTools)
    $UserInput.Add("ShutdownSource", $ShutdownSource)
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    $UserInput.Add("FinalizeMigration", $FinalizeMigration)
    $UserInput.Add("MigrationInstruction", $MigrationInstructionAsHashTable)
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationErrors)

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

    $IsScheduled = ![string]::IsNullOrWhiteSpace($ScheduledAt)
    $MigrationResponse = Invoke-RMDifferentialMigrationPost -DifferentialProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Differential migration started successfully, migration ID"

}

function Confirm-RMVMBasedDMParameterWithMigrationAndSource {
    param(
        [hashtable] $UserParameter,
        [System.Object] $Migration,
        [System.Object] $Source
    )
    $Errors = @()
    $Warnings = @()
    $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."
    }


    $OrganizationId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly
    if ((Test-RMSourceHasRunningMigration -OrganizationId $OrganizationId -SourceId $Source.id)) {
        $Warnings += "Currently there is a migration running for the source of the given migration ID, this differential migration might fail."
    }

    $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."
    }

    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."
    }

    return $Errors, $Warnings
}