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 {
     #Generate a password with at least 2 uppercase, 4 lowercase, 4 digits & 2 special character (!@#$%^&*())
    $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 (defaule value is ZertoDR)
             
            .PARAMETER zertoRole
            Zerto role name (defaule 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{

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

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
    }
}