validations_utils.ps1

$PUBLIC_KEY = ('{0}/ZertoPublicKey.pem' -f $psScriptRoot)
$PASSWORD_REGEX = [regex]"^(?=.*[A-Z])(?=.*[^A-Za-z])(?=.*\d)(?=.*[\W_]).{8,}$"
$UNSUPPORTED_FIRST_LETTER_IN_PASSWORD = "[]{}>|*&!%#`@,"
$UNSUPPORTED_LETTERS_IN_PASSWORD_REGEX_PATTERN = '[\$\s"]'

function Validate-FileBySignature {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "File to verify")]
        [string]$FilePath,

        [Parameter(Mandatory = $true, HelpMessage = "Signature file to verify")]
        [string]$SignatureFilePath
    )

    process {
        Write-Host "Verifying signature for $FilePath"
        Write-Host "The verification process might take a while, please wait..."

        $isVerified = (openssl dgst -sha256 -verify $PUBLIC_KEY -signature $SignatureFilePath $FilePath 2>&1) -join ";"

        if ($isVerified -eq "Verified OK") {
            Write-Host "File signature was verified successfully for $FilePath by $SignatureFilePath"
            return $true
        }
        else {
            Write-Host "Could not verify $FilePath signature by $SignatureFilePath"
            return $false
        }
    }
}

function Validate-NetworkSettings {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "ZVML ip address")]
        [string]
        $ZvmlIp,

        [Parameter(Mandatory = $true, HelpMessage = "SubnetMask address.")]
        [string]
        $SubnetMask,

        [Parameter(Mandatory = $true, HelpMessage = "Default gateway.")]
        [string]
        $DefaultGateway,

        [Parameter(Mandatory = $true, HelpMessage = "DNS server address.")]
        [string]
        $DNS
    )

    process {
        $IpSettingsValidated = $true

        Write-Host "Validating ZVML IP $ZvmlIp"
        if ((Validate-IpAddress $ZvmlIp) -eq $false) {
            $IpSettingsValidated = $false
            $Message = "ZVM could not be configured with the specified IP address $ZvmlIp, due to invalid IP format."
            Write-Host $Message
            Write-Error $Message
        }

        Write-Host "Validating SubnetMask $SubnetMask"
        if ((Validate-IpAddress $SubnetMask) -eq $false) {
            $IpSettingsValidated = $false
            $Message = "ZVM could not be configured with the specified subnet mask $SubnetMask, due to invalid IP format."
            Write-Host $Message
            Write-Error $Message
        }

        Write-Host "Validating DNS IP $DNS"
        if ((Validate-IpAddress $DNS) -eq $false) {
            $IpSettingsValidated = $false
            $Message = "ZVM could not be configured with the specified DNS $DNS, due to invalid IP format."
            Write-Host $Message
            Write-Error $Message
        }

        Write-Host "Validating DefaultGateway IP $DefaultGateway"
        if ((Validate-IpAddress $DefaultGateway) -eq $false) {
            $IpSettingsValidated = $false
            $Message = "ZVM could not be configured with the specified default gateway $DefaultGateway, due to invalid IP format."
            Write-Host $Message
            Write-Error $Message
        }

        if ($IpSettingsValidated -eq $false) {
            Write-Error "provided IP addresses format is invalid" -ErrorAction Stop
        }
    }
}

function Validate-IpAddress {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "ip address")]
        [string]$ip
    )

    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        try {
            if (([ipaddress]$ip).IPAddressToString -eq ("" + $ip)) {
                Write-Host "IP address $ip is valid"
                return $true
            }
            else {
                Write-Host "ZVM could not be configured with the specified address $ip, due to wrong format. $_"
                return $false
            }
        }
        catch {
            Write-Host "ZVM could not be configured with the specified address $ip, due to wrong format. $_"
            return $false
        }
    }
}

function Validate-VcEnvParams {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName,

        [Parameter(Mandatory = $true,
            HelpMessage = "ZVM IP")]
        [string]$ZVMLIp,

        [Parameter(Mandatory = $true,
            HelpMessage = "Subnet Mask")]
        [string]$SubnetMask,

        [Parameter(Mandatory = $true,
            HelpMessage = "Default Gateway")]
        [string]$DefaultGateway,

        [Parameter(Mandatory = $true,
            HelpMessage = "DNS Address")]
        [string]$DNS,

        [Parameter(Mandatory = $true,
            HelpMessage = "Network Name")]
        [string]$NetworkName
    )

    process {

        Write-Host "Starting $($MyInvocation.MyCommand)..."

        if ((Test-VmExists -VmName $ZVM_VM_NAME) -eq $true) {
            Write-Error "$ZVM_VM_NAME already exists. Please remove it with 'Uninstall-Zerto' first." -ErrorAction Stop
        }

        if ((Validate-DatastoreName -DatastoreName $DatastoreName) -ne $true) {
            Write-Error "Datastore=$DatastoreName does not exist. Validation failed" -ErrorAction Stop
        }

        if ((Validate-NetworkName -NetworkName $NetworkName) -ne $true) {
            Write-Error "Network=$NetworkName does not exist. Validation failed" -ErrorAction Stop
        }

        Validate-NetworkSettings -ZvmlIp $ZVMLIp -SubnetMask $SubnetMask -DefaultGateway $DefaultGateway -DNS $DNS

        Write-Host "Validations finished"
    }
}

function Get-ValidatedHostName {
    param(
        [Parameter(Mandatory = $false,
            HelpMessage = "Host Name")]
        [string]$HostName
    )

    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        $AllValidHostNames = (Get-VMHost -ErrorAction SilentlyContinue | Where-Object { $_.ConnectionState -eq "Connected" -and $_.PowerState -eq "PoweredOn" }).Name

        if ($HostName) {
            Write-Host "Host provided by user: $HostName"
            $ValidHostName = $AllValidHostNames | Where-Object { $_ -eq $HostName } | Select-Object -First 1
        }
        else {
            Write-Host "Host was not provided by user, an alternate valid host will be selected"
            $ValidHostName = $AllValidHostNames | Select-Object -First 1
        }


        if ($ValidHostName) {
            Write-Host "Host=$ValidHostName exists. Validation successful"
            return $ValidHostName
        }
        else {
            if ($HostName) {
                Write-Error "Host=$HostName does not exist. Validation failed" -ErrorAction Stop
            }
            else {
                Write-Error "could not find a valid host." -ErrorAction Stop
            }
        }
    }
}

function Validate-ZertoPassword {
    param (
        [Parameter(Mandatory = $true)]
        [SecureString]$Password
    )
    process {
        $isValid = $PASSWORD_REGEX.Matches($( ConvertFrom-SecureString -SecureString $Password -AsPlainText)).Success
        if ($isValid -ne $true) {
            Write-Error "Zerto password requirements are not met. Password should contain at least one uppercase letter, one digit, one non-alphanumeric character, and be at least 8 characters long." -ErrorAction Stop
        }
        if ($UNSUPPORTED_FIRST_LETTER_IN_PASSWORD.Contains($(ConvertFrom-SecureString -SecureString $Password -AsPlainText)[0])) {
            Write-Error "Zerto password requirements are not met. Password should not begin with the following characters $UNSUPPORTED_FIRST_LETTER_IN_PASSWORD ." -ErrorAction Stop
        }
        if ($(ConvertFrom-SecureString -SecureString $Password -AsPlainText) -match $UNSUPPORTED_LETTERS_IN_PASSWORD_REGEX_PATTERN) {
            Write-Error 'Zerto password requirements are not met. Password should not contain $, ", or a space characters.' -ErrorAction Stop
        }
    }
}

function Test-VmExists {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "VM Name")]
        [string]$VmName
    )
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        $VM = Get-VM -Name $VmName -ErrorAction SilentlyContinue | Select-Object -first 1
        if ($null -eq $VM) {
            Write-Host "VM=$VmName does not exist"
            return $false
        }

        Write-Host "VM=$VmName exists"
        return $true
    }
}

function Validate-BiosUUID {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName,
        [Parameter(Mandatory = $true,
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]
        [string]$BiosUuid
    )
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        $Datastore = Get-Datastore -Name $DatastoreName | Select-Object -first 1

        New-PSDrive -Name TgtDS -Location $Datastore -PSProvider VimDatastore -Root '\' | Out-Null
        $DatastoreFolders = Get-ChildItem -Path TgtDS: -Recurse
        Remove-PSDrive -Name TgtDS

        foreach ($DatastoreFolder in $DatastoreFolders) {
            if ($DatastoreFolder.Name -eq $BiosUuid) {
                Write-Host "BiosUuid=$BiosUuid exists. Validation successful"
                return $true
            }
        }

        Write-Error "BiosUuid=$BiosUuid does not exist. Validation failed"
        return $false
    }
}

function Validate-DigitsOnly {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Input string to validate all the characters are numeric")]
        [string]$InputString
    )

    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        if ($InputString -match "^\d+$") {
            Write-Host "InputString=$InputString contains digits only"
            return $true
        }

        Write-Error "Validation failed. InputString=$InputString contains non-numeric characters"
        return $false
    }
}

function Validate-DatastoreName {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName
    )
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        $Datastore = Get-Datastore -Name $DatastoreName -ErrorAction SilentlyContinue | Select-Object -first 1
        if ($null -eq $Datastore) {
            Write-Host "Datastore=$DatastoreName does not exist. Validation failed"
            return $false
        }

        Write-Host "Datastore=$DatastoreName exists. Validation successful"
        return $true
    }
}

function Validate-NetworkName {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Network Name")]
        [string]$NetworkName
    )
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."

        $Network = Get-VirtualNetwork -Name $NetworkName -ErrorAction SilentlyContinue | Select-Object -first 1
        if ($null -eq $Network) {
            Write-Host "Network=$NetworkName does not exist. Validation failed"
            return $false
        }

        Write-Host "Network=$NetworkName exists. Validation successful"
        return $true
    }
}

function Validate-AvsParams {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Tenant ID")][string] $TenantId,
        [Parameter(Mandatory = $true, HelpMessage = "Cleint ID")][string] $ClientId,
        [Parameter(Mandatory = $true, HelpMessage = "Client Secret")][SecureString] $ClientSecret,
        [Parameter(Mandatory = $true, HelpMessage = "Subscription ID")][string] $SubscriptionId,
        [Parameter(Mandatory = $true, HelpMessage = "Resource Group Name")][string] $ResourceGroupName,
        [Parameter(Mandatory = $true, HelpMessage = "Avs Cloud Name")][string] $AvsCloudName
    )
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)"

        $body = @{
            'client_id'     = $ClientId
            'client_secret' = (ConvertFrom-SecureString -SecureString $ClientSecret -AsPlainText)
            'grant_type'    = "client_credentials"
            'resource'      = "https://management.core.windows.net/"
        }
        try {
            $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" `
                -Body $body -ContentType "application/x-www-form-urlencoded"
        }
        catch {
            Write-Error "Authorization failed for Azure. Please check the values of the TenantID-ClientID-ClientSecret combintaion." `
                -ErrorAction Stop
        }

        $authToken = $authResponse.access_token

        $subscriptionUri = "https://management.azure.com/subscriptions/$SubscriptionId/?api-version=2020-01-01"
        try {
            [void] (Invoke-RestMethod -Method Get -Headers @{Authorization = ("Bearer " + $authToken) } -Uri $subscriptionUri)
        }
        catch {
            Write-Error "The subscription '$SubscriptionId' could not be found for the tenant '$TenantId'." -ErrorAction Stop
        }

        $avsCloudNameUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.AVS/privateClouds/$AvsCloudName/?api-version=2020-03-20"
        try {
            [void] (Invoke-RestMethod -Method Get -Headers @{Authorization = ("Bearer " + $authToken) } -Uri $avsCloudNameUri)
        }
        catch {
            Write-Error "The private cloud '$AvsCloudName' could not be found for the tenant '$TenantId'." -ErrorAction Stop
        }

        Write-Host "AVS parameters are valid"
    }
}

function Assert-ReconfigurationToken ($Token) {
    Write-Host "Starting $($MyInvocation.MyCommand)"
    try {
        $Url = "https://www.zerto.com/myzerto/wp-json/services/zerto/s3-ova-employee?key=" + $Token
        $response = Invoke-WebRequest -Uri $Url -ErrorAction Stop -TimeoutSec 1800
        $content = $response.Content
        if ($content -ne '{"success":true}') {
            throw "Reconfiguration token is invalid."
        }
        Write-Host "Reconfiguration token is valid"
    }
    catch {
        throw "Reconfiguration token is invalid."
    }
}