Util/Util.psm1

using module '../Common/Result'
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Common | Join-Path -ChildPath Error | Join-Path -ChildPath Error)
Import-Module -Name @(Join-Path $PSScriptRoot .. | Join-Path -ChildPath Common | Join-Path -ChildPath Wrappers | Join-Path -ChildPath Wrappers)
function Test-UserLoggedIn {
    param()
    [RMReturn] $RMReturn = [RMReturn]::new()
    if (-not (Get-Variable -Name "RMContext-UserLogin" -ErrorAction SilentlyContinue)) {
        $UserMessage = "Please login using the cmdlet 'Invoke-RMLogin' and try again."
        Write-RMError -Message $UserMessage
        [RMError] $RMError = [RMError]::new("login_required", $UserMessage)
        $RMReturn.AddRMError($RMError)
        $RMReturn.SetReturnCode([RMReturn]::ERROR)
        return $RMReturn
    }

    $RMReturn.SetReturnCode([RMReturn]::SUCCESS)
    return $RMReturn
}

# TODO: This wrapper is not doing much, can be removed.
function Invoke-RMRestMethod {
    param(
        [hashtable] $Params
    )
    return Invoke-RestMethod @Params
}

function Invoke-RMMigrationProfilePost {
    param (
        [hashtable] $MigrationProfile
    )
    # 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 Invoke-RMMigrationPost {
    param(
        [System.Object] $MigrationProfileResponse
    )

    $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/" + $MigrationProfileResponse.id + "/migrations"
        Headers = $Headers
        ContentType = "application/json"
    }

    return Invoke-RMRestMethod -Params $Params
}

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,
        [string] $ParameterName
    )

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

    foreach ($item in $InputItems) {
        if (!$item.Contains("=")) {
            throw "$ParameterName is invalid, each item in '$ParameterName' 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[]] $MountPoints,
        [string] $OSType,
        [bool] $IsDifferentialMigration
    )
    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 $OSType) {
            if (!$IsDifferentialMigration -and $MountsToExclude -contains "c") {
                Write-RMError -Message "Cannot exclude 'C' drive, please try again."
                continue
            }
        } elseif ($IsDifferentialMigration) {
            $MountsNotExcludeList = @("/", "/bin", "/boot" , "/dev", "/etc", "/home", "/lib" , "/mnt", "/opt", "/proc", "/root", "/run", "/sbin", "/srv", "/tmp", "/usr", "/var")
            $Results = Compare-Object -ReferenceObject $MountsNotExcludeList -DifferenceObject $MountsToExclude -IncludeEqual
            if ($Results.SideIndicator -contains "==") {
                $ExcludedMountPointList = @()
                foreach ($Result in $Results) {
                    if ($Result.SideIndicator -eq "==") {
                        $ExcludedMountPointList += $Result.InputObject
                    }
                }
                $ExcludedMountPointListAsString = $ExcludedMountPointList -join ", "
                Write-RMError -Message "Cannot exclude mount points '$ExcludedMountPointListAsString', 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 -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,
        [string] $ParameterName
    )

    $Errors = @()
    if("windows" -ieq $Source.os_type) {
        if ($UserInputMountPoints -inotcontains "c") {
            $Errors += "Mount point 'C' was not included in parameter '$ParameterName', mount point 'C' is required."
        }
        return $Errors
    } 

    if ($SourceMountPoints -contains "/" -and $UserInputMountPoints -notcontains "/") {
        $Errors += "Mount point '/' was not included in parameter '$ParameterName', mount point '/' is required." 
    } 
    if ($SourceMountPoints -contains "/boot" -and $UserInputMountPoints -notcontains "/boot") {
        $Errors += "Mount point '/boot' was not included in parameter '$ParameterName', mount point '/boot' is required."
    } 
    if ($SourceMountPoints -contains "/usr" -and $UserInputMountPoints -notcontains "/usr") {
        $Errors += "Mount point '/usr' was not included in parameter '$ParameterName', mount point '/usr' is required."
    }
    return $Errors
}

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 Pro"
        "Windows 10 Enterprise" = "Windows 10 Enterprise"
        "Windows 11 Pro" = "Windows 11 Pro"
        "Windows 11 Enterprise" = "Windows 11 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"
        "RHEL8" = "Red Hat Enterprise Linux Server 8"
        "RHEL9" = "Red Hat Enterprise Linux Server 9"
        "RHEL610" = "Red Hat Enterprise Linux Server 6.1"
        "CENTOS7" = "CentOS 7.9"
        "CENTOS610" = "CentOS 6.1"
        "SLES15SP1" = "SUSE Linux Enterprise Server 15 SP1"
        "UBUNTU1604LTS" = "Ubuntu 16.04 LTS"
        "ROCKY8" = "Rocky Linux 8"
        "ROCKY9" = "Rocky Linux 9"
    }
}

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

    $ResultOSMMapping = @{}
    $OSMMapping = @{}
    if ($null -eq $Source -or $null -eq $Source.attributes `
            -or $null -eq $Source.attributes.os `
            -or $null -eq $Source.attributes.os.source_migration_state) {
        return $ResultOSMMapping
    }

    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.
        if ($OSMMapping.ContainsKey($UpgradeOption)) {
            $ResultOSMMapping.Add($OSMMapping[$UpgradeOption], $UpgradeOption)
        } else {
            Write-Warning "Newer version of cmdlet is needed, couldn't find upgrade option '$UpgradeOption'" | Out-Host
        }
    }
    return $ResultOSMMapping
}

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

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

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

    $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]+$")) {
                $MountsResizeErrors += "Mount point '$MountPath' contains non-integer resize value '$ResizeValue', please enter an integer value only"
                $IsValidData = $false
                continue
            }
            $ResizeValue = $ResizeValue -as [int]
            if (-not($ResizeValue -gt $UsedSpace -and $ResizeValue -lt $TotalSpace)) {
                $MountsResizeErrors += "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, $MountsResizeErrors
    }
    return $MountsResize, $MountsResizeErrors
}

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-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
    )
    
    $Errors = @()
    try {
        $ResizeMounts = Get-RMStringArrayAsHashtable -InputItems $ResizeMountPoints -ParameterName "ResizeMountPoints"
    } catch {
        $Errors += $PSItem.Exception.Message
    }

    $MountsResize = $null
    $MountsResizeErrors = $null
    if ($null -ne $ResizeMounts) {
        $MountsResize, $MountsResizeErrors = Add-RMResizeMount -SelectedMounts $SelectedMountPoints.values -ResizeMounts $ResizeMounts -Source $Source
    }

    if ($Errors -gt 0) {
        $MountsResizeErrors += $Errors
    }
 
    return $MountsResize, $MountsResizeErrors
}

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 Get-RMTransferMethodInternal {
    param (
        [System.Object] $Source,
        [string[]] $SelectedMountPoints
    )

    $Result = @{}
    $Result.Add("Transfer_Method", "file-based")
    $Result.Add("Is_Block_Based_Supported_By_Source", $false)
    $Result.Add("Block_Based_Unsupported_Mounts", @())

    if ($Source.os_type -eq "windows") {
        $Result["Transfer_Method"] = "block-based"
        return  $Result
    }
    if($Source.attributes.discovered_features.features_list -notcontains "linux_block_based") {
        return  $Result
    }

    $Result["Is_Block_Based_Supported_By_Source"] = $true
    $fs = @("ext2", "ext3", "ext4", "xfs")
    $IsBlockBasedSupportedByAllSelectedMounts = $true
    foreach($Mount in $Source.attributes.storage.mounts.psobject.properties.value){
        if ($SelectedMountPoints -inotcontains $Mount.path) {
            continue
        }
        if ($Mount.nature -eq "disk") {
            if ($fs -notcontains $Mount.fs_type) {
                $Result["Block_Based_Unsupported_Mounts"] += $Mount.path
                $IsBlockBasedSupportedByAllSelectedMounts = $false;
            }
        }
    }

    if ($IsBlockBasedSupportedByAllSelectedMounts) {
        $Result["Transfer_Method"] = "block-based"
    }

    return  $Result
}

function Get-RMTransferMethod {
    param(
        [System.Object] $Source,
        [System.Object[]] $SelectedMountPoints,
        [bool] $IsInteractive,
        [string] $TransferMethod
    )

    $MountPoints = @()
    foreach ($SelectedMount in $SelectedMountPoints) {
        if ($SelectedMount -is [hashtable]) {
            $MountPoints += $SelectedMount.values
        } else {
            $MountPoints += $SelectedMount
        }
    }

    $Result = Get-RMTransferMethodInternal -Source $Source -SelectedMountPoints $MountPoints
    if ($IsInteractive) {
        if ("block-based" -ieq $Result["Transfer_Method"]) {
            $ReadValue = Read-RMString -UserMessage "Enter transfer method" -Options "file-based", "block-based" `
                -DefaultValue "block-based" -ParameterName "Transfer method" -IsRequired $false
            return $ReadValue, ""
        } else {
            if ($Result["Is_Block_Based_Supported_By_Source"] -and $Result["Block_Based_Unsupported_Mounts"].Count -gt 0) {
                $BlockBasedUnsupportedMountsAsString = $Result["Block_Based_Unsupported_Mounts"] -join ", "
                Write-Output "Block-based transfer method is not supported by the mount points '$BlockBasedUnsupportedMountsAsString', file-based transfer method will be used." | Out-Host
            } else {
                Write-Output "Block-based transfer method is not supported for the given source, file-based transfer method will be used." | Out-Host
            }
        }
        return $Result["Transfer_Method"], ""
    }

    if ([string]::IsNullOrEmpty($TransferMethod)) {
        return $Result["Transfer_Method"], ""
    }
    if ($TransferMethod -eq "file-based") {
        return $TransferMethod, ""
    }
    $ErrorString = ""
    if ($TransferMethod -eq "block-based" -and $Result["Transfer_Method"] -eq "file-based") {
        if ($Result["Is_Block_Based_Supported_By_Source"] -and $Result["Block_Based_Unsupported_Mounts"].Count -gt 0) {
            $BlockBasedUnsupportedMountsAsString = $Result["Block_Based_Unsupported_Mounts"] -join ", "
            $ErrorString = "Block-based transfer method is not supported by the mount points '$BlockBasedUnsupportedMountsAsString'"
        } else {
            $ErrorString = "Block-based transfer method is not supported for the given source."
        }
    }

    return $Result["Transfer_Method"], $ErrorString
}

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
}

function Edit-RMMountPoints {
    param(
        [System.Object] $TargetProperties,
        [System.Object] $MountPoints
    )   

    $SelectedMounts = $TargetProperties | where { $null -ne $_.selected_mounts}
    $SelectedMounts.selected_mounts = $MountPoints

    return $SelectedMounts
}

function Get-RMDestinationNICConfigWithGatewayAndDNS {
    param(
        [RMVSphereDestinationNetworkConfiguration[]] $DestinationNICConfig
    )
    foreach ($NICConfig in $DestinationNICConfig) {
        if ($NICConfig.IPType -ieq "dhcp") {
            continue
        }
        if (!([string]::IsNullOrWhiteSpace($NICConfig.DefaultGateway) `
            -and [string]::IsNullOrWhiteSpace($NICConfig.PrimaryDNS))) {
            return $NICConfig
        }
    }

    # If all are DHCP, then return the first NIC
    return $DestinationNICConfig[0]
}

function Get-RMInputObjectBySideIndicator {
    param(
        [System.Object[]] $CompareResult,
        [string] $SideIndicator
    )
    $InputObjects = @()
    foreach ($Result in $CompareResult) {
        if ($Result.SideIndicator -eq $SideIndicator) {
            $InputObjects += $Result.InputObject
        }
    }

    return $InputObjects
}