Migration/vSphere/vSphereUtil.psm1

using module './RiverMeadow.VSphereVMTag'
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 .. | Join-Path -ChildPath .. | Join-Path -ChildPath Util `
    | Join-Path -ChildPath Util)

function Get-RMVSphereTagCategory {
    param(
        [System.Object] $TargetInventory
    )
    $TagCategories = @()
    $TagCategoryAsHashtable = @{}
    foreach ($TagCategory in $TargetInventory.attributes.vsphere.tag_categories) {
        $RMTagCategory = [RMVSphereTagCategory]::new($TagCategory.name, $TagCategory.identifier)
        foreach ($Tag in $TagCategory.tags) {
            $RMTag = $RMTagCategory.AddTag($Tag.name, $Tag.identifier)
            if ($TagCategoryAsHashtable.ContainsKey($TagCategory.name)) {
                $TagCategoryAsHashtable[$TagCategory.name] += $RMTag
            } else {
                $TagCategoryAsHashtable.Add($TagCategory.name, @($RMTag))
            }
        }
        $TagCategories += $RMTagCategory
    }

    return $TagCategories, $TagCategoryAsHashtable
}

function Get-RMTagOption {
    param(
        [RMVSphereTagCategory[]] $TagCategory
    )
    $TagOptions = @()
    foreach ($Category in $TagCategory) {
        foreach ($Tag in $Category.Tags) {
            $TagOption = $Tag.Name + "[" + $Category.Name + "]"
            $TagOptions += $TagOption
        }
    }

    return $TagOptions
}

function Get-RMVMTag {
    param(
        [System.Object] $TargetInventory
    )
    $TagCategories, $TagCategoryAsHashtable = Get-RMVSphereTagCategory -TargetInventory $TargetInventory
    $TagOptions = Get-RMTagOption -TagCategory $TagCategories
    while ($true) {
        $VMTags = Read-RMToken -UserMessage "Enter one or more VM tags separated by commas" -Options $TagOptions `
            -DefaultValue "None" -ParameterName "VM tags" -Separator ","
        if ([string]::IsNullOrWhiteSpace($VMTags)) {
            return @{}
        }

        $CompareResults = Compare-Object -ReferenceObject $TagOptions -DifferenceObject $VMTags -IncludeEqual
        $InputObjects = Get-RMInputObjectBySideIndicator -CompareResult $CompareResults -SideIndicator "=="
        if ($InputObjects.Count -ne $VMTags.Count) {
            Write-RMError -Message "One or more VM tags does not belong to the given list of VM tags, please try again."
        } else {
            return (Get-RMSelectedVMTag -VMTag $VMTags -TagCategory $TagCategoryAsHashtable)
        }
    }
}

function Get-RMSelectedVMTag {
    param(
        [string[]] $VMTag,
        [hashtable] $TagCategory
    )
    $SelectedVMTag = @{}
    foreach ($Tag in $VMTag) {
        $TagName = $Tag.Substring(0, $Tag.LastIndexOf("["))
        $CategoryName = $Tag.Substring($Tag.LastIndexOf("[") + 1, $Tag.LastIndexOf("]") - ($Tag.LastIndexOf("[") + 1))
        $RMTags = $TagCategory[$CategoryName]
        foreach ($RMTag in $RMTags) {
            if ($RMTag.name -ine $TagName) {
                continue
            }
            if ($SelectedVMTag.ContainsKey($CategoryName)) {
                $SelectedVMTag[$CategoryName] += $RMTag
            } else {
                $Tags = @($RMTag)
                $SelectedVMTag.Add($CategoryName, $Tags)
            }
        }
    }
    return $SelectedVMTag
}

function Get-RMSelectedVMTagByRMVSphereTag {
    param(
        [RMVSphereTag[]] $InputTag,
        [System.Object] $TargetInventory
    )
    $SelectedVMTag = @{}
    $TagCategories, $TagCategoryAsHashtable = Get-RMVSphereTagCategory -TargetInventory $TargetInventory
    foreach ($RMVSphereTag in $InputTag) {
        if ($null -eq $RMVSphereTag) {
            continue
        }

        $CategoryName = $RMVSphereTag.RMVSphereTagCategory.GetCategoryName()
        $RMTags = $TagCategoryAsHashtable[$CategoryName]
        foreach ($RMTag in $RMTags) {
            if ($RMTag.GetTagName() -ne $RMVSphereTag.GetTagName()) {
                continue
            }
            if ($SelectedVMTag.ContainsKey($CategoryName)) {
                $SelectedVMTag[$CategoryName] += $RMTag
            } else {
                $SelectedVMTag.Add($CategoryName, @($RMTag))
            }
        }
    }

    return $SelectedVMTag
}

function Get-RMStoragePolicy {
    param(
        [System.Object] $TargetInventory,
        [System.Object] $Source,
        [string[]] $SelectedDiskIndex,
        [bool] $IsInteractive
    )
    $DiskCnt = 0
    $SelectedStorageProfileIds = @{}
    $SelectedStorageProfileNames = @{}

    $StoragePolicyOptions = Get-RMStoragePolicyOption -TargetInventory $TargetInventory
    foreach ($DiskIndex in $SelectedDiskIndex) {
        if ($IsInteractive) {
            $Disk = $Source.attributes.storage.vm_disks[$DiskIndex]
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $StoragePolicyName = Read-RMString -UserMessage "Enter storage policy name for disk of size $Size GiB" `
                -Options $StoragePolicyOptions.keys -DefaultValue "None" -ParameterName "Storage policy name" -IsRequired $false
            if (![string]::IsNullOrWhiteSpace($StoragePolicyName)) {
                $SelectedStorageProfileIds.Add($Disk.label, $StoragePolicyOptions[$StoragePolicyName])
                $SelectedStorageProfileNames.Add($Disk.label, $StoragePolicyName)
            }
        } else {
            #TODO: Add non-interactive case functionality
        }
        $DiskCnt ++
    }

    return $SelectedStorageProfileIds, $SelectedStorageProfileNames
}

function Get-RMStoragePolicyOption {
    param(
        [System.Object] $TargetInventory
    )
    $StoragePolicyOptions = @{}
    # Storage profiles are not datacenter specific and live at the vCenter level and applies to
    # all the datacenters; though ME's target inventory puts it under the datacenter JSON block,
    # so if a vCenter has multiple datacenters then the storage profiles under each datacenter
    # will be the same, hence we are just picking up the storage profiles from the first datacenter.
    foreach ($StorageProfile in $TargetInventory.attributes.vsphere.datacenters[0].storage_profiles) {
        # User will be providing the storage profile name and hence using it as the key
        # so that we can send both the UUID and the name to FE.
        # Storage profile names are unique in given vCenter
        $StoragePolicyOptions.Add($StorageProfile.name, $StorageProfile.uuid)
    }

    return $StoragePolicyOptions
}

function Get-RMStorageProfileIdByStoragePolicyName {
    param (
        [hashtable] $StoragePolicyNamePerDiskLabel,
        [System.Object] $TargetInventory
    )
    $ProfileId = @{}
    if ($null -eq $StoragePolicyNamePerDiskLabel -or $StoragePolicyNamePerDiskLabel.Keys.Count -eq 0) {
        return $ProfileId
    }

    $PolicyNameToUUIDMapping = Get-RMStoragePolicyOption -TargetInventory $TargetInventory
    foreach ($DiskLabel in $StoragePolicyNamePerDiskLabel.Keys) {
        $StoragePolicyName = $StoragePolicyNamePerDiskLabel[$DiskLabel].Trim()
        if ($PolicyNameToUUIDMapping.ContainsKey($StoragePolicyName)) {
            $ProfileId.Add($DiskLabel, $PolicyNameToUUIDMapping[$StoragePolicyName])
        }
    }

    return $ProfileId
}

function Get-RMDiskProvisioningTypeForVMBasedSource {
    param(
        [System.Object] $Source,
        [string[]] $DiskProvisioningType,
        [string[]] $SelectedDiskIndex,
        [hashtable] $SelectedStorageProfileName,
        [bool] $IsInteractive
    )

    if ($Source.collection_type -ine "vm") {
        throw "Get-RMDiskProvisioningTypeForVMBasedSource is supported for VM based sources only"
    }

    $ProvisioningTypes = "thick-lazy-zeroed", "thick-eager-zeroed", "thin"
    $DiskWithProvisioningTypeCnt = 0
    $ResultProvisioningTypes = @()

    for ($Index = 0; $Index -lt $Source.attributes.storage.vm_disks.Count; $Index++) {
        if ($SelectedDiskIndex -notcontains $Index) {
            $ResultProvisioningTypes += $null
            continue
        }
        $Disk = $Source.attributes.storage.vm_disks[$Index]
        if ($null -ne $SelectedStorageProfileName -and $SelectedStorageProfileName.ContainsKey($Disk.label)) {
            $ResultProvisioningTypes += $null
            continue
        }
        if ($IsInteractive) {
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)        
            $ResultProvisioningTypes += Read-RMString -UserMessage "Enter disk provisioning type for disk of size $Size GiB" `
                -Options $ProvisioningTypes -DefaultValue "thin" -ParameterName "Disk provisioning type" -IsRequired $false
        } else {
            if ($null -ne $DiskProvisioningType -and $DiskWithProvisioningTypeCnt -lt $DiskProvisioningType.Count) {
                $ResultProvisioningTypes += $DiskProvisioningType[$DiskWithProvisioningTypeCnt].Trim()
                $DiskWithProvisioningTypeCnt ++
            } else {
                $ResultProvisioningTypes += "thin"
            }
        }
    }

    return $ResultProvisioningTypes
}

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

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

    if ($Source.collection_type -ine "vm") {
        Throw "Get-RMDatastoreByVMBasedSource is supported for VM based sources only"
    }

    $Datastores = @()
    for ($Index = 0; $Index -lt $Source.attributes.storage.vm_disks.Count; $Index++) {
        if ($SelectedDiskIndex -contains $Index) {
            $Disk = $Source.attributes.storage.vm_disks[$Index]
            $Size = $Disk.size_kb/(1024*1024)
            $Size = [math]::round($Size, 2)
            $Datastores += Read-RMString -UserMessage "Enter the datastore name for disk of size $Size GiB" `
                -Options $TargetDatastores -DefaultValue $TargetDatastores[0] -ParameterName "Datastore name" -IsRequired $false
        } else {
            $Datastores += ""
        }
    }
    return $Datastores
}

function Get-RMVMBasedSourceDiskLabel {
    param(
        [System.Object] $Source
    )
    $DiskLabels = @()
    foreach ($Disk in $Source.attributes.storage.vm_disks) {
        $DiskLabels += $Disk.label
    }
    return $DiskLabels
}

function Get-RMStoragePolicyNameByTargetInventory {
    param(
        [System.Object] $TargetInventory
    )
    $StoragePolicyNames = @()
    # Read the comment above for loop in method Get-RMStoragePolicyOption as to why first datacenter
    # is being used
    foreach ($StorageProfile in $TargetInventory.attributes.vsphere.datacenters[0].storage_profiles) {
        $StoragePolicyNames += $StorageProfile.name
    }

    return $StoragePolicyNames
}

function Confirm-RMStoragePolicy {
    param(
        [System.Object] $TargetInventory,
        [string[]] $StoragePolicyName
    )
    $StoragePolicyNames = Get-RMStoragePolicyNameByTargetInventory -TargetInventory $TargetInventory
    $ValidPolicyNames = @()
    $InvalidPolicyNames = @()
    foreach ($PolicyName in $StoragePolicyName) {
        if ([string]::IsNullOrWhiteSpace($PolicyName)) {
            continue
        }

        if ($StoragePolicyNames -contains $PolicyName) {
            $ValidPolicyNames += $PolicyName
        } else {
            $InvalidPolicyNames += $PolicyName
        }
    }

    return $ValidPolicyNames, $InvalidPolicyNames
}

function Get-RMValidDiskProvisioningType {
    param(
        [string[]] $DiskProvisioningType
    )
    $DiskProvisioningTypes = @()
    foreach ($ProvisioningType in $DiskProvisioningType) {
        if ([string]::IsNullOrWhiteSpace($ProvisioningType)) {
            continue
        }
        $DiskProvisioningTypes += $ProvisioningType
    }

    return $DiskProvisioningTypes
}
function Get-RMDiskLabelToStoragePolicyMapping {
    param(
        [string[]] $StoragePolicyName,
        [hashtable] $UserParameter
    )
    if ($null -eq $StoragePolicyName -or $StoragePolicyName.Count -eq 0) {
        return $null
    }

    $DiskLabelToStoragePolicyMapping = @{}
    $SelectedDiskLabel = $UserParameter["SelectedDiskLabel"]
    for ($Index = 0; $Index -lt $SelectedDiskLabel.Count; $Index++) {
        # The storage policy names provided by the user are treated positionally to selected disk labels
        if ([string]::IsNullOrWhiteSpace($StoragePolicyName[$Index])) {
            continue
        }
        
        $DiskLabelToStoragePolicyMapping.Add($SelectedDiskLabel[$Index], $StoragePolicyName[$Index])
    }

    return $DiskLabelToStoragePolicyMapping
}