MigrationProfile/AzureMigrationProfile.psm1

Import-Module -Name @(Join-Path  $PSScriptRoot .. | Join-Path -ChildPath Util | Join-Path -ChildPath Util)
function New-RMAzureMigrationProfile {
    param(
        [Parameter(Mandatory)]
        [System.Object] $CloudAccount,

        [Parameter(Mandatory)]
        [System.Object] $CloudAttributes,

        [Parameter(Mandatory)]
        [System.Object] $Entitlement,

        [Parameter(Mandatory)]
        [System.Object] $Source,

        [Parameter(Mandatory)]
        [System.Object] $ScheduledAt,

        [Parameter(Mandatory)]
        [string] $TargetVMName,

        [Parameter(Mandatory)]
        [string] $TransferMethod,

        [Parameter(Mandatory)]
        [string] $ResourceGroup,

        [string] $ResourceGroupRegion,

        [Parameter(Mandatory)]
        [System.Object] $VMSize,

        [System.Object] $DiskTypeMappings,

        [string[]] $VolumeType,

        [Parameter(Mandatory)]
        [System.Object[]] $MountPoints,

        [Parameter(Mandatory)]
        [hashtable] $ResizeMountPoints,

        [hashtable] $ConvertFileSystem,

        [string] $MigrationExtension,

        [string] $MigrationExtensionId,

        [string] $MigrationExtensionOSM,

        [string] $MigrationExtensionIdOSM,

        [Parameter(Mandatory)]
        [string] $VirtualNetwork,

        [Parameter(Mandatory)]
        [string] $DestinationNetworkName,

        [Parameter(Mandatory)]
        [bool] $AssignPublicIP,

        [string] $StaticPrivateIP,

        [string] $SecurityGroup,

        [Parameter(Mandatory)]
        [bool] $DisableTargetDNSRegistration,

        [hashtable] $InstanceTags,

        [string] $AvailabilityZone,

        [string] $AvailabilitySet,

        [System.Object] $FaultDomainCount,

        [System.Object] $UpdateDomainCount,

        [Parameter(Mandatory)]
        [bool] $EnableBootDiagnostics,

        [string] $StorageAccount,

        [string] $EncryptionSet,

        [Parameter(Mandatory)]
        [bool] $EnableHybridUseBenefit,

        [Parameter(Mandatory)]
        [bool] $InPlaceUpgrade,

        [System.Object] $UpgradeOSVersion,

        [System.Object] $SQLServerUpgrade,

        [Parameter(Mandatory)]
        [bool] $ShutdownSource,

        [Parameter(Mandatory)]
        [bool] $ShutdownTarget,

        [Parameter(Mandatory)]
        [bool] $RemoveRMSAgentFromSource,

        [Parameter(Mandatory)]
        [bool] $RemoveRMSAgentFromTarget,

        [hashtable] $MigrationInstructions,

        [bool] $IgnoreValidationErrors,

        [string] $MTUSize,

        [string] $NetBIOSName,

        [system.Object] $KMSKey,

        [bool] $GeneralizeSystem
    )

    if ("" -ne $ResourceGroupRegion) {
        $ReturnedValue = Get-ResourceGroupRegionByUserInput -ResourceGroupRegion $ResourceGroupRegion -CloudAccount $CloudAccount
        if ($null -eq $ReturnedValue) {
            throw "Region '$ResourceGroupRegion' does not exist."
        } else {
            $ResourceGroupRegion = $ReturnedValue
        }
    }

    if ($null -ieq $ConvertFileSystem) {
        $ConvertFileSystem = @{}
    }

    $license_type = $null
    if ($EnableHybridUseBenefit) {
        $license_type = "Windows_Server"
    }

    $EncryptionSetId = $null
    if ("" -ne $EncryptionSet) {
        $EncryptionSetId = Get-DiskEncryptionSetIdByName -EncryptionSetName $EncryptionSet -CachedCloudAttributes $CloudAttributes
    }

    $MigrationExtensionIdArray = @() 
    if (![string]::IsNullOrEmpty($MigrationExtensionId)) {
        $MigrationExtensionIdArray +=  $MigrationExtensionId
    }

    $MigrationExtensionArray = @()
    if (![string]::IsNullOrEmpty($MigrationExtension)) {
        $MigrationExtensionArray +=  $MigrationExtension
    }

    $MigrationExtensionIdOSMArray = @() 
    if (![string]::IsNullOrEmpty($MigrationExtensionIdOSM)) {
        $MigrationExtensionIdOSMArray +=  $MigrationExtensionIdOSM
    }

    $MigrationExtensionOSMArray = @()
    if (![string]::IsNullOrEmpty($MigrationExtensionOSM)) {
        $MigrationExtensionOSMArray +=  $MigrationExtensionOSM
    }

    $MTUSizeObject = $null
    if (![string]::IsNullOrEmpty($MTUSize)) {
        $MTUSizeObject = $MTUSize
    }


    if ([string]::IsNullOrEmpty($VolumeType)) {
        # For the interactive case, take volume type of the first disk - this is what the UI is doing.
        $VolumeTypeString = $DiskTypeMappings[0]["type"]
    } else {
        $DiskTypeMappings = @()
        $ReturnedValue = Get-DiskTypeMappingBySource -Source $Source -VolumeType $VolumeType -IsInteractive $false
        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
        }
        $VolumeTypeString = $DiskTypeMappings[0]["type"]
    }

    $VirtualNetworkResourceGroup = $null
    $VirtualNetworkResourceGroup = Get-VirtualNetworkResourceGroup -CachedCloudAttributes $CloudAttributes -VirtualNetworkName $VirtualNetwork
    if ($null -eq $VirtualNetworkResourceGroup) {
        throw "Virtual network '$VirtualNetwork' does not exists, cannot start migration"
    }

    $SecurityGroupNode = $null
    if ("" -ne $SecurityGroup -and $null -ne $SecurityGroup) {
        $SecurityGroupNode = Get-SecurityGroup -SecurityGroupName $SecurityGroup -CachedCloudAttributes $CloudAttributes
        if ($null -eq $SecurityGroupNode) {
            throw "Security group '$SecurityGroup' does not exists, cannot start migration"
        }
    }

    if (!$VMSize.vmsizeSupported) {
        $Name = $VMSize.name
        throw "RiverMeadow does not support VM size '$Name', please use a different VM size and try again."
    }

    if ("" -ne $AvailabilityZone -and $VMSize.availability_zones -notcontains $AvailabilityZone) {
        $Name = $VMSize.name
        throw "The VM size '$Name' does not support the availability zone $AvailabilityZone"
    }

    $AvailabilitySetHash = @{}
    if ($null -eq $FaultDomainCount -or $null -eq $UpdateDomainCount) {
        $AvailabilitySetHash.Add("name", $AvailabilitySet)
    } else {
        $AvailabilitySetHash.Add("name", $AvailabilitySet)
        $AvailabilitySetHash.Add("fault_domain_count", $FaultDomainCount)
        $AvailabilitySetHash.Add("update_domain_count", $UpdateDomainCount)
    }
    
    if ([string]::IsNullOrEmpty($ScheduledAt)) {
        # To run "now", FE requires that the $ScheduledAt to be null
        $ScheduledAt = $null
    }

    $SQLServerUpgradeAsArray = @()
    if($null -ne $SQLServerUpgrade ) { 
        $SQLServerUpgradeAsArray += $SQLServerUpgrade
    } 

    $CurrentTime = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    $MigrationProfile = @{
        "name"= "powershell-" + $Source.host + "-" + $CurrentTime
        "tags"= @()
        "entitlement"=$Entitlement.id
        "cloud_account"=$CloudAccount.id
        "is_data_only"= $false
        "is_vdi"= $false
        "appliance_id"= $CloudAccount.appliance.id
        "schedule"= $ScheduledAt
        "schedule_delta"= 0
        "transfer_mode"= "run-once"
        "sources"= @(
            @{
                "source"= $Source.id
                "transfer_type"= $TransferMethod
                "target_config"= @{
                    "vm_details"= @{
                        "vm_name"= $TargetVMName
                        "vapp_name"= $TargetVMName
                        "tags"= $InstanceTags
                        "resource_group"= $ResourceGroup
                        "flavor"= @{
                            "flavor_type"= $VMSize.name
                            "volume_type"= $VolumeTypeString
                        }
                        "kms_fqdn" = $KMSKey
                        "enable_accelerated_networking"= $VMSize.enable_accelerated_networking
                        "disk_type_mappings"= $DiskTypeMappings
                        "license_type"= $license_type
                        "availability_set"= $AvailabilitySetHash
                        "availability_zone_id"= $AvailabilityZone
                        "enable_boot_diagnostics"= $EnableBootDiagnostics
                        "boot_diagnostics_storage_account"= $StorageAccount
                        "disk_encryption_set_id"= $EncryptionSetId
                    }
                    "properties"= @{
                        "network"= @{
                            "interfaces"= @{
                                "eth0"= @{
                                    "ip_type"= "dhcp"
                                    "mtu"= $MTUSizeObject
                                    "type"= "Ethernet"
                                    "network_name"= $DestinationNetworkName
                                    "vnet"= $VirtualNetwork
                                    "assign_public_ip"= $AssignPublicIP
                                    "ip_addr"= $StaticPrivateIP
                                    "resource_group"= $VirtualNetworkResourceGroup
                                    "security_group"= $SecurityGroupNode
                                }
                            }
                            "automatic_dns_registration"= !$DisableTargetDNSRegistration
                        }
                        "hostname" = $NetBIOSName
                        "selected_mounts"= $MountPoints
                        "name"= $TargetVMName
                        "mounts_new_size"= $ResizeMountPoints
                        "convert_filesystems"= $ConvertFileSystem
                    }
                    "options"= @{
                        "region"= $CloudAttributes.properties.subscriptions[0].regions[0].name
                        "resource_group_region"= $ResourceGroupRegion
                        "subscription_id"= $CloudAttributes.properties.subscriptions[0].id
                        "network"= @{
                            "name"= $VirtualNetwork
                            "resource_group"= $VirtualNetworkResourceGroup
                        }
                    }
                }
                "os_type"= $Source.os_type
                "name"= $Source.hostname
                "collection_type"= $null
                "generalize_system" = $GeneralizeSystem
                "drive_mapping" = @()
                "binary_asset_groups"= @(
                    @{
                        "binary_assets" = $MigrationExtensionIdArray
                    }
                    @{
                        "binary_asset_names" = $MigrationExtensionArray
                    }
                )
                "binary_asset_groups_osm"= @(
                    @{
                        "osm_assets" = $MigrationExtensionIdOSMArray
                    }

                    @{
                        "osm_asset_names" = $MigrationExtensionOSMArray
                    }
                )
                "product_upgrades" = $SQLServerUpgradeAsArray
                "shutdown_source"= $ShutdownSource
                "shutdown_target"= $ShutdownTarget
                "remove_source_agent" = $RemoveRMSAgentFromSource
                "remove_target_agent"= $RemoveRMSAgentFromTarget
                "in_place_upgrade"= $InPlaceUpgrade
                "upgrade_os_version"= $UpgradeOSVersion
                "migration_instructions"= $MigrationInstructions
                "data_transfer_port"= $null
                "ignore_validation_errors"= $IgnoreValidationErrors
                "preflight_warning"= $false
           }
        )
    }

    # Giving max depth otherwise the cmdlet 'ConvertTo-Json' will truncate the JSON
    $MigrationProfileJson = $MigrationProfile |ConvertTo-Json -Depth 100

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

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

    $Params = @{
        Method = "Post"
        Uri = $Uri + "/migrationprofiles"
        Body = $MigrationProfileJson
        ContentType = "application/json"
        Headers = $Headers
    }

    Invoke-RMRestMethod -Params $Params
}

function New-RMAzureVMBasedMigrationProfile {
    param (
    
        [System.Object] $CloudAccount,
        [System.Object] $CloudAttribute,
        [System.Object] $Entitlement,
        [System.Object] $Source,
        [System.Object] $ScheduledAt,
        [string] $TargetVMName,
        [string] $ResourceGroup,
        [string] $ResourceGroupRegion,
        [System.Object] $VMSize,
        [System.Object] $DiskTypeMapping,
        [string[]] $VolumeType,
        [System.Object[]] $SelectedDisk,
        [string] $VirtualNetwork,
        [string] $DestinationNetworkName,
        [bool] $AssignPublicIP,
        [string] $StaticPrivateIP,
        [string] $SecurityGroup,
        [hashtable] $InstanceTag,
        [string] $AvailabilityZone,
        [string] $AvailabilitySet,
        [System.Object] $FaultDomainCount,
        [System.Object] $UpdateDomainCount,
        [bool] $EnableBootDiagnostic,
        [string] $StorageAccount,
        [string] $EncryptionSet,
        [bool] $EnableHybridUseBenefit,
        [string] $MigrationExtension,
        [string] $MigrationExtensionId,
        [bool] $ShutdownSource,
        [bool] $ShutdownTarget,
        [bool] $FinalizeMigration,
        [hashtable] $MigrationInstruction,
        [bool] $IgnoreValidationError
    )

    $AvailabilitySetHash = @{}
    if ($null -eq $FaultDomainCount -and $null -eq $UpdateDomainCount) {
        $AvailabilitySetHash.Add("name", $AvailabilitySet)
    } else {
        $AvailabilitySetHash.Add("name", $AvailabilitySet)
        $AvailabilitySetHash.Add("fault_domain_count", $FaultDomainCount)
        $AvailabilitySetHash.Add("update_domain_count", $UpdateDomainCount)
    }
    $VirtualNetworkResourceGroup = Get-VirtualNetworkResourceGroup -CachedCloudAttributes $CloudAttribute -VirtualNetworkName $VirtualNetwork

    $EncryptionSetId = $null
    if ("" -ne $EncryptionSet) {
        $EncryptionSetId = Get-DiskEncryptionSetIdByName -EncryptionSetName $EncryptionSet -CachedCloudAttributes $CloudAttribute
    }
    
    if ([string]::IsNullOrEmpty($ScheduledAt)) {
        # To run "now", FE requires that the $ScheduledAt to be null
        $ScheduledAt = $null
    }

    if ([string]::IsNullOrEmpty($VolumeType)) {
        # For the interactive case, take volume type of the first disk - this is what the UI is doing.
        $VolumeTypeString = $DiskTypeMapping[0]["type"]
    } else {
        $DiskTypeMapping = @()
        $ReturnedValue = Get-DiskTypeMappingBySource -Source $Source -VolumeType $VolumeType -IsInteractive $false -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
            $DiskTypeMapping += $ReturnedValue
        } else {
            $DiskTypeMapping = $ReturnedValue
        }
        $VolumeTypeString = $DiskTypeMapping[0]["type"]
    }

    $SecurityGroupNode = $null
    if (![string]::IsNullOrWhiteSpace($SecurityGroup)) {
        $SecurityGroupNode = Get-SecurityGroup -SecurityGroupName $SecurityGroup -CachedCloudAttributes $CloudAttribute
    }

    $LicenseType = $null
    if($EnableHybridUseBenefit) {  
        $LicenseType = "Windows_Server" 
    }

    $MigrationExtensionIdArray = @() 
    if (![string]::IsNullOrEmpty($MigrationExtensionId)) {
        $MigrationExtensionIdArray +=  $MigrationExtensionId
    }

    $MigrationExtensionArray = @()
    if (![string]::IsNullOrEmpty($MigrationExtension)) {
        $MigrationExtensionArray +=  $MigrationExtension
    }

    $CurrentTime = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    $MigrationProfile = @{
        "name"= "powershell-" + $Source.host + "-" + $CurrentTime
        "tags"= @()
        "entitlement" = $Entitlement.id
        "cloud_account" = $CloudAccount.id
        "is_data_only" = $false
        "is_vdi" = $false
        "appliance_id" = $CloudAccount.appliance.id
        "schedule" = $ScheduledAt
        "transfer_mode"= "run-once"
        "sources"= @(
            @{
                "source" = $Source.id
                "target_config"= @{
                    "vm_details"= @{
                        "vm_name" = $TargetVMName
                        "vapp_name" = $TargetVMName
                        "tags" = $InstanceTag
                        "resource_group"= $ResourceGroup
                        "flavor"= @{
                            "flavor_type" = $VMSize.name
                            "volume_type" = $VolumeTypeString
                        }
                        "enable_accelerated_networking" = $VMSize.enable_accelerated_networking
                        "disk_type_mappings" = $DiskTypeMapping
                        "availability_set" = $AvailabilitySetHash
                        "availability_zone_id" = $AvailabilityZone
                        "enable_boot_diagnostics" = $EnableBootDiagnostic
                        "boot_diagnostics_storage_account" = if ([string]::IsNullOrWhiteSpace($StorageAccount)) {$null} else {$StorageAccount}
                        "disk_encryption_set_id" = $EncryptionSetId
                        "license_type" = $LicenseType
                    }
                    "properties" = @{
                        "network" = @{
                            "interfaces" = @{
                                "eth0" = @{
                                    "ip_type" = "dhcp"
                                    "type" = "Ethernet"
                                    "network_name" = $DestinationNetworkName
                                    "vnet" = $VirtualNetwork
                                    "assign_public_ip" = $AssignPublicIP
                                    "ip_addr" = $StaticPrivateIP
                                    "resource_group" = $VirtualNetworkResourceGroup
                                    "security_group" = $SecurityGroupNode
                                }
                            }
                        }
                        "selected_mounts" = $SelectedDisk
                        "name" = $TargetVMName
                        "mounts_new_size" = @{}
                    }
                    "options" = @{
                        "region" = $CloudAttribute.properties.subscriptions[0].regions[0].name
                        "resource_group_region" = $ResourceGroupRegion
                        "subscription_id" = $CloudAttribute.properties.subscriptions[0].id
                        "network" = @{
                            "name" = $VirtualNetwork
                            "resource_group" = $VirtualNetworkResourceGroup
                        }
                    }
                }
                "os_type" = $Source.os_type
                "name" = $Source.host
                "collection_type" = "vm"
                "binary_asset_groups"= @(
                    @{
                        "binary_assets" = $MigrationExtensionIdArray
                    }
                    @{
                        "binary_asset_names" = $MigrationExtensionArray
                    }
                )
                "shutdown_source" = $ShutdownSource
                "shutdown_target" = $ShutdownTarget
                "finalize_target" = $FinalizeMigration
                "in_place_upgrade" = $false
                "upgrade_os_version"= $null
                "migration_instructions" = $MigrationInstruction
                "ignore_validation_errors" = $IgnoreValidationError
                "preflight_warning" = $false
           }
        )
    }

    return Invoke-RMMigrationProfilePost -MigrationProfile $MigrationProfile  
}

function Get-VolumeType {
    param(
        [string] $VolumeType
    )
    $VolumeTypes = @{
        "Standard_SSD" = "StandardSSD_LRS"
        "Standard_HDD" = "Standard_LRS"
        "Premium_SSD" = "Premium_LRS"
    }

    return $VolumeTypes[$VolumeType]
}

function Get-DiskEncryptionSetIdByName {
    param(
        [string] $EncryptionSetName,
        [System.Object] $CachedCloudAttributes
    )
    $DiskEncryptionSets = $CachedCloudAttributes.properties.subscriptions[0].regions[0].disk_encryption_sets
    foreach ($DiskEncryptionSet in $DiskEncryptionSets) {
        if ($DiskEncryptionSet.name -ieq $EncryptionSetName) {
            return $DiskEncryptionSet.id
        }
    }

    return $null
}

function Get-DiskTypeMappingBySource {
    param(
        [System.Object] $Source,
        [System.Object] $VMSize,
        [string[]] $VolumeType,
        [bool] $IsInteractive,
        [bool] $IsVM
    )

    $DiskTypeMappings = @()
    if ($IsVM) {
        $SortedDisks = $Source.attributes.storage.vm_disks
    } else {
        $SortedDisks =  $Source.attributes.storage.disks.psobject.Properties.Value | Sort-Object -Property device
    }

    $Index = 0
    $DiskIndex = 0
    foreach ($Disk in  $SortedDisks) {
        if ($IsInteractive) {
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $Type = ""
            if ($VMSize.premium_disk_support) {
                $Type = Read-RMString -UserMessage "Enter the volume type for disk with size $Size GiB" `
                    -Options "Standard_SSD", "Standard_HDD", "Premium_SSD" -ParameterName "Volume type" `
                    -DefaultValue "Standard_SSD" -IsRequired $false
            } else {
                $Type = Read-RMString -UserMessage "Enter the volume type for disk with size $Size GiB" `
                -Options "Standard_SSD", "Standard_HDD" -ParameterName "Volume type" `
                -DefaultValue "Standard_SSD" -IsRequired $false
            }
            $Type = Get-VolumeType -VolumeType $Type
        } else {
            if ($VolumeType.Count -gt 1) {
                $Type =  Get-VolumeType -VolumeType $VolumeType[$Index] 
                $Index++
            } else {
                $Type = Get-VolumeType -VolumeType $VolumeType[0]
            }
            
        }
        if ($IsVM) {
            $DiskMapping = @{
                "source_disk_identifier" = "$DiskIndex"
                "type" = $Type
            }
            $DiskIndex ++
        } else {
            $DiskMapping = @{
                "source_disk_identifier" = $Disk.device
                "type" = $Type
            }
        }
       
        $DiskTypeMappings += $DiskMapping
    }
    return $DiskTypeMappings
}

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

    foreach($Network in $CachedCloudAttributes.properties.subscriptions[0].regions[0].networks) {
        if ($Network.name -ieq $VirtualNetworkName) {
            return $Network.resource_group
        }
    }

    return $null
}

function Get-SecurityGroup {
    param(
        [string] $SecurityGroupName,
        [System.Object] $CachedCloudAttributes
    )
    if ("" -eq $SecurityGroup) {
        return $null
    }

    foreach($SecurityGroup in $CachedCloudAttributes.properties.subscriptions[0].regions[0].security_groups) {
        if ($SecurityGroup.name -eq $SecurityGroupName) {
            $Result = @{
                "name" = $SecurityGroupName
                "resource_group" = $SecurityGroup.resource_group
            }
            return $Result
        }
    }

    return $null
}

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

    foreach($VMSize in $CloudAttributes.properties.subscriptions[0].regions[0].vm_sizes) {
        if ($VMSize.name -ieq $VMSizeName) {
            return $VMSize
        }
    }

    return $null
}

function Get-ResourceGroupRegionByUserInput {
    param(
        [string] $ResourceGroupRegion,
        [System.Object] $CloudAccount
    )
    $CloudAttributesSummary = Get-CloudAttributesSummary -CloudAccount $CloudAccount
    foreach ($Subscription in $CloudAttributesSummary.properties.subscriptions) {
        if (-not($Subscription.id -eq $CloudAccount.appliance.cloud_properties.subscription_id)) {
            continue
        }
        foreach ($Region in $Subscription.regions) {
            if ($Region.name -eq $ResourceGroupRegion -or $Region.label -eq $ResourceGroupRegion) {
                return $Region.name
            }
        }
    }
    return $null
} 

Export-ModuleMember -Function New-RMAzureMigrationProfile, New-RMAzureVMBasedMigrationProfile, Get-VMSizeByName, Get-DiskTypeMappingBySource