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 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] $DisableTargetDNSRegistration,
        [string] $ClusterName,
        [bool] $CreateResourcePoolName,
        [string] $ResourcePoolName,
        [bool] $CreateFolderName,
        [string] $FolderName,
        [string] $DatastoreClusterName,
        [string[]] $DatastoreName,
        [string[]] $DiskProvisioningType,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgentFromSource,
        [bool] $RemoveRMSAgentFromTarget,
        [string[]] $MigrationInstruction,
        [string] $UpgradeOSVersion,
        [string] $UpgradeOSConversion,
        [string] $MigrationExtension,
        [string] $MigrationExtensionOSM,
        [hashtable[]] $UpgradeSQLServer,
        [bool] $IgnoreValidationError,
        [bool] $OverrideExistingMigration,
        [string] $ConvertFileSystem,
        [string[]]  $VMTags,
        [bool] $ConvertBootModeUEFI,
        [bool] $EnableSecureBoot,
        [bool] $EanbleVTPM,
        [bool] $EnableVBS,
        [bool] $EnableNetAppFileMigration,
        [bool] $Sysprep,
        [string] $NetBIOSName,
        [bool] $KMSActivation,
        [bool] $KMSItopia
    )

    $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 {
                $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

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


    $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.RuntimeException] {
        $Errors += $PSItem.Exception.Message
        $IsRequiredParametersPresentAndValid = $false
    }

    $MigrationExtensionResponse = Get-RMMigrationExtension -OrganizationId $CloudAccount.organization_id
    $MigrationExtensionArray, $MigrationExtensionOSMArray = Get-RMMigrationExtensionFromStep -MigrationExtensionArray $MigrationExtensionResponse.content

    if ($null -ne $Source) {
        $SourceSQLServerMapping = Get-RMSQLMMappingBySource -Source $Source
        $SourceErrors, $RequiredParameterState = Confirm-RMvSphereFullMigrationParameterWithSource `
            -UserParameter $PSBoundParameters -Source $Source -SourceSQLServerMapping $SourceSQLServerMapping `
            -MigrationExtension $MigrationExtensionArray -MigrationExtensionOSM $MigrationExtensionOSMArray `
            -TargetInventory $TargetInventory 
        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", $DisableTargetDNSRegistration)
    } 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)

    if (![string]::IsNullOrWhiteSpace($UpgradeOSVersion)) {
        Add-RMOSUpgrade -UserParameter $PSBoundParameters -UpdatedParameter $UserInput -Source $Source
    } else {
        Add-RMOSConversionUpgrade -UserParameter $PSBoundParameters -UpdatedParameter $UserInput -Source $Source
    }

    Add-RMMigrationExtension -UpdatedParameter $UserInput -MigrationExtension $MigrationExtension `
        -MigrationExtensionOSM $MigrationExtensionOSM -MigrationExtensionResponse $MigrationExtensionResponse

    $VMTagList = Get-RMTags -TargetInventory $TargetInventory
    $Tags =  Get-RMVMTag -TagsValue $VMTags -Tags $VMTagList
    $UserInput.Add("VMTags", $Tags)

    $UserInput.Add("ConvertBootModeUEFI", $ConvertBootModeUEFI)
    $UserInput.Add("EnableSecureBoot", $EnableSecureBoot)
    $UserInput.Add("EanbleVTPM", $EanbleVTPM)
    $UserInput.Add("EnableVirtualizationBasedSecurity", $EnableVBS)
    $UserInput.Add("EnableNetAppFileMigration", $EnableNetAppFileMigration)
    $UserInput.Add("GeneralizeSystem", $Sysprep)
    
    $SQLServerUpgrade = Get-RMUpgradeSQLServer -UpgradeSQLServer $UpgradeSQLServer -Source $Source `
        -SourceSQLServerMapping $SourceSQLServerMapping 
    $UserInput.Add("SQLServerUpgrade", $SQLServerUpgrade)

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

    $ProjectSettingsResponse = Invoke-RMProjectSettings -OrganizationId $CloudAccount.organization_id
    $UserInput.Add("MTUSize", $ProjectSettingsResponse.rivermeadow_standalone.mtu_size)
    Add-RMConvertFileSystem -Source $Source -UpdatedUserInput $UserInput -ConvertFileSystem $ConvertFileSystem

    $KMSKey = $null
    if ($KMSActivation) {
        $KMSKey = ""
        if ($null -ne $ProjectSettingsResponse.rivermeadow_standalone.kms_fqdn) {
            $KMSKey = $ProjectSettingsResponse.rivermeadow_standalone.kms_fqdn
        }
    } elseif ($KMSItopia) {
        $KMSKey = $ProjectSettingsResponse.rivermeadow_standalone.itopia_kms_fqdn
    }
    $UserInput.Add("KMSKey", $KMSKey)

    $UserInput["DisksProvisioningType"] = Update-DiskProvisioningTypeForME -DiskProvisioningTypes $UserInput["DisksProvisioningType"]

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

    Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
    if ($Errors.Count -gt 0 -or $ShouldExit) {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    }
   
    $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( )

    $UserInput = @{}
    $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


    #Placement Settings
    $DatacenterNames = Get-RMDatacenterName -TargetInventory $TargetInventory
    $DatacenterName = Read-RMString -UserMessage "Enter datacenter name" -Options $DatacenterNames `
        -ParameterName "Datacenter name" -IsRequired $true

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

    $Tags = Get-RMTags -TargetInventory $TargetInventory
    $TagsValue = Read-RMToken -UserMessage "Enter one or more network tags separated by commas" `
            -Options $Tags.keys -DefaultValue "None" -Separator ";" `
            -ParameterName "Tags" -IsRequired $false
    $VMTags = Get-RMVMTag -TagsValue $TagsValue -Tags $Tags


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

    $ResourcePoolName = Read-RMResourcePoolName -DatacenterObject $DatacenterObject -ClusterName $ClusterName

    $FolderName = Read-RMFolderName -TargetInventory $TargetInventory -DatacenterName $DatacenterName
   
    $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

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

    #Optimization Settings
    $NetBIOSName = Read-RMString -UserMessage "Enter NetBIOS name" -DefaultValue $Source.hostname `
        -ParameterName "NetBIOS Name" -IsRequired $false
    
    $ProjectSettingsResponse = Invoke-RMProjectSettings -OrganizationId $CloudAccount.organization_id

    $KMSActivation = Read-RMBoolean -UserMessage "Enable KMS Activation" -DefaultValue "false"
    $KMSKey = $null
    if ($KMSActivation) {
        $KMSKey = ""
        if ($null -ne $ProjectSettingsResponse.rivermeadow_standalone.kms_fqdn) {
            $KMSKey = $ProjectSettingsResponse.rivermeadow_standalone.kms_fqdn
        }
    }

    $Sysprep = $false
    if ("windows" -ieq $Source.os_type) {
        $Sysprep = Read-RMBoolean -UserMessage "Enable sysprep" -DefaultValue "false"
    }

    $ReadValue = Read-RMBoolean -UserMessage "Change Target CPUs?" -DefaultValue "false"
    $CoresPerSocket = 1
    $TargetVMVCPUCount = $Source.attributes.cpu.processors.Count
    if ($ReadValue) {
        $TargetVMVCPUCount = Read-RMInt -UserMessage "Enter virtual CPU count" -DefaultValue $TargetVMVCPUCount `
            -ParameterName "Virtual CPUs count" -IsRequired $false

        $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

    $MountsResize = @{}
    $ResizeMountPointsFlag = Read-RMBoolean -UserMessage "Resize mount points" -DefaultValue "false"
    if ($ResizeMountPointsFlag) {
        $MountsResize = Get-RMInteractiveMountsResize -SelectedMountPoints $SelectedMountPoints -Source $Source
    }

    $MigrationExtensionResponse = Get-RMMigrationExtension -OrganizationId $CloudAccount.organization_id
    $MigrationExtensionArray, $MigrationExtensionOSMArray = Get-RMMigrationExtensionFromStep -MigrationExtensionArray $MigrationExtensionResponse.content

    Read-RMMigrationExtension -CloudAccount $CloudAccount -MigrationExtension  $MigrationExtensionArray `
        -MigrationExtensionObject $MigrationExtensionResponse.content -UpdatedUserInput $UserInput

    if ("windows" -ieq $Source.os_type -and !$KMSActivation) {
        $KMSItopia = Read-RMBoolean -UserMessage "Enable itopia KMS" -DefaultValue "false"
        if ($KMSItopia) {
            $KMSKey = $ProjectSettingsResponse.rivermeadow_standalone.itopia_kms_fqdn
        }
    }

    #Modernization Settings

    Read-RMOSUpgradeOption -Source $Source -UpdatedUserInput $UserInput
    
    if ($null -ne $UserInput['UpgradeOSVersion']) {
        Read-RMMigrationExtensionOSM -MigrationExtensionOSM $MigrationExtensionOSMArray -MigrationExtensionObject $MigrationExtensionResponse.content `
            -UpdatedUserInput $UserInput
    }

    $SQLServerUpgrade =  Read-RMSQLServerUpgradeOption -Source $Source


    #Security Settings
    $EnableSecureBoot = $false
    $MigrationState = $Source.attributes.os.source_migration_state | ConvertFrom-Json
    if ("uefi" -ieq $MigrationState.boot_mode_conversion_option) {
        $ConvertBootModeUEFI = Read-RMBoolean -UserMessage "Convert boot mode /UEFI" -DefaultValue "false"
        if ($ConvertBootModeUEFI) {
            $EnableSecureBoot = Read-RMBoolean -UserMessage "Enable secure boot" -DefaultValue "false"
        }
    } else {
        $EnableSecureBoot = Read-RMBoolean -UserMessage "Enable secure boot" -DefaultValue "false"
    }
    
    $EanbleVTPM = Read-RMBoolean -UserMessage "Enable vTPM" -DefaultValue "false"

    $EnableVirtualizationBasedSecurity = $false
    $ShowingEnableVirtualizationBasedSecurity = Get-RMEnableVirtualizationBasedSecurity -Source $Source
    if ($ShowingEnableVirtualizationBasedSecurity) {
        $EnableVirtualizationBasedSecurity = Read-RMBoolean -UserMessage "Enable virtualization based security" -DefaultValue "false"
    }
    
    #Advanced Settings

    $TransferType = "file-based"
    if (!$ResizeMountPointsFlag) {
        $TransferType = (Get-RMTransferMethod -Source $Source -SelectedMountPoints $SelectedMountPoints -IsInteractive $true)[0]
    }
   
    $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"

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

    $ShutdownSource = Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"
    $ShutdownTarget = Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"
    
    $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

    $EnableNetAppFileMigration = $false
    if ("data_store" -ne $Source.collection_type) {
        $EnableNetAppFileMigration =  Read-RMBoolean -UserMessage "Enable NetApp file migration" -DefaultValue $EnableNetAppFileMigration
    }

   
    $MTUSize = $ProjectSettingsResponse.rivermeadow_standalone.mtu_size

    $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
        SQLServerUpgrade = $SQLServerUpgrade
        IgnoreValidationErrors = $IgnoreValidationErrors
        VMTags = $VMTags
        MTUSize = $MTUSize
        ConvertBootModeUEFI = $ConvertBootModeUEFI
        EnableSecureBoot = $EnableSecureBoot
        EanbleVTPM = $EanbleVTPM
        EnableVirtualizationBasedSecurity = $EnableVirtualizationBasedSecurity
        EnableNetAppFileMigration = $EnableNetAppFileMigration
        GeneralizeSystem = $Sysprep
        NetBIOSName = $NetBIOSName
        KMSKey = $KMSKey
    }

    $HashArguments += $UserInput    

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

    #Placement Settings
    $DatacenterNames = Get-RMDatacenterName -TargetInventory $TargetInventory
    $DatacenterName = Read-RMString -UserMessage "Enter datacenter name" -Options $DatacenterNames `
        -ParameterName "Datacenter name" -IsRequired $true
    $UserInput.Add("DatacenterName", $DatacenterName)

    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)

    $Tags = Get-RMTags -TargetInventory $TargetInventory
    $TagsValue = Read-RMToken -UserMessage "Enter one or more network tags separated by commas" `
            -Options $Tags.keys -DefaultValue "None" -Separator ";" `
            -ParameterName "Tags" -IsRequired $false
    $VMTags = Get-RMVMTag -TagsValue $TagsValue -Tags $Tags
    $UserInput.Add("VMTag", $VMTags)

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

    $ResourcePoolName = Read-RMResourcePoolName -DatacenterObject $DatacenterObject -ClusterName $ClusterName
    $UserInput.Add("ResourcePoolName", $ResourcePoolName)

    $FolderName = Read-RMFolderName -TargetInventory $TargetInventory -DatacenterName $DatacenterName
    $UserInput.Add("FolderName", $FolderName)

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

    $VMConfig = $SourceAttributeResult.attributes.vm_config
    if ("toolsNotInstalled" -ne  $VMConfig.toolsStatus -and "guestToolsUnmanaged" -ne $VMConfig.toolsVersionStatus) {
        $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)   

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


    #Optimization Settings

    $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)
   
    #Advanced Settings

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

    $MTUSizeResponse = Invoke-RMProjectSettings -OrganizationId $CloudAccount.organization_id
    $MTUSize = $MTUSizeResponse.rivermeadow_standalone.mtu_size
    $UserInput.Add("MTUSize", $MTUSize)

    $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,
        [bool] $CreateResourcePoolName,
        [string] $ResourcePoolName,
        [bool] $CreateFolderName,
        [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
    )
    

    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
    }

    $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid = `
        Confirm-RMvSphereVMBasedFullMigrationParameter $PSBoundParameters -TargetInventory $TargetInventory

    $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.RuntimeException] {
        $Errors += $PSItem.Exception.Message
        $IsRequiredParametersPresentAndValid = $false
    }

    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 `
            -SourceAttributeResult $Source
        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.host
    }
    $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("DiskProvisioningType", $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)

    Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
    if ($Errors.Count -gt 0 -or $ShouldExit) {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    }

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

    $MTUSizeResponse = Invoke-RMProjectSettings -OrganizationId $CloudAccount.organization_id
    $MTUSize = $MTUSizeResponse.rivermeadow_standalone.mtu_size
    $UserInput.Add("MTUSize", $MTUSize)

    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationError)
    $UserInput["DiskProvisioningType"] = Update-DiskProvisioningTypeForME -DiskProvisioningTypes $UserInput["DiskProvisioningType"]
    $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 Update-RMInteractiveVsphereSource {
    param ()

    $CloudAccountOptions = Get-RMCloudAccountsForCurrentProject

    $HeartBeatingVMBasedAppliance, $NonHeartBeatingVMBasedAppliance = Get-RMVMBasedAppliance -CloudAccountObject $CloudAccountOptions

    $CloudAccountName = Read-RMString -UserMessage "Enter target cloud" -Options $HeartBeatingVMBasedAppliance.keys `
        -ParameterName "Target Cloud" -IsRequired $true

    $CloudAccount = $HeartBeatingVMBasedAppliance[$CloudAccountName]

    $All = Read-RMBoolean -UserMessage "Would you like to refresh all sources?" -DefaultValue "false"

    $Filters = @{}
    if (!$All) {
        $SourceInventory = Get-RMSourceInventory -ApplianceId $CloudAccount.appliance.id -IsCollect $true `
            -CollectCloud $true -CollectVm $false -AddSource $false

        $Datacenter = Read-RMString -UserMessage "Enter datacenter name" -Options $SourceInventory.properties.datacenters.name `
            -ParameterName "Datacenter" -IsRequired $true

        $ClusterObject, $DatacenterObject = Get-ClusterAndDatacenterObject -SourceInventory $SourceInventory -DatacenterName $Datacenter 

        $Cluster = Read-RMString -UserMessage "Enter cluster name" -Options $ClusterObject.name -ParameterName "Cluster" `
            -DefaultValue "None" -IsRequired $false

        $ResourcePool = ""
        if ("" -ne $Cluster ) {
            $ResourcePoolOptions = Get-RMVMResourcePoolname -DatacenterObject $DatacenterObject -ClusterName $Cluster
            if ($ResourcePoolOptions.Count -gt 0) {
                $ResourcePool = Read-RMString -UserMessage "Enter resource pool" -ParameterName "Resource Pool"  -Options  $ResourcePoolOptions `
                    -DefaultValue "None" -IsRequired $false
            }
        }

        $FolderNameOptions = Get-RMFolderNameByDatacenter -DatacenterObject $DatacenterObject
        $FolderName = Read-RMVMFolderString -UserMessage "Enter folder name" -VMFolders $FolderNameOptions -DefaultValue "None"

        $Filters = @{
            "datacenter_name" = $Datacenter
            "cluster_name" = $Cluster
            "resource_pool" = $ResourcePool
            "folder_name" = $FolderName
        }
    }

    $SourceInventoryResponse = Get-RMSourceInventory -ApplianceId $CloudAccount.appliance.id -IsCollect $false `
        -CollectCloud $false -CollectVm $true -AddSource $true -CollectPerfMetric $true -Filters $Filters
    $UserMessage = "Attempting to refresh vSphere sources." -f $SourceInventoryResponse.id
    Write-Output $UserMessage | Out-Host
    return [RMReturn]::new([RMReturn]::SUCCESS, @{"Result" = $UserMessage})
}

function Update-RMNonInteractiveVsphereSource {
    param (
        [string] $TargetCloud,
        [bool] $All,
        [string] $Datacenter,
        [string] $Cluster,
        [string] $ResourcePool,
        [string] $FolderName
    )

    $CloudAccountObject = Get-RMCloudAccountsForCurrentProject

    $HeartBeatingVMBasedAppliance, $NonHeartBeatingVMBasedAppliance = Get-RMVMBasedAppliance -CloudAccountObject $CloudAccountObject

    $Errors += Confirm-RMVsphereUpdateSourceParameter -UserParameter $PSBoundParameters -HeartBeatingVMBasedAppliance $HeartBeatingVMBasedAppliance `
        -NonHeartBeatingVMBasedAppliance $NonHeartBeatingVMBasedAppliance
    
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    if ($Errors.Count -gt 0) {
        Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        return $RMMigrationReturn
    }

    $Filters = @{}

    if (!$All) {
        $CloudAccount = $CloudAccountObject[$TargetCloud]
        $SourceInventory = Get-RMSourceInventory -ApplianceId $CloudAccount.appliance.id -IsCollect $true `
            -CollectCloud $true -CollectVm $false -AddSource $false
        
        $Errors = Confirm-RMVsphereUpdateSourceParameterWithSourceInventory -UserParameter $PSBoundParameters -SourceInventory $SourceInventory
        Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
        if ($Errors.Count -gt 0) {
            Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
            return $RMMigrationReturn
        }

        $Filters = @{
            "datacenter_name" = $Datacenter
            "cluster_name" = $Cluster
            "resource_pool" = $ResourcePool
            "folder_name" = $FolderName
        }
    } 

    $SourceInventoryResponse = Get-RMSourceInventory -ApplianceId $CloudAccount.appliance.id -IsCollect $false `
        -CollectCloud $false -CollectVm $true -AddSource $true -CollectPerfMetric $true -Filters $Filters

    $UserMessage = "Attempting to refresh vSphere sources." -f $SourceInventoryResponse.id
    Write-Output $UserMessage | Out-Host
    return [RMReturn]::new([RMReturn]::SUCCESS, @{"Result" = $UserMessage})
}


function Confirm-RMVsphereUpdateSourceParameter {
    param(
        [hashtable] $UserParameter,
        [System.Object] $HeartBeatingVMBasedAppliance,
        [System.Object] $NonHeartBeatingVMBasedAppliance
    )

    $Errors += @()
    
    if (!$UserParameter.ContainsKey("TargetCloud") -or [string]::IsNullOrWhiteSpace($UserParameter["TargetCloud"])) {
        $Errors += "TargetCloud is reqiured."
    } else {
        $CloudAccount = $HeartBeatingVMBasedAppliance[$UserParameter["TargetCloud"]]
        if ($null -ieq  $CloudAccount) {
            $TargetCloudName = $UserParameter["TargetCloud"]
            if ($null -ne $NonHeartBeatingVMBasedAppliance[$UserParameter["TargetCloud"]]) {
                $Errors += "The Target Cloud '$TargetCloudName' is not ready for use."
            } else {
                $Errors += "TargetCloud '$TargetCloudName' does not exist."
            }
        }
    }

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

    return $Errors
}

function Confirm-RMVsphereUpdateSourceParameterWithSourceInventory {
    param (
        [hashtable] $UserParameter,
        [System.Object] $SourceInventory
    )

    $Errors = @()

    $DatacenterNames = $SourceInventory.properties.datacenters.name
    $Datacenter = $UserParameter["Datacenter"]

    if ($DatacenterNames -notcontains $Datacenter) {
        $Errors += "Datacenter '$Datacenter' does not exist."
    } else {
        $ClusterObject, $DatacenterObject = Get-ClusterAndDatacenterObject -SourceInventory $SourceInventory -DatacenterName $Datacenter

        if ($UserParameter.ContainsKey("Cluster") -and ![string]::IsNullOrWhiteSpace($UserParameter["Cluster"])) {
            $Cluster = $UserParameter["Cluster"]
            if ($ClusterObject.name -notcontains $Cluster) {
                $Errors += "Cluster '$Cluster' does not exist."
            } else {
                if ($UserParameter.ContainsKey("ResourcePool") -and ![string]::IsNullOrWhiteSpace($UserParameter["ResourcePool"])) {
                    $ResourcePoolList = Get-RMVMResourcePoolname -DatacenterObject $DatacenterObject -ClusterName $Cluster
                    if ($ResourcePoolList.Count -gt 0) {
                        $ResourcePool = $UserParameter["ResourcePool"]
                        if ($ResourcePoolList -notcontains $ResourcePool) {
                            $Errors += "ResourcePool '$ResourcePool' does not exist."
                        }
                    } else {
                        $Errors += "ResourcePool '$ResourcePool' does not exist."
                    }
                }
            }
        }
    
        if ($UserParameter.ContainsKey("FolderName") -and ![string]::IsNullOrWhiteSpace($UserParameter["FolderName"])) {
            $FolderName = $UserParameter["FolderName"]
            $FolderNameList = Get-RMFolderNameByDatacenter -DatacenterObject $DatacenterObject
            if ($FolderNameList -notcontains $FolderName) {
                $Errors += "FolderName '$FolderName' does not exist."
            }
        }
    }
    return $Errors
}

function Get-RMSourceInventory {
    param(
        [string] $ApplianceId,
        [bool] $IsCollect,
        [System.Object] $Filters,
        [bool] $CollectCloud,
        [bool] $CollectVm,
        [bool] $AddSource,
        [bool] $CollectPerfMetric
    )

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

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

    $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("vm_folder")
    $CollectList.Add("resource_pool")

    $Body = @{
        "organization_id" = $OrganizationId
        "appliance_id" = $ApplianceId
        "objects_to_collect" = if ($IsCollect)  { $CollectList } else { $null }
        "filters" = $Filters
        "collect_cloud" = $CollectCloud
        "collect_vms" = $CollectVm
        "add_sources" = $AddSource
        "collect_perf_metrics" = $CollectPerfMetric
    } | ConvertTo-Json

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

    if ($IsCollect) {
        Write-Output "Starting source inventory..." | Out-Host
        $SourceInventory = Invoke-RMRestMethod -Param $Params
        return Watch-RMSourceInventoryStatus -SourceInventoryId $SourceInventory.id
    } else {
        return Invoke-RMRestMethod -Param $Params
    }
}

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-RMEnableVirtualizationBasedSecurity {
    param (
        [System.Object] $Source
    )
    
    $Showing = $true
    if ($null -ne $Source.attributes.os.source_migration_state) {
        $MigrationState = $Source.attributes.os.source_migration_state | ConvertFrom-Json
    }
    $OSVersions = $MigrationState.upgrade_options
    $OSVersions += $MigrationState.conversion_options
    $IsLaterVersion = @("2003", "2008", "XP", "Vista", "7", "8", "8.1", "2012")
    foreach ($OSVersion in $OSVersions) {
        foreach ($Version in $IsLaterVersion) {
            if ($OSVersion -like "*$Version*") {
                $Showing = $false
                break
            }
        }
    }
    return $Showing 
}

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

    $TagCategories = $TargetInventory.attributes.vsphere.tag_categories
    $Tags = @{}
    foreach ($TagCategory in $TagCategories) {
        foreach ($Tag in $TagCategory.tags) {
            $TagCategoryName = $TagCategory.name
            $Name = $Tag.name + "[$TagCategoryName]"
            $Value = @{
                "tag_name" = $Tag.name 
                "tag_id" = $Tag.identifier
                "category_name" = $TagCategory.name
            } 
            $Tags.Add($Name, $Value)
        }
    }
    return $Tags
}

function Get-RMVMTag {
    param(
        [array] $TagsValue,
        [hashtable] $Tags
    )

    $TagArray= @()
    if ($TagsValue.Count -gt 0) {
        foreach ($Tag in $TagsValue) {
            $TagArray += $Tags[$Tag]
        }
    }
    return $TagArray
}

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-RMClusterAndDatacenter {
    param (
        [System.Object] $TargetInventory,
        [string] $datacenterName
    )

    $Clusters = @()
    $DatacenterObject = $null
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            $DatacenterObject = $datacenter
            foreach($cluster in $datacenter.clusters) {
                $Clusters += $cluster.name
            }
            break
        }
    }
    return $Clusters, $DatacenterObject
}
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,
        [System.Object] $TargetInventory
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent = Confirm-RMCommonParameter -UserParameter $UserParameter
    $vSphereErrors, $IsRequiredParametersPresentAndValid = Confirm-RMvSphereParameter -UserParameter $UserParameter `
        -TargetInventory $TargetInventory
    $Errors += $vSphereErrors
    return $Errors, $Warnings, $IsSourceAndTargetCloudPresent, $IsRequiredParametersPresentAndValid
}

function Confirm-RMvSphereVMBasedFullMigrationParameter {
    param(
        [hashtable] $UserParameter,
        [System.Object] $TargetInventory
    )

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

function Confirm-RMvSphereFullMigrationParameterWithSource {
    param (
        [hashtable] $UserParameter,
        [System.Object] $Source,
        [System.Object] $SourceSQLServerMapping,
        [System.Array] $MigrationExtension,
        [System.Array] $MigrationExtensionOSM,
        [System.Object] $TargetInventory
    )
    $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
        }
    }

    if ($null -ne $Source.attributes.os.source_migration_state) {
        $MigrationState = $Source.attributes.os.source_migration_state | ConvertFrom-Json
        if ($UserParameter.ContainsKey("ConvertBootModeUEFI") -and $UserParameter["ConvertBootModeUEFI"]) {
            if ("uefi" -ne $MigrationState.boot_mode_conversion_option) {
                $Errors += "'ConvertBootModeUEFI' can not be 'true'"
            } 
        } elseif($UserParameter.ContainsKey("EnableSecureBoot") -and $UserParameter["EnableSecureBoot"] `
            -and "uefi" -ieq $MigrationState.boot_mode_conversion_option) {
            $Errors += "'ConvertBootModeUEFI' is required when 'EnableSecureBoot' is 'true'."
        }
    }

    if ($UserParameter.ContainsKey("EanbleVTPM") -and $UserParameter["EanbleVTPM"] `
        -and ((!$UserParameter.ContainsKey("ConvertBootModeUEFI") -or !$UserParameter["ConvertBootModeUEFI"]) `
        -or (!$UserParameter.ContainsKey("EnableSecureBoot") -or !$UserParameter["EnableSecureBoot"]))) {
        $Errors += "'ConvertBootModeUEFI' and 'EnableSecureBoot' are both required when 'EanbleVTPM' is 'true'."
    }

    if ("data_store" -ieq $Source.collection_type `
        -and $UserParameter.ContainsKey("EnableNetAppFileMigration") -and $UserParameter["EnableNetAppFileMigration"]) {
        $Errors += "'EnableNetAppFileMigration' is not supported for the given source."
    }
   
    $CheckEnableVBS = Get-RMEnableVirtualizationBasedSecurity -Source $Source
    if (!$CheckEnableVBS -and $UserParameter.ContainsKey("EnableVBS") -and $UserParameter["EnableVBS"]) {
        $Errors += "'EnableVBS' can not be true."
    }

    if ($UserParameter.ContainsKey("VMTags") -and $UserParameter["VMTags"].Count -gt 0) {
        $Tags = Get-RMTags -TargetInventory $TargetInventory
        $VMTags = $UserParameter["VMTags"]
        foreach($VMTag in  $VMTags) {
            if ($Tags.Keys -notcontains $VMTag) {
                $Errors += "The vm tag(s) '$VMTag' does not exist."
            }
        }
    }
    
    if ($UserParameter.ContainsKey("DisableTargetDNSRegistration") -and $UserParameter["DisableTargetDNSRegistration"] `
            -and "windows" -ne $Source.os_type) {
        $Errors += "'DisableTargetDNSRegistration' can be 'true' only for Windows source."
    }

    $Errors += Confirm-RMCommonParameterWithSource -UserParameter $UserParameter -Source $Source `
        -SourceSQLServerMapping $SourceSQLServerMapping -MigrationExtension $MigrationExtension `
        -CloudType 'rivermeadow_standalone' -MigrationExtensionOSM $MigrationExtensionOSM
    return $Errors, $IsRequiredParametersPresentAndValid
}

function Confirm-RMvSphereVMBasedFullMigrationParameterWithSource {
    param(
        [hashtable] $UserParameter,
        [System.Object] $Source,
        [System.Object] $TargetInventory,
        [System.Object] $SourceAttributeResult
    )
    $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
        }
    }

    if ($UserParameter.ContainsKey("VMTags") -and $UserParameter["VMTags"].Count -gt 0) {
        $Tags = Get-RMTags -TargetInventory $TargetInventory
        $VMTags = $UserParameter["VMTags"]
        foreach($VMTag in  $VMTags) {
            if ($Tags.Keys -notcontains $VMTag) {
                $Errors += "The vm tag(s) '$VMTag' does not exist."
            }
        }
    }   

    if ($UserParameter.ContainsKey("UpgradeTool") -and $UserParameter["UpgradeTool"]) {
        $VMConfig = $SourceAttributeResult.attributes.vm_config
        if ("toolsNotInstalled" -ieq  $VMConfig.toolsStatus -or "guestToolsUnmanaged" -ieq $VMConfig.toolsVersionStatus) {
            $Errors += "'UpgradeTool' is not supportes for the given source."
        }
    }
    
    $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 'none' for each selected disk. Use 'none' 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 -and `
                    $UserParameter["DiskProvisioningType"] -notcontains 'none')) {
                $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 'none' 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,
        [System.Object] $TargetInventory
    )
    $Errors = @()
    $IsRequiredParametersPresentAndValid = $true
    if (!$UserParameter.ContainsKey("DatacenterName") -or [string]::IsNullOrWhiteSpace($UserParameter["DatacenterName"])) {
        $Errors += "DatacenterName is required."
        $IsRequiredParametersPresentAndValid = $false
    } else {
        $DatacenterNames = Get-RMDatacenterName -TargetInventory $TargetInventory
        if ($DatacenterNames -notcontains $UserParameter["DatacenterName"]) {
            $DatacenterName = $UserParameter["DatacenterName"]
            $Errors += "DatacenterName '$DatacenterName' does not exist."
            $IsRequiredParametersPresentAndValid = $false
        } else {
            $NetworkConfig = $UserParameter["DestinationNICConfig"]
            if ($null -ne $NetworkConfig -and $NetworkConfig.Count -gt 0) {
                $DestinationNetworkName = $NetworkConfig[0].DestinationNetworkName
                $NetworkNames = Get-RMNetworkName -TargetInventory $TargetInventory -datacenterName $DatacenterName
                if (![string]::IsNullOrWhiteSpace($DestinationNetworkName) -and $NetworkNames -notcontains $DestinationNetworkName) {
                    $Errors += "DestinationNetworkName '$DestinationNetworkName' does not exist."
                    $IsRequiredParametersPresentAndValid = $false
                }
            }
            
            if ($UserParameter.ContainsKey("ClusterName") -and ![string]::IsNullOrWhiteSpace($UserParameter["ClusterName"])) {
                $Clusters, $DatacenterObject = Get-RMClusterAndDatacenter -TargetInventory $TargetInventory -datacenterName $UserParameter["DatacenterName"]
                if (![string]::IsNullOrWhiteSpace($UserParameter["ClusterName"])) {
                    $ClusterName = $UserParameter["ClusterName"]
                    if ($Clusters -notcontains $UserParameter["ClusterName"]) {
                        $Errors += "ClusterName '$ClusterName' does not exist."
                        $IsRequiredParametersPresentAndValid = $false
                    } else {
                        $Datastores = Get-RMDatastore -TargetInventory $TargetInventory -DatacenterName $UserParameter["DatacenterName"] `
                            -ClusterName $ClusterName
                        $DatastoreNames = $UserParameter["DatastoreName"]
                        if ($null -ne $DatastoreNames -and $DatacenterNames.Count -gt 0) {
                            foreach($Datastore in $DatastoreNames) {
                                if ($Datastores -notcontains $Datastore) {
                                    $Errors += "DatastoreName '$Datastore' does not exist."
                                    $IsRequiredParametersPresentAndValid = $false
                                }
                            }
                        }
                    }
                }
            }

            if ($UserParameter.ContainsKey("CreateFolderName") -and $UserParameter["CreateFolderName"]) {
                if (!$UserParameter.ContainsKey("FolderName") -or [string]::IsNullOrWhiteSpace($UserParameter["FolderName"])) {
                    $Errors += "'FolderName' is required, when 'CreateFolderName' is true."
                }
            } else {
                if ($UserParameter.ContainsKey("FolderName") -and ![string]::IsNullOrWhiteSpace($UserParameter["FolderName"])) {
                    $VMFolders = Get-RMVMFolderName -TargetInventory $TargetInventory -DatacenterName $UserParameter["DatacenterName"]
                    if ($VMFolders -notcontains $UserParameter["FolderName"]) {
                        $FolderName = $UserParameter["FolderName"]
                        $Errors += "FolderName '$FolderName' does not exist."
                    }
                }
            }
        }
    }

    if (!$UserParameter.ContainsKey("ClusterName") -or [string]::IsNullOrWhiteSpace($UserParameter["ClusterName"])) {
        $Errors += "ClusterName is required"
        $IsRequiredParametersPresentAndValid = $false
    } else {
        if ($UserParameter.ContainsKey("CreateResourcePoolName") -and $UserParameter["CreateResourcePoolName"]) {
            if (!$UserParameter.ContainsKey("ResourcePoolName") -or [string]::IsNullOrWhiteSpace($UserParameter["ResourcePoolName"])) {
                $Errors += "'ResourcePoolName' is required, when 'CreateResourcePoolName' is true."
            }
        } else {
            if ($UserParameter.ContainsKey("ResourcePoolName") -and ![string]::IsNullOrWhiteSpace($UserParameter["ResourcePoolName"])) {
                $Clusters, $DatacenterObject = Get-RMClusterAndDatacenter -TargetInventory $TargetInventory -datacenterName $DatacenterName
                $ResourcePools = Get-RMVMResourcePoolname -DatacenterObject $DatacenterObject -ClusterName $UserParameter["ClusterName"]
                $ResourcePoolName = $UserParameter["ResourcePoolName"]
                if ($ResourcePools -notcontains  $ResourcePoolName) {
                    $Errors += "ResourcePoolName '$ResourcePoolName' does not exist."
                }
            }
        }
    }

    if (!$UserParameter.ContainsKey("DatastoreName") -or [string]::IsNullOrWhiteSpace($UserParameter["DatastoreName"])) {
        $Errors += "DatastoreName 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, Update-RMInteractiveVsphereSource, Update-RMNonInteractiveVsphereSource