Common/Wrappers/Wrappers.psm1

function Read-RMUUID {
    param (
        [string] $UserMessage,
        [string] $ParameterName,
        [bool] $IsRequired
    )
    while ($true) {
        $ReadValue = Read-Host $UserMessage
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            }
        }
    
        $Tokens = $ReadValue -split ","
        $InvalidUUIDs = @()
        $ResultIDs = @()    
        foreach ($Token in $Tokens) {
            $Token = $Token.Trim()
            try {
                $ResultIDs += [System.Guid] $Token
            } catch {
                $InvalidUUIDs += $Token
            }
        }

        if ($InvalidUUIDs.Count -gt 0) {
            $InvalidUUIDsAsString = $InvalidUUIDs -join ", "
            Write-RMError -Message ("Invalid UUID {0}, please try again" -f $InvalidUUIDsAsString)
            continue
        }
    
        return $ResultIDs
    }
}

function Read-RMVMFolderString {
    param (
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [string[]] $VMFolders,
        [string] $DefaultValue
    )

    $UserPrompt += $UserMessage +"[" + $DefaultValue + "]"

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        if ("" -eq $ReadValue) {
            if("None" -ieq $DefaultValue) {
                return ""
            } else {
                return $DefaultValue    
            }
        }

        $FolderName = Confirm-RMVMFolder -VMFolders $VMFolders -VMFolderName $ReadValue
        if ($null -ne $FolderName) {
            Write-RMError -Message ("The VM folder '$FolderName' in the given path does not exist, please try again")
            continue
        }
        return $ReadValue
    }
}

function Confirm-RMVMFolder {
    param (
        [string[]] $VMFolders,
        [string] $VMFolderName
    )

    $VMFolderNameArray = $VMFolderName -split "/"

    if ($VMFolderNameArray.Count -gt 1 ) {
        for ($i = 0; $i -lt $VMFolderNameArray.Count; $i++) {
            if ($i -eq 0) {
                $Name = $VMFolderNameArray[$i];
            } else {
                $Name = $VMFolderNameArray[0..($i)] -join "/"
            }

            if ($VMFolders -notcontains $Name) {
                return $VMFolderNameArray[$i]
            }
        }
    } elseif ($VMFolders -notcontains $VMFolderName) {
        return $VMFolderName
    }

    return $null
}

function Read-RMString {
    param (
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [string[]] $Options,
        [string] $DefaultValue,
        [Parameter(Mandatory)]
        [string] $ParameterName,
        [Parameter(Mandatory)]
        [bool] $IsRequired
    )

    Confirm-RMInputParameter -InputParameter $PSBoundParameters

    $UserPrompt = $UserMessage
    if ($Options.Count -gt 0) {
        $UserPrompt += " (" + ($Options -join ",") + ")"
    }

    if (![string]::IsNullOrEmpty($DefaultValue)) {
        if ($Options.Count -eq 0) {
            $UserPrompt += " [" + $DefaultValue + "]"
        } else {
            $UserPrompt += "[" + $DefaultValue + "]"
        }
    }

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            }elseif("None" -ieq $DefaultValue) {
                return ""
            } else {
                return $DefaultValue
            }
        }
        if (0 -ne $Options.Count -and $Options -notcontains $ReadValue) {
            Write-RMError -Message ($ParameterName + " should be " + ($Options -join " or ") + ", please try again")
            continue
        }
        return $ReadValue
    }
}

function Read-RMBoolean {
    param(
        [string] $UserMessage,
        [Parameter(Mandatory)]
        [string] $DefaultValue
    )
    $UserPrompt = $UserMessage
    if (![string]::IsNullOrEmpty($DefaultValue)) {
        $UserPrompt += " (true,false)" + "[" + $DefaultValue + "]"
    }

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        if ("" -eq $ReadValue) {
            return [System.Convert]::ToBoolean($DefaultValue)
        }
        try {
            return [System.Convert]::ToBoolean($ReadValue)
        } catch {
            Write-RMError -Message "Invalid boolean value '$ReadValue', please try again"
            continue
        }
    }
}

function Read-RMInt {
    param(
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [int[]] $Options,
        [int[]] $OptionsRange,
        [string] $DefaultValue,
        [Parameter(Mandatory)]
        [string] $ParameterName,
        [Parameter(Mandatory)]
        [bool] $IsRequired
    )
    Confirm-RMInputParameter -InputParameter $PSBoundParameters
    if ($OptionsRange.Count -gt 0 -and $OptionsRange.Count -ne 2) {
        throw "The parameter OptionsRange length should be exactly equal to 2, actual length is: " +$OptionsRange.Count
    }

    $UserPrompt = $UserMessage
    if ($Options.Count -eq 1 -and [string]::IsNullOrEmpty($DefaultValue)) {

    }
    if ($Options.Count -gt 0) {
        $UserPrompt += " (" + ($Options -join ",") + ")"
    } elseif ($OptionsRange.Count -gt 0) {
        $UserPrompt += " (" + ($OptionsRange -join "-") + ")"
    }

    if (![string]::IsNullOrEmpty($DefaultValue)) {
        if ($Options.Count -eq 0) {
            $UserPrompt += " [" + $DefaultValue + "]"
        } else {
            $UserPrompt += "[" + $DefaultValue + "]"
        }
    }

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            } elseif("None" -eq $DefaultValue) {
                return 0
            } else {
                return [int] $DefaultValue
            }
        }

        if (-not($ReadValue -match "^[\d]+$")) {
            Write-RMError -Message "Please enter an integer value only"
            continue
        }

        if (0 -ne $Options.Count -and $Options -notcontains $ReadValue) {
            Write-RMError -Message ($ParameterName + " should be " + ($Options -join " or ") + ", please try again")
            continue
        } elseif ($OptionsRange.Count -gt 0) {
            $ReadValue = [int]$ReadValue
            if(-not($ReadValue -ge $OptionsRange[0] -and $ReadValue -le $OptionsRange[1])) {
                Write-RMError -Message ($ParameterName + " should be in range " + ($OptionsRange -join "-") + ", please try again")
                continue
            }
        }
        return $ReadValue
    }
}

function Read-RMIPAddress {
    param(
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [string] $DefaultValue,
        [Parameter(Mandatory)]
        [string] $ParameterName,
        [Parameter(Mandatory)]
        [bool] $IsRequired
    )
    Confirm-RMInputParameter -InputParameter $PSBoundParameters
    $UserPrompt = $UserMessage
    if (![string]::IsNullOrEmpty($DefaultValue)) {
        $UserPrompt += " [" + $DefaultValue + "]"
    }

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            } elseif("None" -ieq $DefaultValue) {
                return ""
            } else {
                return $DefaultValue
            }
        }

        $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 ($ReadValue -notmatch $IPPattern) {
            Write-RMError -Message ($ReadValue + " is not a valid IP address, please try again")
            continue
        }

        return $ReadValue
    }
}

function Read-RMDate {
    param(
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [Parameter(Mandatory)]
        [string] $DateFormat,
        [string] $DefaultValue,
        [Parameter(Mandatory)]
        [string] $ParameterName,
        [bool] $IsRequired
    )
    Confirm-RMInputParameter -InputParameter $PSBoundParameters

    $UserPrompt = $UserMessage
    if (![string]::IsNullOrEmpty($DefaultValue)) {
        $UserPrompt += "[" + $DefaultValue + "]"
    }

    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        $ReadValue = $ReadValue.Trim()
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            } elseif("None" -ieq $DefaultValue) {
                return ""
            } else {
                return $DefaultValue
            }
        }

        try {
            [datetime]::ParseExact($ReadValue, $DateFormat, $null)
            return $ReadValue
        } catch {
            Write-RMError -Message ("{0} format is incorrect, expected date format is {1}" -f $ParameterName, $DateFormat)
            continue
        }
    }
}

function Read-RMToken {
    param(
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [string[]] $Options,
        [Parameter(Mandatory)]
        [string] $Separator,
        [Parameter(Mandatory)]
        [string] $ParameterName,
        [string] $DefaultValue,
        [bool] $IsRequired
    )
    Confirm-RMInputParameter -InputParameter $PSBoundParameters

    $UserPrompt = $UserMessage
    if ($Options.Count -gt 0) {
        $UserPrompt += " (" + ($Options -join ",") + ")"
    }

    if (![string]::IsNullOrEmpty($DefaultValue)) {
        if ($Options.Count -eq 0) {
            $UserPrompt += " [" + $DefaultValue + "]"
        } else {
            $UserPrompt += "[" + $DefaultValue + "]"
        }
    }

    $Result = @()
    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        $ReadValue = $ReadValue.Trim()
        if ("" -eq $ReadValue) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            } elseif("None" -ieq $DefaultValue) {
                return ""
            } else {
                return $DefaultValue
            }
        }
        $Tokens = $ReadValue.Split($Separator)
        foreach ($Token in $Tokens) {
            $Result += $Token.Trim()
        }
        return $Result
    }
}

function Read-RMPair {
    param(
        [Parameter(Mandatory)]
        [string] $UserMessage,
        [string] $Separator,
        [string] $DefaultValue
    )
    $UserPrompt = $UserMessage
    if (![string]::IsNullOrEmpty($DefaultValue)) {
        $UserPrompt += " [" + $DefaultValue + "]"
    }
    while ($true) {
        $ReadValue = Read-Host $UserPrompt
        $ReadValue = $ReadValue.Trim()
        if ("" -eq $ReadValue) {
            if ("none" -ieq $DefaultValue) {
                return ""
            }
            return $DefaultValue
        }

        if (-not(Confirm-RMPair -UserInput $ReadValue -Separator $Separator)) {
            continue
        }
        return $ReadValue
    }
}

function Read-RMMigrationSchedule {
    param (
        [bool] $IsDifferentialMigration
    )

    $Options = "Now", "Later"
    $UserMessage = "Schedule for now or later"
    if ($IsDifferentialMigration) {
        $Options = "Now", "Later", "Continuous"
        $UserMessage = "Schedule for now, later or continuous"
    }

    $Schedule = Read-RMString -UserMessage $UserMessage -Options $Options `
        -DefaultValue "Now" -ParameterName "Schedule" -IsRequired $false

    if ("Now" -ieq $Schedule) {
        return ""
    }elseif ("Continuous" -ieq $Schedule) {
        return $Schedule
    }

    while ($true) {
        $ScheduleTime = Read-RMString -UserMessage "Enter the schedule in the format 'MM/dd/yyyy HH:mm'" -ParameterName "Schedule date time" -IsRequired $true
        $IsValidDateTime = Confirm-RMDateFormat -InputDate $ScheduleTime -DateFormat "MM/dd/yyyy HH:mm"
        if ($IsValidDateTime) {
            if (!(Confirm-RMAfterCurrentDateTime -InputDateTime $ScheduleTime)) {
                Write-RMError -Message "The entered schedule is not after the current time, please try again."
                continue
            }
        } else {
            Write-RMError -Message "The entered schedule is not in the format 'MM/dd/yyyy HH:mm', please try again."
            continue
        }
        return (Convert-RMDateTimeToUTC -InputDateTime $ScheduleTime)
    }
}

function Read-RMSecureString {
    param(
        [string] $UserMessage,
        [string] $ConfirmMessage,
        [string] $ParameterName,
        [string] $ConfirmParameterName,
        [bool] $IsRequired
    )

    while ($true) {
        $ReadValue = Read-Host $UserMessage -AsSecureString

        $SecurePassword = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($ReadValue)
        $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($SecurePassword)
        if ("" -eq $Password) {
            if ($IsRequired) {
                Write-RMError -Message ($ParameterName + " is required, please try again")
                continue
            }
        }
        if ([string]::IsNullOrEmpty($ConfirmMessage)) {
            # No need to confirm the password
            return $Password
        }

        $ReadValue = Read-Host $ConfirmMessage -AsSecureString
        
        $SecureConfirmPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($ReadValue)
        $ConfirmPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($SecureConfirmPassword)  
        if ($Password -ne $ConfirmPassword) {
            Write-RMError -Message ($ParameterName + " and " + $ConfirmParameterName + " do not match, please try again") 
            continue
        }
        
        return $Password
    }
}

function Confirm-RMPair {
    param(
        [string] $UserInput,
        [string] $Separator
    )
    $Tokens = $UserInput -split ","
    $IsValidInput = $true
    foreach ($Token in $Tokens) {
        $Token = $Token.Trim()
        if (!$Token.Contains($Separator)) {
            Write-RMError -Message "'$Token' does not contain '$Separator', please try again"
            $IsValidInput = $false
            continue
        }

        $Pair = $Token -split $Separator
        if ($Pair.Count -ne 2 -or [string]::IsNullOrEmpty($Pair[0]) -or [string]::IsNullOrEmpty($Pair[1])) {
            Write-RMError -Message ("$Token is not in the format key $Separator value, please try again")
            $IsValidInput = $false
            continue
        }
    }

    return $IsValidInput
}

function Confirm-RMInputParameter {
    param(
        [hashtable] $InputParameter
    )
    if ($InputParameter.ContainsKey("DefaultValue") -and $InputParameter.ContainsKey("IsRequired")) {
        if (![string]::IsNullOrEmpty($InputParameter["DefaultValue"]) -and $InputParameter["IsRequired"] -eq $true) {
            throw "Incorrect usage of wrapper method, cannot invoke '{0}' with a default value and IsRequired as true" `
                -f $MyInvocation.MyCommand
        }

        if ($InputParameter["IsRequired"] -eq $false -and [string]::IsNullOrEmpty($InputParameter["DefaultValue"])) {
            throw "Incorrect usage of wrapper method, cannot invoke '{0}' with no default value and IsRequired as false" `
                -f $MyInvocation.MyCommand
        }
    }
}

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-RMAfterCurrentDateTime {
    param(
        [string] $InputDateTime
    )
    $Date, $Time = Split-RMDateTime -InputDateTime $InputDateTime
    $DateTimeObjByInput = Get-Date -Month $Date[0] -Day $Date[1] -Year $Date[2] -Hour $Time[0] -Minute $Time[1]
    return ($DateTimeObjByInput -gt (Get-Date))
}

function Convert-RMDateTimeToUTC {
    param(
        [string] $InputDateTime
    )
    <#
    We could have converted string to DateTime Object but the DateTime object
    will treat given date and time in the format that is standard for the given system locale.
    Hence, we are restricting the date time format to be "MM/dd/yyyy HH:mm" and explicitly
    parsing it here and getting the DateTime object from Get-Date by giving individual date and
    time parameters to it.

    It's the callers responsibility to validate the given date time by calling "Confirm-RMDateFormat"
    and then call this method.
    #>

    $Date, $Time = Split-RMDateTime -InputDateTime $InputDateTime
    return (Get-Date -Month $Date[0] -Day $Date[1] -Year $Date[2] -Hour $Time[0] -Minute $Time[1]).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:00.000K")
}

function Split-RMDateTime {
    param(
        [string] $InputDateTime
    )

    $DateTime = $InputDateTime -split " "
    $Date = $DateTime[0] -split "/"
    $Time = $DateTime[1] -split ":"

    return $Date, $Time
}

function Write-RMError {
    param (
       [string] $Message
    )

    [Console]::ForegroundColor = 'red'
    [Console]::Error.WriteLine($message)
    [Console]::ResetColor()
}