ZertoAVSModule.psm1

using module Microsoft.AVS.Management


$PUBLIC_KEY = ('{0}/ZertoPublicKey.pem' -f $psScriptRoot)
$ZERTO_FOLDER_ON_HOST = "/var/zerto"
$LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS = ('{0}/zertoDriverLogs/' -f $psScriptRoot)
$LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES = ('{0}/filesFromDatastore/' -f $psScriptRoot)

#region Zerto Validations

Function IsZertoUserExists
{
    <#
        .DESCRIPTION
        Get a zertoUsername and a domain, and return whether or not the user exists in the domain.
 
            .PARAMETER zertoUsername
            Zerto user name (default value is ZertoDR)
             
            .PARAMETER domain
            Domain name (default value is vsphere.local)
             
        .EXAMPLE
 
        IsZertoUserExists -zertoUsername <Username> -domain <domain>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [parameter(Mandatory=$false,
            HelpMessage = "Zerto user name for ZVM installation")]
        [string]$zertoUsername = "ZertoDR",
        [parameter(Mandatory=$false,
            HelpMessage = "Domain to search the user at")]
        [string]$domain = "vsphere.local"
    )
        
    Process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        if(Get-SsoPersonUser -Name $zertoUsername -Domain $domain -ErrorAction SilentlyContinue) {
            Write-Host "$zertoUsername already exists in $VC_ADDRESS, domain: $domain."
            return $true;
        }
        
        Write-Host "$zertoUsername doesn't exist in $VC_ADDRESS, domain: $domain."
        return $false;
    }
}

Function IsZertoRoleExists
{
    <#
        .DESCRIPTION
        Return true if ZertoRole exists, otherwise return false.
 
            .PARAMETER zertoRole
            Zerto role name (default value is ZertoRole)
             
        .EXAMPLE
 
        IsZertoRoleExists -zertoRole <role>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [parameter(Mandatory=$false,
            HelpMessage = "Zerto role name for ZVM installation")]
        [string]$zertoRole = "ZertoRole"
    )
    
    Process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        If (Get-VIRole -Name $zertoRole -ErrorAction SilentlyContinue) {
            Write-Host "$zertoRole already exists in $VC_ADDRESS"
            return $true
        } 
        
        Write-Host "$zertoRole doesn't exist in $VC_ADDRESS"
        return $false;
    }
}

Function IsZertoDriverLoaded
{
    <#
        .DESCRIPTION
        Return true if Zerto driver is loaded, otherwise return false.
         
            .PARAMETER HostName
            Host Name to connect with SSH
 
        .EXAMPLE
 
        IsZertoDriverLoaded -HostName <HostName>
    #>

    
    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    
    )
    
    Process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $vmkload = '/tmp/vmklod_output'
        $ZertoDriver = '/tmp/zertoDriver'
        
        $Command =    ('vmkload_mod -l > {0}' -f $vmkload)
        $Res = RunSSHCommands -HostName $HostName -Commands $Command
        $Command = ('grep "zdriver" {0} > {1}' -f $vmkload, $ZertoDriver)
        $Res = RunSSHCommands -HostName $HostName -Commands $Command -ExitStatusAction "Skip"
        
        $ExitStatus = $Res["0_exitStatus"]
        if ($ExitStatus -eq '0') {
            Write-Host "Zerto driver is loaded"
            return $true;
        }
        
        if ($ExitStatus -eq '1') {
            Write-Host "Zerto driver is not loaded"
            return $false;
        }
    }
}

Function GetDriverLogsFromHost
{
    <#
        .DESCRIPTION
        Copy Zerto driver logs from host to datastore
         
            .PARAMETER HostName
            Host Name to connect with SSH
             
            .PARAMETER DatastoreName
            Datastore Name
 
        .EXAMPLE
 
        GetDriverLogsFromHost -HostName <HostName> -DatastoreName <DatastoreName>
    #>

    
    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName
    )
    
    Write-Host "Starting $($MyInvocation.MyCommand)..."
    DownloadDriverLogFilesFromHostToPSEngine -HostName $HostName
    UploadDriverLogFilesFromPSEngineToDatastore -DatastoreName $DatastoreName -HostName $HostName
}

Function DownloadDriverLogFilesFromHostToPSEngine
{
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName
    )

    Write-Host "Starting $($MyInvocation.MyCommand)..."
        
    $sftpSessionId = ($SFTP_Sessions[$HostName]).Value.SessionId
    
    if (!(Test-Path -path $LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS)) {
        New-Item $LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS -Type Directory
        Write-Host "Create $LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS folder on powerShell engine"
    }
    
    $files = '/etc/vmware/zloadmod.txt',
             '/etc/vmware/zunloadmod.txt'
            
    foreach ($file in $files)
    {
        if (Test-SFTPPath -SessionId $sftpSessionId -Path $file) {
            Write-Host "Going to download $file from $HostName to powerShell engine ($LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS)"
            Get-SFTPItem -SessionId $sftpSessionId -Destination $LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS -Path $file -Force
            Write-Host "$file was copied from $HostName to powerShell engine"
        }
        else {
            Write-Host "File $file doesn't exist on $HostName"
        }
    }
}

Function UploadDriverLogFilesFromPSEngineToDatastore
{
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName
    )

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

    $psDriverName = "ds"
    $zertoDriverLogsDSPath = ('{0}:\zertoDriverLogsFromHost\{1}\' -f $psDriverName, $HostName)
    $zertoDriverLogsPSPath = ('{0}*' -f $LOCAL_TEMP_FOLDER_FOR_ZERTO_DRIVER_LOGS)
    $datastore = Get-Datastore $DatastoreName

    New-PSDrive -Location $datastore -Name $psDriverName -PSProvider VimDatastore -Root "\" -ErrorAction Stop
    Copy-DatastoreItem -Item $zertoDriverLogsPSPath -Destination $zertoDriverLogsDSPath -Force -ErrorAction Stop
    
    $files = (Get-ChildItem -Path $zertoDriverLogsDSPath -Name)  -join ";"
    Write-Host "$MyInvocation.MyCommand - ZertoDriverFiles: {$files} were copied from powerShell engine ($zertoDriverLogsPSPath) to datastore ($zertoDriverLogsDSPath)"
    
    Remove-PSDrive -Name $psDriverName
}

Function GetZertoFilesListFromHost
{
    <#
        .DESCRIPTION
        Return a list of all Zerto files on host under /var/zerto
         
            .PARAMETER HostName
            Host Name to connect with SSH
 
        .EXAMPLE
 
        GetZertoFilesListFromHost -HostName <HostName>
    #>

    
    [CmdletBinding()]
    [AVSAttribute(5, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        $Command = ('ls -l {0}' -f $ZERTO_FOLDER_ON_HOST)
        return RunSSHCommands -HostName $HostName -Commands $Command
    }
}

#end Region

Function TestConnection {
    return "TestConnection"
}

Function GenerateRandomPassword {
     #Generate a password with at least 2 uppercase, 4 lowercase, 4 digits & 2 special character (!@#$%^&*())
     
     Write-Host "Starting $($MyInvocation.MyCommand)..."
     
    $upperChars =(65..90)
    $lowerChars    = (97..122)
    $numerics =  (48..57)
    $specialChars = @(33, 35, 36, 37, 38, 40, 41, 42, 45, 64, 94)    
    
    $seedArray = ($upperChars | Get-Random -Count 2)
    $seedArray += ($lowerChars | Get-Random -Count 4)
    $seedArray += ($numerics | Get-Random -Count 4)
    $seedArray += ($specialChars | Get-Random -Count 2)
    
    Foreach ($a in $seedArray){
        $passwordAscii += , [char][byte]$a 
    }
    
    $password = $passwordAscii -join ""
    
    return $password
}

Function CreateZertoUser {
    <#
        .DESCRIPTION
        Create a ZertoDR user and a ZertoDR role which includes required privileges.
        The script creates a permission by assigning the ZertoDR role to the ZertoDR user.
 
            .PARAMETER zertoUsername
            Zerto user name (default value is ZertoDR)
             
            .PARAMETER zertoRole
            Zerto role name (default value is ZertoRole)
             
        .EXAMPLE
 
        CreateZertoUser -zertoUsername <Username> -zertoRole <Role>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [parameter(Mandatory=$false,
            HelpMessage = "Zerto user name for ZVM installation")]
        [string]$zertoUsername = "ZertoDR",
        [parameter(Mandatory=$false,
            HelpMessage = "Zerto role name for ZVM installation")]
        [string]$zertoRole = "ZertoRole"
    )
 
    Process{
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $domain = "vsphere.local"
        $zertoPrincipal = $domain + "\" + $zertoUsername
        $zertoPrivileges = @(
          "Alarm.Create",
          "Alarm.Delete",
          "Authorization.ModifyPermissions",
          "Cryptographer.Access",
          "Datastore.AllocateSpace",
          "Datastore.Browse",
          "Datastore.Config",
          "Datastore.DeleteFile",
          "Datastore.FileManagement",
          "Datastore.UpdateVirtualMachineFiles",
          "StoragePod.Config",
          "Extension.Register",
          "Extension.Unregister",
          "Folder.Create",
          "Global.CancelTask",
          "Global.Diagnostics",
          "Global.DisableMethods",
          "Global.EnableMethods",
          "Global.LogEvent",
          "Host.Config.AdvancedConfig",
          "Host.Config.AutoStart",
          "Host.Config.Settings",
          "Host.Config.NetService",
          "Host.Config.Patch",
          "Host.Inventory.EditCluster",
          "Network.Assign",
          "Resource.AssignVAppToPool",
          "Resource.AssignVMToPool",
          "Resource.ColdMigrate",
          "Resource.HotMigrate",
          "Sessions.ValidateSession",
          "Task.Create",
          "Task.Update",
          "VApp.ApplicationConfig",
          "VApp.AssignResourcePool",
          "VApp.AssignVM",
          "VApp.Create",
          "VApp.Delete",
          "VApp.Import",
          "VApp.PowerOff",
          "VApp.PowerOn",
          "VirtualMachine.Config.AddExistingDisk",
          "VirtualMachine.Config.AddNewDisk",
          "VirtualMachine.Config.AddRemoveDevice",
          "VirtualMachine.Config.AdvancedConfig",
          "VirtualMachine.Config.CPUCount",
          "VirtualMachine.Config.DiskExtend",
          "VirtualMachine.Config.EditDevice",
          "VirtualMachine.Config.ManagedBy",
          "VirtualMachine.Config.Memory",
          "VirtualMachine.Config.RawDevice",
          "VirtualMachine.Config.RemoveDisk",
          "VirtualMachine.Config.Resource",
          "VirtualMachine.Config.Settings",
          "VirtualMachine.Config.SwapPlacement",
          "VirtualMachine.Config.UpgradeVirtualHardware",
          "VirtualMachine.Interact.PowerOff",
          "VirtualMachine.Interact.PowerOn",
          "VirtualMachine.Inventory.CreateFromExisting",
          "VirtualMachine.Inventory.Create",
          "VirtualMachine.Inventory.Register",
          "VirtualMachine.Inventory.Delete",
          "VirtualMachine.Inventory.Unregister",
          "VirtualMachine.State.RemoveSnapshot"
        )


        #if the user already exists - do nothing
        if(IsZertoUserExists $zertoUsername) {
            Write-Host "$zertoUsername already exists in $VC_ADDRESS. Skip creation..."
        }
        else {
            #Create Zerto user
            $PersistentSecrets.ZertoPassword = GenerateRandomPassword
            New-SsoPersonUser -UserName $zertoUsername -Password $PersistentSecrets.ZertoPassword -Description "Zerto DR user" -EmailAddress "ZertoDR@zerto.com" -FirstName "Zerto" -LastName "DR" -ErrorAction Stop
            
            # Add user to CloudAdmins group
            $group = "CloudAdmins"
            $SsoGroup = Get-SsoGroup -Name $group -Domain $domain
            Get-SsoPersonUser -Name $zertoUsername -Domain $domain -ErrorAction Stop | Add-UserToSsoGroup -TargetGroup $SsoGroup -ErrorAction Stop
        }
        
        #If ZertoRole already exists - overwrite it with the new privilages
        If (IsZertoRoleExists $zertoRole) {
            $joinedPrivilgaes = ($zertoPrivileges -join ";")
            Write-Host "Role: $zertoRole already exists. Overwrite it with the following privilages: $joinedPrivilgaes"
            
            Remove-VIRole -Role (Get-VIRole -Name $zertoRole) -Force:$true -Confirm:$false
            New-VIRole -name $zertoRole -Privilege (Get-VIPrivilege -Server $VC_ADDRESS -id $zertoPrivileges) -Server $VC_ADDRESS
        } 
        else {
            #Create a new role
            New-VIRole -name $zertoRole -Privilege (Get-VIPrivilege -Server $VC_ADDRESS -id $zertoPrivileges) -Server $VC_ADDRESS -ErrorAction Stop
            Write-Host "Role $zertoRole created on $VC_ADDRESS"
        }
        
        # Get the Root Folder
        $rootFolder = Get-Folder -NoRecursion
        # Create permission on vCenter object by assigning role to user
        New-VIPermission -Entity $rootFolder -Principal $zertoPrincipal -Role $zertoRole -Propagate:$true -ErrorAction Stop
        Write-Host "Finish to create ZertoUser ($zertoUsername) and assign ZertoRole ($zertoRole)"
    }
}

Function ResetZertoPassword
{
    <#
        .DESCRIPTION
        Change password for Zerto user
 
            .PARAMETER zertoUsername
            Zerto user name (default value is ZertoDR)
             
        .EXAMPLE
 
        ResetZertoPassword -zertoUsername <Username> -domain <domain>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [parameter(Mandatory=$false,
            HelpMessage = "Zerto user name for reset password")]
        [string]$zertoUsername = "ZertoDR",
        [parameter(Mandatory=$false,
            HelpMessage = "Domain to search the user at")]
        [string]$domain = "vsphere.local"
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $zertoUser = Get-SsoPersonUser -Name $zertoUsername -Domain $domain
        if($zertoUser) {
            $newPassword = GenerateRandomPassword
            Set-SsoPersonUser -User $zertoUser -NewPassword $newPassword -ErrorAction Stop
            $PersistentSecrets.ZertoPassword = $newPassword
            
            Write-Host "Password for $zertoUsername changed successfully"
        }
        else{
            throw "Error! Failed to reset Zerto password for $zertoUsername, user doesn't exist"
        }
    }
}

Function SetSSHTimeout{
    <#
        .DESCRIPTION
        Determines how long SSH session remains open
 
            .PARAMETER HostName
            Host Name to connect with SSH
             
            .PARAMETER SSHTimeout
            SSH timeout value
             
        .EXAMPLE
 
        SetSSHTimeout -HostName <HostName> -SSHTimeout <SSHTimeout>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "SSH timeout value")]
        [string]$SSHTimeout
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $vmHost = Get-VMHost -Name $HostName
        
        Get-AdvancedSetting -Entity $vmHost -Name  "UserVars.ESXiShellInteractiveTimeOut" -ErrorAction SilentlyContinue | Set-AdvancedSetting -Value $SSHTimeout -Confirm:$false -ErrorAction SilentlyContinue
        Write-Host "Set configuration setting ""UserVars.ESXiShellInteractiveTimeOut"" on $HostName to $SSHTimeout"
    }
}

Function CreateZertoFolderOnHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $Command = "mkdir -p $ZERTO_FOLDER_ON_HOST"
        $Res = RunSSHCommands -HostName $HostName -Commands $Command
        $ExitStatus = $Res["0_exitStatus"];
        
        if ( $ExitStatus -ne '0' ) {
            throw "failed to create $ZERTO_FOLDER_ON_HOST on host $HostName. Exit status for ""$Command"" is $ExitStatus"
        } 
        else {
            Write-Host "Zerto folder ($ZERTO_FOLDER_ON_HOST) was created on $HostName."
        }
    }
}

Function VerifyAndUploadFilesFromPSEngineToHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName
    )
    
    Write-Host "Starting $($MyInvocation.MyCommand)..."
    
    CreateZertoFolderOnHost -HostName $HostName
    
    foreach ($file in Get-ChildItem $LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES* -Include *.sh, *.o) {
        $signature = ("{0}_signature" -f $file)

        $isVerified = (openssl dgst -sha256 -verify $PUBLIC_KEY -signature $signature $file 2>&1) -join ";"
        
        if ($isVerified -eq "Verified OK") {
            Set-SFTPItem -SessionId ($SFTP_Sessions[$HostName]).Value.SessionId -Destination $ZERTO_FOLDER_ON_HOST -Path $file -Force
        }
        else {
            throw "Error! host $HostName failed to verify $file with $signature, openSSL output: $isVerified"
        }
    }
}

Function DownloadFilesFromDatastoreToPSEngine {
    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
    )
    
    Write-Host "Starting $($MyInvocation.MyCommand)..."

    $psDriverName = "ds"
    $FullRemoteFileLocation = ('{0}:\zagentid\{1}\*' -f $psDriverName, $BiosUuid)
    $datastore = Get-Datastore $DatastoreName

    Write-Host "Going to download files from $darastore ($FullRemoteFileLocation) to PS engine ($LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES)."
    
    New-PSDrive -Location $datastore -Name $psDriverName -PSProvider VimDatastore -Root "\" -ErrorAction Stop
    Copy-DatastoreItem -Item $FullRemoteFileLocation -Destination $LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES -Force -ErrorAction Stop
    
    $files = (Get-ChildItem -Path $LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES -Name)  -join ";"
    Write-Host "ZertoFiles: {$files} were copied from darastore $DatastoreName ($FullRemoteFileLocation) to PS engine ($LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES)"
    
    Remove-PSDrive -Name $psDriverName
}

Function CopyFilesFromDatastoreToHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]
        [string]$BiosUuid
    )
    
    Write-Host "Starting $($MyInvocation.MyCommand)..."
    
    DownloadFilesFromDatastoreToPSEngine -DatastoreName $DatastoreName -BiosUuid $BiosUuid
    VerifyAndUploadFilesFromPSEngineToHost -HostName $HostName
}

Function RunSSHCommands {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Commands to execute")]
        [String[]]$Commands,
        [Parameter(Mandatory = $false, 
            HelpMessage = "Action on exitStatus 1")]
        [string]$ExitStatusAction = "Stop"
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $NamedOutputs = @{}
        Set-Variable -Name NamedOutputs -Value $NamedOutputs -Scope Global
        
        $i = 0
        foreach ($Command in $Commands) {
            $SSH = Invoke-SSHCommand -SSHSession $SSH_Sessions[$HostName].Value -Command $Command

            if (!$SSH) { 
                throw "Error! failed to Invoke-SSHCommand ""$Command"" on host $HostName"
            }

            $ExitStatus = $SSH.ExitStatus
            $Error = $SSH.Error
            $Output = ($SSH.Output -join ";")

            if ($ExitStatus -ne 0 -Or $Error) {
                if (($ExitStatus -eq 1) -And (!$Error) -And ($ExitStatusAction -eq "Skip")) {
                    Write-Host "ExitStatus of ""$Command"" is 1, while ExitStatusAction = Skip. Skipping..."
                }
                else {
                    throw "Error! failed to run ""$Command"" on host $HostName, ExitStatus: $ExitStatus, Output: $Output, Error: $Error, ExitStatusAction: $ExitStatusAction"
                }
            }
            
            Write-Host "Finish to run ""$Command"" on host $HostName, ExitStatus: $ExitStatus, Output: $Output, Error: $Error"
            
            $NamedOutputs["$($i)_cmd"] = $Command
            $NamedOutputs["$($i)_exitStatus"] = $ExitStatus
            $NamedOutputs["$($i)_output"] = $Output
            $NamedOutputs["$($i)_error"] = $Error

            $i++;
        }
        
        return $NamedOutputs
    }
}

Function Get-HostTempFolderInfo {
    <#
        .DESCRIPTION
          
        Display information about the available disk space (For Internal Use)
          
            .PARAMETER HostName
            Host Name to connect with SSH
          
        .EXAMPLE
          
        Get-HostTempFolderInfo -HostName xxx.xxx.xxx.xxx
    #>

    [CmdletBinding()]
    [AVSAttribute(5, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $Command = "vdf"
        return RunSSHCommands -HostName $HostName -Commands $Command
    }
}


Function EnsureConnectivity {   
    <#
        .DESCRIPTION
          
        Check if the host is up and running (For Internal Use)
          
            .PARAMETER HostName
            Host Name to connect with SSH
          
        .EXAMPLE
          
        EnsureConnectivity -HostName xxx.xxx.xxx.xxx
    #>

    [CmdletBinding()]
    [AVSAttribute(5, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $Command = "echo testing123"
        return RunSSHCommands -HostName $HostName -Commands $Command
    }
}

Function Get-HostEsxiVersion {
    <#
        .DESCRIPTION
          
        Retrieve the ESXi version (For Internal Use)
          
            .PARAMETER HostName
            Host Name to connect with SSH
          
        .EXAMPLE
          
        Get-HostEsxiVersion -HostName xxx.xxx.xxx.xxx
    #>

    [CmdletBinding()]
    [AVSAttribute(5, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $Command = "vmware -l"
        return RunSSHCommands -HostName $HostName -Commands $Command
    }
}

Function ChangeStartupFile {
    <#
        .DESCRIPTION
          
        Responsible for loading the driver when the host is booting.
        /etc/rc.local.d/local.sh file is executed after all the normal system services are started
          
            .PARAMETER HostName
            Host Name to connect with SSH
          
            .PARAMETER DatastoreName
            Datastore Name
          
            .PARAMETER BiosUuid
            "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid"
          
        .EXAMPLE
          
        ChangeStartupFile -HostName xxx.xxx.xxx.xxx -DatastoreName xxx -BiosUuid xxx
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [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)..."
        
        if(((ValidateDatastoreName -DatastoreName $DatastoreName) -ne $true) -or ((ValidateBiosUUID -DatastoreName $DatastoreName -BiosUuid $BiosUuid) -ne $true))
        {
            throw "validation failed"
        }
        
        $zloadmod = ('{0}/zloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)

        CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreName $DatastoreName -BiosUuid $BiosUuid

        $startupFile = ('{0}/startup_file.sh' -f $ZERTO_FOLDER_ON_HOST)
    
        $Commands =    ('grep -v "ZeRTO\|exit 0" /etc/rc.local.d/local.sh > {0}' -f $startupFile),
        ('echo \#ZeRTO\ >> {0}' -f $startupFile),
        ('echo sh {0} load {1} {2} 0 \"\" 1 \> /etc/vmware/zloadmod.txt \2\>\&\1 \#ZeRTO\ >> {3}' -f $zloadmod, $DatastoreName, $BiosUuid, $startupFile),
        ('echo \#ZeRTO\ >> {0}' -f $startupFile),
        ('echo "exit 0" >> {0}' -f $startupFile),
        ('cp -f {0} /etc/rc.local.d/local.sh' -f $startupFile),
        ('chmod a+x {0}' -f $zloadmod)
                    
        return RunSSHCommands -HostName $HostName -Commands $Commands
    }
}

Function InstallDriver {
    <#
        .DESCRIPTION
          
        Install the driver
          
            .PARAMETER HostName
            Host Name to connect with SSH
              
            .PARAMETER DatastoreName
            Datastore Name
          
            .PARAMETER BiosUuid
            Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid
          
            .PARAMETER EsxiVersion
            Esxi version
          
        .EXAMPLE
        InstallDriver -HostName xxx.xxx.xxx.xxx -DatastoreName <DatastoreName> -BiosUuid <UUID> -EsxiVersion xx
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName,
        [Parameter(Mandatory = $true,
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]    
        [string]$BiosUuid,
        [Parameter(Mandatory = $true,    
            HelpMessage = "Esxi version")]    
        [string]$EsxiVersion
    )

    Process {
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        if(((ValidateDatastoreName -DatastoreName $DatastoreName) -ne $true) -or ((ValidateBiosUUID -DatastoreName $DatastoreName -BiosUuid $BiosUuid) -ne $true) -or ((ValidateESXiVersion -HostVersion $EsxiVersion) -ne $true))
        {
            throw "validation failed"
        }
        
        if (IsZertoDriverLoaded $HostName)
        {
            Write-Host "Warning! Zerto driver is already loaded on $HostName"
        }
        
        $zloadmod = ('{0}/zloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)
        
        CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreName $DatastoreName -BiosUuid $BiosUuid
        
        $Commands = ('chmod a+x {0}' -f $zloadmod),
            ('{0} init {1} {2} 0 {3} 1 > /etc/vmware/zloadmod.txt' -f $zloadmod, $DatastoreName, $BiosUuid, $EsxiVersion)

        return RunSSHCommands -HostName $HostName -Commands $Commands
    }
}

Function UninstallDriver {
    <#
        .DESCRIPTION
          
        Uninstall the driver
          
            .PARAMETER HostName
            Host Name to connect with SSH
              
            .PARAMETER DatastoreName
            Datastore Name
          
            .PARAMETER BiosUuid
            Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid
          
          
        .EXAMPLE
        UninstallDriver -HostName xxx.xxx.xxx.xxx -DatastoreName <DatastoreName> -BiosUuid <UUID>
    #>

    [CmdletBinding()]
    [AVSAttribute(30, UpdatesSDDC = $false)]
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [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)..."
        
        if (IsZertoDriverLoaded $HostName)
        {
            $zunloadmod = ('{0}/zunloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)
            
            CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreName $DatastoreName -BiosUuid $BiosUuid

            $Commands = ('chmod a+x {0}' -f $zunloadmod),
                        ('{0} cleanup > /etc/vmware/zunloadmod.txt' -f $zunloadmod)

            return RunSSHCommands -HostName $HostName -Commands $Commands
        }
        
        else {
            throw "Error! Failed to run UninstallDriver, Zerto driver is not loaded on $HostName."
        }
    }
}

Function ValidateDatastoreName {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Name")]
        [string]$DatastoreName
)
    process{
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        $DATASTORE = Get-Datastore -Name $DatastoreName | Select-Object -first 1
        if($DATASTORE -eq $null)
        {
            write-host "Datastore=$DatastoreName does not exists. validation failed"
            return $false
        }

        write-host "Datastore=$DatastoreName exists. validation pass"
        return $true
    }
}

Function ValidateBiosUUID {
    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 pass"
                return $true
            }
        }
        
        write-host "BiosUuid=$BiosUuid does not exists. validation failed"
        return $false
    }
}

Function ValidateESXiVersion {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Version")]
        [string]$HostVersion
)
    process{
        Write-Host "Starting $($MyInvocation.MyCommand)..."
        
        if($HostVersion -match "^\d+$")
        {
            write-host "ESXi vesrion= $HOST_VERSION validation pass"
            return $true
        }
        
        write-host "ESXi vesrion= $HOST_VERSION validation failed"
        return $false
    }
}