common_utils.ps1

$ZVM_VM_NAME = "ZVML"
$CONFIGURATION_STATUS_FOLDER_NAME = ".ZvmConfigurationStatus"
$CONFIGURATION_STATUS_FILE_NAME = ".zvmConfigurationStatus.json"

$VC_PASSWORD_ROTATION_STATUS_FOLDER_NAME = ".ZvmVcPasswordRotationStatus"
$VC_PASSWORD_ROTATION_STATUS_FILE_NAME = ".zvmVcPasswordRotationStatus.json"

Function Start-ZVM {

    process {

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

            $ZVM = Get-VM -Name $ZVM_VM_NAME
            if ($null -eq $ZVM) {
                Write-Error "$ZVM_VM_NAME does not exist" -ErrorAction Stop
            }
            else {
                if ($ZVM.PowerState -eq 'PoweredOff') {
                    Write-Host "$ZVM_VM_NAME is powered off, going to power it on"
                    Start-VM $ZVM -ErrorAction Stop | Out-Null
                }
                else {
                    Write-Host "$ZVM_VM_NAME is up and running"
                }
            }
        }
        catch {
            Write-Error "Failed to start $ZVM_VM_NAME, exception = $_" -ErrorAction Stop
        }
    }
}

Function Stop-ZVM {

    process {

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

            $ZVM = Get-VM -Name $ZVM_VM_NAME
            if ($null -eq $ZVM) {
                Write-Error "$ZVM_VM_NAME does not exist" -ErrorAction Stop
            }
            else {
                if ($ZVM.PowerState -eq 'PoweredOn') {
                    Write-Host "$ZVM_VM_NAME is powered on, going to power it off"
                    Stop-VM -VM $ZVM -Confirm:$False -ErrorAction Stop | Out-Null
                }
                else {
                    Write-Host "$ZVM_VM_NAME is off"
                }
            }
        }
        catch {
            Write-Error "Failed to stop $ZVM_VM_NAME, exception = $_" -ErrorAction Stop
        }
    }
}

Function New-RandomPassword {
    Process {
        #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 New-ZertoFolderOnHost {
    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 = Invoke-SSHCommands -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 Wait-ZVMPasswordChange {
    process {
        Write-Host "Waiting for ZVM VC password rotation. It can take up to 30 min."
        Wait-ForFile -FolderName $VC_PASSWORD_ROTATION_STATUS_FOLDER_NAME -FileName $VC_PASSWORD_ROTATION_STATUS_FILE_NAME
    }
}

Function Wait-ZVMConfiguration {
    process {
        Write-Host "Waiting for ZVM configuration to finish. It can take up to 1 hour."
        Wait-ForFile -FolderName $CONFIGURATION_STATUS_FOLDER_NAME -FileName $CONFIGURATION_STATUS_FILE_NAME
    }
}

Function Wait-ForFile {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Path of the folder that contains the file to wait for.")]
        [string]$FolderName,
        [Parameter(Mandatory = $true,
            HelpMessage = "File name on datastore to wait for.")]
        [string]$FileName
    )
    process {
        $vm = Get-Vm -Name $ZVM_VM_NAME
        $dc = $vm | Get-Datacenter
        $ds = $vm | Get-Datastore

        $dcName = $dc.Name
        $dsName = $ds.Name

        $timeout = 3600
        $startTime = Get-Date

        $filePathOnDatastore = Join-Path -Path $FolderName -ChildPath $FileName

        do {
            $tempPath = [System.IO.Path]::GetTempPath()
            $file = Copy-DatastoreItem "vmstore:\$dcName\$dsName\$filePathOnDatastore" `
                -Destination $tempPath -PassThru -ErrorAction SilentlyContinue

            if ($null -ne $file) {
                $jsonContent = Get-Content -Path $file.FullName -Raw
                $statusInfo = $jsonContent | ConvertFrom-Json

                $file.Delete()
                Remove-StatusFileFolder -DatastoreName $dsName -FolderPathOnDatastore $FolderName -DatacenterName $dcName -ShowErrors
                Write-Host "Zerto configuration took: $((Get-Date).Subtract($startTime).TotalSeconds) second(s)"

                return $statusInfo
            }
            else {
                Start-Sleep -Seconds 10
                $elapsedTime = (Get-Date) - $startTime
                if ($elapsedTime.TotalSeconds -ge $timeout) {
                    return [PSCustomObject]@{
                        status      = 'Failure'
                        description = 'Timed out'
                    }
                }
            }
        } until ($null -ne $file)
    }
}

Function Remove-PasswordRotationStatusFile {
    process {
        $vm = Get-Vm -Name $ZVM_VM_NAME
        $dc = $vm | Get-Datacenter
        $ds = $vm | Get-Datastore

        $dcName = $dc.Name
        $dsName = $ds.Name
        Remove-StatusFileFolder -DatastoreName $dsName -FolderPathOnDatastore $VC_PASSWORD_ROTATION_STATUS_FOLDER_NAME -DatacenterName $dcName
    }
}

Function Remove-ZvmConfigurationStatusFile {
    process {
        $vm = Get-Vm -Name $ZVM_VM_NAME
        $dc = $vm | Get-Datacenter
        $ds = $vm | Get-Datastore

        $dcName = $dc.Name
        $dsName = $ds.Name
        Remove-StatusFileFolder -DatastoreName $dsName -FolderPathOnDatastore $CONFIGURATION_STATUS_FOLDER_NAME -DatacenterName $dcName
    }
}

Function Remove-StatusFileFolder {
    param(
        [Parameter(Mandatory = $true,
            HelpMessage = "Datastore name")]
        [string]$DatastoreName,
        [Parameter(Mandatory = $true,
            HelpMessage = "FolderPathOnDatastore")]
        [string]$FolderPathOnDatastore,
        [Parameter(Mandatory = $false,
            HelpMessage = "Host name")]
        [string]$HostName,
        [Parameter(Mandatory = $false,
            HelpMessage = "Datacenter name")]
        [string]$DatacenterName,
        [Parameter(Mandatory = $false,
            HelpMessage = "Show errors")]
        [switch]$ShowErrors
    )
    process {
        try {
            if ($HostName) {
                $vmHost = Get-VMHost -Name $HostName
                $dc = $vmHost | Get-Datacenter
            }
            elseif ($DatacenterName) {
                $dc = Get-Datacenter -Name $DatacenterName
            }
            else {
                throw "Either HostName or DatacenterName must be specified."
            }

            $file = "[$DatastoreName] $FolderPathOnDatastore"
            $si = Get-View ServiceInstance
            $fileMgr = Get-View -Id $si.Content.FileManager
            $fileMgr.DeleteDatastoreFile($file, $dc.ExtensionData.MoRef)
        }
        catch {
            if ($ShowErrors) {
                Write-Error "An error occurred while deleting the status file folder: [$DatastoreName] $FolderPathOnDatastore - $_. Please delete it manually."
            }
        }
    }
}