Migration/vSphere/vSphere.psm1

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

function Start-RMNonInteractiveVsphereOsBasedMigration {
    param (
        [string] $TargetCloud,
        [string] $SourceIP,
        [string] $TargetVMName,
        [string[]] $MountPoints,
        [string[]] $ResizeMountPoints,
        [string] $TransferMethod,
        [int] $TargetVMVCPUCount,
        [string] $CoresPerSocket,
        [int] $TargetVMMemorySize,
        [string] $DatacenterName,
        [bool] $EnableDestinationNetworkIsolation,
        [string] $DestinationNetworkName,
        [string] $IPType,
        [string] $IPAddress,
        [string] $Netmask,
        [string] $DefaultGateway,
        [string] $PrimaryDNS,
        [string] $SecondaryDNS,
        [string] $IsolatedNetworkName,
        [string] $IsolatedIPType,
        [string] $IsolatedIPAddress,
        [string] $IsolatedNetmask,
        [string] $IsolatedDefaultGateway,
        [string] $IsolatedPrimaryDNS,
        [string] $IsolatedSecondaryDNS,
        [bool] $DisableAutomaticDNSRegistrationOnTheTarget,
        [string] $ClusterName,
        [string] $ResourcePoolName,
        [string] $FolderName,
        [string] $DatastoreClusterName,
        [string[]] $DatastoreNames,
        [string[]] $DiskProvisioningTypes,
        [int] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgentFromSource,
        [bool] $RemoveRMSAgentFromTarget,
        [hashtable] $MigrationInstructions,
        [string] $UpgradeOSVersion,
        [bool] $IgnoreValidationErrors
    )

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

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

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

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

    try {
        $Source, $ShouldExit = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
            -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    } catch [System.Management.Automation.RuntimeException] {
        $Errors += $PSItem.Exception.Message
    }

    if ($null -ne $Source) {
        $Errors += Confirm-RMvSphereFullMigrationParameterWithSource -UserParameter $PSBoundParameters -Source $Source
    }

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

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

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

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

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

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

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

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

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

    $UserInput.Add("DatacenterName", $DatacenterName)
    $UserInput.Add("EnableDestinationNetworkIsolation", $EnableDestinationNetworkIsolation)
    if ($Source.os_type -eq "windows" -and "" -ne $EnableDestinationNetworkIsolation -and $EnableDestinationNetworkIsolation) {

        if ([string]::IsNullOrEmpty($IPType)) {
            $IPType = "dhcp"
        } else {
            $IPType = $IPType.ToLower()
        }
        $UserInput.Add("IPType", $IPType)

        if ($IPType -ieq "static") {
            $UserInput.Add("IPAddress", $IPAddress)
            $UserInput.Add("Netmask", $Netmask)
            $UserInput.Add("DefaultGateway", $DefaultGateway)
            $UserInput.Add("PrimaryDNS", $PrimaryDNS)
            $UserInput.Add("SecondaryDNS", $SecondaryDNS)
        }

       $DestinationNetworkName, $DvSwitchName = Get-RMDestinationNetworkAndDvSwitchName -DestinationNetworkName $DestinationNetworkName -IsInteractive $false
       $UserInput.Add("DestinationNetworkName", $DestinationNetworkName)
       $UserInput.Add("DvSwitchName", $DvSwitchName)

       [string[]] $networkNames = $IsolatedNetworkName -split "/"
       if ($networkNames.Count -eq 2) {
           $IsolatedNetworkName = $networkNames[1]
           $IsolatedDvSwitchName = $networkNames[0]
       }
       $UserInput.Add("IsolatedNetworkName", $IsolatedNetworkName)
       $UserInput.Add("IsolatedDvSwitchName", $IsolatedDvSwitchName)

        if ([string]::IsNullOrEmpty($IsolatedIPType)) {
            $IsolatedIPType = "dhcp"
        } else {
            $IsolatedIPType = $IsolatedIPType.ToLower()
        }
        $UserInput.Add("IsolatedIPType", $IsolatedIPType)

        if ($IsolatedIPType -ieq "static") {
            $UserInput.Add("IsolatedIPAddress", $IsolatedIPAddress)
            $UserInput.Add("IsolatedNetmask", $IsolatedNetmask)
            $UserInput.Add("IsolatedDefaultGateway", $IsolatedDefaultGateway)
            $UserInput.Add("IsolatedPrimaryDNS", $IsolatedPrimaryDNS)
            $UserInput.Add("IsolatedSecondaryDNS", $IsolatedSecondaryDNS)
        }
    } else {
        $DestinationNetworkName, $DvSwitchName = Get-RMDestinationNetworkAndDvSwitchName -DestinationNetworkName $DestinationNetworkName -IsInteractive $false
        $UserInput.Add("DestinationNetworkName", $DestinationNetworkName)
        $UserInput.Add("DvSwitchName", $DvSwitchName)

        if ([string]::IsNullOrEmpty($IPType)) {
            $IPType = "dhcp"
        } else {
            $IPType = $IPType.ToLower()
        }
        $UserInput.Add("IPType", $IPType)
        if ($IPType -ieq "static") {
            $UserInput.Add("IPAddress", $IPAddress)
            $UserInput.Add("Netmask", $Netmask)
            $UserInput.Add("DefaultGateway", $DefaultGateway)
            $UserInput.Add("PrimaryDNS", $PrimaryDNS)
            $UserInput.Add("SecondaryDNS", $SecondaryDNS)
        }
    }

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

    $UserInput.Add("ResourcePoolName", $ResourcePoolName)
    $UserInput.Add("FolderName", $FolderName)
    $UserInput.Add("DatastoreClusterName", $DatastoreClusterName)

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

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

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

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

    $UserInput.Add("ToolsPackage", $ToolsPackage)
    if ("" -eq $MigrationInstructions) {
        $MigrationInstructions = @{}
    }
    $UserInput.Add("MigrationInstructions", $MigrationInstructions)

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

    Add-RMOSUpgrade -UserParameter $PSBoundParameters -UpdatedParameter $UserInput -Source $Source

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

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

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

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

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

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

    $MigrationResponse = Invoke-RMRestMethod -Params $Params
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse -RMMigrationReturn $RMMigrationReturn
}

function Start-RMInteractiveVSphereOSBasedMigration {
    param( )

    $Entitlement = Get-RMEntitlement
    $CloudAccount = Read-RMCloudAccount -AccountType "rivermeadow_standalone"
    $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount
   
    $Source = Read-RMSource
    $IgnoreValidationErrors = Read-RMBoolean -UserMessage "Ignore validation errors" -DefaultValue "false"

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    $Source, $ShouldExit = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }

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

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

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

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

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

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

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


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

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

    $EnableDestinationNetworkIsolation = $false

    if ($Source.os_type -eq "windows") {
        $EnableDestinationNetworkIsolation = Read-RMBoolean -UserMessage "Enable destination network isolation" -DefaultValue "false"
        if ($EnableDestinationNetworkIsolation) {
            $IPType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-RMMigrationNetworkIPConfig
            $DestinationNetworkName, $DvSwitchName = Get-RMDestinationNetworkAndDvSwitchName -NetworkNames $NetworkNames `
                -IsInteractive $true

            $IsolatedNetworkName = Read-RMString -UserMessage "Enter isolated network name" -Options $NetworkNames `
                -ParameterName "Isolated network name" -IsRequired $true

            [string[]] $networkNamesArray = $IsolatedNetworkName -split "/"
            if ($networkNamesArray.Count -eq 2) {
                $IsolatedNetworkName = $networkNamesArray[1]
                $IsolatedDvSwitchName = $networkNamesArray[0]
            }
            $IsolatedIPType = "dhcp"
            $IPType = (Read-RMString -UserMessage "Enter isolated network IP type" -Options "static", "DHCP" -DefaultValue "DHCP" `
                -ParameterName "Isolated network IP type" -IsRequired $false).ToLower()

            if ($IPType -ieq "static") {
                $IsolatedIPType = $IPType
                $IsolatedIPAddress = Read-RMIPAddress -UserMessage "Enter isolated network IP address" `
                    -ParameterName "Isolated network IP address" -IsRequired $true
                $IsolatedNetmask = Read-RMIPAddress -UserMessage "Enter isolated network netmask" `
                    -ParameterName "Isolated network netmask" -IsRequired $true
                $IsolatedDefaultGateway = Read-RMIPAddress -UserMessage "Enter isolated network default gateway" `
                    -ParameterName "Isolated network default gateway" -IsRequired $true
                $IsolatedPrimaryDNS = Read-RMIPAddress -UserMessage "Enter isolated network primary DNS" `
                    -ParameterName "Isolated network primary DNS" -IsRequired $true
                $IsolatedSecondaryDNS = Read-RMIPAddress -UserMessage "Enter isolated network secondary DNS" `
                    -ParameterName "Isolated network secondary DNS" -DefaultValue "None" -IsRequired $false
            }
        } else {
            $IPType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-RMMigrationNetworkIPConfig
            $DestinationNetworkName, $DvSwitchName = Get-RMDestinationNetworkAndDvSwitchName -NetworkNames $NetworkNames -IsInteractive $true
        }
    } else {
        $IPType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-RMMigrationNetworkIPConfig
        $DestinationNetworkName, $DvSwitchName = Get-RMDestinationNetworkAndDvSwitchName -NetworkNames $NetworkNames -IsInteractive $true
    }

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

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

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

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

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

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

    $SelectedProvisioningTypes = Get-DiskProvisioningTypeBySource -Source $Source -IsInteractive $true

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

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

    $ShutdownSource = Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"
    $ShutdownTarget = Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"
    $RemoveRMSAgentFromSource = Read-RMBoolean -UserMessage "Remove RMS agent post migration from source" -DefaultValue "false"
    $RemoveRMSAgentFromTarget = Read-RMBoolean -UserMessage "Remove RMS agent post migration from target" -DefaultValue "false"

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

    $HashArguments = @{
        CloudAccount = $CloudAccount
        Entitlement = $Entitlement
        Source = $Source
        TargetVMName = $TargetVMName
        SelectedMounts = $SelectedMountPoints
        ResizeMountPoints = $MountsResize
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveRMSAgentFromSource = $RemoveRMSAgentFromSource
        RemoveRMSAgentFromTarget = $RemoveRMSAgentFromTarget
        DatacenterName = $DatacenterName
        EnableDestinationNetworkIsolation = $EnableDestinationNetworkIsolation
        DestinationNetworkName = $DestinationNetworkName
        IPType = $IPType
        IPAddress = $IPAddress
        Netmask = $Netmask
        DefaultGateway = $DefaultGateway
        PrimaryDNS = $PrimaryDNS
        SecondaryDNS = $SecondaryDNS
        IsolatedNetworkName = $IsolatedNetworkName
        IsolatedDvSwitchName = $IsolatedDvSwitchName
        IsolatedIPType = $IsolatedIPType
        IsolatedIPAddress = $IsolatedIPAddress
        IsolatedNetmask = $IsolatedNetmask
        IsolatedDefaultGateway = $IsolatedDefaultGateway
        IsolatedPrimaryDNS = $IsolatedPrimaryDNS
        IsolatedSecondaryDNS = $IsolatedSecondaryDNS
        DvSwitchName = $DvSwitchName
        DisableAutomaticDNSRegistrationOnTheTarget = $DisableAutomaticDNSRegistrationOnTheTarget
        ClusterName = $ClusterName
        DatastoreClusterName =  $DatastoreClusterName
        DatastoreLocations = $SelectedDatastores
        FolderName =  $FolderName
        DisksProvisioningType = $SelectedProvisioningTypes
        TransferType = $TransferType
        TargetVMVCPUCount = $TargetVMVCPUCount
        CoresPerSocket = $CoresPerSocket
        TargetVMMemorySize = $TargetVMMemorySize
        ResourcePoolName = $ResourcePoolName
        HardwareVersion = $HardwareVersion
        ToolsPackage = $ToolsPackage
        MigrationInstructions = $MigrationInstructions
        UpgradeOSVersion = $UpgradeOSVersion
        IgnoreValidationErrors = $IgnoreValidationErrors
        InPlaceUpgrade = $InPlaceUpgrade
    }

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

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

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

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

    $MigrationResponse = Invoke-RMRestMethod -Params $Params
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse -RMMigrationReturn $RMMigrationReturn
}

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

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

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

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

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

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

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

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

    return Watch-RMTargetInventoryStatus -TargetInventoryId $TargetInventory.id
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    $DefaultVersion = $CloudAccount.vsphere_version

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

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

    return $HardwareVersions, $DefaultHardwareVersion, $ToolsPackage
}

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

    $ToolsPackage = ""
    $HardwareVersion = ""

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

function Get-RMVsphereVersionHashtable {
    param ()

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

    return $VersionInfo
}

function Get-RMMigrationNetworkIPConfig {
    $IPType = "dhcp"
    $IPType = (Read-RMString -UserMessage "Enter IP type" -Options "static", "DHCP" -DefaultValue "DHCP" `
        -ParameterName "IP Type" -IsRequired $false).ToLower()
    if ($IPType -ieq "dhcp") {
        return $IPType, "", "", "", "", ""
    }

    $IPAddress = Read-RMIPAddress -UserMessage "Enter IP address" -ParameterName "IP address" -IsRequired $true
    $Netmask = Read-RMIPAddress -UserMessage "Enter netmask" -ParameterName "Netmask" -IsRequired $true
    $DefaultGateway = Read-RMIPAddress -UserMessage "Enter default gateway" -ParameterName "Default gateway" -IsRequired $true
    $PrimaryDNS = Read-RMIPAddress -UserMessage "Enter primary DNS" -ParameterName "Primary DNS" -IsRequired $true
    $SecondaryDNS = Read-RMIPAddress -UserMessage "Enter secondary DNS" -ParameterName "Secondary DNS" -DefaultValue "None" -IsRequired $false
    return $IPType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS
}

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

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

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

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

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

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

    return $ResultProvisioningTypes
}

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

    return $Result
}

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

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

    return $DestinationNetworkName, $DvSwitchName
}

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

    $Errors, $Warnings, $IsSourceAndTargetCloudPresent = Confirm-RMCommonParameter -UserParameter $UserParameter

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

    if (!$UserParameter.ContainsKey("DestinationNetworkName") -or [string]::IsNullOrEmpty($UserParameter["DestinationNetworkName"])) {
        $Errors += "DestinationNetworkName is required."
    }
    
    if ($UserParameter.ContainsKey("IPType") -and $UserParameter["IPType"] -ieq "static") {
        if (!$UserParameter.ContainsKey("IPAddress") -or [string]::IsNullOrEmpty($UserParameter["IPAddress"])) {
            $Errors += "IpAddress is required."
        } else {
            $IsValidIPAddress = Confirm-RMIPAddress -IPAddress $UserParameter.IPAddress
            if (!$IsValidIPAddress) {
                $Errors += "Invalid IP address."
            }
        }
       
        if (!$UserParameter.ContainsKey("Netmask") -or [string]::IsNullOrEmpty($UserParameter["Netmask"])) {
            $Errors += "Netmask is required."
        } else {
            $IsValidNetmask = Confirm-RMIPAddress -IPAddress $UserParameter.Netmask
            if (!$IsValidNetmask) {
                $Errors += "Invalid Netmask."
            }
        }
        
        if (!$UserParameter.ContainsKey("DefaultGateway") -or [string]::IsNullOrEmpty($UserParameter["DefaultGateway"])) {
            $Errors += "DefaultGateway is required."
        } else {
            $IsValidDefaultGateway = Confirm-RMIPAddress -IPAddress $UserParameter.DefaultGateway
            if(!$IsValidDefaultGateway) {
                $Errors += "Invalid Default Gateway."
            }
        }
        
        if (!$UserParameter.ContainsKey("PrimaryDNS") -or [string]::IsNullOrEmpty($UserParameter["PrimaryDNS"])) {
            $Errors += "PrimaryDNS is required."
        } else {
            $IsValidPrimaryDNS = Confirm-RMIPAddress -IPAddress $UserParameter.PrimaryDNS
            if (!$IsValidPrimaryDNS) {
                $Errors += "Invalid Primary DNS."
            }
        }
        
        if ($UserParameter.ContainsKey("SecondaryDNS") -and  ![string]::IsNullOrEmpty($UserParameter["SecondaryDNS"])) {
            $IsValidSecondaryDNS = Confirm-RMIPAddress -IPAddress $UserParameter.SecondaryDNS
            if (!$IsValidSecondaryDNS) {
                $Errors += "Invalid Secondary DNS."
            }
        }
    }

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

    return $Errors, $Warnings, $IsSourceAndTargetCloudPresent
}

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

    $Errors = @()
    if ($Source.os_type -ieq "windows" -and $UserParameter.ContainsKey("EnableDestinationNetworkIsolation") `
            -and $UserParameter["EnableDestinationNetworkIsolation"] -eq $true) {
        if (!$UserParameter.ContainsKey("IsolatedNetworkName") -or [string]::IsNullOrEmpty($UserParameter["IsolatedNetworkName"])) {
            $Errors += "IsolatedNetworkName is required."
        }
        if ($UserParameter.ContainsKey("IsolatedIPType") -and $UserParameter["IsolatedIPType"] -ieq "static") {
            if (!$UserParameter.ContainsKey("IsolatedIPAddress") -or [string]::IsNullOrEmpty($UserParameter["IsolatedIPAddress"])) {
                $Errors += "IsolatedIpAddress is required."
            } else {
                $IsValidIsolatedIpAddress = Confirm-RMIPAddress -IPAddress $UserParameter.IsolatedIPAddress
                if (!$IsValidIsolatedIpAddress) {
                    $Errors += "Invalid Isolated IP address."
                }
            }
            
            if (!$UserParameter.ContainsKey("IsolatedNetmask") -or [string]::IsNullOrEmpty($UserParameter["IsolatedNetmask"])) {
                $Errors += "IsolatedNetmask is required."
            } else {
                $IsValidIsolatedNetmask = Confirm-RMIPAddress -IPAddress $UserParameter.IsolatedNetmask
                if (!$IsValidIsolatedNetmask) {
                    $Errors += "Invalid Isolated Netmask."
                }
            }
            
            if (!$UserParameter.ContainsKey("IsolatedDefaultGateway") -or [string]::IsNullOrEmpty($UserParameter["IsolatedDefaultGateway"])) {
                $Errors += "IsolatedDefaultGateway is required."
            } else {
                $IsValidIsolatedDefaultGatewayk = Confirm-RMIPAddress -IPAddress $UserParameter.IsolatedDefaultGateway
                if (!$IsValidIsolatedDefaultGatewayk) {
                    $Errors += "Invalid Isolated Default Gateway."
                }
            }
            
            if (!$UserParameter.ContainsKey("IsolatedPrimaryDNS") -or [string]::IsNullOrEmpty($UserParameter["IsolatedPrimaryDNS"])) {
                $Errors += "IsolatedPrimaryDNS is required."
            } else {
                $IsValidIsolatedPrimaryDNS = Confirm-RMIPAddress -IPAddress $UserParameter.IsolatedPrimaryDNS
                if (!$IsValidIsolatedPrimaryDNS) {
                    $Errors += "Invalid Isolated Primary DNS."
                }
            }
            
            if ($UserParameter.ContainsKey("IsolatedSecondaryDNS") -and ![string]::IsNullOrEmpty($UserParameter["IsolatedSecondaryDNS"])) {
                $IsValidIsolatedSecondaryDNS = Confirm-RMIPAddress -IPAddress $UserParameter.IsolatedSecondaryDNS
                if (!$IsValidIsolatedSecondaryDNS) {
                    $Errors += "Invalid Isolated Secondary DNS."
                }
            }
        }
    }

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

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

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