Public/Lifecycling/New-vCAVAppliance.ps1

function New-vCAVAppliance(){
    <#
    .SYNOPSIS
    This cmdlet deploys a vCloud Availability componet to the currently connected vCenter Server and configures the basic networking.
 
    .DESCRIPTION
    The cmdlet deploys the vCloud Availability OVA and configures the basic settings for the appliance (regardless of the deployment type).
    The steps taken are as follows:
    - Set the parameters for the OVA
    - Validate the provided vSphere objects
    - Deploy the appliance and Power On
    - Optionally add an additional northbound adapter (for Tunnel DMZ if required)
    - Set the root password (after first boot) and hostname of the appliance
    - Configure any static routing or host file entires
 
    Note: Only IPv4 addressing is available using this command at this time.
    The vNIC0 IP address must be routable from the machine executing this cmdlet.
 
    .PARAMETER Component
    The vCloud Availability component type to deploy.
    Valid: "cloud","replicator","tunnel"
 
    .PARAMETER OVAImage
    The fully qualified path to the vCloud Availability OVA
 
    .PARAMETER Cluster
    The vSphere HA/DRS Cluster the OVA should be deployed to
 
    .PARAMETER VMFolder
    The VM Folder (Virtual Machines and Templates folder) the object should be placed
 
    .PARAMETER VMName
    The VM Name in vSphere
 
    .PARAMETER StorageType
    A switch to specify if the value provided in -Datastore is a DatastoreCluster or Datastore.
    Valid: "DatastoreCluster","Datastore"
    Default: Datastore
 
    .PARAMETER RootPassword
    The Root Password to be set for the vCAV Appliance
 
    .PARAMETER Datastore
    The Datastore or Datastore Cluster to place the VM on
 
    .PARAMETER vNIC0_PortGroup
    The vSphere dvPort Group for vNIC with Index 0 (Admin/Main Interface)
 
    .PARAMETER vNIC0_IP
    The IPv4 address for the vNIC with Index 0 (Admin/Main Interface)
 
    .PARAMETER vNIC0_Netmask
    The IPv4 subnet mask for the vNIC with Index 0 (Admin/Main Interface) eg. 255.255.255.0 (for 24-bit)
 
    .PARAMETER vNIC0_BuildGateway
    IPv4 Routable Gateway for build. This is the gateway/router for vNIC0 when the -SecondaryNIC switch is set. It is set so that during build a routable gateway is set to continue to communicate with the machine for configuration. This address is discarded after first boot.
 
    .PARAMETER DefaultGatewayIP
    The default gateway IP address that should be set.
    NOTE: If -SecondaryNIC is set this is set on the vNIC1 adapater otherwise is set on vNIC0
 
    .PARAMETER SSHEnabled
    Specifies if the SSH daemon should be enabled. This can be set via the API after deployment also.
 
    .PARAMETER SecondaryNIC
    If this parameter is set two vNICs are added to the machine with the default gateway set on vNIC Index 1 (North)
 
    .PARAMETER vNIC1_PortGroup
    The vSphere dvPort Group for vNIC with Index 1 (Public Interface)
 
    .PARAMETER vNIC1_IP
    The IPv4 address for the vNIC with Index 1 (Public Interface)
 
    .PARAMETER vNIC1_Netmask
    The IPv4 subnet mask for the vNIC with Index 1 (Public Interface) eg. 255.255.255.0 (for 24-bit)
 
    .PARAMETER NTPServers
    A comma-seperated list of NTP Servers to configure for the appliance
 
    .PARAMETER DNSServers
    A comma-seperated list of DNS Servers to configure for the appliance
 
    .PARAMETER HostName
    The hostname for the appliance.
 
    .PARAMETER DNSSearchPath
    The DNS Search Path for the to configure for the appliance
 
    .EXAMPLE
    New-vCAVAppliance @ApplianceConfig
    Deploys a new appliance to the connected vCenter Server by splatting the configuration in the hashtable ApplianceConfig. Deploys the appliance and configures the base networking.
 
    .NOTES
    AUTHOR: Adrian Begg
    LASTEDIT: 2019-09-11
    VERSION: 1.0
    #>

    Param(
        [Parameter(Mandatory=$True, ParameterSetName="Default")]
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateSet("cloud","replicator","tunnel")]  [string] $Component,
            [ValidateNotNullorEmpty()] [string] $OVAImage,
            [ValidateNotNullorEmpty()] [string] $Cluster,
            [ValidateNotNullorEmpty()] [string] $VMFolder,
            [ValidateNotNullorEmpty()] [string] $VMName,
        [Parameter(Mandatory=$False, ParameterSetName="Default")]
        [Parameter(Mandatory=$False, ParameterSetName="NorthSouthNic")]
            [ValidateSet("DatastoreCluster","Datastore")]  [string] $StorageType="Datastore",
        [Parameter(Mandatory=$True, ParameterSetName="Default")]
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $Datastore,
            [ValidateNotNullorEmpty()] [SecureString] $RootPassword,
            [ValidateNotNullorEmpty()] [string] $vNIC0_PortGroup,
            [ValidateNotNullorEmpty()] [string] $vNIC0_IP,
            [ValidateNotNullorEmpty()] [string] $vNIC0_Netmask,
            [ValidateNotNullorEmpty()] [string] $DefaultGatewayIP,
            [ValidateNotNullorEmpty()] [string] $HostName,
            [ValidateNotNullorEmpty()] [string] $NTPServers,
            [ValidateNotNullorEmpty()] [string] $DNSServers,
            [ValidateNotNullorEmpty()] [string] $DNSSearchPath,
            [ValidateNotNullorEmpty()] [bool] $SSHEnabled,
        [Parameter(Mandatory=$False, ParameterSetName="Default")]
        [Parameter(Mandatory=$False, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $HostEntries,
            [ValidateNotNullorEmpty()] [string] $vNIC0_StaticRoutes,
        [Parameter(Mandatory=$False, ParameterSetName="NorthSouthNic")]
            [switch] $SecondaryNIC,
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $vNIC1_PortGroup,
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $vNIC1_IP,
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $vNIC1_Netmask,
        [Parameter(Mandatory=$True, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $vNIC0_BuildGateway,
        [Parameter(Mandatory=$False, ParameterSetName="NorthSouthNic")]
            [ValidateNotNullorEmpty()] [string] $vNIC1_StaticRoutes
    )
    # First check if vCenter is connected and if not prompt for details
    if(!($Global:DefaultVIServer.IsConnected)){
        try{
            Connect-VIServer -Server (Read-Host -Prompt "Enter the Management vCenter Server address to deploy the new appliance")
        } catch {
            throw "An error occured connecting to the Management vCenter, not continuing with Execution. Exception: $_"
        }
    }
    # Set the required values for the deploy the OVA to vSphere
    try{
        $targetCluster = Get-Cluster -Name $Cluster
    } catch {
        throw "An error occured attempting to retrieve the vSphere Cluster $Cluster. Exception: $_"
    }
    try{
        $targetVMHost = (Get-VMHost -Location $targetCluster -State "Connected")[0]
    } catch {
        throw "An error occured attempting to retrieve a connected vSphere Host in cluster $Cluster. Exception: $_"
    }
    try{
        $targetFolder = Get-Folder -Name $VMFolder
    } catch {
        throw "An error occured attempting to retrieve the vSphere Folder $VMFolder. Exception: $_"
    }
    try{
        if($StorageType -eq "DatastoreCluster"){
            $targetDatastore = Get-DatastoreCluster -Name $Datastore
        } else {
            $targetDatastore = Get-Datastore -Name $Datastore
        }
    } catch {
        throw "An error occured attempting to retrieve the vSphere storage object $Datastore. Exception: $_"
    }
    # Load the OVA and set the variables for vCAV
    try {
        $ComponentOVA = Get-OvfConfiguration -Ovf $OVAImage
    } catch {
        throw "An error occured reading the OVA file $OVAImage. The following Exception was thrown $_"
    }
    # Set the main networking elements
    $ComponentOVA.Common.guestinfo.cis.appliance.root.password.Value = "Password!123" # This is a temp password, it needs to be set here to overcome plain-text password transmission by OVF deploy
    # Appears that the SSHEnabled flag is ignored
    if($SSHEnabled){
        $ComponentOVA.Common.guestinfo.cis.appliance.ssh.enabled.Value = "True"
    } else {
        $ComponentOVA.Common.guestinfo.cis.appliance.ssh.enabled.Value = "False"
    }
    $ComponentOVA.DeploymentOption.Value = $Component
    $ComponentOVA.vami.VMware_vCloud_Availability.domain.Value = $HostName
    $ComponentOVA.vami.VMware_vCloud_Availability.DNS.Value = $DNSServers
    $ComponentOVA.vami.VMware_vCloud_Availability.searchpath.Value = $DNSSearchPath
    $ComponentOVA.IpAssignment.IpProtocol.Value = "IPv4"
    if($PSBoundParameters.ContainsKey("NTPServers")){
        $ComponentOVA.Common.guestinfo.cis.appliance.net.ntp.Value = $NTPServers
    }
    # Set the Network addresses for vNIC0
    $ComponentOVA.NetworkMapping.VM_Network.Value = $vNIC0_PortGroup
    $ComponentOVA.vami.VMware_vCloud_Availability.ip0.Value = $vNIC0_IP
    $ComponentOVA.vami.VMware_vCloud_Availability.netmask0.Value = $vNIC0_Netmask
    # Now customise depending on if a Second NIC is added or not
    if($PSBoundParameters.ContainsKey("SecondaryNIC")){
        # If multi-NIC configuration is to be used (e.g. DMZ dual-homed for the tunnel) a temporary default gateway on the Management Network interface for inital configuration
        $ComponentOVA.vami.VMware_vCloud_Availability.gateway.Value = $vNIC0_BuildGateway
    } else {
        # Only one NIC set the default gateway to be NIC0
        $ComponentOVA.vami.VMware_vCloud_Availability.gateway.Value = $DefaultGatewayIP
    }
    # Deploy the OVA Component
    try{
        $result = $targetVMHost | Import-VApp $OVAImage -Name $VMName -OvfConfiguration $ComponentOVA -Datastore $targetDatastore -DiskStorageFormat "Thin" -InventoryLocation $targetfolder
    } catch {
        throw "Deployment of the the OVA to vSphere has failed. The following exception was thrown $_"
    }
    # Check if a second Network Adapter is required for the component, add the adapter to the appliance
    if($PSBoundParameters.ContainsKey("SecondaryNIC")){
        try {
            $DVPortGroup = Get-VDPortgroup -Name $vNIC1_PortGroup
            $result = New-NetworkAdapter -VM $VMName -Portgroup $DVPortGroup -StartConnected -Type "Vmxnet3"
        } catch {
            throw "An error occured adding additional NIC to $VMName. The following exception was thrown $_"
        }
    }
    # Now Power On the VM and Wait for the Tools Service to Start
    try{
        $result = Start-VM -VM $VMName
    } catch {
        throw "An error occured Powering On VM $VMName. The following exception was thrown $_"
    }
    Write-Information -Message "Waiting for the VMWare Tools Service and application services to start after inital Power On. This may take up to 5 minutes."
    $result = Wait-Tools -VM $VMName -TimeoutSeconds 300
    Start-Sleep -Seconds 45

    # Connect to the appliance via the API and set basic parameters (root password and the hostname for the appliance)
    $APIPassword = ConvertTo-SecureString "Password!123" -AsPlainText -Force
    $APICred = New-Object System.Management.Automation.PSCredential ("root", $APIPassword)
    $ApplianceCredentials = New-Object System.Management.Automation.PSCredential ("root", $RootPassword)
    try{
        Connect-vCAVService -Server $vNIC0_IP -Port 443 -Credentials $APICred -AuthProvider "Local"
        # Create a PSCredential for the New Password
        $result = Set-vCAVApplianceRootPassword -OldPassword "Password!123" -NewPassword ($ApplianceCredentials.GetNetworkCredential().Password)
        $result = Set-VCAVApplianceHostname -Hostname $HostName
    } catch {
        throw "An error occurred conencting to the vCAV API Service to reset the root password and Hostname. Exception was thrown $_"
    }
    # Disconnect from the service
    Disconnect-vCAVService

    # Prepare custom network configuration scripts (/etc/hosts)
    if($PSBoundParameters.ContainsKey("HostEntries")){
        try{
            $hostFileConfig = "cat << 'EOF' >> /etc/hosts`n$($HostEntries.Replace("\n","`n"))`nEOF`n"
            $result = Invoke-VMScript -VM $VMName -ScriptText $hostFileConfig -GuestUser "root" -GuestPassword ($ApplianceCredentials.GetNetworkCredential().Password) -ScriptType "Bash"
        } catch {
            throw "$_"
        }
    }
    # Configure the Second adapter (Public) for Northbound traffic as default route if required
    # If a second NIC exists; Remove the gateway used to configure during build from /etc/systemd/network/10-eth0.network and configure the adapter
    if($PSBoundParameters.ContainsKey("SecondaryNIC")){
        [string] $strNetConfigScript = "" # Initalise
        # Network configuration must be manually re-written due to a limitation with /opt/vmware/share/vami/vami_set_network cmd which does not allow an address to be set without an IPv4 gateway
        [string] $strNetConfigScript += "sed '/^Gateway=/ d' /etc/systemd/network/10-eth0.network > /tmp/10-eth0.network`n"
        [string] $strNetConfigScript += "cp /tmp/10-eth0.network /etc/systemd/network/10-eth0.network`n"
        [string] $strNetConfigScript += "/opt/vmware/share/vami/vami_set_network eth1 STATICV4 $($vNIC1_IP) $($vNIC1_Netmask) $($DefaultGatewayIP)`n"
    }
    # Add any static routing required to the Network configuration files
    if($PSBoundParameters.ContainsKey("vNIC0_StaticRoutes")){
        [string] $strNetConfigScript += "cat << 'EOF' >> /etc/systemd/network/10-eth0.network`n$($vNIC0_StaticRoutes.Replace("\n","`n"))`nEOF`n"
    }
    if($PSBoundParameters.ContainsKey("vNIC1_StaticRoutes")){
        [string] $strNetConfigScript += "cat << 'EOF' >> /etc/systemd/network/10-eth1.network`n$($vNIC1_StaticRoutes.Replace("\n","`n"))`nEOF`n"
    }
    $strNetConfigScript += "`nsystemctl restart systemd-networkd`n"

    # Execute using VMWare Tools to inject the script to the guest
    try{
        $result = Invoke-VMScript -VM $VMName -ScriptText $strNetConfigScript -GuestUser "root" -GuestPassword ($ApplianceCredentials.GetNetworkCredential().Password) -ScriptType "Bash"
    } catch {
        throw "An error occured during customisation of network configuration Exception: $_"
    }
    # Return something
    $true
}