Migration/AZURE/AZURE.psm1

using module '../../Common/Result'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath .. | Join-Path -ChildPath MigrationProfile | Join-Path -ChildPath AzureMigrationProfile)
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)

function Start-RMAzureOSBasedInteractiveMigration {
    param()
    $UserInput = @{}
    $CloudAccount = Read-RMCloudAccount -AccountType "azure" -UserMessage "Enter target cloud"
    $CloudAttributes = Get-RMCloudAttribute -CloudAccount $CloudAccount

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

    $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"
    $UserInput.Add("IgnoreValidationErrors", $IgnoreValidationErrors)

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn  -AccountType "azure"
    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
        }
    }

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

    $ScheduledAt = Read-RMMigrationSchedule
    $UserInput.Add("ScheduledAt", $ScheduledAt)

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

    $ResourceGroupRegion = ""
    $ReadValue = Read-RMBoolean -UserMessage "Create a new resource group" -DefaultValue "false"
    if ($ReadValue) {
        $ResourceGroup = Read-RMString -UserMessage "Enter the name of the resource group to be created" `
            -ParameterName "Resource group name" -IsRequired $true
        $ResourceGroupRegion = Read-RMString -UserMessage "Enter the region name where the resource group will be created" `
            -ParameterName "Resource group region name" -IsRequired $true
    } else {
        $ResourceGroup = $CloudAccount.appliance.cloud_properties.resource_group
        $ResourceGroup = Read-RMString -UserMessage "Enter existing resource group name" -DefaultValue $ResourceGroup `
            -ParameterName "Resource group name" -IsRequired $false
    }
    $UserInput.Add("ResourceGroup", $ResourceGroup)
    $UserInput.Add("ResourceGroupRegion", $ResourceGroupRegion)

    $VMSize = Read-VMSize -CloudAttributes $CloudAttributes
    $UserInput.Add("VMSize", $VMSize)

    $DiskTypeMappings = @()
    $ReturnedValue = Get-DiskTypeMappingBySource -Source $Source -VMSize $VMSize -IsInteractive $true
    if ($ReturnedValue -is [hashtable]) {
        # The method "Get-DiskTypeMappingBySource" returns an array of hashtables, but it
        # has been observed that if the source contains a single disk, the returned value
        # is of type HashTable; hence we are checking the type and re-adding it to an array
        $DiskTypeMappings += $ReturnedValue
    } else {
        $DiskTypeMappings = $ReturnedValue
    }

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

    $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
    }
    $UserInput.Add("MountPoints", $SelectedMountPoints)

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

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

    $UserInput.Add("TransferMethod", $TransferMethod)
    $UserInput.Add("ResizeMountPoints", $MountsResize)

    $VirtualNetwork = $CloudAccount.appliance.cloud_properties.virtual_network.name
    $VirtualNetwork = Read-RMString -UserMessage "Enter virtual network name" -DefaultValue $VirtualNetwork `
        -ParameterName "Virtual network name" -IsRequired $false
    $UserInput.Add("VirtualNetwork", $VirtualNetwork)

    $DestinationNetworkName = $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name
    $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -DefaultValue $DestinationNetworkName `
        -ParameterName "Destination network name" -IsRequired $false
    $UserInput.Add("DestinationNetworkName", $DestinationNetworkName)

    $AssignPublicIP = Read-RMBoolean -UserMessage "Auto assign public IP" -DefaultValue "false"
    $UserInput.Add("AssignPublicIP", $AssignPublicIP)

    $StaticPrivateIP = Read-RMIPAddress -UserMessage "Enter static private IP to be assigned to target" -DefaultValue "None" `
        -ParameterName "Static private IP" -IsRequired $false
    $UserInput.Add("StaticPrivateIP", $StaticPrivateIP)

    $EnforceTargetNetworkIsolation = Read-RMBoolean -UserMessage "Enforce target network isolation" -DefaultValue "true"
    $SecurityGroup = $null
    if (!$EnforceTargetNetworkIsolation) {
        $SecurityGroup = Read-RMString -UserMessage "Enter security group name" -ParameterName "Security group name" -IsRequired $true
    }
    $UserInput.Add("SecurityGroup", $SecurityGroup)

    $DisableTargetDNSRegistration = $false
    if("windows" -ieq $Source.os_type) {
        $DisableTargetDNSRegistration = Read-RMBoolean -UserMessage "Disable automatic DNS registration" -DefaultValue "false"
    }
    $UserInput.Add("DisableTargetDNSRegistration", $DisableTargetDNSRegistration)

    $ReadValue = Read-RMPair -UserMessage "Enter one or more instance tags in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    $InstanceTags = Get-RMStringAsHashtable -InputString $ReadValue
    $UserInput.Add("InstanceTags", $InstanceTags)

    $AvailabilitySet = $null
    $AvailabilityZone = $null
    $FaultDomainCount = $null
    $UpdateDomainCount = $null

    $ReadValue = Read-RMString -UserMessage "Enter availability option" -Options "None", "AvailabilityZone", "AvailabilitySet" `
        -DefaultValue "None" -ParameterName "Availability option" -IsRequired $false
    if ("" -ne $ReadValue -and "none" -ine $ReadValue) {
        if ("AvailabilityZone" -ieq $ReadValue) {
            $AvailabilityZone = Read-RMString -UserMessage "Enter the availability zone" -Options $VMSize.availability_zones `
                -ParameterName "Availability zone" -IsRequired $true
        } else {
            $ReadValue = Read-RMBoolean -UserMessage "Create a new availability set" -DefaultValue "false"
            if ($ReadValue) {
                $AvailabilitySet = Read-RMString -UserMessage "Enter the name of the availability set to be created" `
                    -ParameterName "Availability set name" -IsRequired $true
                $FaultDomainCount = Read-RMInt -UserMessage "Enter fault domains" -OptionsRange 1, 3 `
                    -DefaultValue "2" -ParameterName "Fault domains" -IsRequired $false
                $UpdateDomainCount = Read-RMInt -UserMessage "Enter update domains" -OptionsRange 1, 20 `
                    -DefaultValue "5" -ParameterName "Update domains" -IsRequired $false
            } else {
                $AvailabilitySets = Get-RMAvailabilitySet -CloudAttributes $CloudAttributes -ResourceGroup $ResourceGroup
                $AvailabilitySet = Read-RMString -UserMessage "Enter the existing availability set name" -Options $AvailabilitySets `
                    -ParameterName "Availability set name" -IsRequired $true
            }
        }
    }

    $UserInput.Add("AvailabilityZone", $AvailabilityZone)
    $UserInput.Add("AvailabilitySet", $AvailabilitySet)
    $UserInput.Add("FaultDomainCount", $FaultDomainCount)
    $UserInput.Add("UpdateDomainCount", $UpdateDomainCount)

    $StorageAccount = $null
    $EnableBootDiagnostics = Read-RMBoolean -UserMessage "Enable boot diagnostics" -DefaultValue "false"
    if ($EnableBootDiagnostics) {
        $StorageAccount = Read-RMString -UserMessage "Enter azure storage account name for boot diagnostics" `
            -ParameterName "Storage account name" -IsRequired $true
    }

    $UserInput.Add("EnableBootDiagnostics", $EnableBootDiagnostics)
    $UserInput.Add("StorageAccount", $StorageAccount)

    $EncryptionSet = $null
    $ReadValue = Read-RMString -UserMessage "Enter disk encryption type" -Options "platform-managed", "customer-managed" `
        -DefaultValue "platform-managed" -ParameterName "Disk encryption type" -IsRequired $false
    if ("customer-managed" -ieq $ReadValue) {
        $EncryptionSet = Read-RMString -UserMessage "Enter encryption set name" -ParameterName "Encryption set name" -IsRequired $true
    }
    $UserInput.Add("EncryptionSet", $EncryptionSet)

    $EnableHybridUseBenefit = $false
    if("windows" -ieq $Source.os_type) {
        $EnableHybridUseBenefit = Read-RMBoolean -UserMessage "Enable Azure hybrid use benefit" -DefaultValue "false"
    }
    $UserInput.Add("EnableHybridUseBenefit", $EnableHybridUseBenefit)

    Read-RMOSMUpgradeOption -Source $Source -UpdatedUserInput $UserInput

    $ShutdownSource = Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"
    $UserInput.Add("ShutdownSource", $ShutdownSource)

    $ShutdownTarget = Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)

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

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

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

    $Response = New-RMAzureMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id -IgnoreValidationErrors $IgnoreValidationErrors -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $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 -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function Start-RMAzureOSBasedNonInteractiveMigration {
    param(
        [string] $TargetCloud,
        [string] $SourceIP,
        [string] $ScheduledAt,
        [string] $TargetVMName,
        [string] $TransferMethod,
        [bool] $CreateResourceGroup,
        [string] $ResourceGroup,
        [string] $ResourceGroupRegion,
        [string] $VMSizeName,
        [string[]] $VolumeType,
        [string[]] $MountPoint,
        [string[]] $ResizeMountPoint,
        [string] $VirtualNetwork,
        [string] $DestinationNetworkName,
        [bool] $AssignPublicIP,
        [string] $StaticPrivateIP,
        [bool] $EnforceTargetNetworkIsolation,
        [string] $SecurityGroup,
        [bool] $DisableTargetDNSRegistration,
        [string[]] $InstanceTag,
        [string] $AvailabilityOption,
        [int] $AvailabilityZone,
        [bool] $CreateAvailabilitySet,
        [string] $AvailabilitySet,
        [int] $FaultDomain,
        [int] $UpdateDomain,
        [bool] $EnableBootDiagnostics,
        [string] $StorageAccount,
        [string] $DiskEncryptionType,
        [string] $EncryptionSet,
        [bool] $EnableHybridUseBenefit,
        [string] $UpgradeOSVersion,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $RemoveRMSAgentFromSource,
        [bool] $RemoveRMSAgentFromTarget,
        [string[]] $MigrationInstruction,
        [bool] $IgnoreValidationError,
        [bool] $OverrideExistingMigration
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent = Confirm-RMAzureFullMigrationParameter $PSBoundParameters

    $CloudAccount = $null
    $CloudAttributes = $null
    $Source = $null
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    try {
        if (![string]::IsNullOrEmpty($TargetCloud)) {
            $CloudAccount, $ErrorString = Get-RMCloudAccountByName -CloudAccountName $TargetCloud -AccountType "azure"
            if (![string]::IsNullOrEmpty($ErrorString)) {
                $Errors += $ErrorString
                $IsSourceAndTargetCloudPresent = $false
            } else {
                #TODO: Move this call after adding validation of user input through FE endpoint, at that time
                # this call should be just before we trigger validation.
                $CloudAttributes = Get-RMCloudAttribute -CloudAccount $CloudAccount
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

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

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

    $Errors += Confirm-RMAzureFullMigrationParameterWithSource -UserParameter $PSBoundParameters -Source $Source

    $UserInput = @{}
    $Entitlement = Get-RMEntitlement

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


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

    $VMSize = Get-VMSizeByName -VMSizeName $VMSizeName -CloudAttributes $CloudAttributes
    if ($null -eq $VMSize) {
       $Errors += "VM size by name '$VMSizeName' does not exists."
    }
    $UserInput.Add("VMSize", $VMSize)

    if ([string]::IsNullOrEmpty($VolumeType)) {
        $VolumeType = "Standard_SSD"
    }
    $UserInput.Add("VolumeType", $VolumeType)

    $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("MountPoints", $SelectedMountPoints)

    $MountsResize = @{}
    if ($null -ne $ResizeMountPoint -and $ResizeMountPoint.Count -gt 0) {
        try {
            $ResizeMounts = Get-RMStringArrayAsHashtable -InputItems $ResizeMountPoint  -ParameterName "ResizeMountPoint"
        } catch {
            $Errors += $PSItem.Exception.Message
        }

        if ($null -ne $ResizeMounts) {
            $MountsResize, $MountsResizeErrors = Add-RMResizeMount -SelectedMounts $SelectedMountPoints.values -ResizeMounts $ResizeMounts -Source $Source
            if ($null -eq $MountsResize) {
                $Errors += $MountsResizeErrors
            }
        }
        
        if ("" -ne $TransferMethod -and "block-based" -eq $TransferMethod ) {
            $Errors += "Transfer method 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("TransferMethod", $TransferMethod)
    $UserInput.Add("ResizeMountPoints", $MountsResize)

    $UserInput.Add("VirtualNetwork", $VirtualNetwork)
    $UserInput.Add("DestinationNetworkName", $DestinationNetworkName)
    $UserInput.Add("AssignPublicIP", $AssignPublicIP)
    $UserInput.Add("StaticPrivateIP", $StaticPrivateIP)

    if (!$EnforceTargetNetworkIsolation) {
        $UserInput.Add("SecurityGroup", $SecurityGroup)
    }
    if("windows" -ieq $Source.os_type) {
        $UserInput.Add("DisableTargetDNSRegistration", $DisableTargetDNSRegistration)
    } else {
        $UserInput.Add("DisableTargetDNSRegistration", $false)
    }

    $InstanceTagsAsHashTable = $null
    try {
        $InstanceTagsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $InstanceTag -ParameterName "InstanceTag"
    } catch {
        $Errors += $PSItem.Exception.Message
    }
    
    $UserInput.Add("InstanceTags", $InstanceTagsAsHashTable)
    if ("AvailabilitySet" -ieq $AvailabilityOption) {
        $UserInput.Add("AvailabilitySet", $AvailabilitySet)
        $UserInput.Add("AvailabilityZone", "")

        if ($CreateAvailabilitySet) {
            $UserInput.Add("FaultDomainCount", $FaultDomain.ToString())
            $UserInput.Add("UpdateDomainCount", $UpdateDomain.ToString())
        } else {
            $UserInput.Add("FaultDomainCount", $null)
            $UserInput.Add("UpdateDomainCount", $null)
        }
    } elseif("AvailabilityZone" -ieq $AvailabilityOption) {
        $UserInput.Add("AvailabilityZone", $AvailabilityZone.ToString())
        $UserInput.Add("AvailabilitySet", "")
        $UserInput.Add("FaultDomainCount", $null)
        $UserInput.Add("UpdateDomainCount", $null)
    }

    $UserInput.Add("EnableBootDiagnostics", $EnableBootDiagnostics)
    $UserInput.Add("StorageAccount", $StorageAccount)
    $UserInput.Add("EncryptionSet", $EncryptionSet)

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

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

    $UserInput.Add("ShutdownSource", $ShutdownSource)
    $UserInput.Add("ShutdownTarget", $ShutdownTarget)
    $UserInput.Add("RemoveRMSAgentFromSource", $RemoveRMSAgentFromSource)
    $UserInput.Add("RemoveRMSAgentFromTarget", $RemoveRMSAgentFromTarget)
    $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("IgnoreValidationErrors", $IgnoreValidationError)

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

    $Response = New-RMAzureMigrationProfile @UserInput
    $ShouldExit = Start-RMMigrationPreflight -MigrationProfileId $Response.id -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn
    if ($ShouldExit) {
        return $RMMigrationReturn
    }
    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)

    $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 -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function Get-RMAvailabilitySet {
    param(
        [System.Object] $CloudAttributes,
        [string] $ResourceGroup
    )
    if ($null -eq $CloudAttributes -or 0 -eq $CloudAttributes.properties.subscriptions.Count -or 0 -eq $CloudAttributes.properties.subscriptions[0].regions) {
        throw "Internal error, cloud attributes doesn't exist"
    }

    $AvailabilitySets = @()
    foreach ($AvailabilitySet in $CloudAttributes.properties.subscriptions[0].regions[0].availability_sets) {
        if ($AvailabilitySet.resource_group -eq $ResourceGroup) {
            $AvailabilitySets += $AvailabilitySet.name
        }
    }

    return $AvailabilitySets
}

function Confirm-RMVolumeType {
    param (
        [System.Object] $Source,
        [string[]] $VolumeTypes
    )
    
    $VolumeTypeCnt = $VolumeType.Count
    $DisksCnt = $Source.attributes.storage.disks.psobject.Properties.Value.device.Count
    if ( $VolumeTypeCnt -ne $DisksCnt) {
        $ErrorString += "The source has '$DisksCnt' disks and the number of volume types given by the parameter 'VolumeType' are '$VolumeTypeCnt'. The number of volume types given should be equal to the number of disks on the source."
    }

    return $ErrorString
}

function Confirm-RMAzureFullMigrationParameter {
    param(
        [hashtable] $UserParameter
    )
    $Errors, $Warnings, $IsSourceAndTargetCloudPresent = Confirm-RMCommonParameter -UserParameter $UserParameter
    if ($UserParameter.ContainsKey($CreateResourceGroup) -and $UserParameter["CreateResourceGroup"] -eq $true) {
        if (!$UserParameter.ContainsKey("ResourceGroupRegion") -or [string]::IsNullOrEmpty($UserParameter["ResourceGroupRegion"])) {
            $Errors += "ResourceGroupRegion is required, when parameter 'CreateResourceGroup' is true."
        }
    }
    if (!$UserParameter.ContainsKey("ResourceGroup") -or [string]::IsNullOrEmpty($UserParameter["ResourceGroup"])) {
        $Errors += "ResourceGroup is required."
    }

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

    if (!$UserParameter.ContainsKey("VirtualNetwork") -or [string]::IsNullOrEmpty($UserParameter["VirtualNetwork"])) {
        $Errors += "VirtualNetwork is required."
    }
    if (!$UserParameter.ContainsKey("DestinationNetworkName") -or [string]::IsNullOrEmpty($UserParameter["DestinationNetworkName"])) {
        $Errors += "DestinationNetworkName is required."
    }
    if($UserParameter.ContainsKey("EnforceTargetNetworkIsolation") -and !$UserParameter["EnforceTargetNetworkIsolation"]) {
        if (!$UserParameter.ContainsKey("SecurityGroup") -or [string]::IsNullOrEmpty($UserParameter["SecurityGroup"])) {
            $Errors += "SecurityGroup is required, when parameter 'EnforceTargetNetworkIsolation' is false."
        }
    }
    if ($UserParameter.ContainsKey("StaticPrivateIP") -and ![string]::IsNullOrEmpty($UserParameter["StaticPrivateIP"])) {
        $IsValidStaticPrivateIP = Confirm-RMIPAddress -IPAddress $UserParameter.StaticPrivateIP
        if (!$IsValidStaticPrivateIP) {
            $Errors += "Invalid Static Private IP"
        }
    }
    if ($UserParameter.ContainsKey("AvailabilityOption")) {
        if ($UserParameter.AvailabilityOption -ieq "AvailabilitySet") {
            if (!$UserParameter.ContainsKey("AvailabilitySet") -or [string]::IsNullOrEmpty($UserParameter["AvailabilitySet"])) {
                $Errors += "AvailabilitySet is required."
            }
            if ($UserParameter.ContainsKey("CreateAvailabilitySet") -and $UserParameter["CreateAvailabilitySet"] -eq $true) {
                if (!$UserParameter.ContainsKey("FaultDomain")) {
                    $Errors += "FaultDomain is required, when parameter 'CreateAvailabilitySet' is true."
                } else {
                    $FaultDomain = $UserParameter["FaultDomain"]
                    if (-not($FaultDomain -ge 1 -and $FaultDomain -le 3)) {
                        $Errors += "FaultDomain should be in the range 1 and 3."
                    }
                }

                if (!$UserParameter.ContainsKey("UpdateDomain")) {
                    $Errors += "UpdateDomain is required, when parameter 'CreateAvailabilitySet' is true."
                } else {
                    $UpdateDomain = $UserParameter["UpdateDomain"]
                    if (-not($UpdateDomain -ge 1 -and $UpdateDomain -le 20)) {
                        $Errors += "UpdateDomain should be in the range 1 and 20."
                    }
                }
            }
        } elseif ($UserParameter.AvailabilityOption -ieq "AvailabilityZone") {
            if (!$UserParameter.ContainsKey("AvailabilityZone")) {
                $Errors += "AvailabilityZone is required."
            } else {
                $AvailabilityZone = $UserParameter["AvailabilityZone"]
                if (-not($AvailabilityZone -ge 1 -and $AvailabilityZone -le 3)) {
                    $Errors += "AvailabilityZone should be in the range 1 and 3."
                }
            }
        }
    }

    if ($UserParameter.ContainsKey("DiskEncryptionType") -and $UserParameter["DiskEncryptionType"] -ieq "customer-managed") {
        if (!$UserParameter.ContainsKey("EncryptionSet") -or [string]::IsNullOrEmpty($UserParameter["EncryptionSet"])) {
            $Errors += "EncryptionSet is required when parameter 'DiskEncryptionType' is 'customer-managed'"
        }
    }
    return $Errors, $Warnings, $IsSourceAndTargetCloudPresent
}

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

    $Errors = Confirm-RMCommonParameterWithSource -UserParameter $UserParameter -Source $Source

    if (![string]::IsNullOrEmpty($UserParameter["VolumeType"])) {
        $ErrorString = Confirm-RMVolumeType -Source $Source -VolumeTypes $UserParameter["VolumeType"]
        if (![string]::IsNullOrEmpty($ErrorString)) {
            $Errors += $ErrorString
        }
    }

    return $Errors
}

function Read-VMSize {
    param(
        [System.Object] $CloudAttributes
    )
    while ($true) {
        $VMSizeName = Read-Host "Enter VM Size name"
        if ("" -eq $VMSizeName) {
            Write-RMError -Message "VM size name is required, please try again"
            continue
        }

        $VMSize = Get-VMSizeByName -VMSizeName $VMSizeName -CloudAttributes $CloudAttributes
        if ($null -eq $VMSize) {
            Write-RMError -Message "VM size '$VMSizeName' does not exist"
            continue
        }
        return $VMSize
    }
}
Export-ModuleMember -Function Start-RMAzureOSBasedInteractiveMigration, Start-RMAzureOSBasedNonInteractiveMigration