Migration/vSphere/vSphere.psm1

using module '../../Common/Result'
using module './RMVSphereNetworkConfiguration'
using module './Tag'
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[]] $MountPoints,
        [string[]] $ResizeMountPoints,
        [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[]] $DatastoreNames,
        [string[]] $DiskProvisioningTypes,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgentFromSource,
        [bool] $RemoveRMSAgentFromTarget,
        [string[]] $MigrationInstructions,
        [string] $UpgradeOSVersion,
        [bool] $IgnoreValidationErrors,
        [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.RuntimeException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    $ShouldExit = $false
    try {
        if (![string]::IsNullOrEmpty($SourceIP)) {
            $Source = Get-RMSourceByIP -IPAddress $SourceIP
        }
    } catch [System.Management.Automation.RuntimeException] {
        $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 $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
        if (!$IgnoreValidationErrors -and $OverrideExistingMigrationError -and !$OverrideExistingMigration) {
            $Errors += "Please set 'OverrideExistingMigration' to true and try again."
        }
    } catch [System.Management.Automation.RuntimeException] {
        $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 ($MountPoints.Count -gt 0) {
        $SelectedMountPoints = Get-RMSelectedMount -MountPoints $SourceMountPoints -DifferenceList $MountPoints -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 $ResizeMountPoints -and $ResizeMountPoints -gt 0) {
        $MountsResize, $MountsResizeErrors = Get-RMResizeMountsPoint -ResizeMountPoints $ResizeMountPoints -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)

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

    $UserInput.Add("DatastoreLocations", $DatastoreNames)
    $UserInput.Add("DisksProvisioningType", $DiskProvisioningTypes)

    $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 (($IgnoreValidationErrors -and $OverrideExistingMigration) -or (!$IgnoreValidationErrors -and $OverrideExistingMigration) -or $OverrideExistingMigrationWarning) {
            $MigrationInstructions += "override_source_migration=true"
        }
        $MigrationInstructionsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $MigrationInstructions -ParameterName "MigrationInstructions"
    } 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)

    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", $IgnoreValidationErrors)

    $Response = New-RMVSphereMigrationProfile @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-RMInteractiveVSphereOSBasedMigration {
    param( )

    $Entitlement = Get-RMEntitlement
    $CloudAccount = Read-RMCloudAccount -AccountType "rivermeadow_standalone"
    $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

    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

    $FolderName = Read-RMString -UserMessage "Enter VM folder name" -DefaultValue "None" `
        -ParameterName "VM folder name" -IsRequired $false

    $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 = @{}
    $Source = Read-RMVMBasedSource -UserMessage "Enter the VM name of the source machine to be migrated" `
        -ParameterName "Source VM name" -IsRequired $true

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

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    $CloudAccount = Get-RMVMBasedCloudAccountBySource -Source $Source -AccountType "rivermeadow_standalone"
    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
    $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" -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] $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,
        [hashtable] $StoragePolicyNamePerDiskLabel,
        [string[]] $DiskProvisioningType,
        [bool] $UpgradeTools,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $FinalizeMigration,
        [string[]] $MigrationInstructions,
        [bool] $IgnoreValidationErrors,
        [bool] $OverrideExistingMigration
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid = `
        Confirm-RMvSphereVMBasedFullMigrationParameter $PSBoundParameters

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

    $TargetInventory = $null
    $CloudAccount = $null
    if ($null -ne $Source) {
        $CloudAccount = Get-RMVMBasedCloudAccountBySource -Source $Source -AccountType "rivermeadow_standalone"
        if ($null -eq $CloudAccount) {
            $Errors += "The migration appliance associated with the given source is not ready for use, cannot start the migration"
            $IsSourceAndTargetCloudPresent = $false
        } else {
            $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount
        }
    }

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    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 $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
        if (!$IgnoreValidationErrors -and $OverrideExistingMigrationError -and !$OverrideExistingMigration) {
            $Errors += "Please set 'OverrideExistingMigration' to true and try again."
        }
    } catch [System.Management.Automation.RuntimeException] {
        $Errors += $PSItem.Exception.Message
    }

    # removing the "Datastore Default" to facilitate further processing
    $StoragePolicyNamePerDiskLabel, $PSBoundParameters = Remove-RMDefaultStoragePolicy `
        -StoragePolicyNamePerDiskLabel $StoragePolicyNamePerDiskLabel -UserParameter $PSBoundParameters

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

    $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)

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

    $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 (($IgnoreValidationErrors -and $OverrideExistingMigration) -or `
                (!$IgnoreValidationErrors -and $OverrideExistingMigration) -or `
                $OverrideExistingMigrationWarning) {
            $MigrationInstructions += "override_source_migration=true"
        }
        $MigrationInstructionsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $MigrationInstructions -ParameterName "MigrationInstructions"
    } 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", $IgnoreValidationErrors)
    $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 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-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"
        }
    }

    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
        }
        $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 {
        $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("DatastoreNames") -or [string]::IsNullOrEmpty($UserParameter["DatastoreNames"])) {
            $Errors += "DatastoreNames is required and since the source has dynamic disk(s), please provide only one datastore name."
            $IsRequiredParametersPresentAndValid = $false
        } elseif($UserParameter["DatastoreNames"].Count -gt 1) {
            $Errors += "Source has dynamic disks, please provide only one datastore name."
        }
    } else {
        if (!$UserParameter.ContainsKey("DatastoreNames") -or [string]::IsNullOrEmpty($UserParameter["DatastoreNames"])) {
            $Errors += "DatastoreNames is required."
            $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
    if (!$UserParameter.ContainsKey("DatastoreName") -or [string]::IsNullOrWhiteSpace($UserParameter["DatastoreName"])) {
        $Errors += "DatastoreName is required."
        $IsRequiredParametersPresentAndValid = $false
    } elseif ($UserParameter["DatastoreName"].Count -gt $Source.attributes.storage.vm_disks.count) {
        $Errors += "The parameter 'DatastoreName' contains more datastores than the disks on the source"
    }

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

    if ($null -ne $UserParameter["StoragePolicyNamePerDiskLabel"] `
            -and $UserParameter["StoragePolicyNamePerDiskLabel"].Keys.Count -gt 0) {
        $DiskLabelsNotFound = @()
        $NotSelectedDiskLabel = @()
        $SourceDiskLabels = Get-RMVMBasedSourceDiskLabel -Source $Source
        foreach ($DiskLabel in $UserParameter["StoragePolicyNamePerDiskLabel"].Keys) {
            if ($SourceDiskLabels -cnotcontains $DiskLabel) {
                $DiskLabelsNotFound += $DiskLabel
            } elseif ($UserParameter.ContainsKey("SelectedDiskLabel") `
                    -and $UserParameter["SelectedDiskLabel"] -cnotcontains $DiskLabel) {
                $NotSelectedDiskLabel += $DiskLabel
            }
        }

        $StorageLabelsNotFound = @()
        $StoragePolicyNames = Get-RMStoragePolicyNameByTargetInventory -TargetInventory $TargetInventory
        foreach ($StoragePolicyName in $UserParameter["StoragePolicyNamePerDiskLabel"].Values) {
            if ($StoragePolicyNames -notcontains $StoragePolicyName) {
                $StorageLabelsNotFound += $StoragePolicyName
            }
        }

        if ($UserParameter["StoragePolicyNamePerDiskLabel"].Keys.Count -gt $SelectedDiskLabel.Count) {
            $StoragePolicyMappingCnt  = $UserParameter["StoragePolicyNamePerDiskLabel"].Keys.Count
            $SelectedDiskLabelCnt = $SelectedDiskLabel.Count
            $Errors += "The parameter 'StoragePolicyNamePerDiskLabel' contains $StoragePolicyMappingCnt entries whereas the 'SelectedDiskLabel' count is $SelectedDiskLabelCnt. The number of entries in 'StoragePolicyNamePerDiskLabel' cannot be greater than the 'SelectedDiskLabel' count."
        }

        if ($NotSelectedDiskLabel.Count -gt 0) {
            $NotSelectedDiskLabelAsString = $NotSelectedDiskLabel -join ", "
            $Errors += "The disk label(s) '$NotSelectedDiskLabelAsString' specified in the parameter 'StoragePolicyNamePerDiskLabel' has not specified in the parameter 'SelectedDiskLabel'"
        }

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

        if ($StorageLabelsNotFound.Count -gt 0) {
            $StorageLabelsNotFoundAsString = $StorageLabelsNotFound -join ", "
            $Errors += "The storage policy name(s) '$StorageLabelsNotFoundAsString' specified in the parameter 'StoragePolicyNamePerDiskLabel' does not exist on the target vCenter"
        }
    }

    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