Migration/vSphere/vSphere.psm1

using module '../../Common/Result'
using module './RiverMeadow.VSphereNetworkConfiguration'
using module './RiverMeadow.VSphereVMTag'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath MigrationProfile | Join-Path -ChildPath VSphereMigrationProfile)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath DifferentialProfile | Join-Path -ChildPath DifferentialProfile)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Preflight | Join-Path -ChildPath Preflight)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Util | Join-Path -ChildPath Util)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Util | Join-Path -ChildPath MigrationUtil)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath CloudAccount | Join-Path -ChildPath CloudAccount)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath RiverMeadow.Development.Source | Join-Path -ChildPath SourceUtil | Join-Path -ChildPath SourceUtil)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Validate | Join-Path -ChildPath Validate)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Common | Join-Path -ChildPath Common)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath Common | Join-Path -ChildPath Wrappers | Join-Path -ChildPath Wrappers)
Import-Module -Name @(Join-Path $PSScriptRoot vSphereUtil)

function Start-RMNonInteractiveVsphereOsBasedMigration {
    param (
        [string] $TargetCloud,
        [string] $SourceIP,
        [string] $ScheduledAt,
        [string] $TargetVMName,
        [string[]] $MountPoint,
        [string[]] $ResizeMountPoint,
        [string] $TransferMethod,
        [int] $TargetVMVCPUCount,
        [string] $CoresPerSocket,
        [int] $TargetVMMemorySize,
        [string] $DatacenterName,
        [bool] $EnableDestinationNetworkIsolation,
        [RMVSphereMigrationNetworkConfiguration] $MigrationNetworkConfig,
        [RMVSphereIsolatedNetworkConfiguration] $IsolatedNetworkConfig,
        [RMVSphereDestinationNetworkConfiguration[]] $DestinationNICConfig,
        [bool] $DisableAutomaticDNSRegistrationOnTheTarget,
        [string] $ClusterName,
        [string] $ResourcePoolName,
        [string] $FolderName,
        [string] $DatastoreClusterName,
        [string[]] $DatastoreName,
        [string[]] $DiskProvisioningType,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgentFromSource,
        [bool] $RemoveRMSAgentFromTarget,
        [string[]] $MigrationInstruction,
        [string] $UpgradeOSVersion,
        [bool] $IgnoreValidationError,
        [bool] $OverrideExistingMigration
    )

    $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid = Confirm-RMvSphereFullMigrationParameter $PSBoundParameters 

    $CloudAccount = $null
    $Source = $null
    try {
        if (![string]::IsNullOrEmpty($TargetCloud)) {
            $CloudAccount, $ErrorString = Get-RMCloudAccountByName -CloudAccountName $TargetCloud -AccountType "rivermeadow_standalone"
            if (![string]::IsNullOrEmpty($ErrorString)) {
                $Errors += $ErrorString
                $IsSourceAndTargetCloudPresent = $false
            } else {
                Get-RMTargetInventory -CloudAccount $CloudAccount | Out-Null
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    $ShouldExit = $false
    try {
        if (![string]::IsNullOrEmpty($SourceIP)) {
            $Source = Get-RMSourceByIP -IPAddress $SourceIP
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    if (!$IsSourceAndTargetCloudPresent) {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    }

    try {
        $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = `
            Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
                -IgnoreValidationErrors $IgnoreValidationError -AccountType "rivermeadow_standalone" `
                -RMMigrationReturn $RMMigrationReturn
        if (!$IgnoreValidationError -and $OverrideExistingMigrationError -and !$OverrideExistingMigration) {
            $Errors += "Please set 'OverrideExistingMigration' to true and try again."
        }
    } catch [System.InvalidOperationException], [System.Management.Automation.JobFailedException] {
        $Errors += $PSItem.Exception.Message
    }

    if ($null -ne $Source) {
        $SourceErrors, $RequiredParameterState = Confirm-RMvSphereFullMigrationParameterWithSource `
            -UserParameter $PSBoundParameters -Source $Source
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $RequiredParameterState
        }
        $Errors += $SourceErrors
    }

    $UserInput = @{}
    $Entitlement = Get-RMEntitlement
    $UserInput.Add("CloudAccount", $CloudAccount)
    $UserInput.Add("Entitlement", $Entitlement)
    $UserInput.Add("Source", $Source)

    if ([string]::IsNullOrEmpty($TargetVMName)) {
        $TargetVMName = $Source.hostname
    }

    $UserInput.Add("TargetVMName", $TargetVMName)

    $SelectedMountPoints = $null
    $SourceMountPoints = Get-MountPoint -Source $Source
    if ($MountPoint.Count -gt 0) {
        $SelectedMountPoints = Get-RMSelectedMount -MountPoints $SourceMountPoints -DifferenceList $MountPoint -IncludeEqual $true
    } else {
        # If no mount points are given then take all mount points on the source as the selected mount points
        $SelectedMountPoints = $SourceMountPoints
    }

    $UserInput.Add("SelectedMounts", $SelectedMountPoints)

    $MountsResize = @{}
    if ($null -ne $ResizeMountPoint -and $ResizeMountPoint.Count -gt 0) {
        $MountsResize, $MountsResizeErrors = Get-RMResizeMountsPoint -ResizeMountPoints $ResizeMountPoint -SelectedMountPoints $SelectedMountPoints -Source $Source
        if($null -eq $MountsResize) {
            $Errors += $MountsResizeErrors
        }
        if ("" -ne $TransferMethod -and "block-based" -eq $TransferMethod ) {
            $Errors += "TransferMethod needs to be 'file-based' if you want to resize the mount points."
        }
        $TransferMethod = "file-based"
    } else {
        $TransferMethod, $ErrorString = Get-RMTransferMethod -Source $Source -SelectedMountPoints $SelectedMountPoints `
            -TransferMethod $TransferMethod
        if (![string]::IsNullOrEmpty($ErrorString)) {
            $Errors += $ErrorString
        }
    }
    $UserInput.Add("ResizeMountPoints", $MountsResize)
    $UserInput.Add("TransferType", $TransferMethod)

    if(0 -eq $TargetVMVCPUCount) {
        $TargetVMVCPUCount = $Source.attributes.cpu.processors.Count
    }
    $UserInput.Add("TargetVMVCPUCount", $TargetVMVCPUCount)

    if ("" -eq $CoresPerSocket) {
        $CoresPerSocket = 1
    } else {
        $CoresPerSockets = Get-RMCoresPerSocket -TargetVMVCPUCount $TargetVMVCPUCount
        if(!($CoresPerSocket -in $CoresPerSockets)) {
            $CoresPerSocketsAsString = $CoresPerSockets -join ", "
            $Errors += "Invalid CoresPerSocket '$CoresPerSocket', valid cores per socket are: '$CoresPerSocketsAsString'"
        }
    }
    $UserInput.Add("CoresPerSocket", $CoresPerSocket)
    $UserInput.Add("TargetVMMemorySize", $TargetVMMemorySize)

    if ($Source.os_type -eq "windows") {
        $UserInput.Add("DisableAutomaticDNSRegistrationOnTheTarget", $DisableAutomaticDNSRegistrationOnTheTarget)
    } else {
        $UserInput.Add("DisableAutomaticDNSRegistrationOnTheTarget", $false)
    }

    $UserInput.Add("DatacenterName", $DatacenterName)
    $UserInput.Add("EnableDestinationNetworkIsolation", $EnableDestinationNetworkIsolation)
    $UserInput.Add("MigrationNetworkConfig", $MigrationNetworkConfig)
    $UserInput.Add("IsolatedNetworkConfig", $IsolatedNetworkConfig)
    $UserInput.Add("DestinationNICConfig", $DestinationNICConfig)
    $UserInput.Add("ClusterName", $ClusterName)
    $UserInput.Add("ResourcePoolName", $ResourcePoolName)
    $UserInput.Add("FolderName", $FolderName)
    $UserInput.Add("DatastoreClusterName", $DatastoreClusterName)

    $DiskProvisioningType = Get-DiskProvisioningTypeBySource -Source $Source -DiskProvisioningTypes $DiskProvisioningType -IsInteractive $false

    $UserInput.Add("DatastoreLocations", $DatastoreName)
    $UserInput.Add("DisksProvisioningType", $DiskProvisioningType)

    $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage = Get-RMHardwareVersionsAndToolsVersion -CloudAccount $CloudAccount
    if (0 -eq $HardwareVersion) {
        $HardwareVersion = $DefaultHardwareVersion
    } else {
        if ($HardwareVersions -notcontains $HardwareVersion) {
            $HardwareVersionsAsString = $HardwareVersions -join ", "
            $Errors += "Invalid HardwareVersion '$HardwareVersion', valid hardware versions are: '$HardwareVersionsAsString'"
        }
    }

    $UserInput.Add("HardwareVersion", $HardwareVersion)

    $UserInput.Add("ToolsPackage", $ToolsPackage)
    $MigrationInstructionsAsHashTable = $null
    try {
        if (($IgnoreValidationError -and $OverrideExistingMigration) -or (!$IgnoreValidationError -and $OverrideExistingMigration) -or $OverrideExistingMigrationWarning) {
            $MigrationInstruction += "override_source_migration=true"
        }
        $MigrationInstructionsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $MigrationInstruction -ParameterName "MigrationInstruction"
    } catch {
        $Errors += $PSItem.Exception.Message
    }
   
    $UserInput.Add("MigrationInstructions", $MigrationInstructionsAsHashTable)

    $UserInput.Add("ShutdownSource", $ShutdownSource)
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    $UserInput.Add("RemoveRMSAgentFromSource", $RemoveRMSAgentFromSource)
    $UserInput.Add("RemoveRMSAgentFromTarget", $RemoveRMSAgentFromTarget)
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationError)

    Add-RMOSUpgrade -UserParameter $PSBoundParameters -UpdatedParameter $UserInput -Source $Source
    
    $ValidationErrors = $null
    if ($IsRequiredParametersPresentAndValid) {
        $ValidationErrors = Confirm-RMVSphereValidateOSBasedMigrationProfile @UserInput
    }
    if ($null -ne $ValidationErrors) {
        # validation errors have already been printed, so add after the below call
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        $Errors += $ValidationErrors
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    } else {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        if ($Errors.Count -gt 0 -or $ShouldExit) {
            return $RMMigrationReturn
        }
    }
   
    if ([string]::IsNullOrEmpty($ScheduledAt)) {
        $UserInput.Add("ScheduledAt", "")
    } else {
        $UserInput.Add("ScheduledAt", (Convert-RMDateTimeToUTC -InputDateTime $ScheduledAt))
    }

    $UserInput["DisksProvisioningType"] = Update-DiskProvisioningTypeForME -DiskProvisioningTypes $UserInput["DisksProvisioningType"]
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationError)

    $Response = New-RMVSphereMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function Start-RMInteractiveVSphereOSBasedMigration {
    param( )

    $Entitlement = Get-RMEntitlement
    $CloudAccount = Read-RMCloudAccount -AccountType "rivermeadow_standalone" -UserMessage "Enter target cloud"
    $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount

    $Source = Read-RMSource -UserMessage "Enter the IP address of the source machine to be migrated" -ParameterName "Source IP address" -IsRequired $true
    $IgnoreValidationErrors = Read-RMBoolean -UserMessage "Ignore validation errors" -DefaultValue "false"

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn  -AccountType "rivermeadow_standalone"

    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    if ($OverrideExistingMigrationError) {
        $OverrideExistingMigrationError = Read-RMBoolean -UserMessage "Would you like to override the previous migration attempt" -DefaultValue $false
        if (!$IgnoreValidationErrors -and !$OverrideExistingMigrationError) {
            return $RMMigrationReturn
        }
    }

    $ScheduledAt = Read-RMMigrationSchedule
    $TargetVMName = Read-RMString -UserMessage "Enter target VM Name" -DefaultValue $Source.hostname `
        -ParameterName "Target VM Name" -IsRequired $false

    $MountPoints = Get-MountPoint -Source $Source
    if (0 -eq $MountPoints.count) {
        throw "Source has no mount points, cannot be migrated"
    }

    $MountPointsAsString = $MountPoints.values -join ", "
    Write-Output "Mount points to be migrated [$MountPointsAsString]" | Out-Host
    $ExcludedMountPoints = Get-RMExcludedMountPoint -OSType $Source.os_type -MountPoints $MountPoints.Values
    $SelectedMountPoints = $MountPoints
    if ("" -ne $ExcludedMountPoints) {
        $ExcludedList = $ExcludedMountPoints.Split(",").Trim()
        $SelectedMountPoints = Get-RMSelectedMount -MountPoints $MountPoints -DifferenceList $ExcludedList -IncludeEqual $false
    }

    $ReadValue = Read-RMBoolean -UserMessage "Resize mount points" -DefaultValue "false"

    $MountsResize = @{}
    $TransferType = "file-based"
    if ($ReadValue) {
        $MountsResize = Get-RMInteractiveMountsResize -SelectedMountPoints $SelectedMountPoints -Source $Source
    } else {
        $TransferType = (Get-RMTransferMethod -Source $Source -SelectedMountPoints $SelectedMountPoints -IsInteractive $true)[0]
    }

    $TargetVMVCPUCount = $Source.attributes.cpu.processors.Count
    $TargetVMVCPUCount = Read-RMInt -UserMessage "Enter virtual CPU count" -DefaultValue $TargetVMVCPUCount `
    -ParameterName "Virtual CPUs count" -IsRequired $false

    $CoresPerSocket = 1
    $CoresPerSockets = Get-RMCoresPerSocket -TargetVMVCPUCount $TargetVMVCPUCount
    $CoresPerSocket = Read-RMInt -UserMessage "Enter cores per socket" -Options $CoresPerSockets -DefaultValue $CoresPerSocket `
        -ParameterName "Cores per socket" -IsRequired $false


    $SourceMemorySize = [math]::Round($Source.attributes.memory.total_kb/(1024 * 1024),2)
    $TargetVMMemorySize = Read-RMInt -UserMessage "Enter target VM's memory size in GiB" -DefaultValue $SourceMemorySize `
        -ParameterName "Target VM's memory size" -IsRequired $false
   
    $DatacenterNames = Get-RMDatacenterName -TargetInventory $TargetInventory
    $DatacenterName = Read-RMString -UserMessage "Enter datacenter name" -Options $DatacenterNames `
        -ParameterName "Datacenter name" -IsRequired $true

    $NetworkNames = Get-RMNetworkName -TargetInventory $TargetInventory -datacenterName $DatacenterName

    $EnableDestinationNetworkIsolation = $false
    [RMVSphereMigrationNetworkConfiguration] $MigrationNetworkConfig = $null
    [RMVSphereIsolatedNetworkConfiguration] $IsolatedNetworkConfig = $null
    [RMVSphereDestinationNetworkConfiguration[]] $DestinationNetworkConfigs = $null
    if ($Source.os_type -eq "windows") {
        $EnableDestinationNetworkIsolation = Read-RMBoolean -UserMessage "Enable destination network isolation" -DefaultValue "false"
        if ($EnableDestinationNetworkIsolation) {
            $MigrationNetworkConfig = Get-RMMigrationNetworkConfig -NetworkName $NetworkNames
            $IsolatedNetworkConfig = Get-RMIsolatedNetworkConfig -NetworkName $NetworkNames
        } else {
            $DestinationNetworkConfigs = Get-RMDestinationNetworkConfig -NetworkName $NetworkNames
        }
    } else {
        $DestinationNetworkConfigs = Get-RMDestinationNetworkConfig -NetworkName $NetworkNames
    }

    $DisableAutomaticDNSRegistrationOnTheTarget = $false
    if ($Source.os_type -eq "windows") {
        $DisableAutomaticDNSRegistrationOnTheTarget = Read-RMBoolean -UserMessage "Disable automatic DNS registration on the Target" `
            -DefaultValue "false"
    }

    $Clusters = Get-RMCluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $ClusterName = Read-RMString -UserMessage "Enter cluster name" -Options $Clusters -ParameterName "Cluster name" -IsRequired $true

    $ResourcePoolName = Read-RMString -UserMessage "Enter resource pool name" -DefaultValue "None" `
        -ParameterName "Resource pool name" -IsRequired $false

    $VMFolders = Get-RMVMFolderName -TargetInventory $TargetInventory -DatacenterName $DatacenterName
    $FolderName = Read-RMVMFolderString -UserMessage "Enter the VM folder path using '/' as the path separator" -VMFolders  $VMFolders -DefaultValue "None"

    $DatastoreClusters = Get-RMDatastoreCluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $DatastoreClusterName = Read-RMString -UserMessage "Enter datastore cluster name" -Options $DatastoreClusters `
        -DefaultValue "None" -ParameterName "Datastore cluster name" -IsRequired $false

    $Datastores = Get-RMDatastore -TargetInventory $TargetInventory -DatacenterName $DatacenterName -ClusterName $ClusterName
    $SelectedDatastores = Get-RMDatastoreBySource -Source $Source -TargetDatastores $Datastores -IsInteractive $true

    $SelectedProvisioningTypes = Get-DiskProvisioningTypeBySource -Source $Source -IsInteractive $true
    $SelectedProvisioningTypes = Update-DiskProvisioningTypeForME -DiskProvisioningTypes $SelectedProvisioningTypes

    $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage = Get-RMHardwareVersionsAndToolsVersion -CloudAccount $CloudAccount
    $HardwareVersion = Read-RMInt -UserMessage "Enter VM hardware version" -Options $HardwareVersions `
        -DefaultValue $DefaultHardwareVersion -ParameterName "VM hardware version" -IsRequired $false

    $OSMUpgradeUserInput = @{}
    Read-RMOSMUpgradeOption -Source $Source -UpdatedUserInput $OSMUpgradeUserInput
    $UpgradeOSVersion = $OSMUpgradeUserInput["UpgradeOSVersion"]
    $InPlaceUpgrade = $OSMUpgradeUserInput["InPlaceUpgrade"]

    $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"
    $RemoveRMSAgentFromSource = Read-RMBoolean -UserMessage "Remove RMS agent post migration from source" -DefaultValue "false"
    $RemoveRMSAgentFromTarget = Read-RMBoolean -UserMessage "Remove RMS agent post migration from target" -DefaultValue "false"

    $ReadValue = Read-RMPair -UserMessage "Enter migration instructions in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    if (($IgnoreValidationErrors -and $OverrideExistingMigrationError) -or (!$IgnoreValidationErrors -and $OverrideExistingMigrationError) -or $OverrideExistingMigrationWarning) {
        if ("" -eq $ReadValue) {
            $ReadValue = "override_source_migration=true"
        } else {
            $ReadValue += ",override_source_migration=true"
        }
    }
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue

    $HashArguments = @{
        CloudAccount = $CloudAccount
        Entitlement = $Entitlement
        Source = $Source
        ScheduledAt = $ScheduledAt
        TargetVMName = $TargetVMName
        SelectedMounts = $SelectedMountPoints
        ResizeMountPoints = $MountsResize
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveRMSAgentFromSource = $RemoveRMSAgentFromSource
        RemoveRMSAgentFromTarget = $RemoveRMSAgentFromTarget
        DatacenterName = $DatacenterName
        EnableDestinationNetworkIsolation = $EnableDestinationNetworkIsolation
        MigrationNetworkConfig = $MigrationNetworkConfig
        IsolatedNetworkConfig = $IsolatedNetworkConfig
        DestinationNICConfig = $DestinationNetworkConfigs
        DisableAutomaticDNSRegistrationOnTheTarget = $DisableAutomaticDNSRegistrationOnTheTarget
        ClusterName = $ClusterName
        DatastoreClusterName =  $DatastoreClusterName
        DatastoreLocations = $SelectedDatastores
        FolderName =  $FolderName
        DisksProvisioningType = $SelectedProvisioningTypes
        TransferType = $TransferType
        TargetVMVCPUCount = $TargetVMVCPUCount
        CoresPerSocket = $CoresPerSocket
        TargetVMMemorySize = $TargetVMMemorySize
        ResourcePoolName = $ResourcePoolName
        HardwareVersion = $HardwareVersion
        ToolsPackage = $ToolsPackage
        MigrationInstructions = $MigrationInstructions
        UpgradeOSVersion = $UpgradeOSVersion
        IgnoreValidationErrors = $IgnoreValidationErrors
        InPlaceUpgrade = $InPlaceUpgrade
    }

    $Response = New-RMVSphereMigrationProfile @HashArguments
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function Start-RMInteractiveVSphereVMBasedMigration {
    param ()

    $UserInput = @{}
    $CloudAccount = Read-RMCloudAccount -AccountType "rivermeadow_standalone" `
        -UserMessage "Enter target cloud that supports VM-based migrations" -VMBasedAppliancesOnly $true
    $Source = Read-RMVMBasedSource -UserMessage "Enter the VM name of the source machine to be migrated" `
        -ParameterName "Source VM name" -IsRequired $true -CloudAccount $CloudAccount

    $Entitlement = Get-RMEntitlement
    $UserInput.Add("Entitlement", $Entitlement)

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    if ($null -eq $CloudAccount) {
        return Update-RMMigrationReturnAsError `
            -Message "The migration appliance associated with the given source is not ready for use, cannot start the migration" `
            -RMMigrationReturn $RMMigrationReturn
    }

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

    $SourceAttributeResult = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn `
        -AccountType "rivermeadow_standalone"
    $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = $SourceAttributeResult
    $UserInput.Add("Source", $Source)
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    
    if ($OverrideExistingMigrationError) {
        $OverrideExistingMigrationError = Read-RMBoolean -UserMessage "Would you like to override the previous migration attempt" -DefaultValue $false
        if (!$IgnoreValidationErrors -and !$OverrideExistingMigrationError) {
            return $RMMigrationReturn
        }
    }

    $ScheduledAt = Read-RMMigrationSchedule
    $UserInput.Add("ScheduledAt", $ScheduledAt)    
    $TargetVMName = Read-RMString -UserMessage "Enter target VM Name" -DefaultValue $Source.host `
        -ParameterName "Target VM Name" -IsRequired $false
    $UserInput.Add("TargetVMName", $TargetVMName)        

    if (0 -eq $Source.attributes.storage.vm_disks.Count) {
        return Update-RMMigrationReturnAsError `
            -Message "Source has no disks, cannot be migrated" -RMMigrationReturn $RMMigrationReturn
    }

    $SourceDisksAsString = "Disks to be migrated [" + ((Get-RMVMDiskProperty -Source $Source) -join ", ") + "]"
    Write-Output $SourceDisksAsString | Out-Host

    $SelectedDisks = @()
    $ReturnedValue = Get-RMSelectedDisk -Source $Source
    if ($SelectedDisks -is [hashtable]) {
        $SelectedDisks += $ReturnedValue
    } else {
        $SelectedDisks = $ReturnedValue
    }
    $UserInput.Add("SelectedDisk", $SelectedDisks)
    $SelectedDiskIndices = @()
    foreach ($Item in $SelectedDisks) {
        $SelectedDiskIndices += $Item.Values
    }
    $UserInput.Add("SelectedDiskIndex", $SelectedDiskIndices)

    $TargetVMVCPUCount = $Source.attributes.cpu.processors.Count
    $TargetVMVCPUCount = Read-RMInt -UserMessage "Enter virtual CPU count" -DefaultValue $TargetVMVCPUCount `
        -ParameterName "Virtual CPUs count" -IsRequired $false
    $UserInput.Add("TargetVMVCPUCount", $TargetVMVCPUCount)

    $CoresPerSocket = 1
    $CoresPerSockets = Get-RMCoresPerSocket -TargetVMVCPUCount $TargetVMVCPUCount
    $CoresPerSocket = Read-RMInt -UserMessage "Enter cores per socket" -Options $CoresPerSockets -DefaultValue $CoresPerSocket `
        -ParameterName "Cores per socket" -IsRequired $false
    $UserInput.Add("CoresPerSocket", $CoresPerSocket)

    $SourceMemorySize = [math]::Round($Source.attributes.memory.total_kb/(1024 * 1024),2)
    $TargetVMMemorySize = Read-RMInt -UserMessage "Enter target VM's memory size in GiB" -DefaultValue $SourceMemorySize `
        -ParameterName "Target VM's memory size" -IsRequired $false
    $UserInput.Add("TargetVMMemorySize", $TargetVMMemorySize)
   
    $DatacenterNames = Get-RMDatacenterName -TargetInventory $TargetInventory
    $DatacenterName = Read-RMString -UserMessage "Enter datacenter name" -Options $DatacenterNames `
        -ParameterName "Datacenter name" -IsRequired $true
    $UserInput.Add("DatacenterName", $DatacenterName)

    $NetworkNames = Get-RMNetworkName -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $PreserveIPAddress = Read-RMBoolean -UserMessage "Do you want to preserve the IP address" -DefaultValue "false"
    $UserInput.Add("PreserveIPAddress", $PreserveIPAddress)

    [RMVSphereDestinationNetworkConfiguration[]] $DestinationNetworkConfigs = $null
    if ($PreserveIPAddress) {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" `
            -ParameterName "Destination network name" -Options $NetworkNames -IsRequired $true
        $DestinationNetworkConfigs += [RMVSphereDestinationNetworkConfiguration]::new(`
            $DestinationNetworkName, "dhcp")
    } else {
        $DestinationNetworkConfigs = Get-RMDestinationNetworkConfig -NetworkName $NetworkNames -Source $Source
    }
    $UserInput.Add("DestinationNICConfig", $DestinationNetworkConfigs)
    $UserInput.Add("PreserveMACAddress", (Read-RMBoolean -UserMessage "Do you want to preserve the MAC address" `
        -DefaultValue "false"))

    $UserInput.Add("VMTag", (Get-RMVMTag -TargetInventory $TargetInventory))
    $Clusters = Get-RMCluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $ClusterName = Read-RMString -UserMessage "Enter cluster name" -Options $Clusters `
        -ParameterName "Cluster name" -IsRequired $true
    $UserInput.Add("ClusterName", $ClusterName)

    $UserInput.Add("ResourcePoolName", (Read-RMString -UserMessage "Enter resource pool name" -DefaultValue "None" `
        -ParameterName "Resource pool name" -IsRequired $false))

    $UserInput.Add("FolderName", (Read-RMString -UserMessage "Enter VM folder name" -DefaultValue "None" `
        -ParameterName "VM folder name" -IsRequired $false))

    $DatastoreClusters = Get-RMDatastoreCluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $UserInput.Add("DatastoreClusterName", (Read-RMString -UserMessage "Enter datastore cluster name" -Options $DatastoreClusters `
        -DefaultValue "None" -ParameterName "Datastore cluster name" -IsRequired $false))

    $Datastores = Get-RMDatastore -TargetInventory $TargetInventory -DatacenterName $DatacenterName -ClusterName $ClusterName
    $UserInput.Add("DatastoreLocation", (Get-RMDatastoreByVMBasedSource -Source $Source `
        -TargetDatastores $Datastores -SelectedDiskIndex $SelectedDiskIndices -IsInteractive $true))

    $SelectedStorageProfileIds, $SelectedStorageProfileNames = Get-RMStoragePolicy -TargetInventory $TargetInventory `
        -Source $Source -SelectedDiskIndex $SelectedDiskIndices -IsInteractive $true
    $UserInput.Add("StorageProfileId", $SelectedStorageProfileIds)
    $UserInput.Add("StorageProfileName", $SelectedStorageProfileNames)

    $SelectedDiskProvisioningTypes = Get-RMDiskProvisioningTypeForVMBasedSource -Source $Source `
        -SelectedStorageProfileName $SelectedStorageProfileNames -SelectedDiskIndex $SelectedDiskIndices -IsInteractive $true
    $UserInput.Add("DiskProvisioningType", (Update-DiskProvisioningTypeForME -DiskProvisioningTypes $SelectedDiskProvisioningTypes))

    $UserInput.Add("UpgradeTool", (Read-RMBoolean -UserMessage "Do you want to upgrade the VM tools on the target VM" -DefaultValue "false"))

    $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage = Get-RMHardwareVersionsAndToolsVersion -CloudAccount $CloudAccount
    $UserInput.Add("HardwareVersion", (Read-RMInt -UserMessage "Enter VM hardware version" -Options $HardwareVersions `
        -DefaultValue $DefaultHardwareVersion -ParameterName "VM hardware version" -IsRequired $false))
    $UserInput.Add("ToolsPackage", $ToolsPackage)    

    $UserInput.Add("ShutdownSource", (Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"))
    $UserInput.Add("ShutdownTarget", (Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"))
    $UserInput.Add("FinalizeMigration", (Read-RMBoolean -UserMessage "Remove snapshot(s) from the Target VM in preparation for a cutover" `
        -DefaultValue "false"))

    $ReadValue = Read-RMPair -UserMessage "Enter migration instructions in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    if (($IgnoreValidationErrors -and $OverrideExistingMigrationError) -or (!$IgnoreValidationErrors -and $OverrideExistingMigrationError) -or $OverrideExistingMigrationWarning) {
        if ("" -eq $ReadValue) {
            $ReadValue = "override_source_migration=true"
        } else {
            $ReadValue += ",override_source_migration=true"
        }
    }
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue
    $UserInput.Add("MigrationInstruction", $MigrationInstructions)

    $Response = New-RMVSphereVMBasedMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function Start-RMNonInteractiveVsphereVMBasedMigration {
    param(
        [string] $SourceVMName,
        [string] $SourceVMFolderPath,
        [string] $TargetCloud,
        [string] $ScheduledAt,
        [string] $TargetVMName,
        [string[]] $SelectedDiskLabel,
        [int] $TargetVMVCPUCount,
        [string] $CoresPerSocket,
        [int] $TargetVMMemorySize,
        [string] $DatacenterName,
        [bool] $PreserveIPAddress,
        [RMVSphereDestinationNetworkConfiguration[]] $DestinationNICConfig,
        [bool] $PreserveMACAddress,
        [RMVSphereTag[]] $VMTag,
        [string] $ClusterName,
        [string] $ResourcePoolName,
        [string] $FolderName,
        [string] $DatastoreClusterName,
        [string[]] $DatastoreName,
        [string[]] $StoragePolicyName,
        [string[]] $DiskProvisioningType,
        [bool] $UpgradeTool,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $FinalizeMigration,
        [string[]] $MigrationInstruction,
        [bool] $IgnoreValidationError,
        [bool] $OverrideExistingMigration
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid = `
        Confirm-RMvSphereVMBasedFullMigrationParameter $PSBoundParameters

    try {
        $TargetInventory = $null
        if (![string]::IsNullOrEmpty($TargetCloud)) {
            $CloudAccount, $ErrorString = Get-RMCloudAccountByName -CloudAccountName $TargetCloud `
                -AccountType "rivermeadow_standalone" -VMBasedAppliancesOnly $true
            if ($null -eq $CloudAccount) {
                $Errors += $ErrorString
                $IsSourceAndTargetCloudPresent = $false
            } else {
                $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    $Source = $null
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    try {
        if (![string]::IsNullOrEmpty($SourceVMName) -and $null -ne $CloudAccount) {
            $Source = Get-RMSourceByVMName -VMName $SourceVMName -SourceVMFolderPath $SourceVMFolderPath `
                        -CloudAccount  $CloudAccount
            if ($Source.collection_type -ine "VM") {
                $Errors += "Source '$SourceVMName' is not a VM-Based source, to migrate this source, please use the cmdlet 'Start-RMVSphereOSBasedMigration'."
                Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
                Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
                return $RMMigrationReturn        
            }

        }
    } catch [System.Management.Automation.ItemNotFoundException], [System.Data.DuplicateNameException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    if (!$IsSourceAndTargetCloudPresent) {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    }

    $ShouldExit = $false
    try {
        $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = `
            Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
                -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn `
                -AccountType "rivermeadow_standalone"
        if (!$IgnoreValidationError -and $OverrideExistingMigrationError -and !$OverrideExistingMigration) {
            $Errors += "Please set 'OverrideExistingMigration' to true and try again."
        }
    } catch [System.InvalidOperationException], [System.Management.Automation.JobFailedException] {
        $Errors += $PSItem.Exception.Message
    }

    if ($null -ne $Source) {
        if ($null -eq $SelectedDiskLabel -or $SelectedDiskLabel.Count -eq 0) {
            # When the user has not selected any disk labels to migrate,
            # select all for the source.
            $Warnings += "No disk labels have been given, all the disks on the source will be migrated."
            $SelectedDiskLabel = Get-RMVMBasedSourceDiskLabel -Source $Source
            $PSBoundParameters["SelectedDiskLabel"] = $SelectedDiskLabel
        }
    
        $SourceErrors, $SourceWarnings, $RequiredParameterState = Confirm-RMvSphereVMBasedFullMigrationParameterWithSource `
            -UserParameter $PSBoundParameters -Source $Source -TargetInventory $TargetInventory
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $RequiredParameterState
        }
        $Errors += $SourceErrors
        $Warnings += $SourceWarnings
    }

    $StoragePolicyNamePerDiskLabel = Get-RMDiskLabelToStoragePolicyMapping `
        -StoragePolicyName $StoragePolicyName -UserParameter $PSBoundParameters

    $UserInput = @{}
    $Entitlement = Get-RMEntitlement
    $UserInput.Add("CloudAccount", $CloudAccount)
    $UserInput.Add("Entitlement", $Entitlement)
    $UserInput.Add("Source", $Source)

    if ([string]::IsNullOrEmpty($TargetVMName)) {
        $TargetVMName = $Source.hostname
    }
    $UserInput.Add("TargetVMName", $TargetVMName)

    $SelectedDisks = Get-RMSelectedDiskByDiskLabel -DiskLabel $SelectedDiskLabel -Source $Source
    $UserInput.Add("SelectedDiskLabel", $SelectedDiskLabel)
    $UserInput.Add("SelectedDisk", $SelectedDisks)
    $SelectedDiskIndices = @()
    foreach ($Item in $SelectedDisks) {
        $SelectedDiskIndices += $Item.Values
    }
    $UserInput.Add("SelectedDiskIndex", $SelectedDiskIndices)

    if(0 -eq $TargetVMVCPUCount) {
        $TargetVMVCPUCount = $Source.attributes.cpu.processors.Count
    }
    $UserInput.Add("TargetVMVCPUCount", $TargetVMVCPUCount)

    if ("" -eq $CoresPerSocket) {
        $CoresPerSocket = 1
    } else {
        $CoresPerSockets = Get-RMCoresPerSocket -TargetVMVCPUCount $TargetVMVCPUCount
        if(!($CoresPerSocket -in $CoresPerSockets)) {
            $CoresPerSocketsAsString = $CoresPerSockets -join ", "
            $Errors += "Invalid CoresPerSocket '$CoresPerSocket', valid cores per socket are: '$CoresPerSocketsAsString'"
        }
    }
    $UserInput.Add("CoresPerSocket", $CoresPerSocket)
    $UserInput.Add("TargetVMMemorySize", $TargetVMMemorySize)

    $UserInput.Add("DatacenterName", $DatacenterName)
    $UserInput.Add("PreserveIPAddress", $PreserveIPAddress)
    $UserInput.Add("DestinationNICConfig", $DestinationNICConfig)
    $UserInput.Add("PreserveMACAddress", $PreserveMACAddress)
    $UserInput.Add("VMTag", (Get-RMSelectedVMTagByRMVSphereTag -InputTag $VMTag -TargetInventory $TargetInventory))
    $UserInput.Add("ClusterName", $ClusterName)
    $UserInput.Add("ResourcePoolName", $ResourcePoolName)
    $UserInput.Add("FolderName", $FolderName)
    $UserInput.Add("DatastoreClusterName", $DatastoreClusterName)
    $UserInput.Add("DatastoreLocation", $DatastoreName)

    $StorageProfileIds = Get-RMStorageProfileIdByStoragePolicyName -StoragePolicyNamePerDiskLabel $StoragePolicyNamePerDiskLabel `
        -TargetInventory $TargetInventory
    $UserInput.Add("StorageProfileId", $StorageProfileIds)
    $UserInput.Add("StorageProfileName", $StoragePolicyNamePerDiskLabel)

    $DiskProvisioningType = Get-RMDiskProvisioningTypeForVMBasedSource -Source $Source `
        -DiskProvisioningType (Get-RMValidDiskProvisioningType -DiskProvisioningType $DiskProvisioningType) `
        -SelectedDiskIndex $SelectedDiskIndices -SelectedStorageProfileName $StoragePolicyNamePerDiskLabel -IsInteractive $false
    $UserInput.Add("DiskProvisioningTypes", $DiskProvisioningType)
    $UserInput.Add("UpgradeTool", $UpgradeTool)

    $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage = Get-RMHardwareVersionsAndToolsVersion -CloudAccount $CloudAccount
    if (0 -eq $HardwareVersion) {
        $HardwareVersion = $DefaultHardwareVersion
    } else {
        if ($HardwareVersions -notcontains $HardwareVersion) {
            $HardwareVersionsAsString = $HardwareVersions -join ", "
            $Errors += "Invalid HardwareVersion '$HardwareVersion', valid hardware versions are: '$HardwareVersionsAsString'"
        }
    }

    $UserInput.Add("HardwareVersion", $HardwareVersion)
    $UserInput.Add("ToolsPackage", $ToolsPackage)
    $MigrationInstructionsAsHashTable = $null
    try {
        if (($IgnoreValidationError -and $OverrideExistingMigration) -or `
                (!$IgnoreValidationError -and $OverrideExistingMigration) -or `
                $OverrideExistingMigrationWarning) {
            $MigrationInstruction += "override_source_migration=true"
        }
        $MigrationInstructionsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $MigrationInstruction -ParameterName "MigrationInstruction"
    } catch {
        $Errors += $PSItem.Exception.Message
    }
    $UserInput.Add("MigrationInstructions", $MigrationInstructionsAsHashTable)
    $UserInput.Add("ShutdownSource", $ShutdownSource)
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    $UserInput.Add("FinalizeMigration", $FinalizeMigration)

    $ValidationErrors = $null
    if ($IsRequiredParametersPresentAndValid) {
        $ValidationErrors = Confirm-RMVSphereValidateVMBasedMigrationProfile @UserInput
    }
    if ($null -ne $ValidationErrors) {
        # validation errors have already been printed, so add after the below call
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        $Errors += $ValidationErrors
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    } else {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        if ($Errors.Count -gt 0 -or $ShouldExit) {
            return $RMMigrationReturn
        }
    }

    if ([string]::IsNullOrEmpty($ScheduledAt)) {
        $UserInput.Add("ScheduledAt", "")
    } else {
        $UserInput.Add("ScheduledAt", (Convert-RMDateTimeToUTC -InputDateTime $ScheduledAt))
    }

    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationError)
    $UserInput["DiskProvisioningTypes"] = Update-DiskProvisioningTypeForME -DiskProvisioningTypes $UserInput["DiskProvisioningTypes"]
    $Response = New-RMVSphereVMBasedMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id `
        -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"

}
function Get-RMTargetInventory {
    param(
        [System.Object] $CloudAccount
    )

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

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

    $CollectList = New-Object Collections.Generic.List[string]

    $CollectList.Add("datacenter")
    $CollectList.Add("cluster")
    $CollectList.Add("datastore")
    $CollectList.Add("datastore_cluster")
    $CollectList.Add("resource_pool")
    $CollectList.Add("storage_profile")
    $CollectList.Add("vm_folder")
    $CollectList.Add("network")

    $Body = @{
        "appliance_id" = $CloudAccount.appliance.id
        "organization_id" = $CloudAccount.organization_id
        "objects_to_collect" = $CollectList
    } | ConvertTo-Json

    $Params = @{
        Method = "Post"
        Uri = $Uri.Value + "/targetinventories"
        Headers = $Headers
        Body = $Body
        ContentType = "application/json"
    }

    Write-Output "Starting target inventory..." | Out-Host
    $TargetInventory = Invoke-RMRestMethod -Param $Params

    return Watch-RMTargetInventoryStatus -TargetInventoryId $TargetInventory.id
}

function Get-RMDatacenterName {
    param(
        [System.Object] $TargetInventory
    )

    $DatacenterNames = @()
    foreach ($datacenterName in $TargetInventory.attributes.vSphere.datacenters) {
        $DatacenterNames += $datacenterName.name
    }
    return $DatacenterNames
}

function  Get-RMNetworkName {
    param (
        [System.Object] $TargetInventory,
        [string] $datacenterName
    )

    $NetworkNames = @()
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            foreach($network in $datacenter.networks) {
                if ([string]::IsNullOrEmpty($network.switch_name)) {
                    $NetworkNames += $network.name
                } else {
                    $NetworkNames += ($network.switch_name + "/" + $network.name)
                }
            }
            break
        }
    }
    return  $NetworkNames
}

function Get-RMVMFolderName {
    param (
        [System.Object] $TargetInventory,
        [string] $DatacenterName
    )

    $VMFolders = @()
    foreach ($Datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($DatacenterName -ieq $Datacenter.name) {
            foreach ($Folder in $Datacenter.vm_folder.folders) {
                $VMFolders +=  Get-RMPath -Folder $Folder
            }
        }
    }
    return $VMFolders
}


function Get-RMPath {
    param(
        [System.Object] $Folder,
        [array] $Paths
    )

    $Paths += $Folder.path
    if ($null -ne $Folder.folders) {
        foreach ($F in $Folder.folders) {
            $Paths = Get-RMPath -Folder $F -Paths $Paths
        }
    }
    return $Paths
}


function Get-RMCluster {
    param (
        [System.Object] $TargetInventory,
        [string] $datacenterName
    )

    $Clusters = @()
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            foreach($cluster in $datacenter.clusters) {
                $Clusters += $cluster.name
            }
            break
        }
    }
    return $Clusters
}

function Get-RMDatastoreCluster {
    param(
        [System.Object] $TargetInventory,
        [string] $datacenterName
    )

    $DatastoreClusters = @()
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            foreach($datastoreCluster in $datacenter.datastore_clusters) {
                $DatastoreClusters += $datastoreCluster.name
            }
            break
        }
    }
    return  $DatastoreClusters
}

function Get-RMDatastore {
    param (
        [System.Object] $TargetInventory,
        [string] $DatacenterName,
        [string] $ClusterName
    )

    $Datastores = @()
    foreach ($Datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($Datacenter.name -ne $DatacenterName) {
            continue
        }

        foreach ($Cluster in $Datacenter.clusters) {
            if ($Cluster.name -ne $ClusterName) {
                continue
            }

            foreach ($Datastore in $Cluster.datastores) {
                $Datastores += $Datastore.name
            }
            break
        }
        break
    }
    return  $Datastores
}

function Get-RMHardwareVersionsAndToolsVersion {
    param(
        [System.Object] $CloudAccount
    )

    $HardwareVersions = @()
    $HardwareVersionList = @(8, 9, 10, 11, 13, 14, 15, 17, 18, 19)

    $DefaultVersion = $CloudAccount.vsphere_version

    $ToolsPackage , $DefaultHardwareVersion = Get-RMVsphereVersionInfo -Version $DefaultVersion

    foreach($HardwareVersion in $HardwareVersionList) {
        if ($DefaultHardwareVersion -gt $HardwareVersion -or
         $DefaultHardwareVersion -eq $HardwareVersion) {
            $HardwareVersions += $HardwareVersion
        }
    }

    return $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage
}

function Get-RMVsphereVersionInfo {
    param (
       [string] $Version
    )
    
    $Versions = Get-RMVsphereVersionHashtable

    $ToolsPackage = ""
    $HardwareVersion = ""

    foreach($VsphereVersion in $Versions.GetEnumerator()) {
        if ($Version  -eq $VsphereVersion.Name) {
            $ToolsPackage = $VsphereVersion.Value.tools_version
            $HardwareVersion = $VsphereVersion.Value.hardware_version
            Break
        }
    }
    return $ToolsPackage, $HardwareVersion
}

function Get-RMVsphereVersionHashtable {
    param ()

    $VersionInfo = @{
        "VMC" = @{
            "tools_version" = "vmwaretools-11.3.0.tar.gz"
            "hardware_version" = 19
        }
        "GCVE" = @{
            "tools_version" = "vmwaretools-11.3.0.tar.gz"
            "hardware_version" = 19
        }
        "AVS" = @{
            "tools_version" = "vmwaretools-11.3.0.tar.gz"
            "hardware_version" = 19
        }
        "ESXi_7_0" = @{
            "tools_version" = "vmwaretools-11.0.5.tar.gz"
            "hardware_version" = 17
        }
        "ESXi_6_7_U3" = @{
            "tools_version" = "vmwaretools-10.3.5.tar.gz"
            "hardware_version" = 15
        }
        "ESXi_6_7_U2" = @{
            "tools_version" = "vmwaretools-10.3.5.tar.gz"
            "hardware_version" = 15
        }
        "ESXi_6_7" = @{
            "tools_version" = "vmwaretools-10.3.2.tar.gz"
            "hardware_version" = 14
        }
        "ESXi_6_5" = @{
            "tools_version" = "vmwaretools-10.1.0.tar.gz"
            "hardware_version" = 13
        }
        "ESXi_6_0" = @{
            "tools_version" = "vmwaretools-9.10.0.tar.gz"
            "hardware_version" = 11
        }
    }

    return $VersionInfo
}

function Get-RMMigrationNetworkConfig {
    param(
        [string[]] $NetworkName
    )
    $MigrationNetworkName = Read-RMString -UserMessage "Enter migration network name" `
        -ParameterName "Migration network name" -Options $NetworkName -IsRequired $true
    $IPType = (Read-RMString -UserMessage "Enter migration network's IP type" `
        -Options "static", "DHCP" -DefaultValue "DHCP" `
        -ParameterName "IP Type" -IsRequired $false).ToLower()
    if ($IPType -ieq "dhcp") {
        return [RMVSphereMigrationNetworkConfiguration]::new($MigrationNetworkName, $IPType)
    }

    $IPAddress = Read-RMIPAddress -UserMessage "Enter migration network's IP address" `
        -ParameterName "IP address" -IsRequired $true
    $Netmask = Read-RMIPAddress -UserMessage "Enter migration network's netmask" `
        -ParameterName "Netmask" -IsRequired $true
    $DefaultGateway = Read-RMIPAddress -UserMessage "Enter migration network's default gateway" `
        -ParameterName "Default gateway" -IsRequired $true
    $PrimaryDNS = Read-RMIPAddress -UserMessage "Enter migration network's primary DNS" `
        -ParameterName "Primary DNS" -IsRequired $true
    $SecondaryDNS = Read-RMIPAddress -UserMessage "Enter migration network's secondary DNS" `
        -ParameterName "Secondary DNS" -DefaultValue "None" -IsRequired $false

    return [RMVSphereMigrationNetworkConfiguration]::new($MigrationNetworkName, $IPType, `
        $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS)
}

function Get-RMIsolatedNetworkConfig {
    param(
        [string[]] $NetworkName
    )
    $IsolatedNetworkName = Read-RMString -UserMessage "Enter isolated network name" `
        -ParameterName "Isolated network name" -Options $NetworkName -IsRequired $true
    $IPType = (Read-RMString -UserMessage "Enter isolated network's IP type" `
        -Options "static", "DHCP" -DefaultValue "DHCP" `
        -ParameterName "IP Type" -IsRequired $false).ToLower()
    if ($IPType -ieq "dhcp") {
        return [RMVSphereIsolatedNetworkConfiguration]::new($IsolatedNetworkName, $IPType)
    }

    $IPAddress = Read-RMIPAddress -UserMessage "Enter isolated network's IP address" `
        -ParameterName "IP address" -IsRequired $true
    $Netmask = Read-RMIPAddress -UserMessage "Enter isolated network's netmask" `
        -ParameterName "Netmask" -IsRequired $true
    $DefaultGateway = Read-RMIPAddress -UserMessage "Enter isolated network's default gateway" `
        -ParameterName "Default gateway" -IsRequired $true
    $PrimaryDNS = Read-RMIPAddress -UserMessage "Enter isolated network's primary DNS" `
        -ParameterName "Primary DNS" -IsRequired $true
    $SecondaryDNS = Read-RMIPAddress -UserMessage "Enter isolated network's secondary DNS" `
        -ParameterName "Secondary DNS" -DefaultValue "None" -IsRequired $false

    return [RMVSphereIsolatedNetworkConfiguration]::new($IsolatedNetworkName, $IPType, $IPAddress, `
        $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS)
}

function Get-RMDestinationNetworkConfig {
    param(
        [string[]] $NetworkName,
        [System.Object] $Source
    )
    $DestinationNetworkConfigs = @()
    $AddMoreNIC = $false
    $IsGatewayDNSProvided = $false
    do {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" `
            -ParameterName "Destination network name" -Options $NetworkName -IsRequired $true
        $IPType = (Read-RMString -UserMessage "Enter destination network's IP type" `
            -Options "static", "DHCP" -DefaultValue "DHCP" `
            -ParameterName "IP Type" -IsRequired $false).ToLower()
        if ($IPType -ieq "dhcp") {
            $DestinationNetworkConfigs += [RMVSphereDestinationNetworkConfiguration]::new(`
                $DestinationNetworkName, $IPType)
            $AddMoreNIC = Read-RMBoolean -UserMessage "Do you want to add more network adapters" -DefaultValue "false"
            if ($AddMoreNIC) {
                continue
            }
            return $DestinationNetworkConfigs
        }

        $IPAddress = Read-RMIPAddress -UserMessage "Enter destination network's IP address" `
            -ParameterName "IP address" -IsRequired $true
        $Netmask = Read-RMIPAddress -UserMessage "Enter destination network's netmask" `
            -ParameterName "Netmask" -IsRequired $true
        if (!$IsGatewayDNSProvided) {
            $DefaultGateway = Read-RMIPAddress -UserMessage "Enter destination network's default gateway" `
                -ParameterName "Default gateway" -IsRequired $true
            if ($null -ne $Source -and $Source.collection_type -ieq "vm") {
                $DestinationNetworkConfigs += [RMVSphereDestinationNetworkConfiguration]::new($DestinationNetworkName, `
                    $IPType, $IPAddress, $Netmask, $DefaultGateway, "", "")
                $IsGatewayDNSProvided = $true
            } else {
                $PrimaryDNS = Read-RMIPAddress -UserMessage "Enter destination network's primary DNS" `
                    -ParameterName "Primary DNS" -IsRequired $true
                $SecondaryDNS = Read-RMIPAddress -UserMessage "Enter destination network's secondary DNS" `
                    -ParameterName "Secondary DNS" -DefaultValue "None" -IsRequired $false
                $DestinationNetworkConfigs += [RMVSphereDestinationNetworkConfiguration]::new($DestinationNetworkName, `
                    $IPType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS)
                $IsGatewayDNSProvided = $true
            }
        } else {
            $DestinationNetworkConfigs += [RMVSphereDestinationNetworkConfiguration]::new($DestinationNetworkName, `
                $IPType, $IPAddress, $Netmask)
        }

        $AddMoreNIC = Read-RMBoolean -UserMessage "Do you want to add more network adapters" -DefaultValue "false"
    } while ($AddMoreNIC)

    return $DestinationNetworkConfigs
}

function Get-RMDatastoreBySource {
    param(
        [System.Object] $Source,
        [string[]] $TargetDatastores,
        [bool] $IsInteractive
    )
    if (!$IsInteractive) {
        Throw "The function Get-RMDatastoreBySource does not support non-interactive case"
    }

    if ($TargetDatastores.Count -eq 0) {
        Throw "No datastores were found, migration cannot be started."
    }

    $Datastores = @()
    $SortedDisks = $Source.attributes.storage.disks.psobject.Properties.Value | Sort-Object -property device
    foreach ($Disk in $SortedDisks) {
        $Size = $Disk.size_kb/(1024*1024)
        $Size = [math]::round($Size, 2)
        $Datastores += Read-RMString -UserMessage "Enter the datastore name for disk of size $Size GiB" `
            -Options $TargetDatastores -DefaultValue $TargetDatastores[0] -ParameterName "Datastore name" -IsRequired $false
    }
    return $Datastores
}

function Get-DiskProvisioningTypeBySource {
    param(
        [System.Object] $Source,
        [string[]] $DiskProvisioningTypes,
        [bool] $IsInteractive
    )

    $ProvisioningTypes = "thick-lazy-zeroed", "thick-eager-zeroed", "thin"
    $SortedDisks = $Source.attributes.storage.disks.psobject.Properties.Value | Sort-Object -property device
    $DiskCnt = 0
    $ResultProvisioningTypes = @()

    foreach ($Disk in $SortedDisks) {
        if ($IsInteractive) {
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $ResultProvisioningTypes += Read-RMString -UserMessage "Enter disk provisioning type for disk of size $Size GiB" `
                -Options $ProvisioningTypes -DefaultValue "thin" -ParameterName "Disk provisioning type" -IsRequired $false
        } else {
            if ($null -ne $DiskProvisioningTypes -and $DiskCnt -lt $DiskProvisioningTypes.Count) {
                $ResultProvisioningTypes += $DiskProvisioningTypes[$DiskCnt].Trim()
            } else {
                $ResultProvisioningTypes += "thin"
            }
        }
        $DiskCnt ++
    }

    return $ResultProvisioningTypes
}

function Update-DiskProvisioningTypeForME {
    param(
        [string[]] $DiskProvisioningTypes
    )
    $Result = @()
    switch ($DiskProvisioningTypes) {
        "thick-lazy-zeroed" {
            $Result += "thick_lazy_zero"
        }
        "thick-eager-zeroed" {
            $Result += "thick_eager_zero"
        }
        "thin" {
            $Result += "thin"
        }
        default {
            $Result += $null
        }
    }

    return $Result
}

function Get-RMDestinationNetworkAndDvSwitchName {
    param(
        [string[]] $NetworkNames,
        [string] $DestinationNetworkName,
        [bool] $IsInteractive
    )
    if ($IsInteractive) {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -Options $NetworkNames `
            -ParameterName "Destination network name" -IsRequired $true
    }

    $DvSwitchName = ""
    [string[]] $networkNamesArray = $DestinationNetworkName -split "/"
    if ($networkNamesArray.Count -eq 2) {
        $DestinationNetworkName = $networkNamesArray[1]
        $DvSwitchName = $networkNamesArray[0]
    }

    return $DestinationNetworkName, $DvSwitchName
}

function Confirm-RMvSphereFullMigrationParameter {
    param(
        [hashtable] $UserParameter
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent = Confirm-RMCommonParameter -UserParameter $UserParameter
    $vSphereErrors, $IsRequiredParametersPresentAndValid = Confirm-RMvSphereParameter -UserParameter $UserParameter
    $Errors += $vSphereErrors
    return $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid
}

function Confirm-RMvSphereVMBasedFullMigrationParameter {
    param(
        [hashtable] $UserParameter
    )

    $Errors, $Warnings, $IsSourcePresent = Confirm-RMVMBasedCommonParameter -UserParameter $UserParameter
    $vSphereErrors, $IsRequiredParametersPresentAndValid = Confirm-RMvSphereParameter -UserParameter $UserParameter
    $Errors += $vSphereErrors
    return $Errors, $Warnings, $IsSourcePresent, $IsRequiredParametersPresentAndValid
}

function Confirm-RMvSphereFullMigrationParameterWithSource {
    param (
        [hashtable] $UserParameter,
        [System.Object] $Source
    )
    $Errors = @()
    $IsRequiredParametersPresentAndValid = $true
    if ($Source.os_type -ieq "linux") {
        if ($UserParameter.ContainsKey("EnableDestinationNetworkIsolation") -and `
                $UserParameter["EnableDestinationNetworkIsolation"] -eq $true) {
            $Errors += "EnableDestinationNetworkIsolation can be 'true' only for a Windows source"
            $IsRequiredParametersPresentAndValid = $false
        }

        [string[]] $NetworkIsolationConfigs = "MigrationNetworkConfig", "IsolatedNetworkConfig"
        foreach($NetworkIsolationConfig in $NetworkIsolationConfigs) {
            if ($UserParameter.ContainsKey($NetworkIsolationConfig) -or `
                    $null -ne $UserParameter[$NetworkIsolationConfig]) {
                $Errors += "$NetworkIsolationConfig can be not null only for a Windows source"
            }
        }


        $NetworkConfigErrors, $RequiredParameterState = `
            Confirm-RMDestinationNetworkConfig -UserParameter $UserParameter
        $Errors += $NetworkConfigErrors
        # Update the flag only if everything is good so far
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $RequiredParameterState
        }
    } elseif ($UserParameter.ContainsKey("EnableDestinationNetworkIsolation") `
                -and $UserParameter["EnableDestinationNetworkIsolation"] -eq $true) {

        [string[]] $NetworkIsolationConfigs = "MigrationNetworkConfig", "IsolatedNetworkConfig"
        foreach ($NetworkIsolationConfig in $NetworkIsolationConfigs) {
            if (!$UserParameter.ContainsKey($NetworkIsolationConfig) -or `
                    $null -eq $UserParameter[$NetworkIsolationConfig]) {
                $Errors += "$NetworkIsolationConfig is required when 'EnableDestinationNetworkIsolation' is 'true'"
                $IsRequiredParametersPresentAndValid = $false
                continue
            }
            $NetworkConfig = $UserParameter[$NetworkIsolationConfig]
            $NetworkConfigErrors, $RequiredParameterState = `
                Confirm-RMNetworkIsolationConfig -NetworkConfig $NetworkConfig
            $Errors += $NetworkConfigErrors
            # Update the flag only if everything is good so far
            if ($IsRequiredParametersPresentAndValid) {
                $IsRequiredParametersPresentAndValid = $RequiredParameterState
            }
        }
    } else {
        if ($UserParameter.ContainsKey("MigrationNetworkConfig") -and $null -ne $UserParameter["MigrationNetworkConfig"] -and `
                $UserParameter.ContainsKey("IsolatedNetworkConfig") -and $null -ne $UserParameter["IsolatedNetworkConfig"]) {
            $Errors += "'EnableDestinationNetworkIsolation' is required when 'MigrationNetworkConfig' and 'IsolatedNetworkConfig' are not null."
            $IsRequiredParametersPresentAndValid = $false
        }

        $NetworkConfigErrors, $RequiredParameterState = `
            Confirm-RMDestinationNetworkConfig -UserParameter $UserParameter
        $Errors += $NetworkConfigErrors
        # Update the flag only if everything is good so far
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $RequiredParameterState
        }
    }

    $HasDynamicDisks = Test-RMSourceHasDynamicDisks -Source $Source
    if ($HasDynamicDisks) {
        if (!$UserParameter.ContainsKey("DatastoreName") -or $UserParameter["DatastoreName"].Count -eq 0) {
            $Errors += "DatastoreName is required and since the source has dynamic disk(s), please provide only one datastore name."
            $IsRequiredParametersPresentAndValid = $false
        } elseif ($UserParameter["DatastoreName"].Count -gt 1) {
            $Errors += "Source has dynamic disks, please provide only one datastore name."
        }

        if (!$UserParameter.ContainsKey("DiskProvisioningType") -or $UserParameter["DiskProvisioningType"].Count -eq 0) {
            $Errors += "DiskProvisioningType is required and since the source has dynamic disk(s), please provide only one disk provisioning type."
            $IsRequiredParametersPresentAndValid = $false
        } elseif ($UserParameter["DiskProvisioningType"].Count -gt 1) {
            $Errors += "Source has dynamic disks, please provide only one disk provisioning type."
        }
    } else {
        $SourceDiskCnt = $Source.attributes.storage.disks.psobject.properties.name.Count
        if (!$UserParameter.ContainsKey("DatastoreName") -or $UserParameter["DatastoreName"].Count -eq 0) {
            $Errors += "DatastoreName is required."
            $IsRequiredParametersPresentAndValid = $false
        } elseif ($UserParameter["DatastoreName"].Count -lt $SourceDiskCnt) {
            $DatastoreCnt = $UserParameter["DatastoreName"].Count
            $Errors += "The parameter 'DatastoreName' contains '$DatastoreCnt' datastore names but the number of disks found on the source are '$SourceDiskCnt'. The number of datastore names given should be equal to the number of disks on the source."
            $IsRequiredParametersPresentAndValid = $false
        }

        if (!$UserParameter.ContainsKey("DiskProvisioningType") -or $UserParameter["DiskProvisioningType"].Count -eq 0) {
            $Errors += "DiskProvisioningType is required."
            $IsRequiredParametersPresentAndValid = $false
        } elseif ($UserParameter["DiskProvisioningType"].Count -lt $SourceDiskCnt) {
            $DiskProvCnt = $UserParameter["DiskProvisioningType"].Count
            $Errors += "The parameter 'DiskProvisioningType' contains '$DiskProvCnt' disk provisioning types but the number of disks found on the source are '$SourceDiskCnt'. The number of disk provisioning types given should be equal to the number of disks on the source."
            $IsRequiredParametersPresentAndValid = $false
        }
    }

    $Errors += Confirm-RMCommonParameterWithSource -UserParameter $UserParameter -Source $Source
    return $Errors, $IsRequiredParametersPresentAndValid
}

function Confirm-RMvSphereVMBasedFullMigrationParameterWithSource {
    param(
        [hashtable] $UserParameter,
        [System.Object] $Source,
        [System.Object] $TargetInventory
    )
    $Errors = @()
    $Warnings = @()
    $IsRequiredParametersPresentAndValid = $true
    $SelectedDiskLabelCnt = $UserParameter["SelectedDiskLabel"].Count
    if (!$UserParameter.ContainsKey("DatastoreName") -or [string]::IsNullOrWhiteSpace($UserParameter["DatastoreName"])) {
        $Errors += "DatastoreName is required."
        $IsRequiredParametersPresentAndValid = $false
    } elseif ($UserParameter["DatastoreName"].Count -ne $UserParameter["SelectedDiskLabel"].Count) {
        $GivenDatastoreCount = $UserParameter["DatastoreName"].Count
        $Errors += "The parameter 'DatastoreName' contains '$GivenDatastoreCount' datastore names but there are '$SelectedDiskLabelCnt' selected disks on the source. Please provide the same number of datastore names as the disks selected for migration."
    }

    if ($UserParameter.ContainsKey("PreserveIPAddress") -and $UserParameter["PreserveIPAddress"] `
            -and $UserParameter.ContainsKey("DestinationNICConfig")) {
        $NetworkConfigs = $UserParameter["DestinationNICConfig"]
        if ($NetworkConfigs.Count -gt 1) {
            $Warnings += "The parameter 'PreserveIPAddress' is true and the parameter 'DestinationNICConfig' contains more than one NIC configuration, `
                only the destination network name will be used from the first NIC configuration and all other NIC configurations will be ignored."

        } elseif ($NetworkConfigs[0].IPType -ieq "static" `
                -or ![string]::IsNullOrWhiteSpace($NetworkConfigs[0].IPAddress) `
                -or ![string]::IsNullOrWhiteSpace($NetworkConfigs[0].Netmask) `
                -or ![string]::IsNullOrWhiteSpace($NetworkConfigs[0].DefaultGateway) `
                -or ![string]::IsNullOrWhiteSpace($NetworkConfigs[0].PrimaryDNS)) {
            $Warnings += "When parameter 'PreserveIPAddress' is true then only the destination network name will be used from the parameter 'DestinationNICConfig'"
        }

        if ([string]::IsNullOrWhiteSpace($NetworkConfigs[0].DestinationNetworkName)) {
            $Errors += "DestinationNICConfig's DestinationNetworkName is required"
            $IsRequiredParametersPresentAndValid = $false
        }
    } else {
        $NetworkConfigErrors, $RequiredParameterState = `
            Confirm-RMDestinationNetworkConfig -UserParameter $UserParameter
        $Errors += $NetworkConfigErrors
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $RequiredParameterState
        }
    }

    $SourceDiskLabels = Get-RMVMBasedSourceDiskLabel -Source $Source
    $DiskLabelsNotFound = @()
    foreach ($DiskLabel in $UserParameter["SelectedDiskLabel"]) {
        if ($SourceDiskLabels -notcontains $DiskLabel) {
            $DiskLabelsNotFound += $DiskLabel
        }
    }

    if ($DiskLabelsNotFound.Count -gt 0) {
        $DiskLabelsNotFoundAsString = $DiskLabelsNotFound -join ", "
        $Errors += "The disk label(s) '$DiskLabelsNotFoundAsString' specified in the parameter 'SelectedDiskLabel' does not exist on the source"
    }

    if ($null -ne $UserParameter["StoragePolicyName"] `
            -and $UserParameter["StoragePolicyName"].Count -gt 0) {
        $ValidPolicyNames, $InvalidPolicyNames = Confirm-RMStoragePolicy -TargetInventory $TargetInventory `
            -StoragePolicyName $UserParameter["StoragePolicyName"]
        $ValidPolicyCnt = $ValidPolicyNames.Count
        if ($InvalidPolicyNames.Count -gt 0) {
            $InvalidPolicyNamesAsString = $InvalidPolicyNames -join ", "
            $Errors += "The storage policy name(s) '$InvalidPolicyNamesAsString' specified by the parameter 'StoragePolicyName' do not exist on the target vCenter"
        }
    
        $StoragePolicyCnt  = $UserParameter["StoragePolicyName"].Count
        if ($StoragePolicyCnt -ne $SelectedDiskLabel.Count) {
            $Errors += "The parameter 'StoragePolicyName' has '$StoragePolicyCnt' storage policy names but '$SelectedDiskLabelCnt' disks have been selected for migration. When the parameter 'StoragePolicyName' is used it should either provide a storage policy name or `$null for each selected disk. Use `$null when you do not want to assign a storage policy to a disk but a disk provisioning type should be given for that disk."
        } else {
            if ($ValidPolicyNames.Count -eq $SelectedDiskLabel.Count -and `
                    ($null -ne $UserParameter["DiskProvisioningType"] -and `
                    $UserParameter["DiskProvisioningType"].Count -gt 0)) {
                $Warnings += "The parameter 'StoragePolicyName' has '$ValidPolicyCnt' valid storage policy names and there are '$SelectedDiskLabelCnt' selected disks for migration. The given disk provisioning types will be ignored."
            } elseif ($ValidPolicyNames.Count -gt $SelectedDiskLabel.Count) {
                $Errors += "The parameter 'StoragePolicyName' has '$ValidPolicyCnt' valid storage policy names and the number of disks selected for migration are '$SelectedDiskLabelCnt'. The number of storage policy names should be equal to the number of disks selected for migration."
            } elseif ($ValidPolicyNames.Count -lt $SelectedDiskLabel.Count `
                        -and $null -eq $UserParameter["DiskProvisioningType"]) {
                $Errors += "The parameter 'StoragePolicyName' has '$ValidPolicyCnt' valid storage policy names but '$SelectedDiskLabelCnt' disk(s) have been selected for migration. Please provide additional storage policy names or disk provisioning types for the remaining disks."
            } elseif ($null -ne $UserParameter["DiskProvisioningType"] `
                        -and $UserParameter["DiskProvisioningType"].Count -gt 0) {
                $ValidDiskProvisioningTypes = Get-RMValidDiskProvisioningType -DiskProvisioningType $UserParameter["DiskProvisioningType"]
                $SumOfPolicyAndDiskProvCnt = $ValidPolicyCnt + $ValidDiskProvisioningTypes.Count
                if ($SumOfPolicyAndDiskProvCnt -lt $SelectedDiskLabelCnt) {
                    $Errors += "The sum of given storage policies and disk provisioning types is less than the number of disks selected for migration. When using both storage policies and disk provisioning types, the sum of storage policy names and disk provisioning types should be equal to the number of selected disks."
                } elseif ($SumOfPolicyAndDiskProvCnt -gt $SelectedDiskLabelCnt) {
                    $Errors += "The sum of given storage policies and disk provisioning types is greater than the number of disks selected for migration. When using both storage policies and disk provisioning types, the sum of storage policy names and disk provisioning types should be equal to the number of selected disks."
                }
            }
        }
    } elseif ($null -ne $UserParameter["DiskProvisioningType"] -and `
                $UserParameter["DiskProvisioningType"].Count -gt 0) {
        $ValidDiskProvisioningTypes = Get-RMValidDiskProvisioningType -DiskProvisioningType $UserParameter["DiskProvisioningType"]                    
        $DiskProvCnt = $ValidDiskProvisioningTypes.Count
        if ($DiskProvCnt -ne $SelectedDiskLabelCnt) {
            $Errors += "The parameter 'DiskProvisioningType' contains '$DiskProvCnt' disk provisioning types whereas the number of disks selected for migration are '$SelectedDiskLabelCnt'. The number of disk provisioning types should be equal to the number of disks selected for migration."
        }
    } else {
        $Errors += "Please provide the storage policy name(s) by using the parameter 'StoragePolicyName' or disk provisioning type(s) by using parameter 'DiskProvisioningType'. You can also use a combination of these two parameters such that any disk that you want to assign a storage policy should have a `$null value for the corresponding disk in the parameter 'DiskProvisioningType' and vice versa."
    }

    return $Errors, $Warnings, $IsRequiredParametersPresentAndValid
}

function Confirm-RMNetworkIsolationConfig {
    param(
        [RMVSphereBaseNetworkConfiguration] $NetworkConfig
    )
    $Errors = @()
    $IsRequiredParametersPresentAndValid = $true
    $NetworkConfigType = ""
    if ($NetworkConfig -is [RMVSphereMigrationNetworkConfiguration]) {
        $NetworkConfigType = "MigrationNetworkConfig's"
        if ([string]::IsNullOrWhiteSpace($NetworkConfig.MigrationNetworkName)) {
            $Errors += "$NetworkConfigType MigrationNetworkName is required"
            $IsRequiredParametersPresentAndValid = $false
        }
    }

    if ($NetworkConfig -is [RMVSphereIsolatedNetworkConfiguration]) {
        $NetworkConfigType = "IsolatedNetworkConfig's"
        if ([string]::IsNullOrWhiteSpace($NetworkConfig.IsolatedNetworkName)) {
            $Errors += "$NetworkConfigType IsolatedNetworkName is required"
            $IsRequiredParametersPresentAndValid = $false
        }
    }

    if (!(Confirm-RMIPType -NetworkConfig $NetworkConfig)) {
        $GivenIPType = $NetworkConfig.IPType
        $Errors += "$NetworkConfigType IPType should be 'static' or 'DHCP', given IPType is '$GivenIPType'"    
        $IsRequiredParametersPresentAndValid = $false
        return $Errors, $IsRequiredParametersPresentAndValid
    }

    if ($NetworkConfig.IPType -ieq "dhcp") {
        return $Errors, $IsRequiredParametersPresentAndValid
    }

    $ReturnedData = Confirm-RMStaticNetworkConfig -NetworkConfig $NetworkConfig `
        -NetworkConfigPrefix $NetworkConfigType -NICName $null
    $Errors += $ReturnedData[0]
    # Update the flag only if everything is good so far, to avoid overwriting bad state
    if ($IsRequiredParametersPresentAndValid) {
        $IsRequiredParametersPresentAndValid = $ReturnedData[1]
    }

    return $Errors, $IsRequiredParametersPresentAndValid
}

function Confirm-RMDestinationNetworkConfig {
    param(
        [hashtable] $UserParameter
    )
    $Errors = @()
    if (!$UserParameter.ContainsKey("DestinationNICConfig") -or `
            $null -eq $UserParameter["DestinationNICConfig"]) {
        $Errors += "DestinationNICConfig is required"
        return $Errors, $false
    }

    $IsRequiredParametersPresentAndValid = $true
    $SkipGatewayAndDNSValidation = $false    
    $NetworkConfig = $UserParameter["DestinationNICConfig"]
    for ($Index = 0; $Index -lt $NetworkConfig.Count; $Index++) {
        $NICWithIndex = "NIC" + $Index
        if ([string]::IsNullOrWhiteSpace($NetworkConfig[$Index].DestinationNetworkName)) {
            $Errors += "DestinationNICConfig's DestinationNetworkName is required for $NICWithIndex"
            $IsRequiredParametersPresentAndValid = $false
        }

        if (!(Confirm-RMIPType -NetworkConfig $NetworkConfig[$Index])) {
            $GivenIPType = $NetworkConfig[$Index].IPType
            $Errors += "DestinationNICConfig's IPType should be 'static' or 'DHCP', given IPType for " + $NICWithIndex + " is '$GivenIPType'"
            $IsRequiredParametersPresentAndValid = $false
            continue
        }

        if ($NetworkConfig[$Index].IPType -ieq "dhcp") {
            continue
        }
        if ($SkipGatewayAndDNSValidation -and !([string]::IsNullOrWhiteSpace($NetworkConfig[$Index].DefaultGateway) `
                -or [string]::IsNullOrWhiteSpace($NetworkConfig[$Index].PrimaryDNS))) {
            $Errors += "DestinationNICConfig's DefaultGateway, PrimaryDNS and optionally Secondary DNS should be provided for one NIC only"
        }

        $ReturnedData = Confirm-RMStaticNetworkConfig -NetworkConfig $NetworkConfig[$Index] `
            -NetworkConfigPrefix "DestinationNICConfig's" -SkipGatewayAndDNS $SkipGatewayAndDNSValidation -NICName $NICWithIndex
        $Errors += $ReturnedData[0]
        # Update the flag only if everything is good so far, to avoid overwriting bad state
        if ($IsRequiredParametersPresentAndValid) {
            $IsRequiredParametersPresentAndValid = $ReturnedData[1]
        }
    
        # Validate gateway and DNS for one NIC
        if ($ReturnedData[2]) {
            $SkipGatewayAndDNSValidation = $true
        }
    }

    return $Errors, $IsRequiredParametersPresentAndValid
}

function Confirm-RMStaticNetworkConfig {
    param(
        [RMVSphereBaseNetworkConfiguration] $NetworkConfig,
        [string] $NetworkConfigPrefix,
        [string] $NICName,
        [bool] $SkipGatewayAndDNS
    )

    $Errors = @()
    $IsRequiredParametersPresentAndValid = $true
    if ([string]::IsNullOrWhiteSpace($NetworkConfig.IPAddress)) {
        if ([string]::IsNullOrEmpty($NICName)) {
            $Errors += $NetworkConfigPrefix + " IPAddress is required, when IPType is 'static'"
        } else {
            $Errors += $NetworkConfigPrefix + " IPAddress is required for " + $NICName + ", when IPType is 'static'"
        }
        $IsRequiredParametersPresentAndValid = $false
    } else {
        if (!(Confirm-RMIPAddress -IPAddress $NetworkConfig.IPAddress)) {
            if ([string]::IsNullOrEmpty($NICName)) {
                $Errors += $NetworkConfigPrefix + " IPAddress is not valid"
            } else {
                $Errors += $NetworkConfigPrefix + " IPAddress is not valid for $NICName"
            }
            $IsRequiredParametersPresentAndValid = $false
        }
    }

    if ([string]::IsNullOrWhiteSpace($NetworkConfig.Netmask)) {
        if ([string]::IsNullOrEmpty($NICName)) {
            $Errors += $NetworkConfigPrefix + " Netmask is required, when IPType is 'static'"
        } else {
            $Errors += $NetworkConfigPrefix + " Netmask is required for " + $NICName + ", when IPType is 'static'"
        }
        $IsRequiredParametersPresentAndValid = $false
    } else {
        if (!(Confirm-RMIPAddress -IPAddress $NetworkConfig.Netmask)) {
            if ([string]::IsNullOrEmpty($NICName)) {
                $Errors += $NetworkConfigPrefix + " Netmask is not valid"
            } else {
                $Errors += $NetworkConfigPrefix + " Netmask is not valid for $NICName"
            }
            $IsRequiredParametersPresentAndValid = $false
        }
    }

    if ($SkipGatewayAndDNS) {
        return $Errors, $IsRequiredParametersPresentAndValid, $false
    }

    $IsValidGatewayAndDNS = $true
    if ([string]::IsNullOrWhiteSpace($NetworkConfig.DefaultGateway)) {
        if ([string]::IsNullOrEmpty($NICName)) {
            $Errors += $NetworkConfigPrefix + " DefaultGateway is required, when IPType is 'static'"
        } else {
            $Errors += $NetworkConfigPrefix + " DefaultGateway is required for " + $NICName + ", when IPType is 'static'"
        }
        $IsRequiredParametersPresentAndValid = $false
        $IsValidGatewayAndDNS = $false
    } else {
        if (!(Confirm-RMIPAddress -IPAddress $NetworkConfig.DefaultGateway)) {
            if ([string]::IsNullOrEmpty($NICName)) {
                $Errors += $NetworkConfigPrefix + " DefaultGateway is not valid"
            } else {
                $Errors += $NetworkConfigPrefix + " DefaultGateway is not valid for $NICName"
            }
            $IsRequiredParametersPresentAndValid = $false
            $IsValidGatewayAndDNS = $false
        }
    }

    if ([string]::IsNullOrWhiteSpace($NetworkConfig.PrimaryDNS)) {
        if ([string]::IsNullOrEmpty($NICName)) {
            $Errors += $NetworkConfigPrefix + " PrimaryDNS is required, when IPType is 'static'"
        } else {
            $Errors += $NetworkConfigPrefix + " PrimaryDNS is required for " + $NICName + ", when IPType is 'static'"
        }
        $IsRequiredParametersPresentAndValid = $false
        $IsValidGatewayAndDNS = $false
    } else {
        if (!(Confirm-RMIPAddress -IPAddress $NetworkConfig.PrimaryDNS)) {
            if ([string]::IsNullOrEmpty($NICName)) {            
                $Errors += $NetworkConfigPrefix + " PrimaryDNS is not valid"
            } else {
                $Errors += $NetworkConfigPrefix + " PrimaryDNS is not valid for $NICName"
            }
            $IsRequiredParametersPresentAndValid = $false
            $IsValidGatewayAndDNS = $false
        }
    }

    if (![string]::IsNullOrWhiteSpace($NetworkConfig.SecondaryDNS)) {
        if (!(Confirm-RMIPAddress -IPAddress $NetworkConfig.PrimaryDNS)) {
            if ([string]::IsNullOrEmpty($NICName)) {
                $Errors += $NetworkConfigPrefix + " SecondaryDNS is not valid"
            } else {
                $Errors += $NetworkConfigPrefix + " SecondaryDNS is not valid for $NICName"                
            }
        }
    }

    return $Errors, $IsRequiredParametersPresentAndValid, $IsValidGatewayAndDNS
}

function Confirm-RMIPType {
    param (
        [RMVSphereBaseNetworkConfiguration] $NetworkConfig
    )
    if ($NetworkConfig.IPType -ine "dhcp" -and $NetworkConfig.IPType -ine "static") {
        return $false
    }
    return $true
}

function Confirm-RMvSphereParameter {
    param(
        [hashtable] $UserParameter
    )
    $Errors = @()
    $IsRequiredParametersPresentAndValid = $true
    if (!$UserParameter.ContainsKey("DatacenterName") -or [string]::IsNullOrWhiteSpace($UserParameter["DatacenterName"])) {
        $Errors += "DatacenterName is required."
        $IsRequiredParametersPresentAndValid = $false
    }

    if (!$UserParameter.ContainsKey("ClusterName") -or [string]::IsNullOrWhiteSpace($UserParameter["ClusterName"])) {
        $Errors += "ClusterName is required"
        $IsRequiredParametersPresentAndValid = $false
    }

    return $Errors, $IsRequiredParametersPresentAndValid
}

function Get-RMCoresPerSocket {
    param (
        [int] $TargetVMVCPUCount
    )

    $CoresPerSocketList = @()
    for ([int]$i = 1; $i -lt $TargetVMVCPUCount+1; $i++) {
        if ($TargetVMVCPUCount % $i -eq 0) {
            $CoresPerSocketList += $i
        }
    }
    return $CoresPerSocketList
}

Export-ModuleMember -Function Start-RMInteractiveVSphereOSBasedMigration, Start-RMNonInteractiveVsphereOsBasedMigration, `
Start-RMInteractiveVSphereVMBasedMigration, Start-RMNonInteractiveVsphereVMBasedMigration