Util/Util.psm1

Import-Module -Name $PSScriptRoot\..\Common\Error\Error
Import-Module -Name $PSScriptRoot\..\Common\Wrappers\Wrappers
function Test-UserLoggedIn {
    param()
    if (-not (Get-Variable -Name "RMContext-UserLogin" -ErrorAction SilentlyContinue)) {
        Write-RMError -Message "Please login using the cmdlet 'Invoke-RMLogin' and try again."
        return $false
    }
    return $true
}

function Invoke-RMRestMethod {
    param(
        [hashtable] $Params
    )
    try {
        return Invoke-RestMethod @Params
    } catch [System.Exception] {
        Show-RMError -ErrorObj $PSItem
        throw
    }
}

function Get-RMStringAsHashtable {
    param(
        [string] $InputString
    )

    $Result = @{}
    if (!("" -ne $InputString -and $InputString.Contains("="))) {
        return $Result
    }

    foreach ($item in $InputString.Split(",").Trim()) {
        $item = $item.Split("=")
        $Result.add($item[0].Trim(), $item[1].Trim())
    }

    return $Result
}

function Get-RMStringArrayAsHashtable {
    param(
        [string[]] $InputItems
    )

    $Result = @{}
    if (0 -eq $InputItems.Count) {
        return $Result
    }

    foreach ($item in $InputItems) {
        if (!$item.Contains("=")) {
            throw "Invalid token '$item', each token must be of the format 'key=value'"
        }
        $item = $item.Split("=")
        $Result.add($item[0].Trim(), $item[1].Trim())
    }

    return $Result
}

function Get-RMVolumeType {
    param(
        [string] $VolumeType
    )
    if ("GP2" -ieq $VolumeType) {
        return "ssd2"
    }
    if ("GP3" -ieq $VolumeType) {
        return "ssd3"
    }
    if ("magnetic" -ieq $VolumeType) {
        return "magnetic"
    }
    if ("IO1" -ieq $VolumeType) {
        return "iops_ssd"
    }
    if ("IO2" -ieq $VolumeType) {
        return "iops2_ssd"
    }
    throw "Unsupported volume type '$VolumeType'"
}

function Get-RMExcludedMountPoint {
    param(
        [System.Object] $Source,
        [System.Object[]] $MountPoints
    )
    while ($true) {    
        $ReadValue = Read-RMString -UserMessage "Enter the mount points to be excluded, separated by commas" `
            -DefaultValue "None" -IsRequired $false -ParameterName "Mount points to be excluded"
        if ("" -eq $ReadValue) {
            return $ReadValue
        }

        $MountsToExclude = $ReadValue.Split(",").Trim()
        if ("windows" -ieq $Source.os_type) {
            if ($MountsToExclude -contains "c") {
                Write-RMError -Message "Cannot exclude 'C' drive, please try again."
                continue
            }
        } elseif ($MountsToExclude -contains "/" -or $MountsToExclude -contains "/boot" -or $MountsToExclude -contains "/usr") {
            Write-RMError -Message "Cannot exclude mount points '/', '/boot' or '/usr', please try again."
            continue
        }

        $Result = Test-RMNonExistentMountPoints -SourceMountPoints $MountPoints.Values -UserInputMountPoints $MountsToExclude
        if (0 -ne $Result.Count) {
            $ResultAsString = $Result -join ", "
            Write-RMError -Message "Mount points '$ResultAsString' does not exist on source and hence cannot be excluded, please try again."
            continue
        }
        return $ReadValue
    }
}

function Get-RMSelectedMount {
    param(
        [System.Object[]] $MountPoints,
        [System.Object[]] $DifferenceList,
        [bool] $IncludeEqual
    )

    $SelectedMountPoints = @()
    foreach($MountPoint in $MountPoints) {
        $Result = Compare-Object -ReferenceObject $MountPoint.values -DifferenceObject $DifferenceList -IncludeEqual
        if ($IncludeEqual) {
            if ($Result.SideIndicator -contains "==") {
                $SelectedMountPoints += $MountPoint
            }    
        } elseif (!($Result.SideIndicator -contains "==")) {
                $SelectedMountPoints += $MountPoint
        }
    }

    return $SelectedMountPoints
}

function Compare-RMMountPoint {
    param(
        [System.Object] $Source,
        [System.Object[]] $SourceMountPoints,
        [System.Object[]] $UserInputMountPoints
    )

    if("windows" -ieq $Source.os_type) {
        if ($UserInputMountPoints -inotcontains "c") {
            Write-RMError -Message "No mount points were given or mount point 'C' was not included, mount point 'C' is required."
            return $true
        }
    } else {
        if ($SourceMountPoints.values -contains "/" -and $UserInputMountPoints -notcontains "/") {
            Write-RMError -Message "No mount points were given or mount point '/' was not included, mount point '/' is required." 
            return $true
        } elseif ($SourceMountPoints.values -contains "/boot" -and $UserInputMountPoints -notcontains "/boot") {
            Write-RMError -Message "No mount points were given or mount point '/boot' was not included, mount point '/boot' is required."
            return $true
        } elseif ($SourceMountPoints.values -contains "/usr" -and $UserInputMountPoints -notcontains "/usr") {
            Write-RMError -Message "No mount points were given or mount point '/usr' was not included, mount point '/usr' is required."
            return $true
        }
    }

    return $false
}

function Test-RMNonExistentMountPoints {
    param(
        [System.Object[]] $SourceMountPoints,
        [System.Object[]] $UserInputMountPoints
    )

    $NonExistentMountPoints = @()
    $Results =  Compare-Object -ReferenceObject $SourceMountPoints -DifferenceObject $UserInputMountPoints -IncludeEqual
    if ($Results.SideIndicator -contains "=>") {
        foreach ($Result in $Results) {
            if ($Result.SideIndicator -eq "=>") {
                $NonExistentMountPoints += $Result.InputObject
            }
        }
    }
    return $NonExistentMountPoints
}

function Watch-RMPreflightStatus {
    param(
        [string] $PreflightId,
        [string] $TimeOutMessage
    )

    $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 = "Get"
        Uri = $Uri.Value + "/preflights/" + $PreflightId
        Headers = $Headers
    }
    
    $Timeout30MinsInSeconds = 30 * 60
    $StartTime = [DateTimeOffset]::Now.ToUnixTimeSeconds()
    $Response = Invoke-RMRestMethod -Params $Params
    while ($Response.state -ne "success" -and $Response.state -ne "error") {
        $TimeNow = [DateTimeOffset]::Now.ToUnixTimeSeconds()
        if (($TimeNow - $StartTime) -gt $Timeout30MinsInSeconds) {
            throw $TimeOutMessage
        }
        Start-Sleep -Seconds 5
        $Response = Invoke-RMRestMethod -Params $Params
    }

    return $Response
}

function Watch-RMTargetInventoryStatus {
    param(
        [string] $TargetInventoryId
    )

    $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 = "Get"
        Uri = $Uri.Value + "/targetinventories/" + $TargetInventoryId
        Headers = $Headers
    }

    $Timeout30MinsInSeconds = 30 * 60
    $StartTime = [DateTimeOffset]::Now.ToUnixTimeSeconds()
    Write-Output "Waiting for target inventory to complete..." | Out-Host
    $Response = Invoke-RMRestMethod -Params $Params
    while ($Response.state -ne "success" -and $Response.state -ne "error") {
        $TimeNow = [DateTimeOffset]::Now.ToUnixTimeSeconds()
        if (($TimeNow - $StartTime) -gt $Timeout30MinsInSeconds) {
            throw "Timed out while waiting for target inventory to complete"
        }
        Start-Sleep -Seconds 5
        $Response = Invoke-RMRestMethod -Params $Params
    }

    return $Response
}

function Get-RMWindowsOSMMapping {
    param()
    # ME OSM name to user label
    return @{
        "Windows 10 Pro" = "Windows 10 Professional"
        "Windows 10 Enterprise" = "Windows 10 Enterprise"

        "Windows Server 2008 R2 SERVERSTANDARD" = "Windows Server 2008 R2 Standard"
        "Windows Server 2008 R2 SERVERDATACENTER" = "Windows Server 2008 R2 Datacenter"

        "Windows Server 2012 SERVERSTANDARD" = "Windows Server 2012 Standard"
        "Windows Server 2012 SERVERDATACENTER" = "Windows Server 2012 Datacenter"
        "Windows Server 2012 R2 SERVERSTANDARD" = "Windows Server 2012 R2 Standard"
        "Windows Server 2012 R2 SERVERDATACENTER" = "Windows Server 2012 R2 Datacenter"
        
        "Windows Server 2016 SERVERSTANDARD" = "Windows Server 2016 Standard"
        "Windows Server 2016 SERVERSTANDARDCORE" = "Windows Server 2016 Standard Core"
        "Windows Server 2016 SERVERDATACENTER" = "Windows Server 2016 Datacenter"
        "Windows Server 2016 SERVERDATACENTERCORE" = "Windows Server 2016 Datacenter Core"

        "Windows Server 2019 SERVERSTANDARD" = "Windows Server 2019 Standard"
        "Windows Server 2019 SERVERSTANDARDCORE" = "Windows Server 2019 Standard Core"
        "Windows Server 2019 SERVERDATACENTER" = "Windows Server 2019 Datacenter"
        "Windows Server 2019 SERVERDATACENTERCORE" = "Windows Server 2019 Datacenter Core"

        "Windows Server 2022 SERVERSTANDARD" = "Windows Server 2022 Standard"
        "Windows Server 2022 SERVERSTANDARDCORE" = "Windows Server 2022 Standard Core"
        "Windows Server 2022 SERVERDATACENTER" = "Windows Server 2022 Datacenter"
        "Windows Server 2022 SERVERDATACENTERCORE" = "Windows Server 2022 Datacenter Core"
    }
}

function Get-RMLinuxOSMMapping {
    param()
    # ME OSM name to user label
    return @{
        "RHEL7" = "Red Hat Enterprise Linux Server 7.9"
        "CENTOS7" = "CentOS 7.9"
        "UBUNTU1604LTS" = "Ubuntu 16.04 LTS"
        "SLES15SP1" = "SUSE Linux Enterprise Server 15 SP1"
    }
}

function Get-RMOSMMappingBySource {
    param(
        [System.Object] $Source
    )

    $ResultOSMMapping = @{}
    $OSMMapping = @{}
    if ($Source.os_type -eq "windows") {
        $OSMMapping = Get-RMWindowsOSMMapping
    } else {
        $OSMMapping = Get-RMLinuxOSMMapping
    }

    $SourceMigrationState = $Source.attributes.os.source_migration_state | ConvertFrom-Json
    foreach ($UpgradeOption in $SourceMigrationState.upgrade_options) {
        # Mapping of label to ME OSM name - this will be helpful as the user will be
        # providing labels and we need to send ME OSM name to ME.
        $ResultOSMMapping.Add($OSMMapping[$UpgradeOption], $UpgradeOption)
    }
    return $ResultOSMMapping
}

function Add-RMResizeMount {
    param(
        [string[]] $SelectedMounts,
        [hashtable] $ResizeMounts,
        [object] $Source
    )

    $ResizeMountList = @()
    $ResizeMounts.keys | ForEach-Object {
        $ResizeMountList += $_
    }

    $Result = Compare-Object -ReferenceObject $SelectedMounts -DifferenceObject $ResizeMountList -IncludeEqual
    if ($Result.SideIndicator -contains "=>") {
        Write-RMError -Message "Resize mount points contains a mount point that has not been selected for migration, please check and try again"
        return $null
    }

    $IsValidData = $true
    $MountsResize = @{}
    $SourceMountPointObjects = Get-RMMountPointObject -Source $Source
    foreach ($Mount in $ResizeMounts.keys) {
        foreach ($MountPoint in $SourceMountPointObjects) {
            $MountPath = $MountPoint.path
            if ($MountPath -ne $Mount) {
                continue
            }

            $TotalSpace = [math]::round($MountPoint.size_kb/(1024 * 1024), 2)
            $UsedSpace = [math]::round($MountPoint.used_kb/(1024 * 1024), 2)
            $ResizeValue = $ResizeMounts[$Mount]
            if (-not($ResizeValue -match "^[\d]+$")) {
                Write-RMError -Message "Mount point '$MountPath' contains non-integer value $ResizeValue, please enter an integer value only"
                $IsValidData = $false
                continue
            }
            $ResizeValue = $ResizeValue -as [int]
            if (-not($ResizeValue -gt $UsedSpace -and $ResizeValue -lt $TotalSpace)) {
                Write-RMError -Message "Resize value for mount point '$MountPath' is not valid, resize value should not be greater than the current total size or less than the used size"
                $IsValidData = $false
                continue
            }
            $MountResizeInKiB = $ResizeValue * 1024 * 1024
            $MountsResize.Add($MountPath, $MountResizeInKiB)
            break
        }
    }

    if (!$IsValidData) {
        return $null
    }
    return $MountsResize
}

function Get-RMMountPointObject {
    param(
        [System.Object] $Source
    )
    $MountPoints = @()
    if ("windows" -ieq $Source.os_type) {
        foreach ($Mount in $Source.attributes.storage.mounts.psobject.properties.value) {
            $MountPoints += $Mount
        }
    } else {
        foreach ($Mount in $Source.attributes.storage.mounts.psobject.properties.value) {
            if ("disk" -ieq $Mount.nature -or "subvolume" -ieq $Mount.nature -and "squashfs" -ine $Mount.fs_type) {
                $MountPoints += $Mount
            }
        }
    }

    return $MountPoints
}

function Get-RMMigrationInstruction {
    param (
        [hashtable] $MigrationInstructions
    )

    $MigrationInstructionList = @()
    if ($null -ne $MigrationInstructions) {
        $MigrationInstructionList =  $MigrationInstructions.Keys | foreach-object { "$_/$($MigrationInstructions[$_])"}
    }
    return  $MigrationInstructionList
}

function Get-RMPartition {
    param (
        [System.Object[]] $SelectedMounts
    )

    $Partitions = @()
    foreach($MountPoint in $SelectedMounts) {
        $Partitions += $MountPoint.mount_point
    }

    return  $Partitions
}

function Get-RMResizeMountsPoint {
    param (
        [string[]] $ResizeMountPoints,
        [array] $SelectedMountPoints,
        [System.Object] $Source
    )
    $MountPointList = @()
    $ResizeMounts = Get-RMStringArrayAsHashtable -InputItems $ResizeMountPoints
    foreach($SelectedMountPoint in $SelectedMountPoints) {
        $MountPointList += $SelectedMountPoint.values
    }
    return Add-RMResizeMount -SelectedMounts $MountPointList -ResizeMounts $ResizeMounts -Source $Source
}

function Get-RMInteractiveMountsResize {
    param (
        [array] $SelectedMountPoints,
        [System.Object] $Source
    )

    $MountPointList = @()
    $SelectedMountPoints | ForEach-Object {
        $MountPointList += $_.values
    }
    $MountsResize = @{}
    $SourceMountPointObjects = Get-RMMountPointObject -Source $Source
    foreach ($Mount in $MountPointList) {
        foreach ($MountPoint in $SourceMountPointObjects) {
            if ($MountPoint.path -ne $Mount) {
                continue
            }
            $TotalSpace = [math]::round($MountPoint.size_kb/(1024 * 1024), 2)
            if ([int]$TotalSpace -le 1) {
                # Cannot resize the disk that is <= 1GiB
                break
            }
            $UsedSpace = [math]::round($MountPoint.used_kb/(1024 * 1024), 2)
            $RoundedTotalSpace = [math]::round($TotalSpace)
            while ($true) {
                $ReadValue = Read-Host "Enter new size in GiB for mount point $Mount, current used space is $UsedSpace GiB of total space $TotalSpace GiB [$RoundedTotalSpace GiB]"
                if ("" -eq $ReadValue) {
                    $ReadValue = $RoundedTotalSpace
                }
                if (-not($ReadValue -match "^[\d]+$")) {
                    Write-RMError -Message "Please enter an integer value only"
                    continue
                }

                $ReadValue = $ReadValue -as [int]
                if (-not($ReadValue -gt $UsedSpace -and $ReadValue -le $RoundedTotalSpace)) {
                    Write-RMError -Message "Resize value for mount point '$Mount' is not valid, resize value should not be greater than the current total size or less than the used size"
                    continue
                }
                $MountResizeInKiB = $ReadValue * 1024 * 1024
                $MountsResize.Add($Mount, $MountResizeInKiB)    
                break
            }
            break
        }
    }
    return  $MountsResize
}

function Get-RMMoveGroupList {
    param (
        [string] $OrganizationId,
        [int] $PageNumber
    )

    $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 = "Get"
        Uri = $Uri + "/organizations/" + $OrganizationId + "/movegroups?size=25&page=" + $PageNumber +"&sort=name,desc"
        Headers = $Headers
    }
        
    $Response = Invoke-RMRestMethod -Params $Params
    return $Response

    }


function Get-MoveGroupByName {
    param (
        [string] $MoveGroupName,
        [string] $OrganizationId
    )

    $MoveGroupList = @()
    $Response = Get-RMMoveGroupList -OrganizationId $OrganizationId  -PageNumber 0
    $MoveGroupList += $Response
    $MoveGroup = Get-MoveGroup -MoveGroupName $MoveGroupName -MoveGroupList $MoveGroupList.content
    if ($null -eq $MoveGroup) {
        for ($index = 1; $index -lt $Response.page.totalPages; $index++) {
            $MoveGroupList = Get-RMMoveGroupList -OrganizationId $OrganizationId -PageNumber $index
            $MoveGroup = Get-MoveGroup -MoveGroupName $MoveGroupName -MoveGroupList $MoveGroupList.content
            if ($null -ne $MoveGroup) {
                Set-Variable -Name "RMContext-MoveGroup" -Value  $MoveGroup -Scope Global
                return $MoveGroup
            }
        }
    } else {
        Set-Variable -Name "RMContext-MoveGroup" -Value  $MoveGroup -Scope Global
        return  $MoveGroup
    }
   return $null
}

function Get-MoveGroup {
    param(
        [string] $MoveGroupName,
        [array] $MoveGroupList
    )

    foreach($MoveGroup in $MoveGroupList) {
        if ($MoveGroupName -eq $MoveGroup.name) {
            return $MoveGroup
        }
    }

    return $null
}

function Get-RMEntitlement {
    param()
    $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly
    $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 = "Get"
        Uri = $Uri + "/organizations/" + $CurrentProjectId + "/entitlements"
        Headers = $Headers
    }

    $Entitlements = Invoke-RMRestMethod -Params $Params
    foreach ($Entitlement in $Entitlements.content) {
        $Remaining = $Entitlement.total - $Entitlement.used
        if ($Remaining -ge 1) {
            return $Entitlement
        }
    }

    throw "Not enough entitlements available to start the migration"
}
function Out-MigrationIdFromResponse {
    param(
        [System.Object] $Response
    )
    $MigrationId = ""
    # we expect 3 links in the response
    if ($Response.links.Count -lt 3) {
        return $MigrationId
    }

    if ($Response.links[1].rel -ine "migrations") {
        return $MigrationId
    }

    $SplitData = $Response.links[1].href.split("/")
    if ($SplitData.Count -lt 7) {
        return $MigrationId
    }

    $MigrationId = $SplitData[6].SubString(0, $SplitData[6].IndexOf("{"))
    Write-Output "Migration started successfully, migration ID: $MigrationId"
}

function Get-RMTransferMethod {
    param (
        [System.Object] $Source
    )

    $TransferType = "file-based"

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

function Confirm-RMDateFormat {
    param(
        [string] $InputDate,
        [string] $DateFormat
    )
    if([string]::IsNullOrEmpty($InputDate)) {
        return $true
    }

    try {
        [datetime]::ParseExact($InputDate, $DateFormat, $null) |Out-Null
    } catch {
        return $false
    }
    return $true
}


function Confirm-RMIPAddress {
    param (
        [string] $IPAddress
    )
        
    $IPPattern = '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'

    if ($IPAddress -notmatch $IPPattern) {
        return $false
    }

    return $true
}