Migration/vSphere/vSphere.psm1

Import-Module -Name $PSScriptRoot\..\..\MigrationProfile\VSphereMigrationProfile
Import-Module -Name $PSScriptRoot\..\..\DifferentialProfile\DifferentialProfile
Import-Module -Name $PSScriptRoot\..\..\Preflight\Preflight
Import-Module -Name $PSScriptRoot\..\..\Util\Util
Import-Module -Name $PSScriptRoot\..\..\CloudAccount\CloudAccount
Import-Module -Name $PSScriptRoot\..\..\RiverMeadow.Source\SourceUtil\SourceUtil
Import-Module -Name $PSScriptRoot\..\..\Validate\Validate

function Start-RMNonInteractiveVsphereOsBasedMigration {
    param (
        [string] $CloudAccountName,
        [string] $SourceIP,
        [bool] $RefreshSourceAttributes,
        [string] $TargetVMName,
        [string[]] $MountPoints,
        [string[]] $ResizeMountPoints,
        [string] $TransferMethod,
        [string] $CoresPerSocket,
        [bool] $OverrideMemory,
        [int] $OverrideMemorySize,
        [string] $DatacenterName,
        [bool] $EnableDestinationNetworkIsolation,
        [string] $MigrationNetworkName,
        [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] $DisableAuthomaticDNSRegistrationOnTheTarget,
        [string] $Cluster,
        [string] $ResourcePool,
        [string] $FolderName,
        [string] $DatastoreCluster,
        [string] $DatastoreName,
        [string] $DisksProvisioningType,
        [string] $HardwareVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgent,
        [hashtable] $MigrationInstructions,
        [string] $UpgradeOSVersion
    )

    $Entitlement = Get-RMEntitlement

    $UserInput = @{}

    $CloudAccount = Get-RMCloudAccountByName -CloudAccountName $CloudAccountName
    if ($null -eq $CloudAccount) {
        $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly
        $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly
        throw "Cloud account '$CloudAccountName' does not exist in current project '$ProjectName' of organization '$OrgName'"
    }

    $UserInput.Add("CloudAccount", $CloudAccount)
    $UserInput.Add("Entitlement", $Entitlement)

    $Source = $null
    if ("" -eq $SourceIP) {
        throw "Source IP address is required to start a migration."
    } else {
        $Source = Get-RMSourceWithAttribute -IPAddress $SourceIP -RefreshSourceAttributes $RefreshSourceAttributes -CloudAccount $CloudAccount
    }
    $UserInput.Add("Source", $Source)

    if ("" -eq $TargetVMName) {
        throw "Target VM name is required."
    }
    $UserInput.Add("TargetVMName", $TargetVMName)

    $SourceMountPoints = Get-MountPoint -Source $Source
    Compare-RMMountPoint -Source $Source -SourceMountPoints $SourceMountPoints -UserInputMountPoints $MountPoints
    $SelectedMountPoints = Get-RMSelectedMount -MountPoints $SourceMountPoints -DifferenceList $MountPoints -IncludeEqual $true
    $UserInput.Add("SelectedMounts", $SelectedMountPoints)

    $MountsResize = @{}
    if (![string]::IsNullOrEmpty($ResizeMountPoints)) {

        $MountsResize = Get-RMResizeMountsPoint -ResizeMountPoints $ResizeMountPoints -SelectedMountPoints $SelectedMountPoints -Source $Source

        if ("" -ne $TransferMethod -and "block-based" -eq $TransferMethod ) {
            throw "Transfer method needs to be 'file-based' if you want to resize the mount points."
        } else {
            $TransferMethod = "file-based"
        }
    } elseif ("" -eq $TransferMethod) {
        $TransferMethod =  Get-TransferType -Source $Source
    }
    $UserInput.Add("MountsResize", $MountsResize)
    $UserInput.Add("TransferType", $TransferMethod)

    if ("" -eq $CoresPerSocket) {
        $CoresPerSocket = 1
    }
    $UserInput.Add("CoresPerSocket", $CoresPerSocket)

    if ("" -ne $OverrideMemory -and $OverrideMemory) {
        if (0 -eq $OverrideMemorySize ) {
            throw "Override memory size is required"
        }
    }
    $UserInput.Add("OverrideMemory", $OverrideMemory)
    $UserInput.Add("OverrideMemorySize", $OverrideMemorySize)

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

    if ("" -eq $DatacenterName) {
        throw "Datacenter Name is required, please provide the datacenter name."
    }
    $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 -eq "static") {
            if ([string]::IsNullOrEmpty($IpAddress)) {
                throw "IP Address is required."
            }
            $UserInput.Add("IpAddress", $IpAddress)

            if ([string]::IsNullOrEmpty($Netmask)) {
                throw "Netmask is required."
            }
            $UserInput.Add("Netmask", $Netmask)

            if ([string]::IsNullOrEmpty($DefaultGateway)) {
                throw "Default gateway is required."
            }
            $UserInput.Add("DefaultGateway", $DefaultGateway)

            if ([string]::IsNullOrEmpty($PrimaryDNS)) {
                throw "Primary DNS is required."
            }
            $UserInput.Add("PrimaryDNS", $PrimaryDNS)
            $UserInput.Add("SecondaryDNS", $SecondaryDNS)
        }

       if ([string]::IsNullOrEmpty($MigrationNetworkName)) {
            throw "Migration network name is required."
       }

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

       if ([string]::IsNullOrEmpty($IsolatedNetworkName)) {
            throw "Isolated network name is required."
       }

       [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 -eq "static") {
            if ([string]::IsNullOrEmpty($IsolatedIpAddress)) {
                throw "Isolated network IP Address is required."
            }
            $UserInput.Add("IsolatedIpAddress", $IsolatedIpAddress)

            if ([string]::IsNullOrEmpty($IsolatedNetmask)) {
                throw "Isolated network netmask is required."
            }
            $UserInput.Add("IsolatedNetmask", $IsolatedNetmask)

            if ([string]::IsNullOrEmpty($IsolatedDefaultGateway)) {
                throw "Isolated network default gateway is required."
            }
            $UserInput.Add("IsolatedDefaultGateway", $IsolatedDefaultGateway)

            if ([string]::IsNullOrEmpty($IsolatedPrimaryDNS)) {
                throw "Isolated network primary DNS is required."
            }
            $UserInput.Add("IsolatedPrimaryDNS", $IsolatedPrimaryDNS)
            $UserInput.Add("IsolatedSecondaryDNS", $IsolatedSecondaryDNS)
        }
    } else {
        if ([string]::IsNullOrEmpty($MigrationNetworkName)) {
            throw "Destination network name is required"
        }

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

        $UserInput.Add("MigrationNetworkName", $MigrationNetworkName)
        $UserInput.Add("DvSwitchName", $DvSwitchName)

        if ([string]::IsNullOrEmpty($IpType)) {
            $IpType = "dhcp"
        } else {
            $IpType = $IpType.ToLower()
        }
        $UserInput.Add("IpType", $IpType)
        if ($IpType -eq "static") {
            if ([string]::IsNullOrEmpty($IpAddress)) {
                throw " IP Address is required."
            }
            $UserInput.Add("IpAddress", $IpAddress)

            if ([string]::IsNullOrEmpty($Netmask)) {
                throw "Netmask is required."
            }
            $UserInput.Add("Netmask", $Netmask)

            if ([string]::IsNullOrEmpty($DefaultGateway)) {
                throw "Default gateway is required."
            }
            $UserInput.Add("DefaultGateway", $DefaultGateway)

            if ([string]::IsNullOrEmpty($PrimaryDNS)) {
                throw "Primary DNS is required."
            }
            $UserInput.Add("PrimaryDNS", $PrimaryDNS)
            $UserInput.Add("SecondaryDNS", $SecondaryDNS)
        }
    }

    if ([string]::IsNullOrEmpty($Cluster)) {
        throw "Cluster is required"
    }
    $UserInput.Add("Cluster", $Cluster)

    $UserInput.Add("ResourcePool", $ResourcePool)
    $UserInput.Add("FolderName", $FolderName)
    $UserInput.Add("DatastoreCluster", $DatastoreCluster)

    if ([string]::IsNullOrEmpty($DatastoreName)) {
        throw "Datastore name is required"
    }

    if ([string]::IsNullOrEmpty($DisksProvisioningType)) {
        $DisksProvisioningType = "thin"
    } else {
        switch ($DisksProvisioningType) {
         "Thin_Provision" {  $DisksProvisioningType = "thin" }
         "Thin_Provision_Eager_Zeroed" { $DisksProvisioningType = "thin_eager_zero" }
         "Thin_Provision_Lazy_Zeroed" { $DisksProvisioningType = "thin_lazy_zero" }
        }
    }
    
    $Datastores = Get-DatastoreBySource -Source $Source -DatastoreName $DatastoreName -IsInteractive $false
    $DiskProvisioningTypes = Get-DiskProvisioningTypeBySource -Source $Source -DisksProvisioningType $DisksProvisioningType -IsInteractive $false

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

    if ([string]::IsNullOrEmpty($HardwareVersion)) {
        $HardwareVersion = Get-Hardware-Version -CloudAccount $CloudAccount
    }

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

    $ToolsPackage = Get-Tools-Package -CloudAccount $CloudAccount -HardwareVersion $HardwareVersion
    $UserInput.Add("ToolsPackage", $ToolsPackage)
    if ("" -eq $MigrationInstructions) {
        $MigrationInstructions = @{}
    }
    $UserInput.Add("MigrationInstructions", $MigrationInstructions)

    $UserInput.Add("ShutdownSource", $ShutdownSource)
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    $UserInput.Add("RemoveRMSAgent", $RemoveRMSAgent)

    if ("" -ne $UpgradeOSVersion) {
        $SourceOSMMapping = Get-RMOSMMappingBySource -Source $Source
        $OSMLabels = $SourceOSMMapping.keys -join ", "
        $UpgradeOSVersion = $UpgradeOSVersion.Trim('"')
        $UpgradeOSVersion = $UpgradeOSVersion.Trim("'")
        if ($SourceOSMMapping.keys -notcontains $UpgradeOSVersion) {
            throw "Please enter one of '$OSMLabels' and try again"
        }
        $UserInput.Add("UpgradeOSVersion", $SourceOSMMapping[$UpgradeOSVersion])
        $UserInput.Add("InPlaceUpgrade", $true)
    } else {
        $UserInput.Add("UpgradeOSVersion", $null)
        $UserInput.Add("InPlaceUpgrade", $false)
    }

    $Valid = Confirm-RMVSphereValidateOSBasedMigrationProfile @UserInput
    if (!$Valid) {
        return
    }
    $Response = New-RMVSphereMigrationProfile @UserInput

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

    return Invoke-RMRestMethod -Params $Params
}



function Start-RMInteractiveVSphereOSBasedMigration {
    param( )

    $Entitlement = Get-RMEntitlement

    $CloudAccountName = Read-Host "Enter cloud account name"
    $CloudAccount = Get-RMCloudAccountByName -CloudAccountName $CloudAccountName
    if ($null -eq $CloudAccount) {
        $ProjectName = Get-Variable -Name "RMContext-CurrentProjectName" -ValueOnly
        $OrgName = Get-Variable -Name "RMContext-CurrentOrganizationName" -ValueOnly
        throw "Cloud account '$CloudAccountName' does not exist in current project '$ProjectName' of organization '$OrgName'"
    }

    $Source = $null
    $SourceIP = Read-Host "Enter the IP address of the source machine to be migrated"
    if ("" -eq $SourceIP) {
        throw "Source IP address is required to start a migration."
    }

    $RefreshSourceAttributes = $false
    $ReadValue = Read-Host "Refresh source attributes (true/false)[false]"
    if ("" -ne $ReadValue) {
        $RefreshSourceAttributes = [System.Convert]::ToBoolean($ReadValue)
    }
    $Source = Get-RMSourceWithAttribute -IPAddress $SourceIP -RefreshSourceAttributes $RefreshSourceAttributes -CloudAccount $CloudAccount

    $TargetInventory = Get-RMTargetInventory -CloudAccount $CloudAccount

    $TargetVMName = Read-Host "Enter target VM Name"

    $MountPoints = Get-MountPoint -Source $Source

    if (0 -eq $MountPoints.count) {
        Write-Error "Source has no mount points, cannot be migrated"
        return
    }

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

    $ReadValue = Read-Host "Resize mount points (true/false) [false]"
    $MountsResize = @{}
    $ResizeMountsBool = $false
    if ("" -ne $ReadValue) {
        $ReadValue = [System.Convert]::ToBoolean($ReadValue)
        if ($ReadValue) {
            $ResizeMountsBool = $true
            $MountsResize = Get-RMInteractiveMountsResize -SelectedMountPoints $SelectedMountPoints -Source $Source
        }
    }

    if (!$ResizeMountsBool) {
        $TransferType = Get-TransferType -Source $Source

        $ReadValue =  Read-Host "Enter transfer type (file-based/block-based)[$TransferType]"
        if ("" -ne $ReadValue) {
            $TransferType = $ReadValue
        }
    } else {
        $TransferType = "file-based"
    }

    $CoresPerSocket = 1
    $ReadValue = Read-Host "Enter cores per socket (1,2)[1]"
    if ("" -ne $ReadValue) {
        if (!($ReadValue -eq "1" -or $ReadValue -eq "2")) {
            Throw "Cores per socket can be 1 or 2"
        }
        $CoresPerSocket = $ReadValue
    }

    $OverrideMemory = $false
    $ReadValue = Read-Host "Override memory (true/false)[false]"
    if ("" -ne $ReadValue) {
        $OverrideMemory = [System.Convert]::ToBoolean($ReadValue)
        if ($OverrideMemory) {
            $OverrideMemorySize = Read-Host "Enter the memory in GB"
        }
    }

    $DatacenterNames = Get-DatacenterList -TargetInventory $TargetInventory
    $DatecenterNamesAsString = $DatacenterNames -join ", "
    $DatacenterName = Read-Host "Enter datacenter name ($DatecenterNamesAsString)"
    if ("" -eq $DatacenterName) {
        Throw "Datacenter name is required"
    } elseif (!($DatacenterName -in $DatacenterNames)) {
        Throw "Datacenter '$DatacenterName' does not exists."
    }

    $NetworkNames = Get-NetworkName -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $NetworkNamesAsString = $NetworkNames -join ", "

    $EnableDestinationNetworkIsolation = $false

    if ($Source.os_type -eq "windows") {
        $ReadValue = Read-Host "Enable Destination network isolation (true/false)[false]"
        if ("" -ne $ReadValue) {
            $EnableDestinationNetworkIsolation = [System.Convert]::ToBoolean($ReadValue)
            if ($EnableDestinationNetworkIsolation) {
                $MigrationNetworkName = Read-Host "Enter migration network name ($NetworkNamesAsString)"
                if ("" -eq $MigrationNetworkName) {
                    Throw "Migration network name is required"
                } elseif (!($MigrationNetworkName -in $NetworkNames)) {
                    Throw "Migration network name '$MigrationNetworkName' does not exists."
                }

                [string[]] $networkNamesArray = $MigrationNetworkName -split "/"
                if ($networkNamesArray.Count -eq 2) {
                    $MigrationNetworkName = $networkNamesArray[1]
                    $DvSwitchName = $networkNamesArray[0]
                }
                $IpType, $IpAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-MigrationNetworkIPConfig

                $IsolatedNetworkName = Read-Host "Enter isolated network name ($NetworkNamesAsString)"
                if ("" -eq $IsolatedNetworkName) {
                    Throw "Isolated network name is required"
                } elseif(!($IsolatedNetworkName -in $NetworkNames)) {
                    Throw "Isolated network name '$IsolatedNetworkName' does not exists."
                }

                [string[]] $networkNamesArray = $IsolatedNetworkName -split "/"
                if ($networkNamesArray.Count -eq 2) {
                    $IsolatedNetworkName = $networkNamesArray[1]
                    $IsolatedDvSwitchName = $networkNamesArray[0]
                }
                $IsolatedIpType = "dhcp"
                $ReadValue = Read-Host "Enter isolated IP type (static/DHCP)[DHCP]"
                if ("" -ne $ReadValue) {
                    if (!($ReadValue -ieq "static" -or $ReadValue -ieq "DHCP")) {
                        Throw "IP type can be 'static' or 'DHCP'"
                    }
                    $IpType = $ReadValue.ToLower()
                }

                if ($ReadValue -ieq "static") {
                    $IsolatedIpType = $ReadValue
                    $IsolatedIpAddress = Read-Host "Enter isolated IP address"
                    $IsolatedNetmask = Read-Host "Enter isolated netmask"
                    $IsolatedDefaultGateway = Read-Host "Enter isolated default gateway"
                    $IsolatedPrimaryDNS = Read-Host "Enter isolated primary DNS"
                    $IsolatedSecondaryDNS = Read-Host "Enter isolated Secondary DNS" 
                }
            } else {
                $IpType, $IpAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-MigrationNetworkIPConfig
                $MigrationNetworkName = Read-Host "Enter destination network name ($NetworkNamesAsString)"
                if ("" -eq $MigrationNetworkName) {
                    Throw "Destination network name is required"
                } elseif (!($MigrationNetworkName -in $NetworkNames)) {
                    Throw "Destination network name '$MigrationNetworkName' does not exists."
                }

                [string[]] $networkNamesArray = $MigrationNetworkName -split "/"
                if ($networkNamesArray.Count -eq 2) {
                    $MigrationNetworkName = $networkNamesArray[1]
                    $DvSwitchName = $networkNamesArray[0]
                }
            }
        } else {
            $IpType, $IpAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-MigrationNetworkIPConfig
            $MigrationNetworkName = Read-Host "Enter destination network name ($NetworkNamesAsString)"
            if ("" -eq $MigrationNetworkName) {
                Throw "Destination network name is required"
            } elseif (!($MigrationNetworkName -in $NetworkNames)) {
                Throw "Destination network name '$MigrationNetworkName' does not exists."
            }

            [string[]] $networkNamesArray = $MigrationNetworkName -split "/"
            if ($networkNamesArray.Count -eq 2) {
                $MigrationNetworkName = $networkNamesArray[1]
                $DvSwitchName = $networkNamesArray[0]
            }
        }
    } else {
        $IpType, $IpAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS = Get-MigrationNetworkIPConfig
        $MigrationNetworkName = Read-Host "Enter destination network name ($NetworkNamesAsString)"
        if ("" -eq $MigrationNetworkName) {
            Throw "Destination network name is required"
        } elseif (!($MigrationNetworkName -in $NetworkNames)) {
            Throw "Destination network name '$MigrationNetworkName' does not exists."
        }

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


    if ($Source.os_type -eq "windows") {
        $DisableAuthomaticDNSRegistrationOnTheTarget = $false
        $ReadValue = Read-Host "Disable automatic DNS registration on the Target (true/false)[false]"
        if ("" -ne $ReadValue) {
            $DisableAuthomaticDNSRegistrationOnTheTarget =  [System.Convert]::ToBoolean($ReadValue)
        }
    } else {
        $DisableAuthomaticDNSRegistrationOnTheTarget = $false
    }

    $Clusters = Get-Cluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $ClustersAsString = $Clusters -join ", "
    $Cluster = Read-Host "Enter cluster name ($ClustersAsString)"
    if ("" -eq $Cluster) {
        Throw "Cluster name is required"
    } elseif(!($Cluster -in $Clusters)) {
        Throw "Cluster '$Cluster' does not exists."
    }

    $ResourcePool = Read-Host "Enter resource pool [None]"

    $FolderName = Read-Host "Enter VM folder name [None]"

    $DatastoreClusters = Get-Datastore-Cluster -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $DatastoreClustersAsString = $DatastoreClusters -join ", "
    $ReadValue = Read-Host "Enter datastore cluster ($DatastoreClustersAsString)[None]"
    if ("" -ne $ReadValue) {
        if (!($ReadValue -in $DatastoreClusters)) {
            Throw "Datastore cluster '$ReadValue' does not exists."
        }
        $DatastoreCluster = $ReadValue
    }

    $Datastores = Get-Datastore -TargetInventory $TargetInventory -datacenterName $DatacenterName
    $SelectedDatastores = Get-DatastoreBySource -Source $Source -TargetDatastores $Datastores -IsInteractive $true

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

    $HardwareVersions = Get-Hardware-Version -CloudAccount $CloudAccount
    $HardwareVersion = Read-Host "Enter VM hardware version [$HardwareVersions]"
    if ("" -eq $HardwareVersion) {
        $HardwareVersion = $HardwareVersions
    }

    $ShutdownSource = $false
    $ReadValue = Read-Host "Shutdown source after data is fully migrated (true/false)[false]"
    if ("" -ne $ReadValue) {
        $ShutdownSource = [System.Convert]::ToBoolean($ReadValue)
    }

    $ShutdownTarget = $false
    $ReadValue = Read-Host "Shutdown target after data is fully migrated (true/false)[false]"
    if ("" -ne $ReadValue) {
        $ShutdownTarget = [System.Convert]::ToBoolean($ReadValue)
    }

    $SourceOSMMapping = Get-RMOSMMappingBySource -Source $Source
    $OSMLabels = $SourceOSMMapping.keys -join ", "
    $ReadValue = Read-Host "Enter the OS version to upgrade to ($OSMLabels)[None]"
    if ("" -ne $ReadValue) {
        $ReadValue = $ReadValue.Trim('"')
        $ReadValue = $ReadValue.Trim("'")
        if ($SourceOSMMapping.keys -notcontains $ReadValue) {
            throw "Please enter one of $OSMLabels and try again"
        }
        $UpgradeOSVersion = $SourceOSMMapping[$ReadValue]
        $InPlaceUpgrade = $true
    } else {
        $UpgradeOSVersion = $null
        $InPlaceUpgrade =  $false
    }

    $RemoveRMSAgent = $false
    $ReadValue = Read-Host "Remove RMS agent post migration (true/false)[false]"
    if ("" -ne $ReadValue) {
        $RemoveRMSAgent = [System.Convert]::ToBoolean($ReadValue)
    }

    $ReadValue = Read-Host "Enter migration instructions in the format 'key/value' and separated by commas [None]"
    $MigrationInstructions = Get-RMStringAsHashtable -InputString $ReadValue

    $ToolsPackage = Get-Tools-Package -CloudAccount $CloudAccount -HardwareVersion $HardwareVersion


    $HashArguments = @{
        CloudAccount = $CloudAccount
        Entitlement = $Entitlement
        Source = $Source
        TargetVMName = $TargetVMName
        SelectedMounts = $SelectedMountPoints
        MountsResize = $MountsResize
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        RemoveRMSAgent = $RemoveRMSAgent
        DatacenterName = $DatacenterName
        EnableDestinationNetworkIsolation = $EnableDestinationNetworkIsolation
        MigrationNetworkName = $MigrationNetworkName
        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
        DisableAuthomaticDNSRegistrationOnTheTarget = $DisableAuthomaticDNSRegistrationOnTheTarget
        Cluster = $Cluster
        DatastoreCluster =  $DatastoreCluster
        DatastoreLocations = $SelectedDatastores
        FolderName =  $FolderName
        DisksProvisioningType = $SelectedProvisioningTypes
        TransferType = $TransferType
        CoresPerSocket = $CoresPerSocket
        OverrideMemory = $OverrideMemory
        OverrideMemorySize = $OverrideMemorySize
        ResourcePool = $ResourcePool
        HardwareVersion = $HardwareVersion
        ToolsPackage = $ToolsPackage
        MigrationInstructions = $MigrationInstructions
        UpgradeOSVersion = $UpgradeOSVersion
        InPlaceUpgrade = $InPlaceUpgrade
    }

    $Response = New-RMVSphereMigrationProfile @HashArguments

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

    return Invoke-RMRestMethod -Params $Params
}

function Start-RMVSphereOSBasedDifferentialMigration {
    param (
        [string] $MigrationId
    )

    Import-Module -Name $PSScriptRoot\..\..\DifferentialProfile\DifferentialProfile -Force

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

    $Name = "PowerShell - Differential Profile - " + [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()

    $Entitlement = Get-RMEntitlement
   
    $Migration = Get-Migration -MigrationId $MigrationId
    
    $MigrationProfile = Get-MigrationProfile -MigrationProfileId $Migration.migration_profile

    $Source = @{
        "host" = $Migration.hostname
    }

    $TransferType = $Migration.transfer_type

    $TargetProperties = $Migration.target.properties
    $MigrationInstructions =  $Migration.migration_instructions

    $HashArguments = @{
        Name = $Name
        Entitlement = $Entitlement
        OrganizationId = $OrganizationId
        Description = $Description
        ApplianceId = $MigrationProfile.appliance_id
        Schedule = $Schedule
        TransferMode = $TransferMode
        MigrationId = $Migration.id
        SourceId = $Migration.source_id
        Target = $Target
        Source =  $Source
        ShutdownSource = $false
        ShutdownTarget = $false
        RemoveTargetAgent = $false
        PublishMigrationHub = $false
        TransferType = $TransferType
        MigrationInstructions = $MigrationInstructions
        TargetProperties = $TargetProperties
        TargetIp = $Migration.target.ip 
    }
    
    $Response = New-RMDifferentialProfile @HashArguments

    $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 + "/differentialprofiles/" + $Response.id + "/migrations"
        Headers = $Headers
        ContentType = "application/json"
    }

    return Invoke-RMRestMethod -Params $Params |Out-Null
}

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-DatacenterList {
    param(
        [System.Object] $TargetInventory
    )

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

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

    $NetworkNames = @()
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            foreach($network in $datacenter.networks) {
                $NetworkNames +=$network.name
            }
        }
    }
    return  $NetworkNames
}

function Get-Cluster {
    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
            }
        }
    }
    return $Clusters
}

function Get-Datastore-Cluster {
    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
            }
        }
    }
    return  $DatastoreClusters
}

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

    $Datastores = @()
    foreach ($datacenter in $TargetInventory.attributes.vSphere.datacenters) {
        if ($datacenterName -eq $datacenter.name) {
            foreach($datastore in $datacenter.datastores) {
                $Datastores += $datastore.name
            }
        }
    }
    return  $Datastores
}

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

    $HardwareVersions = @()
    $Version = $CloudAccount.vsphere_version

    switch ($Version) {
        "VMC" {  $HardwareVersions += "19" }
        "GCVE" {  $HardwareVersions += "18"  }
        "ESXi_7_0" { $HardwareVersions += "17" }
        "ESXi_6_7_U3" { $HardwareVersions += "15" }
        "ESXi_6_7_U2" { $HardwareVersions += "15" }
        "ESXi_6_7" { $HardwareVersions += "14" }
        "ESXi_6_5" { $HardwareVersions += "13" }
        "ESXi_6_0" { $HardwareVersions += "11" }
    }

    return $HardwareVersions -join ", "
}
function Get-TransferType {
    param (
        [System.Object] $Source
    )

    $TransferType = "file-based"

    if ($Source.os_type -eq "windows") {
        $TransferType = "block-based"
    } else {
        if($Source.attributes.discovered_features.features_list -contains "linux_block_based") {
            $fs = @("ext2", "ext3", "ext4", "xfs")
            $check = $true
            foreach($Mount in $Source.attributes.storage.mounts.psobject.properties.value){
                if ($Mount.nature -eq "disk") {
                    if ($fs -notcontains $Mount.fs_type) {
                        $check = $false;
                        break
                    }
                }
            }
            if ($check) {
                $TransferType = "block-based"
            }
        }
    }
    return  $TransferType
}

function Get-Tools-Package {
    param (
       [System.Object] $CloudAccount,
       [string] $HardwareVersion
    )

    $Version = $CloudAccount.vsphere_version
    switch ($Version) {
        "VMC" {
            return "vmwaretools-10.3.10.tar.gz"
        }
        "GCVE" {
            return "vmwaretools-10.3.10.tar.gz"
        }
        "ESXi_7_0" {
            return "vmwaretools-10.3.10.tar.gz"
        }
        "ESXi_6_7_U3" {
            return "vmwaretools-10.3.5.tar.gz"
        }
        "ESXi_6_7_U2" {
            return "vmwaretools-10.3.5.tar.gz"
        }
        "ESXi_6_7" {
            return "vmwaretools-10.3.2.tar.gz"
        }
        "ESXi_6_5" {
            return "vmwaretools-10.1.0.tar.gz"
        }
        "ESXi_6_0" {
            return "vmwaretools-9.10.0.tar.gz"
        }
    }

}

function Get-MigrationNetworkIPConfig {
    $IpType = "dhcp"
    $ReadValue = Read-Host "Enter IP type (static/DHCP)[DHCP]"
    if ("" -ne $ReadValue) {
        if (!($ReadValue -ieq "static" -or $ReadValue -ieq "DHCP")) {
            Throw "IP type can be 'static' or 'DHCP'"
        }
        $IpType = $ReadValue.ToLower()
    }
    $IPAddress = ""
    $Netmask = ""
    $DefaultGateway = ""
    $PrimaryDNS = ""
    $SecondaryDNS = ""
    if ($IpType -ieq "static") {
        $IPAddress = Read-Host "Enter IP address"
        $Netmask = Read-Host "Enter netmask"
        $DefaultGateway = Read-Host "Enter default gateway"
        $PrimaryDNS = Read-Host "Enter primary DNS"
        $SecondaryDNS = Read-Host "Enter Secondary DNS"
    }

    return $IpType, $IPAddress, $Netmask, $DefaultGateway, $PrimaryDNS, $SecondaryDNS
}

function Get-DatastoreBySource {
    param(
        [System.Object] $Source,
        [string[]] $TargetDatastores,
        [string] $DatastoreName,
        [bool] $IsInteractive
    )
    if ($IsInteractive -and $TargetDatastores.Count -eq 0) {
        Throw "No datastores were found, migration cannot be started."
    }

    $TargetDatastoresAsString = $TargetDatastores -join ", "
    $Datastores = @()
    foreach ($Disk in $Source.attributes.storage.disks.psobject.Properties.Value) {
        if ($IsInteractive) {
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $DefaultDatastore = $TargetDatastores[0]
            $ReadValue = Read-Host "Enter the datastore name for disk of size $Size GiB ($TargetDatastoresAsString)[$DefaultDatastore]"
            if ("" -ne $ReadValue) {
                if (!($ReadValue -in $TargetDatastores)) {
                    Throw "Datastore '$ReadValue' does not exist in the given cluster"
                }
                $Datastores += $ReadValue
            } else {
                $Datastores += $DefaultDatastore
            }
        } else {
            $Datastores += $DatastoreName
        }
    }
    return $Datastores
}

function Get-DiskProvisioningTypeBySource {
    param(
        [System.Object] $Source,
        [string] $DisksProvisioningType,
        [bool] $IsInteractive
    )
    $DiskProvisioningTypes = @()
    $ProvisioningTypes = "Thin_Provision", "Thin_Provision_Eager_Zeroed", "Thin_Provision_Lazy_Zeroed"
    foreach ($Disk in $Source.attributes.storage.disks.psobject.Properties.Value) {
        if ($IsInteractive) {
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $ProvisioningTypesAsString = $ProvisioningTypes -join ", "
            $ReadValue = Read-Host "Enter disk provisioning type for disk of size $Size GiB ($ProvisioningTypesAsString)[Thin_Provision]"
            if ("" -ne $ReadValue) {
                if (!($ReadValue -in $ProvisioningTypes)) {
                    Throw "Disk provisioning type '$ReadValue' is not valid"
                }
               switch ($ReadValue) {
                "Thin_Provision" { $DiskProvisioningTypes += "thin" }
                "Thin_Provision_Eager_Zeroed" { $DiskProvisioningTypes += "thin_eager_zero" }
                "Thin_Provision_Lazy_Zeroed" { $DiskProvisioningTypes += "thin_lazy_zero" }
               }
            } else {
                $DiskProvisioningTypes += "thin"
            }
        } else {
            $DiskProvisioningTypes += "thin"
        }
    }
    return $DiskProvisioningTypes
}

Export-ModuleMember -Function Start-RMInteractiveVSphereOSBasedMigration, Start-RMNonInteractiveVsphereOsBasedMigration, Start-RMVSphereOSBasedDifferentialMigration