ZertoAVSModule.psm1

using module Microsoft.AVS.Management


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

Function TestConnection {
    return "TestConnection"
}

Function GenerateRandomPassword {

    $length = Get-Random -Minimum 8 -Maximum 24

    # seedArray holds only valid characters for the password by removing double quotes, slash and black slash
    $seedArray = (48..57) + (65..90) + (97..122) + @(33, 36, 37, 94)
    $seedArray = $seedArray | Sort-Object Get-Random

    $asciiCharsList = @()
    foreach ($a in $seedArray){
        $asciiCharsList += , [char][byte]$a 
    }
    
    #regExp includes at least one uppercase, lowercase, number & special character (@!#$%?^) and ensure password is between 8 to $length characters long
    $regExp = "^((?=.*[a-z])(?=.*[A-Z])(?=.*[^A-Za-z0-9]))([A-Za-z\d@!#$%?^]){8,$length}$"

    do {
        $password = ""
        $loops = 1..$length
        Foreach ($loop in $loops) {
            $password += $asciiCharsList | Get-Random
        }
    } until ($password -match $regExp )

    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 (defaule value is ZertoDR)
             
            .PARAMETER zertoRole
            Zerto role name (defaule value is ZertoRole)
             
            .PARAMETER group
            An existing group to assign Zerto user to (Optional)
 
        .EXAMPLE
 
        CreateZertoUser -zertoUsername <Username> -zertoRole <Role> -group <Group>
    #>

    [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",
        [parameter(Mandatory=$false,
            HelpMessage = "Group name for Zerto user")]
        [string]$group
    )
 
    Process{

        $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"
        )

        $PersistentSecrets.ZertoPassword = GenerateRandomPassword
        New-SsoPersonUser -UserName $zertoUsername -Password $PersistentSecrets.ZertoPassword -Description "Zerto DR user" -EmailAddress "ZertoDR@zerto.com" -FirstName "Zerto" -LastName "DR" | Out-Null
        # Add user to group
        if ($group) {
          $SsoGroup = Get-SsoGroup -Name $group -Domain $domain
          Get-SsoPersonUser -Name $zertoUsername -Domain $domain | Add-UserToSsoGroup -TargetGroup $SsoGroup
        }

        # Create a new role
        New-VIRole -name $zertoRole -Privilege (Get-VIPrivilege -Server $VC_ADDRESS -id $zertoPrivileges) -Server $VC_ADDRESS | Out-Null
        # 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 | Out-Null
    }
}

Function CreateZertoFolderOnHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName 
    )
    
    process {
        $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"
        }
    }
}

Function VerifyAndUploadFilesFromPSEngineToHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName
    )
    
    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 Uuid")]
        [string]$DatastoreUuid,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]
        [string]$BiosUuid
    )

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

    New-PSDrive -Location $datastore -Name $psDriverName -PSProvider VimDatastore -Root "\"
    Copy-DatastoreItem -Item $FullRemoteFileLocation -Destination $LOCAL_TEMP_FOLDER_FOR_DOWNLOADED_FILES -Force
    Remove-PSDrive -Name $psDriverName
}

Function CopyFilesFromDatastoreToHost {
    param(
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Name to connect with SSH")]
        [string]$HostName,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Datastore Uuid")]
        [string]$DatastoreUuid,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]
        [string]$BiosUuid
    )
    
    DownloadFilesFromDatastoreToPSEngine -DatastoreUuid $DatastoreUuid -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
    )
    
    process {
        $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
            
            if ($ExitStatus -ne 0 -Or $Error) 
            {
                throw "Error! failed to run ""$Command"" on host $HostName, exitStatus: $ExitStatus, error: $Error"
            }

            $NamedOutputs["$($i)_cmd"] = $Command
            $NamedOutputs["$($i)_exitStatus"] = $ExitStatus
            $NamedOutputs["$($i)_output"] = ($SSH.Output -join ";")
            $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 {
        $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 {
        $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 {
        $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 DatastoreUuid
            Datastore Uuid
          
            .PARAMETER BiosUuid
            "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid"
          
        .EXAMPLE
          
        ChangeStartupFile -HostName xxx.xxx.xxx.xxx -DatastoreUuid 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 Uuid")]
        [string]$DatastoreUuid,
        [Parameter(Mandatory = $true, 
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]    
        [string]$BiosUuid
    )

    Process {

        $zloadmod = ('{0}/zloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)

        CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreUuid $DatastoreUuid -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} \"\" \"\" 1 \> /etc/vmware/zloadmod.txt \2\>\&\1 \#ZeRTO\ >> {3}' -f $zloadmod, $DatastoreUuid, $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 DatastoreUuid
            Datastore Uuid
          
            .PARAMETER BiosUuid
            Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid
          
            .PARAMETER IsInit
            Init or load the driver
          
            .PARAMETER EsxiVersion
            Esxi version
          
        .EXAMPLE
        InstallDriver -HostName xxx.xxx.xxx.xxx -DatastoreUuid <UUID> -BiosUuid <UUID> -IsInit <init/load> -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 Uuid")]
        [string]$DatastoreUuid,
        [Parameter(Mandatory = $true,
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]    
        [string]$BiosUuid,
        [Parameter(Mandatory = $true,    
            HelpMessage = "Init or load the driver")]    
        [string]$IsInit,
        [Parameter(Mandatory = $true,    
            HelpMessage = "Esxi version")]    
        [string]$EsxiVersion
    )

    Process {

        $zloadmod = ('{0}/zloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)
        
        CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreUuid $DatastoreUuid -BiosUuid $BiosUuid
        
        $Commands = ('chmod a+x {0}' -f $zloadmod),
            ('{0} {1} {2} {3} 1 {4} 1 > /etc/vmware/zloadmod.txt' -f $zloadmod, $IsInit, $DatastoreUuid, $BiosUuid, $EsxiVersion)

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

Function UninstallDriver {
    <#
        .DESCRIPTION
          
        Uninstall the driver
          
            .PARAMETER HostName
            Host Name to connect with SSH
              
            .PARAMETER DatastoreUuid
            Datastore Uuid
          
            .PARAMETER BiosUuid
            Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid
          
          
        .EXAMPLE
        UninstallDriver -HostName xxx.xxx.xxx.xxx -DatastoreUuid <UUID> -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 Uuid")]
        [string]$DatastoreUuid,
        [Parameter(Mandatory = $true,
            HelpMessage = "Host Bios Uuid || mob-> Property Path: host.hardware.systemInfo.uuid")]
        [string]$BiosUuid
    )
    
    process {

        $zunloadmod = ('{0}/zunloadmod.sh' -f $ZERTO_FOLDER_ON_HOST)
        
        CopyFilesFromDatastoreToHost -HostName  $HostName -DatastoreUuid $DatastoreUuid -BiosUuid $BiosUuid

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

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