RiverMeadow.Source/RiverMeadow.Source.psm1

Import-Module -Name $PSScriptRoot\..\Util\Util
Import-Module -Name $PSScriptRoot\..\Common\Common

function Add-RMSource {
    param(
        [Alias("sip")]
        [string] $SourceIP,

        [Alias("shpa")]
        [bool] $SourceHasPreinstalledAgent,

        [Alias("scoa")]
        [bool] $StoreCredsOnAppliance,

        [Alias("un")]
        [string] $Username,

        [Alias("pw")]
        [string] $Password,

        [Alias("cpw")]
        [string] $ConfirmPassword,

        [Alias('usshpk')]
        [bool] $UseSSHPrivateKey,

        [Alias("pk")]
        [string] $PrivateKey,

        [Alias("pp")]
        [string] $Passphrase,

        [Alias("cpp")]
        [string] $ConfirmPassphrase,

        [Alias("dm")]
        [string] $Domain,

        [Alias("rmt")]
        [string[]] $RiverMeadowTags,
        
        [Alias("ai")]
        [string[]] $AdvancedInstructions,

        [Alias("cmg")]
        [bool] $CreateMoveGroup,

        [Alias("mgn")]
        [string] $MoveGroupName,

        [Alias("tmd")]
        [string] $TargetMigrationDate,

        [Alias("are")]
        [string] $AssignedResourceEmail
    )

    $UserLoginStatus = Test-UserLoggedIn
    if (!$UserLoginStatus) {
        return
    }

    if (0 -eq $PSBoundParameters.Count) {
        Add-RMSourceInteractive
    } else {
        Add-RMSourceNonInteractive @PSBoundParameters
    }
}

function Add-RMSourceInteractive {
    $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly

    $ReadValue = Read-Host "Enter the source IP address"
    if ("" -eq $ReadValue) {
        throw "Source IP address is required"
    } 
    $SourceIP = $ReadValue
    
    $SourceHasPreinstalledAgent = $false
    $ReadValue = Read-Host "Does source has preinstalled agent (true/false)[false]"
    if ("" -ne $ReadValue) {
        $SourceHasPreinstalledAgent = [System.Convert]::ToBoolean($ReadValue)
    }

    if(!$SourceHasPreinstalledAgent){
        $StoreCredsOnAppliance = $false
        $ReadValue = Read-Host "Store the source credentials on migration appliance (true/false)[false]"
        if ("" -ne $ReadValue) {
            $StoreCredsOnAppliance = [System.Convert]::ToBoolean($ReadValue)
        }
    }

    if (!$SourceHasPreinstalledAgent -and !$StoreCredsOnAppliance) {
        $ReadValue = Read-Host "Enter the username"
        if ("" -eq $ReadValue) {
            throw "Username is required."
        }
        $Username = $ReadValue

        $UseSSHPrivateKey = $false
        $ReadValue = Read-Host "Use SSH private key (true/false)[false]"
        if ("" -ne $ReadValue) {
            $UseSSHPrivateKey = [System.Convert]::ToBoolean($ReadValue)
        }

        if ($UseSSHPrivateKey) {
            $ReadValue = Read-Host "Enter the private key"

            if ("" -eq $ReadValue) {
                throw "Private key is required."
            }
            $PrivateKey = $ReadValue
            $Password = Compare-RMSecureString -Message "Enter the passphrase" -ConfirmMessage "Confirm the passphrase" `
             -ConfirmErrorMessage "Confirm passphrase is reqiured." -CompareErrorMessage "Passphrase and confirm passphrase must match."  -SSHPrivateKey $true
        } else {
            $Password = Compare-RMSecureString -Message "Enter the password" -ConfirmMessage "Confirm the password" `
            -ErrorMessage "Password is required." -ConfirmErrorMessage "Confirm password is reqiured." -CompareErrorMessage "Password and confirm password must match."
        }

        $ReadValue = Read-Host "Enter the domain"
        if ("" -ne $ReadValue) {
            $Domain = $ReadValue
        }
    }

    $ReadValue = Read-Host "Enter one or more RiverMeadow tags, separated by commas [None]"
    if ("" -ne $ReadValue) {
        $RiverMeadowTags = $ReadValue.Split(",").Trim()
    } else {
        $RiverMeadowTags = @()
    }

    $ReadValue = Read-Host "Enter one or more advanced instructions in the format 'key=value' and separated by commas [None]"   
    $AdvancedInstructions = Get-RMStringAsHashtable -InputString $ReadValue

    $CreateMoveGroup = $false
    $ReadValue = Read-Host "Create new move group (true/false)[false]"
    if ("" -ne $ReadValue) {
        $CreateMoveGroup = [System.Convert]::ToBoolean($ReadValue)
        if ($CreateMoveGroup) {
            $ReadValue = Read-Host "Enter new move group name"
            if ("" -eq $ReadValue) {
                throw  "New move group name is required."
            }
            $MoveGroupName = $ReadValue

            $ReadValue = Read-Host "Enter target migration date in the format 'mm/dd/yyyy' [None]"
            
            if("" -ne $ReadValue) {
                try {
                    [datetime]::ParseExact($ReadValue, "MM/dd/yyyy", $null)
                    $TargetMigrationDate = $ReadValue
                }
                catch {
                    throw "Please enter a valid target migration date in the format 'mm/dd/yyyy'."
                }
            }
           
            $AssignedResourceEmail = Read-Host "Enter assigned resource email [None]"
        }
    }

    if (!$CreateMoveGroup) {
        $MoveGroupList = @()
        $Response = Get-RMMoveGroupList -Organization $CurrentProjectId -PageNumber 0
        $MoveGroupList += $Response
        for ($index = 1; $index -lt $Response.page.totalPages; $index++) {
            $MoveGroupList += Get-RMMoveGroupList -OrganizationId $CurrentProjectId -PageNumber $index
        }
        $MoveGroups = $MoveGroupList.content.name -join ", "
        $ReadValue = Read-Host "Enter the move group name to which the source should be added ($MoveGroups)"
        if ("" -ne $ReadValue) {
            $MoveGroupName = $ReadValue
        }
    }
   
    $UserInput = @{
        SourceIP = $SourceIP
        CurrentProjectId = $CurrentProjectId
        SourceHasPreinstalledAgent = $SourceHasPreinstalledAgent
        StoreCredsOnAppliance = $StoreCredsOnAppliance
        Username = $Username
        Password = $Password
        PrivateKey = $PrivateKey
        Domain = $Domain
        RiverMeadowTags = $RiverMeadowTags
        AdvancedInstructions = $AdvancedInstructions
        CreateMoveGroup = $CreateMoveGroup
        MoveGroupName = $MoveGroupName
        TargetMigrationDate = $TargetMigrationDate
        AssignedResourceEmail = $AssignedResourceEmail
    }

    New-RMSource @UserInput
}

function Compare-RMSecureString {
    param (
        [string] $Message,
        [string] $ConfirmMessage,
        [string] $ErrorMessage,
        [string] $ConfirmErrorMessage,
        [string] $CompareErrorMessage,
        [bool] $SSHPrivateKey
    )

    $SecurePassword = Read-Host "$Message" -AsSecureString
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
    $Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
    if (!$SSHPrivateKey -and "" -eq $Password) {
        throw "$ErrorMessage"
    }

    $ConfirmSecurePassword = Read-Host "$ConfirmMessage" -AsSecureString
    $ConfirmBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ConfirmSecurePassword)
    $ConfirmPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ConfirmBSTR)
    if ($SSHPrivateKey ) {
        if ("" -ne $Password) {
            if ("" -eq $ConfirmPassword) {
                throw $ConfirmErrorMessage
            }
            if ($Password -ne  $ConfirmPassword) {
                throw "$CompareErrorMessage"
            }
        }
    } else {
        if ("" -eq $ConfirmPassword ) {
            throw "$ConfirmErrorMessage"
        }
    
        if ($Password -ne  $ConfirmPassword) {
            throw "$CompareErrorMessage"
        }
    }
   
    return $Password
}

function Add-RMSourceNonInteractive {
    param (
        [string] $SourceIP,
        [bool] $SourceHasPreinstalledAgent,
        [bool] $StoreCredsOnAppliance,
        [string] $Username,
        [string] $Password,
        [string] $ConfirmPassword,
        [bool] $UseSSHPrivateKey,
        [string] $PrivateKey,
        [string] $Passphrase,
        [string] $ConfirmPassphrase,
        [string] $Domain,
        [string[]] $RiverMeadowTags,
        [string[]] $AdvancedInstructions,
        [bool] $CreateMoveGroup,
        [string] $MoveGroupName,
        [string] $TargetMigrationDate,
        [string] $AssignedResourceEmail
    )
    $Errors = Confirm-RMAddSourceParameter -UserParameter $PSBoundParameters
    $IsValidDate = Confirm-RMDateFormat -InputDate $TargetMigrationDate -DateFormat "MM/dd/yyyy"

    if ($Errors.Count -gt 0) {
        if (!$IsValidDate) {
            Write-RMError -Message "TargetMigrationDate is invalid, the TargetMigrationDate should be in the format 'mm/dd/yyyy'."
        }
        Out-RMUserParameterResult -ErrorMessage $Errors
        return
    }

    if (!$IsValidDate) {
        Write-RMError -Message "TargetMigrationDate is invalid, the TargetMigrationDate should be in the format 'mm/dd/yyyy'."
        return
    }

    $UserInput = @{}
    $CurrentProjectId = Get-Variable -Name "RMContext-CurrentProjectId" -ValueOnly
    $UserInput.Add("CurrentProjectId", $CurrentProjectId)
    $UserInput.Add("SourceIP", $SourceIP)

    if ($SourceHasPreinstalledAgent) {
        $UserInput.Add("SourceHasPreinstalledAgent", $SourceHasPreinstalledAgent)
    } else {
        if ($StoreCredsOnAppliance) {
            $UserInput.Add("StoreCredsOnAppliance", $StoreCredsOnAppliance)
        }
    }

    if (!$SourceHasPreinstalledAgent -and !$StoreCredsOnAppliance) {
        $UserInput.Add("Username", $Username)
        if ($UseSSHPrivateKey) {
            $UserInput.Add("PrivateKey", $PrivateKey)
            $UserInput.Add("Password", $Passphrase)
        } else {
            $UserInput.Add("Password", $Password)
        }
        $UserInput.Add("Domain", $Domain)
    }

    if ($null -eq $RiverMeadowTags) {
        $RiverMeadowTags = @()
    }
    $UserInput.Add("RiverMeadowTags", $RiverMeadowTags)

    $AdvancedInstructionsAsHashTable = Get-RMStringArrayAsHashtable -InputItems $AdvancedInstructions
    $UserInput.Add("AdvancedInstructions", $AdvancedInstructionsAsHashTable)

    if ($CreateMoveGroup) {
        $UserInput.Add("CreateMoveGroup", $CreateMoveGroup)
        $UserInput.Add("MoveGroupName", $MoveGroupName)
        $UserInput.Add("TargetMigrationDate", $TargetMigrationDate)
        $UserInput.Add("AssignedResourceEmail", $AssignedResourceEmail)
    } else {
        $UserInput.Add("MoveGroupName", $MoveGroupName)
    }
    

    New-RMSource @UserInput    
}

function New-RMSource {
    param(
        [string] $SourceIP,
        [string] $CurrentProjectId,
        [bool] $SourceHasPreinstalledAgent,
        [bool] $StoreCredsOnAppliance,
        [string] $Username,
        [string] $Password,
        [string] $PrivateKey,
        [string] $Domain,
        [string[]] $RiverMeadowTags,
        [hashtable] $AdvancedInstructions,
        [bool] $CreateMoveGroup,
        [string] $MoveGroupName,
        [string] $TargetMigrationDate,
        [string] $AssignedResourceEmail
    )

    $CredsStorage = "local"
    if ($StoreCredsOnAppliance) {
        $CredsStorage = "ca"
    }

    $MoveGroupID = $null
    if ($CreateMoveGroup) {
        $MoveGroupID = "00000000-0000-0000-0000-000000000000"
    } else {
        if ("" -ne $MoveGroupName) {
            $MoveGroup = Get-MoveGroupByName -MoveGroupName $MoveGroupName -OrganizationId $CurrentProjectId
            if ($null -eq $MoveGroup) {
                throw "Invalid move group."
            }
            $MoveGroupID = $MoveGroup.id
        }
    }
    
    $ControlConnectionType = $null
    if ($SourceHasPreinstalledAgent) {
        $ControlConnectionType = "agent"
    }

    $SourceRequest = @{
        "name"= $SourceIP
        "host"= $SourceIP
        #e.g string format:"Azure:Standard_D5_v2"
        #TODO We will add cloud size features in the future
        "cloud_sizing"= ""
        "instructions"= $AdvancedInstructions
        "credentials"= @{
            "storage" = $CredsStorage
            "username" = $Username
            "password" = $Password
            "domain" = $Domain
            "private_key" = $PrivateKey
        }
        "move_group_id" = $MoveGroupID
        "move_group_name" = $MoveGroupName
        "target_migration_date" = $TargetMigrationDate
        "assigned_resource_email" = $AssignedResourceEmail
        "tags"= $RiverMeadowTags
        "organization_id" = $CurrentProjectId
        "control_connection_type" = $ControlConnectionType
    }
    $SourceRequestJson = $SourceRequest |ConvertTo-Json -Depth 100


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

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

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

    try {
        Invoke-RMRestMethod -Params $Params | Out-Null
    } catch {
        # Invoke-RMRestMethod has already shown the error, so just return
        return
    }
    Write-Output "Source has been added successfully"
}

function Confirm-RMAddSourceParameter {
    param(
        [hashtable] $UserParameter
    )
    $Errors = @()
    if (!$UserParameter.ContainsKey("SourceIP") -or [string]::IsNullOrEmpty("SourceIP")) {
        $Errors += "SourceIP is required"
    }

    if (!(($UserParameter.ContainsKey("SourceHasPreinstalledAgent") -and $UserParameter["SourceHasPreinstalledAgent"]) `
            -or ($UserParameter.ContainsKey("StoreCredsOnAppliance") -and $UserParameter["StoreCredsOnAppliance"]))) {
        if (!$UserParameter.ContainsKey("Username") -or [string]::IsNullOrEmpty($UserParameter["Username"])) {
            $Errors += "Username is required"
        }
        if ($UserParameter.ContainsKey("UseSSHPrivateKey") -and $UserParameter["UseSSHPrivateKey"] -eq $true) {
            if (!$UserParameter.ContainsKey("PrivateKey") -or [string]::IsNullOrEmpty($UserParameter["PrivateKey"])) {
                $Errors += "PrivateKey is required, when 'UseSSHPrivateKey' is true"
            }
            if ($UserParameter["Passphrase"] -ne $UserParameter["ConfirmPassphrase"]) {
                $Errors += "Passphrase and ConfirmPassphrase must match"
            }
        } else {
            if(!$UserParameter.ContainsKey("Password") -or [string]::IsNullOrEmpty($UserParameter["Password"])) {
                $Errors += "Password is required"
            }
            if (!$UserParameter.ContainsKey("ConfirmPassword") -or [string]::IsNullOrEmpty($UserParameter["ConfirmPassword"])) {
                $Errors += "ConfirmPassword is required"
            }
            if ($UserParameter["Password"] -ne $UserParameter["ConfirmPassword"]) {
                $Errors += "Password and ConfirmPassword must match"
            }
        }
    }

    if ($UserParameter.ContainsKey("CreateMoveGroup") -and $UserParameter["CreateMoveGroup"] -eq $true) {
        if (!$UserParameter.ContainsKey("MoveGroupName") -or [string]::IsNullOrEmpty($UserParameter["MoveGroupName"])) {
            $Errors += "MoveGroupName is required, when 'CreateMoveGroup' is true"
        }
    }

    return $Errors
}

Export-ModuleMember -Function Add-RMSource