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)
Import-Module -Name @(Join-Path $PSScriptRoot AZUREUtil)
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)

    $DestinationNetworkOptions = Get-RMDestinationNetworkName -CloudAttributes $CloudAttributes -VirtualNetworkName  $VirtualNetwork
    $DestinationNetworkName = $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name
    if ($VirtualNetwork -ieq $CloudAccount.appliance.cloud_properties.virtual_network.name) {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -DefaultValue $DestinationNetworkName `
            -ParameterName "Destination network name" -IsRequired $false -Options $DestinationNetworkOptions.name
    } else {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -ParameterName "Destination network name" `
             -IsRequired $true -Options $DestinationNetworkOptions.name
    }
    $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-RMSecurityGroup -CloudAttribute $CloudAttributes
    }
    $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

    if ("" -ieq $VMSize.availability_zones) {
        $ReadValue = Read-RMString -UserMessage "Enter availability option" -Options "None",  "AvailabilitySet" `
        -DefaultValue "None" -ParameterName "Availability option" -IsRequired $false
    } else {
        $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
                if ($AvailabilitySets.Count -lt 1) {
                    Write-RMError -Message "No existing availability sets in current resource group and location"
                } else {
                    $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" `
            -Options $CloudAttributes.properties.subscriptions.regions.storage_accounts.name `
            -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 `
            -Options $CloudAttributes.properties.subscriptions.regions.disk_encryption_sets.name
    }
    $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

    $SQLServerUpgrade =  Read-RMSQLServerUpgradeOption -Source $Source
    $UserInput.Add("SQLServerUpgrade", $SQLServerUpgrade)

    $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,
        [hashtable[]] $UpgradeSQLServer,
        [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.RuntimeException] {
        $Errors += $PSItem.Exception.Message
    }

    $SourceSQLServerMapping = Get-RMSQLMMappingBySource -Source $Source
    $Errors += Confirm-RMAzureFullMigrationParameterWithSource -UserParameter $PSBoundParameters -Source $Source `
        -SourceSQLServerMapping $SourceSQLServerMapping

    $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

    $SQLServerUpgrade = Get-RMUpgradeSQLServer -UpgradeSQLServer $UpgradeSQLServer -Source $Source `
        -SourceSQLServerMapping $SourceSQLServerMapping 
    $UserInput.Add("SQLServerUpgrade", $SQLServerUpgrade)

    $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 Start-RMInteractiveAzureVMBasedMigration {
    param ()
    
    $CloudAccount = Read-RMCloudAccount -AccountType "azure" `
        -UserMessage "Enter target cloud that supports VM-based migrations" -VMBasedAppliancesOnly $true
    $Source = Read-RMVMBasedSource -UserMessage "Enter the VM name of the source machine to be migrated" `
        -ParameterName "Source VM name" -IsRequired $true -CloudAccount $CloudAccount

    $Entitlement = Get-RMEntitlement

    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    if ($null -eq $CloudAccount) {
        return Update-RMMigrationReturnAsError `
            -Message "The migration appliance associated with the given source is not ready for use, cannot start the migration" `
            -RMMigrationReturn $RMMigrationReturn
    }

    $CloudAttributes = Get-RMCloudAttribute -CloudAccount $CloudAccount
    $IgnoreValidationError = Read-RMBoolean -UserMessage "Ignore validation errors" -DefaultValue "false"

    $SourceAttributeResult = Get-RMSourceWithAttribute -Source $Source -CloudAccount $CloudAccount `
        -IgnoreValidationErrors $IgnoreValidationError -RMMigrationReturn $RMMigrationReturn `
        -AccountType "azure"
    $Source, $ShouldExit, $OverrideExistingMigrationWarning, $OverrideExistingMigrationError = $SourceAttributeResult


    if ($ShouldExit) {
        return $RMMigrationReturn
    }

    if ($OverrideExistingMigrationError) {
        $OverrideExistingMigrationError = Read-RMBoolean -UserMessage "Would you like to override the previous migration attempt" -DefaultValue $false
        if (!$IgnoreValidationError -and !$OverrideExistingMigrationError) {
            return $RMMigrationReturn
        }
    }

    $ScheduledAt = Read-RMMigrationSchedule
    $CloudSummary = Get-CloudAttributesSummary -CloudAccount $CloudAccount

    $TargetVMName = Read-RMString -UserMessage "Enter target VM Name" -DefaultValue $Source.host `
        -ParameterName "Target VM Name" -IsRequired $false
    
    $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-RMResourceGroupRegion -CloudAttributeSummary $CloudSummary 
    } else {
        $ExistingOptions = $CloudAttributes.properties.subscriptions.resource_groups.name
        $ResourceGroup = $CloudAccount.appliance.cloud_properties.resource_group
        $ResourceGroup = Read-RMString -UserMessage "Enter existing resource group name" -DefaultValue $ResourceGroup `
            -ParameterName "Resource group name" -IsRequired $false -Options $ExistingOptions
    }

    $VMSize = Read-VMSize -CloudAttributes $CloudAttributes

    $DiskTypeMappings = @()
    $ReturnedValue = Get-DiskTypeMappingBySource -Source $Source -VMSize $VMSize -IsInteractive $true -IsVM $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
    }

    if (0 -eq $Source.attributes.storage.vm_disks.Count) {
        return Update-RMMigrationReturnAsError `
            -Message "Source has no disks, cannot be migrated" -RMMigrationReturn $RMMigrationReturn
    }
    
    $SourceDisksAsString = "Disks to be migrated [" + ((Get-RMVMDiskProperty -Source $Source) -join ", ") + "]"
    Write-Output $SourceDisksAsString | Out-Host

    $SelectedDisk = @()
    $ReturnedValue = Get-RMSelectedDisk -Source $Source
    if ($SelectedDisk -is [hashtable]) {
        $SelectedDisk += $ReturnedValue
    } else {
        $SelectedDisk = $ReturnedValue
    }


    $VirtualNetwork = $CloudAccount.appliance.cloud_properties.virtual_network.name
    $VirtualNetwork = Read-RMString -UserMessage "Enter virtual network name" -DefaultValue $VirtualNetwork `
         -Options $CloudAttributes.properties.subscriptions.regions.networks.name `
         -ParameterName "Virtual Network Name" -IsRequired $false


    $DestinationNetworkOptions = Get-RMDestinationNetworkName -CloudAttributes $CloudAttributes -VirtualNetworkName  $VirtualNetwork
    $DestinationNetworkName = $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name
    if ($VirtualNetwork -ieq $CloudAccount.appliance.cloud_properties.virtual_network.name) {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -DefaultValue $DestinationNetworkName `
            -ParameterName "Destination network name" -IsRequired $false -Options $DestinationNetworkOptions.name
    } else {
        $DestinationNetworkName = Read-RMString -UserMessage "Enter destination network name" -ParameterName "Destination network name" `
             -IsRequired $true -Options $DestinationNetworkOptions.name
    }
   


    $AssignPublicIP = Read-RMBoolean -UserMessage "Auto assign public IP" -DefaultValue $false

    $StaticPrivateIP = Read-RMIPAddress -UserMessage "Enter static private IP to be assigned to target" -DefaultValue "None" `
        -ParameterName "Static private IP" -IsRequired $false


    $EnforceTargetNetworkIsolation = Read-RMBoolean -UserMessage "Enforce target network isolation" -DefaultValue "true"
    $SecurityGroup = $null

    if (!$EnforceTargetNetworkIsolation) {
        $SecurityGroup = Read-RMSecurityGroup -CloudAttribute $CloudAttributes
    }
    
    $ReadValue = Read-RMPair -UserMessage "Enter one or more instance tags in the format 'key=value', separated by commas" `
        -Separator "=" -DefaultValue "None"
    $InstanceTag = Get-RMStringAsHashtable -InputString $ReadValue

    if ("" -ieq $VMSize.availability_zones) {
        $ReadValue = Read-RMString -UserMessage "Enter availability option" -Options "None",  "AvailabilitySet" `
        -DefaultValue "None" -ParameterName "Availability option" -IsRequired $false
    } else {
        $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
                if ($AvailabilitySets.Count -lt 1) {
                    Write-RMError -Message "No existing availability sets in current resource group and location"
                } else {
                    $AvailabilitySet = Read-RMString -UserMessage "Enter the existing availability set name" -Options $AvailabilitySets `
                    -ParameterName "Availability set name" -IsRequired $true
                }
            }
        }
    }

    $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" `
             -Options $CloudAttributes.properties.subscriptions.regions.storage_accounts.name -IsRequired $true `
             -ParameterName "Storage account name" 
    }

    $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 `
            -Options $CloudAttributes.properties.subscriptions.regions.disk_encryption_sets.name
    }

    $EnableHybridUseBenefit = $false
    if("windows" -ieq $Source.os_type) {
        $EnableHybridUseBenefit = Read-RMBoolean -UserMessage "Enable Azure hybrid use benefit" -DefaultValue "false"
    }
    
    $ShutdownSource = Read-RMBoolean -UserMessage "Shutdown source after data is fully migrated" -DefaultValue "false"

    $ShutdownTarget = Read-RMBoolean -UserMessage "Shutdown target after data is fully migrated" -DefaultValue "false"

    $FinalizeMigration = Read-RMBoolean -UserMessage "Removes the snapshot(s) from the target vm in preparation for a cutover" `
        -DefaultValue $false
    
    $ReadValue = Read-RMPair -UserMessage "Enter migration instructions in the format 'key=value' and separated by commas" `
        -Separator "=" -DefaultValue "None"
    if (($IgnoreValidationError -and $OverrideExistingMigrationError) -or (!$IgnoreValidationError -and $OverrideExistingMigrationError) -or $OverrideExistingMigrationWarning) {
        if ("" -eq $ReadValue) {
            $ReadValue = "override_source_migration=true"
        } else {
            $ReadValue += ",override_source_migration=true"
        }
    }
    $MigrationInstruction = Get-RMStringAsHashtable -InputString $ReadValue

    $HashArguments = @{
        CloudAccount = $CloudAccount
        CloudAttribute = $CloudAttributes
        Entitlement = $Entitlement
        Source = $Source
        ScheduledAt = $ScheduledAt
        TargetVMName = $TargetVMName
        TransferMethod = $TransferMethod
        ResourceGroup = $ResourceGroup
        ResourceGroupRegion = $ResourceGroupRegion
        VMSize = $VMSize
        DiskTypeMapping = $DiskTypeMappings
        VolumeType = $VolumeType
        SelectedDisk = $SelectedDisk
        VirtualNetwork = $VirtualNetwork
        DestinationNetworkName = $DestinationNetworkName
        AssignPublicIP = $AssignPublicIP
        StaticPrivateIP = $StaticPrivateIP
        SecurityGroup = $SecurityGroup
        InstanceTag = $InstanceTag
        AvailabilityZone = $AvailabilityZone
        AvailabilitySet = $AvailabilitySet
        FaultDomainCount = $FaultDomainCount
        UpdateDomainCount = $UpdateDomainCount
        EnableBootDiagnostics = $EnableBootDiagnostics
        StorageAccount = $StorageAccount
        EncryptionSet = $EncryptionSet
        EnableHybridUseBenefit = $EnableHybridUseBenefit
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        FinalizeMigration = $FinalizeMigration
        MigrationInstruction = $MigrationInstruction
        IgnoreValidationError = $IgnoreValidationError
    }

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

    $IsScheduled = ![string]::IsNullOrWhiteSpace($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"   
}


function Start-RMNonInteractiveAzureVMBasedMigration {
    param (
        [string] $SourceVMName,
        [string]  $SourceVMFolderPath,
        [string] $TargetCloud,
        [string] $ScheduledAt,
        [string] $TargetVMName,
        [bool] $CreateResourceGroup,
        [string] $ResourceGroup,
        [string] $ResourceGroupRegion,
        [string] $VMSizeName,
        [string[]] $VolumeType,
        [string[]] $SelectedDiskLabel,
        [string] $VirtualNetwork,
        [string] $DestinationNetworkName,
        [bool] $AssignPublicIP,
        [string] $StaticPrivateIP,
        [bool] $EnforceTargetNetworkIsolation,
        [string] $SecurityGroup,
        [string[]] $InstanceTag,
        [string] $AvailabilityOption,
        [int] $AvailabilityZone,
        [bool] $CreateAvailabilitySet,
        [string] $AvailabilitySet,
        [int] $FaultDomain,
        [int] $UpdateDomain,
        [bool] $EnableBootDiagnostics,
        [string] $StorageAccount,
        [string] $DiskEncryptionType,
        [string] $EncryptionSet,
        [bool] $EnableHybridUseBenefit,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $FinalizeMigration,
        [string[]] $MigrationInstruction,
        [bool] $IgnoreValidationError,
        [bool] $OverrideExistingMigration
    )

    $CloudAccount = $null
    $Source = $null
    $Errors = @()
    $ShouldExit = $false
    [RMMigrationReturn] $RMMigrationReturn = [RMMigrationReturn]::new()
    

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


    try {
        if (![string]::IsNullOrWhiteSpace($TargetCloud)) {
            $CloudAccount, $ErrorString = Get-RMCloudAccountByName -CloudAccountName $TargetCloud -AccountType "azure" -VMBasedAppliancesOnly $true
            if (![string]::IsNullOrWhiteSpace($ErrorString)) {
                $Errors += $ErrorString
                $IsSourceAndTargetCloudPresent = $false
            } else {    
                $CloudAttributes = Get-RMCloudAttribute -CloudAccount $CloudAccount
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

    try {
        if (![string]::IsNullOrWhiteSpace($SourceVMName) -and $null -ne $CloudAccount) {
            $Source = Get-RMSourceByVMName -VMName $SourceVMName -SourceVMFolderPath $SourceVMFolderPath `
                        -CloudAccount  $CloudAccount
            if ($Source.collection_type -ine "VM") {
                $Errors += "Source '$SourceVMName' is not a VM-Based source, to migrate this source, please use the cmdlet 'Start-RMGCPOSBasedMigration'."
                Add-RMErrorAndWarning -RMReturnObject $RMMigrationReturn -ErrorMessage $Errors -WarningMessage $Warnings
                Out-RMUserParameterResult -ErrorMessage $Errors -WarningMessage $Warnings
                return $RMMigrationReturn        
            }
        }
    } catch [System.Management.Automation.ItemNotFoundException], [System.Data.DuplicateNameException] {
        $Errors += $PSItem.Exception.Message
        $IsSourceAndTargetCloudPresent = $false
    }

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

    $Entitlement = Get-RMEntitlement

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

    if ($null -ne $Source) {
        if ($null -eq $SelectedDiskLabel -or $SelectedDiskLabel.Count -eq 0) {
            # When the user has not selected any disk labels to migrate,
            # select all for the source.
            $Warnings += "No disk labels have been given, all the disks on the source will be migrated."
            $SelectedDiskLabel = Get-RMVMBasedSourceDiskLabel -Source $Source
            $PSBoundParameters["SelectedDiskLabel"] = $SelectedDiskLabel
        }
    
        $SourceErrors = Confirm-RMAzureVMBasedFullMigrationParameterWithSource -UserParameter $PSBoundParameters -Source $Source
        if ($SourceErrors.Count -gt 0) {
            $Errors += $SourceErrors
        }

        $CloudAttributesSummary = Get-CloudAttributesSummary -CloudAccount $CloudAccount
        $VMSize = Get-VMSizeByName -VMSizeName $VMSizeName -CloudAttributes $CloudAttributes
        $ErrorsCloudAttributes = Confirm-RMCloudAttributes -UserParameter $PSBoundParameters -CloudAccount $CloudAccount `
            -VMSize $VMSize -CloudAttribute $CloudAttributes -CloudAttributesSummary $CloudAttributesSummary
        $Errors += $ErrorsCloudAttributes
    }


    if ([string]::IsNullOrWhiteSpace($ScheduledAt)) {
        $ScheduledAt = ""
    } else {
        $ScheduledAt = Convert-RMDateTimeToUTC -InputDateTime $ScheduledAt
    }

    if ([string]::IsNullOrWhiteSpace($TargetVMName)) {
        $TargetVMName = $Source.host
    }
   
    if ([string]::IsNullOrWhiteSpace($ResourceGroup)) {
        $ResourceGroup = $CloudAccount.appliance.cloud_properties.resource_group
    }
    if ([string]::IsNullOrWhiteSpace($VirtualNetwork)) {
        $VirtualNetwork = $CloudAccount.appliance.cloud_properties.virtual_network.name
    }

    if ([string]::IsNullOrWhiteSpace($DestinationNetworkName)) {
        $DestinationNetworkName = $CloudAccount.appliance.cloud_properties.network.interfaces.eth0.network_name
    }

    if ([string]::IsNullOrWhiteSpace($EncryptionSet)) {
        $EncryptionSet = "platform-managed"
    }

    $AvailabilityZoneString = $null
    if ($AvailabilityZone -gt 0) {
        $AvailabilityZoneString = $AvailabilityZone.ToString()
    }

    $FaultDomainCount = $null 
    if ($FaultDomain -gt 0) {
        $FaultDomainCount = $FaultDomain
    }

    $UpdateDomainCount = $null
    if ($UpdateDomain -gt 0) {
        $UpdateDomainCount = $UpdateDomain
    }

    $SelectedDisk = Get-RMSelectedDiskByDiskLabel -DiskLabel $SelectedDiskLabel -Source $Source

    $InstanceTagsAsHashTable = $null
    try {
        $InstanceTagsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $InstanceTag -ParameterName "InstanceTag"
    } catch {
        $Errors += $PSItem.Exception.Message
    }

    $MigrationInstructionAsHashTable = $null
    try {
        if (($IgnoreValidationError -and $OverrideExistingMigration) -or `
                (!$IgnoreValidationError -and $OverrideExistingMigration) -or `
                $OverrideExistingMigrationWarning) {
            $MigrationInstruction += "override_source_migration=true"
        }
        $MigrationInstructionAsHashTable = Get-RMStringArrayAsHashtable -InputItems $MigrationInstruction -ParameterName "MigrationInstruction"
    } catch {
        $Errors += $PSItem.Exception.Message
    }

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

    $HashArguments = @{
        CloudAccount = $CloudAccount
        CloudAttribute = $CloudAttributes
        Entitlement = $Entitlement
        Source = $Source
        ScheduledAt = $ScheduledAt
        TargetVMName = $TargetVMName
        ResourceGroup = $ResourceGroup
        ResourceGroupRegion = $ResourceGroupRegion
        VMSize = $VMSize
        VolumeType = $VolumeType
        SelectedDisk = $SelectedDisk
        VirtualNetwork = $VirtualNetwork
        DestinationNetworkName = $DestinationNetworkName
        AssignPublicIP = $AssignPublicIP
        StaticPrivateIP = $StaticPrivateIP
        SecurityGroup = $SecurityGroup
        InstanceTag = $InstanceTagsAsHashTable
        AvailabilityZone = $AvailabilityZoneString
        AvailabilitySet = $AvailabilitySet
        FaultDomainCount = $FaultDomainCount
        UpdateDomainCount = $UpdateDomainCount
        EnableBootDiagnostic = $EnableBootDiagnostics
        StorageAccount = $StorageAccount
        EncryptionSet = $EncryptionSet
        EnableHybridUseBenefit = $EnableHybridUseBenefit
        ShutdownSource = $ShutdownSource
        ShutdownTarget = $ShutdownTarget
        FinalizeMigration = $FinalizeMigration
        MigrationInstruction = $MigrationInstructionAsHashTable
        IgnoreValidationError = $IgnoreValidationError
    }

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

    $IsScheduled = ![string]::IsNullOrEmpty($ScheduledAt)
    $MigrationResponse = Invoke-RMMigrationPost -MigrationProfileResponse $Response
    return Update-RMMigrationReturnAsSuccess -MigrationResponse $MigrationResponse `
        -RMMigrationReturn $RMMigrationReturn -IsScheduledMigration $IsScheduled `
        -ReturnMessage "Migration started successfully, migration ID"
}

function 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[]] $VolumeType
    )
    
    $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
    $AzureErrors  = Confirm-RMAzureParameter -UserParameter $UserParameter
    $Errors += $AzureErrors

    if (!$UserParameter.ContainsKey("ResourceGroup") -or [string]::IsNullOrEmpty($UserParameter["ResourceGroup"])) {
        $Errors += "ResourceGroup 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."
    }

    return $Errors, $Warnings, $IsSourceAndTargetCloudPresent
}

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

    $Errors = @()
    $Errors, $Warnings, $IsSourcePresent = Confirm-RMVMBasedCommonParameter -UserParameter $UserParameter
    $AzureErrors = Confirm-RMAzureParameter -UserParameter $UserParameter
    $Errors += $AzureErrors
    return $Errors, $Warnings, $IsSourcePresent
}

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

    $Errors = @()
    if ($UserParameter.ContainsKey("CreateResourceGroup") -and $UserParameter["CreateResourceGroup"]) {
        if (!$UserParameter.ContainsKey("ResourceGroupRegion") -or [string]::IsNullOrEmpty($UserParameter["ResourceGroupRegion"])) {
            $Errors += "ResourceGroupRegion is required, when parameter 'CreateResourceGroup' is true."
        }
    }

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

    if (!$UserParameter.ContainsKey("VolumeType") -or [string]::IsNullOrWhiteSpace($UserParameter["VolumeType"])) {
        $Errors += "VolumeType 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("EnableBootDiagnostics") -and $UserParameter["EnableBootDiagnostics"]) {
        if (!$UserParameter.ContainsKey("StorageAccount") -or [string]::IsNullOrEmpty($UserParameter["StorageAccount"])) {
            $Errors += "StorageAccount is required, when parameter 'EnableBootDiagnostics' is true."
        }
    }

    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
}

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

    $Errors = @()
    $Errors = Confirm-RMCommonParameterWithSource -UserParameter $UserParameter -Source $Source `
        -SourceSQLServerMapping $SourceSQLServerMapping

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

    return $Errors
}

function Confirm-RMAzureVMBasedFullMigrationParameterWithSource {
    param (
        [hashtable] $UserParameter,
        [System.Object] $Source
    )
    
    $Errors = @()
    $SourceDiskLabels = Get-RMVMBasedSourceDiskLabel -Source $Source
    $DiskLabelsNotFound = @()
    foreach ($DiskLabel in $UserParameter["SelectedDiskLabel"]) {
        if ($SourceDiskLabels -notcontains $DiskLabel) {
            $DiskLabelsNotFound += $DiskLabel
        }
    }

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

    if ($UserParameter.ContainsKey("VolumeType") -and ![string]::IsNullOrWhiteSpace($UserParameter["VolumeType"])) {
        $ErrorString = Confirm-RMVMBasedVolumeType -SelectedDiskCount $UserParameter["SelectedDiskLabel"] -VolumeType $UserParameter["VolumeType"]
        if (![string]::IsNullOrEmpty($ErrorString)) {
            $Errors += $ErrorString
        }
    }

    
    return $Errors
}

function Confirm-RMCloudAttributes {
    param (
        [hashtable] $UserParameter,
        [System.Object] $CloudAccount,
        [System.Object] $VMSize,
        [System.Object] $CloudAttribute,
        [System.Object] $CloudAttributesSummary
    )
    
    $Errors = @()

    if ($UserParameter.ContainsKey("CreateResourceGroup") -and $UserParameter["CreateResourceGroup"] -and `
        $UserParameter.ContainsKey("ResourceGroupRegion") -and ![string]::IsNullOrWhiteSpace($UserParameter["ResourceGroupRegion"])) {
            
        $ResourceGroupRegionIsValid = Confirm-RMResourceGroupRegion -CloudAttributesSummary $CloudAttributesSummary `
            -ResourceGroupRegionName $UserParameter["ResourceGroupRegion"]
        if (!$ResourceGroupRegionIsValid) {
            $Errors += "Invalid ResourceGroupRegion"
        }
    }

    if ($UserParameter.ContainsKey("ResourceGroup") -and ![string]::IsNullOrWhiteSpace($UserParameter["ResourceGroup"])) {
        $ResourceGroupIsValid = Confirm-RMResourceGroup -CloudAttribute $CloudAttribute -ResourceGroup $UserParameter["ResourceGroup"]
        if (!$ResourceGroupIsValid) {
            $Errors += "Invalid ResourceGroup"
        }
    }

    if ($UserParameter.ContainsKey("VMSizeName") -and ![string]::IsNullOrWhiteSpace($UserParameter["VMSizeName"])) {
        $VMSizeIsValid = Confirm-RMVMSize -CloudAttribute $CloudAttribute -VMSize $UserParameter["VMSizeName"]
        if (!$VMSizeIsValid) {
            $Errors += "Invalid VMSizeName"
        }
    }

    $DefaultVirtualNetwork = $CloudAccount.appliance.cloud_properties.virtual_network.name
    if ($UserParameter.ContainsKey("VirtualNetwork") -and ![string]::IsNullOrWhiteSpace($UserParameter["VirtualNetwork"])) {
        if ($DefaultVirtualNetwork -ieq $UserParameter["VirtualNetwork"]) {
            if ($UserParameter.ContainsKey("DestinationNetworkName") -and `
                ![string]::IsNullOrWhiteSpace($UserParameter["DestinationNetworkName"])) {
                $DestinationNetworkNameIsValid =  Confirm-RMDestinationNetworkName -CloudAttribute $CloudAttribute `
                    -VirtualNetwork $UserParameter["VirtualNetwork"] -DestinationNetworkName $UserParameter["DestinationNetworkName"]
                if (!$DestinationNetworkNameIsValid) {
                    $Errors += "Invalid DestinationNetworkName"
                }  
            }
        } else {
            if ($UserParameter.ContainsKey("DestinationNetworkName") -and `
                ![string]::IsNullOrWhiteSpace($UserParameter["DestinationNetworkName"])) {
                $DestinationNetworkNameIsValid =  Confirm-RMDestinationNetworkName -CloudAttribute $CloudAttribute `
                    -VirtualNetwork $UserParameter["VirtualNetwork"] -DestinationNetworkName $UserParameter["DestinationNetworkName"]
                if (!$DestinationNetworkNameIsValid) {
                    $Errors += "Invalid DestinationNetworkName"
                }  
            } else {
                $Errors += "DestinationNetworkName is required"
            }
        }
    }
    
    if ($UserParameter.ContainsKey("SecurityGroup") -and ![string]::IsNullOrWhiteSpace($UserParameter["SecurityGroup"])) {
        $SecurityGroupIsValid = Confirm-RMSecurityGroup -CloudAttribute $CloudAttribute -SecurityGroup $UserParameter["SecurityGroup"]
        if (!$SecurityGroupIsValid) {
            $Errors += "Invalid SecurityGroup"
        }
    }

    if ($UserParameter.ContainsKey("AvailabilityOption") -and ![string]::IsNullOrWhiteSpace($UserParameter["AvailabilityOption"])) {
        if ("AvailabilityZone" -ieq $UserParameter["AvailabilityOption"] -and `
            "" -ieq $VMSize.availability_zones) {
            $Errors += "AvailabilityZone is not available in the current resource group and location"
        }
        
        if ("AvailabilitySet" -ieq $UserParameter["AvailabilityOption"] -and $ResourceGroupIsValid -and `
            (!$UserParameter.ContainsKey("CreateAvailabilitySet") -or !$UserParameter["CreateAvailabilitySet"]) -and `
            $UserParameter.ContainsKey("AvailabilitySet") -and ![string]::IsNullOrWhiteSpace($UserParameter["AvailabilitySet"])) {

                $AvailabilitySets = Get-RMAvailabilitySet -CloudAttributes $CloudAttribute -ResourceGroup $UserParameter["ResourceGroup"]
                if ($AvailabilitySets.Count -lt 1) {
                    $Errors += "No existing 'AvailabilitySet' in current resource group and location"
                } else {
                    $AvailabilitySetIsValid = Confirm-RMAvailabilitySet -AvailabilitySet $AvailabilitySets -AvailabilitySetName
                    if (!$AvailabilitySetIsValid) {
                        $Errors += "Invalid AvailabilitySet"
                    }
                }
        }
    }

    if ($UserParameter.ContainsKey("StorageAccount") -and ![string]::IsNullOrWhiteSpace($UserParameter["StorageAccount"])) {
        $StorageAccountIsValid = Confirm-RMStorageAccount -CloudAttribute $CloudAttribute -StorageAccount $UserParameter["StorageAccount"]
        if (!$StorageAccountIsValid) {
            $Errors += "Invalid StorageAccount"
        }
    }

    if ($UserParameter.ContainsKey("EncryptionSet") -and ![string]::IsNullOrWhiteSpace($UserParameter["EncryptionSet"])) {
        $EncryptionSetIsValid = Confirm-RMEncryptionSet -CloudAttribute $CloudAttribute -EncryptionSet $UserParameter["EncryptionSet"]
        if (!$EncryptionSetIsValid) {
            $Errors += "Invalid EncryptionSet"
        }
    }

    return $Errors
}

function Confirm-RMResourceGroupRegion {
    param (
        [System.Object] $CloudAttributesSummary,
        [string] $ResourceGroupRegion
    )

    
    $ResourceGroupRegionIsValid = $true
    if ($CloudAttributeSummary.properties.subscriptions.regions.label -notcontains $ResourceGroupRegion) {
        $ResourceGroupRegionIsValid = $false
    }
    return $ResourceGroupRegionIsValid
}

function Confirm-RMResourceGroup {
    param (
        [System.Object] $CloudAttribute,
        [string] $ResourceGroup
    )

    $ResourceGroupRegionIsValid = $true
    if ($CloudAttribute.properties.subscriptions.resource_groups.name -notcontains $ResourceGroup) {
        $ResourceGroupRegionIsValid = $false
    }
    return $ResourceGroupRegionIsValid
}

function Confirm-RMAvailabilitySet {
    param (
        [string[]] $AvailabilitySet,
        [string] $AvailabilitySetName
    )

    $AvailabilitySetIsValid = $true
    if ($AvailabilitySet -notcontains $AvailabilitySetName) {
        $AvailabilitySetIsValid = $false
    }
    return $AvailabilitySetIsValid
    
}

function Confirm-RMVMSize {
    param (
        [System.Object] $CloudAttribute,
        [string] $VMSize
    )
    
    $VMSizeIsValid = $true
    if ($CloudAttributes.properties.subscriptions.regions.vm_sizes.name -notcontains $VMSize) {
        $VMSizeIsValid = $false
    }
    return $VMSizeIsValid
}

function Confirm-RMVirtualNetowrok {
    param (
        [System.Object] $CloudAttribute,
        [string] $VirtualNetwork
    )

    $VirtualNetworkIsValid = $true
    if ($CloudAttribute.properties.subscriptions.regions.networks.name -notcontains $VirtualNetwork) {
        $VirtualNetworkIsValid = $false
    }
    return  $VirtualNetworkIsValid
    
}

function Confirm-RMDestinationNetworkName {
    param (
        [System.Object] $CloudAttribute,
        [string] $VirtualNetwork,
        [string] $DestinationNetworkName
    )

    $DestinationNetworkNameIsValid = $true
    $DestinationNetworkArray = Get-RMDestinationNetworkName -CloudAttributes $CloudAttribute `
        -VirtualNetworkName  $VirtualNetwork
    if ($DestinationNetworkArray.name -notcontains $DestinationNetworkName) {
        $DestinationNetworkNameIsValid = $false
    }
    return $DestinationNetworkNameIsValid
    
}

function Confirm-RMSecurityGroup {
    param(
        [System.Object] $CloudAttribute,
        [string] $SecurityGroup
    )

    $SecurityGroupIsValid = $true
    if ($CloudAttribute.properties.subscriptions.regions.security_groups.name -notcontains $SecurityGroup) {
        $SecurityGroupIsValid = $false
    }
    return $SecurityGroupIsValid
}

function Confirm-RMStorageAccount {
    param(
        [System.Object] $CloudAttribute,
        [string] $StorageAccount
    )

    $StorageAccountIsValid = $true
    if ($CloudAttributes.properties.subscriptions.regions.storage_accounts.name -notcontains $StorageAccount) {
        $StorageAccountIsValid = $false
    }
    return $StorageAccountIsValid
}

function Confirm-RMEncryptionSet {
    param (
        [System.Object] $CloudAttribute,
        [string] $EncryptionSet
    )

    $EncryptionSetIsValid = $true
    if ($CloudAttributes.properties.subscriptions.regions.disk_encryption_sets.name -notcontains $EncryptionSet) {
        $EncryptionSetIsValid = $false
    }
    return $EncryptionSetIsValid
}

function Confirm-RMVMBasedVolumeType {
    param (
        [string[]] $SelectedDiskCount,
        [string[]] $VolumeType
    )
    
    $VolumeTypeCnt = $VolumeType.Count
    $DisksCnt = $SelectedDiskCount.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 Read-VMSize {
    param(
        [System.Object] $CloudAttributes
    )
    while ($true) {
        $VMSizeName = Read-RMString -UserMessage "Enter VM Size name, e.g. Standard_A7" -ParameterName "VM Size" `
            -IsRequired $true

        $VMSize = Get-VMSizeByName -VMSizeName $VMSizeName -CloudAttributes $CloudAttributes
        if ($null -eq $VMSize) {
            Write-RMError -Message "VM size '$VMSizeName' does not exist"
            continue
        }
        return $VMSize
    }
}

function Get-RMDestinationNetworkName {
    param (
        [System.Object] $CloudAttributes,
        [string] $VirtualNetworkName
    )

    foreach ($VirtualNetwork in $CloudAttributes.properties.subscriptions.regions.networks) {
        if ($VirtualNetworkName -ieq $VirtualNetwork.name) {
            return $VirtualNetwork.subnets
        }
    }

    return $null
}

Export-ModuleMember -Function Start-RMAzureOSBasedInteractiveMigration, Start-RMAzureOSBasedNonInteractiveMigration, `
Start-RMInteractiveAzureVMBasedMigration, Start-RMNonInteractiveAzureVMBasedMigration