In this chapter, you will learn to:
As vSphere has developed, the term vApp has meant many different things. In the past many would refer to any virtualized workload as a virtual application, or vApp, whereas others use vApp to refer to a VM that has been exported to an Open Virtualization Format (OVF) template. As of vSphere 4.1, the term vApp has referred to a virtual container similar to a resource pool. A vApp can contain one or more VMs and is managed as a single logical unit. The modern vApp can be powered on or off and even cloned like a standard VM. vApps allow you to simplify complex applications by providing vSphere with valuable metadata about a group of VMs. For instance, you can capture the startup/shutdown sequence for a web farm. Then, from within vSphere, you simply power on or off the vApp and vSphere handles the rest. In this chapter you will learn to import virtual appliances, create and manage vApps, and automate some of the more advanced vCenter features surrounding vApps.
In 2006, VMware opened the VMware Virtual Appliance Marketplace. The idea was that third-party developers could provide whole VMs preconfigured, thereby eliminating complex installers and simplifying the deployment of new applications. The Marketplace has proven to be a little hit or miss, but the concept itself has been a smash hit. Virtual appliances have become mainstream, and virtually all software/hardware vendors offer them. vApps are distributed in either Open Virtual Appliance (OVA) or Open Virtualization File (OVF) formats. In reality, they are identical as every OVA contains a tape archive (TAR)-compressed copy of an OVF.
The installation of a vApp is straightforward. Simply download the zip file containing the OVF, or the self-contained OVA, and then import the appliance. However, this does bring a slight complication. Most OVA/OVF appliances contain configuration data that is used at the initial power-on to automatically configure the vApp. When importing through the vSphere web client, this is all done in one step, but when importing from PowerCLI you must first inspect the configuration data to determine the settings. This is accomplished with the Get-OvfConfiguration
cmdlet. Listing 10-1 will read the metadata and return an object with all the configurable parameters in the OVF/OVA.
Listing 10-1: Getting the OVF metadata
Get-OvfConfiguration .VMware-vCenter-Server-Appliance-6.0.0.ova
Common : System.Object
DeploymentOption : VMware.VimAutomation.ViCore.Impl.V1.Ovf.OvfPropertyImpl
IpAssignment : System.Object
NetworkMapping : System.Object
vami : System.Object
You can then leverage the metadata to programmatically deploy a complete vApp from any OVA/OVF. Listing 10-2 fully automates the deployment of the vCenter Server appliance.
Listing 10-2: Deploying vCenter Server appliance from PowerCLI
# Read Metadata and create the configuration object
$Path = ".VMware-vCenter-Server-Appliance-6.0.0.ova"
$OvfConfig = Get-OvfConfiguration -Ovf $Path
# Set all the required configuration parameters.
$OvfConfig.NetworkMapping.Network_1.Value = 'Public'
$OvfConfig.DeploymentOption.Value = 'tiny'
$OvfConfig.Common.guestinfo.cis.appliance.net.addr.family.Value = "ipv4"
$OvfConfig.Common.guestinfo.cis.appliance.net.mode.Value = "static"
$OvfConfig.Common.guestinfo.cis.appliance.net.addr_1.Value = "192.168.2.8"
$OvfConfig.Common.guestinfo.cis.appliance.net.prefix.Value = "24"
$OvfConfig.Common.guestinfo.cis.appliance.net.gateway.Value = "192.168.2.1"
$OvfConfig.Common.guestinfo.cis.appliance.net.dns.servers.Value = "192.168.2.1"
$OvfConfig.Common.guestinfo.cis.vmdir.password.Value = "VMware1!"
$OvfConfig.Common.guestinfo.cis.appliance.root.passwd.Value = "VMware1!"
$OvfConfig.Common.guestinfo.cis.appliance.time.tools_sync.Value = "True"
$OvfConfig.Common.guestinfo.cis.appliance.ssh.enabled.Value = "True"
# create a hash table with all the configuration
$Splat = @{
'Name' = 'VCSA'
'VMHost' = (Get-VMHost ESX1)
'Source' = $Path
'OvfConfig' = $OvfConfig
'DiskStorageFormat' = 'Thin'
}
#Import the vApp using Splatting to inject parameters
Import-VApp @Splat
Virtual appliances can simplify the deployment of a new VM. vApps bring that simplicity at a scale that was previously only available to the largest hosting providers.
vApps are a resource pool at their base, so when creating a new vApp, resource guarantees are where it all starts. There are three ways to create a new vApp in your environment:
Listing 10-3 creates a new vApp that conforms to the following specification:
Prod01
Listing 10-3: Creating a new vApp
New-VApp -Name App01 `
-Location (Get-Cluster prod01) `
-CpuExpandableReservation $true `
-CpuReservationMhz 4000 `
-MemExpandableReservation $true `
-MemReservationGB 6
vApps can also be cloned with the New-vApp
cmdlet. As shown in Listing 10-4, App01 is cloned to a new vApp named App02. Be aware that when a vApp is cloned, the clone includes any child VMs or vApps.
Listing 10-4: Cloning an existing vApp
New-VApp -Name App02 `
-Location (Get-Cluster prod01) `
-VApp (Get-VApp App01) `
-Datastore datastore1
The third way to create a vApp is to import one from an OVF. This is often used either as an inexpensive vApp backup or to copy vApps between vCenter Servers. Before a vApp can be imported, it must first be exported. For that, PowerCLI provides the Export-VApp
cmdlet. Like cloning, exporting a vApp results in the VMs being exported as well. This is very useful because it enables entire systems to be transported using one logical container. Nothing is lost in the move—settings such as the startup order, IP allocation, and resource allocation are all encompassed in the exported OVF. Listing 10-5 exports App02 into an OVF container.
Listing 10-5: Exporting an existing vApp
Export-VApp -VApp (Get-VApp App02) `
-Name "app02_$(Get-Date -Format MM_dd_yyyy)"
Once the vApp is exported, importing is similar to importing a virtual appliance. The difference is that when you import an OVF, the whole vApp is re-created along with its child vApps and VMs. To reimport the vApp named App02 that was exported in Listing 10-5, simply point the Import-vApp
cmdlet to the OVF file, VMHost, and a datastore (Listing 10-6).
Listing 10-6: Importing a vApp from OVF
Import-VApp -Source .app02_01_10_2015.ovf `
-Name 'App02' `
-VMHost 'ESX1*' `
-Datastore datastore1
To remove an existing vApp from the system, the Remove-vApp
cmdlet is provided within PowerCLI. By default the cmdlet will merely remove the vApp from inventory. To permanently delete the vApp and delete the data from disk, you must apply the DeletePermanently
switch. Listing 10-7 removes App02 from the environment, deleting its metadata and removing the child VMs from the datastores.
Listing 10-7: Removing an existing vApp
Remove-VApp -VApp App02 -DeletePermanently
Over the last 5 years, vApps have grown in importance and are now integral to more advanced systems such as vCloud Director. They are on par with VMs when it comes to deployment options, with one notable exception: there is no way to target a specific hard drive or VM to a datastore when deploying a vApp. This leads to a two-step process when there is a need to spread the I/O load of a vApp. Deploy to a datastore large enough to hold the entire vApp, and then, after it’s been deployed, SVMotion individual hard disks and VMs to different datastores.
With the basics of vApps established, the fun really starts: maintaining and modifying existing vApps, using them not only as a provisioning mechanism, but as a logical management container to group a service into a single container regardless of the number of VMs that comprise said service. That is where the true power of vApps begins to shine. To add an existing VM to an existing vApp, use the standard VM mobility cmdlet Move-VM
and target the vApp as the destination (Listing 10-8).
Listing 10-8: Adding VMs to an existing vApp
Get-VM Web01, Web02, SQL01 |
Move-VM -Destination (Get-VApp -Name 'App01')
Start order sequencing is perhaps the most powerful feature of a vApp. By setting the start order, you ensure that any administrator can safely power on or off an application no matter now complicated. Unfortunately, PowerCLI doesn’t currently contain any cmdlets for managing the startup order. But Listing 10-9 provides the Get-vAppStartOrder
function for that purpose.
Listing 10-9: The Get-vAppStartOrder
function
<#
.SYNOPSIS
Get the vApp Startup Order for a given VM
.DESCRIPTION
Get the vApp Startup Order for a given VM if no VM is
provided will return the startup order for every VM
in the vApp.
.PARAMETER VM
VM to retrieve the startup order for.
.PARAMETER vApp
vApp to retrieve the startup order from.
.EXAMPLE
Get-vApp | Get-vAppStartOrder
.EXAMPLE
Get-vAppStartOrder -VM (get-vm sql01)
#>
function Get-vAppStartOrder {
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]
$VM
,
[parameter(ValueFromPipeline=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VAppImpl]
$vApp
)
Process
{
if ($VM)
{
try
{
$vApp = Get-VIObjectByVIView $VM.ExtensionData.ParentVApp
}
catch
{
Write-Warning "$($VM.name) doesn’t belong to a vApp."
continue;
}
}
elseif (-not $vApp)
{
Write-Warning 'vApp was not specified'
break;
}
$vApp.ExtensionData.VAppConfig.EntityConfig |
Where-Object {$_.Key -match $VM.Id} |
Select-Object @{
Name='VM'
Expression={Get-VIObjectByVIView $_.Key}
},
@{
Name='vApp'
Expression={$vApp.name}
},
'StartOrder','StartDelay',
'WaitingForGuest','StartAction',
'StopDelay','StopAction',
@{
Name='DestroyWithParent'
Expression={if ($_.DestroyWithParent -eq $null){
$false
}
else
{
$_.DestroyWithParent
}
}
}
}
}
Listing 10-10 retrieves the current start order settings using the Get-vAppStartOrder
function.
Listing 10-10: Getting the start order settings for the App01 vApp
Get-VApp App01 | Get-vAppStartOrder
VM : Web01
vApp : App01
StartOrder : 1
StartDelay : 120
WaitingForGuest : False
StartAction : powerOn
StopDelay : 120
StopAction : powerOff
DestroyWithParent : False
VM : Web02
vApp : App01
StartOrder : 2
StartDelay : 120
WaitingForGuest : False
StartAction : powerOn
StopDelay : 120
StopAction : powerOff
DestroyWithParent : False
VM : SQL01
vApp : App01
StartOrder : 3
StartDelay : 120
WaitingForGuest : False
StartAction : powerOn
StopDelay : 120
StopAction : powerOff
DestroyWithParent : False
By default a vApp powers on and off based on the order in which the VMs were added. This will likely not be the desired order. To adjust these settings, use the Set-vAppStartOrder
function (Listing 10-11).
Listing 10-11: The Set-vAppStartOrder
function
<#
.SYNOPSIS
Set the vApp Startup Order for a given VM
.DESCRIPTION
Set the vApp Startup Order for a given VM
.PARAMETER VM
VM to modify the startup order for.
.PARAMETER StartOrder
Specifies the start order for this entity. Entities are
started from lower numbers to higher-numbers and
reverse on shutdown. Multiple entities with the same
start-order can be started in parallel and the order is
unspecified. This value must be 0 or higher.
.PARAMETER StartDelay
Delay in seconds before continuing with the next entity
in the order of entities to be started
.PARAMETER WaitingForGuest
Determines if the virtual machine should start after
receiving a heartbeat, from the guest. When a virtual
machine is next in the start order, the system either
waits a specified period of time for a virtual machine
to power on or it waits until it receives a successful
heartbeat from a powered on virtual machine. By
default, this is set to false
.PARAMETER StartAction
How to start the entity. Valid settings are none or
powerOn. If set to none, then the entity does not
participate in auto-start.
.PARAMETER StopDelay
Delay in seconds before continuing with the next entity
in the order sequence. This is only used if the stopAction
is guestShutdown.
.PARAMETER StopAction
Defines the stop action for the entity. Can be set to none,
powerOff, guestShutdown, or suspend. If set to none, then
the entity does not participate in auto-stop.
.PARAMETER DestroyWithParent
ethier the entity should be removed, when this vApp is
removed. This is only set for linked children.
.PARAMETER PassThru
return the vApp object
.EXAMPLE
Get-vAppStartOrder -VM (get-vm sql01)
#>
function Set-vAppStartOrder {
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[parameter(Mandatory=$true
, ValueFromPipelineByPropertyName=$true
, ValueFromPipeline=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]
$VM
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[int]
$StartOrder
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[int]
$StartDelay
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$WaitingForGuest
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet("none","powerOn")]
[string]
$StartAction
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[int]
$StopDelay
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('none','powerOff','guestShutdown','suspend')]
[string]
$StopAction
,
[parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$DestroyWithParent
,
[Switch]
$PassThru
)
process
{
try
{
$vApp = Get-VIObjectByVIView $VM.ExtensionData.ParentVApp
}
catch
{
Write-Warning "$($VM.name) doesn't belong to a vApp."
continue;
}
$EntityConfig = $vApp.ExtensionData.VAppConfig.EntityConfig
$spec = New-Object VMware.Vim.VAppConfigSpec
$spec.EntityConfig = `
foreach ($Conf in ($EntityConfig.GetEnumerator()))
{
if ($Conf.Key.ToString() -eq $VM.Id.ToString())
{
$msg = "Setting $($VM.Name) start order to:"
Switch ($PSCmdlet.MyInvocation.BoundParameters.Keys)
{
'StartOrder'
{
$msg = "{0} StartOrder:{1}" -f $msg, $StartOrder
$Conf.StartOrder = $StartOrder
}
'StartDelay'
{
$msg = "{0} StartDelay:{1}" -f $msg, $StartDelay
$Conf.StartDelay = $StartDelay
}
'WaitingForGuest'
{
$msg = "{0} WaitingForGuest:{1}" -f $msg, $WaitingForGuest
$Conf.WaitingForGuest = $WaitingForGuest
}
'StartAction'
{
$msg = "{0} StartAction:{1}" -f $msg, $StartAction
$Conf.StartAction = $StartAction
}
'StopDelay'
{
$msg = "{0} StopDelay:{1}" -f $msg, $StopDelay
$Conf.StopDelay = $StopDelay
}
'StopAction'
{
$msg = "{0} StopAction:{1}" -f $msg, $StopAction
$Conf.StopAction = $StopAction
}
'DestroyWithParent'
{
$msg = "{0} DestroyWithParent:{1}" -f $msg,
$DestroyWithParent
$Conf.DestroyWithParent = $DestroyWithParent
}
}
}
$Conf
}
if ($PSCmdlet.ShouldProcess($vApp.Name, $msg))
{
$vApp.ExtensionData.UpdateVAppConfig($spec)
if ($PassThru)
{
Get-vAppStartOrder -VM $VM
}
}
}
}
Using this new function, the startup order can now be fully configured from PowerCLI. Listing 10-12 sets the startup order to the following specification:
Startup Group 1
Startup Group 2
Listing 10-12: Setting the start order for the VMs in App01
Get-VApp App01 | Get-VM 'SQL01' |
Set-vAppStartOrder -StartOrder 1 `
-StartAction 'powerOn' `
-StartDelay 120 `
-WaitingForGuest $true `
-StopAction 'guestShutdown'
Get-VApp App01 | Get-VM Web0[12] |
Set-vAppStartOrder -StartOrder 2 `
-StartAction 'powerOn' `
-StartDelay 120 `
-WaitingForGuest $true `
-StopAction 'guestShutdown' `
-StopDelay 120
As mentioned previously, one of the advantages of vApps is how they natively include startup sequencing. Once configured, power operations are accomplished using the native cmdlets. For instance, Listing 10-13 powers on the App01 vApp that has recently been configured. Listing 10-14 powers it down. Notice that the power operation is run at the vApp, not on the individual VMs.
Listing 10-13: Powering on a vApp
Start-VApp –Vapp App01 | Format-Table Name, Status
Name Status
---- ------
App01 Started
Listing 10-14: Powering off a vApp
Stop-VApp –Vapp App01 | Format-Table Name, Status
Name Status
---- ------
App01 Stopped
Of course, power management is only part of vApps. To ease deployment of application, vApps can also supply the IP information for the VM(s) within a vApp. This is accomplished by passing the request to an external DHCP server, or a vCenter Server can issue an IP from a network protocol profile (formerly known as an IP pool). Although the name has changed in the vSphere Web Client, the underlying API has not—for that reason, we’ll continue to use the old name. Listing 10-15 obtains a list of existing IP pools by running the Get-IPPool
function.
Listing 10-15: The Get-IPPool
function
<#
.SYNOPSIS
Get existing IP Pools from vCenter
.DESCRIPTION
Get existing IP Pools from vCenter
.PARAMETER Datacenter
Datacenter to query for IP Pools.
.PARAMETER Name
Name of the IP Pool to retrieve.
.EXAMPLE
Get-Datacenter | Get-IPPool
#>
function Get-IPPool
{
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.DatacenterImpl[]]
$Datacenter = (Get-Datacenter)
,
[parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[String]
$Name = "*"
)
Process
{
foreach ($dc in $Datacenter)
{
$IPPoolManager = Get-View -Id 'IpPoolManager-IpPoolManager'
$IPPoolManager.QueryIpPools($dc.Id) |
Where-Object {$_.Name -like $Name} | Foreach-Object {
New-Object PSObject -Property @{
'Name' = $_.Name
'DnsDomain' = $_.DNSDomain
'DNSSearchPath' = $_.DNSSearchPath
'HostPrefix' = $_.HostPrefix
'HttpProxy' = $_.HttpProxy
'NetworkAssociation' = $_.NetworkAssociation
'IPv4SubnetAddress' = $_.Ipv4Config.SubnetAddress
'IPv4Netmask' = $_.Ipv4Config.Netmask
'IPv4Gateway' = $_.Ipv4Config.Gateway
'IPv4Range' = $_.Ipv4Config.Range
'IPv4DNS' = $_.Ipv4Config.DNS
'IPv4DHCP' = $_.Ipv4Config.DhcpServerAvailable
'IPv4IpPoolEnabled' = $_.Ipv4Config.IpPoolEnabled
'IPv6SubnetAddress' = $_.Ipv6Config.SubnetAddress
'IPv6Netmask' = $_.Ipv6Config.Netmask
'IPv6Gateway' = $_.Ipv6Config.Gateway
'IPv6Range' = $_.Ipv6Config.Range
'IPv6DNS' = $_.Ipv6Config.DNS
'IPv6DHCP' = $_.Ipv6Config.DhcpServerAvailable
'IPv6IpPoolEnabled' = $_.Ipv6Config.IpPoolEnabled
'Datacenter' = $dc
}
}
}
}
}
By default there are no IP pools configured. To create a new IP pool within vCenter Server, use the New-IPPool
function in Listing 10-16.
Listing 10-16: The New-IPPool
function
<#
.SYNOPSIS
Create a new IP Pool within vCenter
.DESCRIPTION
Create a new IP Pool within vCenter
.PARAMETER Datacenter
Datacenter to create the new IP Pool in.
.PARAMETER Name
Pool name. Must be unique.
.PARAMETER DnsDomain
DNS Domain. For example, vmware.com. This can be an
empty string if no domain is configured.
.PARAMETER DNSSearchPath
DNS Search Path. For example, eng.vmware.com;vmware.com
.PARAMETER HostPrefix
Prefix for hostnames.
.PARAMETER HttpProxy
The HTTP proxy to use on this network
.PARAMETER NetworkAssociation
The networks that are associated with this IP pool.
Use the Get-Network function to get the objects this
parameter requires.
.PARAMETER IPv4SubnetAddress
Address of the subnet.
.PARAMETER IPv4Netmask
Netmask
.PARAMETER IPv4Gateway
Gateway. This can be an empty string
.PARAMETER IPv4Range
IP range. This is specified as a set of ranges
separated with commas. One range is given by a start
address, a hash (#), and the length of the range.
For example:
192.0.2.235#20 = IPv4 range 192.0.2.235-192.0.2.254
192.0.2.0#24 = IPv4 range 192.0.2.1-192.0.2.254
.PARAMETER IPv4DNS
DNS servers
.PARAMETER IPv4DHCP
Whether a DHCP server is available on this network.
.PARAMETER IPv4IpPoolEnabled
IP addresses can only be allocated from the range if
the IP pool is enabled.
.PARAMETER IPv6SubnetAddress
Address of the subnet.
.PARAMETER IPv6Netmask
Netmask
.PARAMETER IPv6Gateway
Gateway. This can be an empty string
.PARAMETER IPv6Range
IP range. This is specified as a set of ranges
separated with commas. One range is given by a start
address, a hash (#), and the length of the range.
For example:
2001::7334 # 20 = IPv6 range 2001::7334 - 2001::7347
.PARAMETER IPv6DNS
DNS servers
.PARAMETER IPv6DHCP
Whether a DHCP server is available on this network.
.PARAMETER IPv6IpPoolEnabled
IP addresses can only be allocated from the range if
the IP pool is enabled.
#>
function New-IPPool
{
[CmdletBinding()]
Param(
[parameter(Mandatory=$true
, ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.DatacenterImpl]
$Datacenter
, [parameter(Mandatory=$true
, ValueFromPipelineByPropertyName=$true)]
[String]
$Name
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$DnsDomain = ""
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$DNSSearchPath = ""
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$HostPrefix = ""
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$HttpProxy = ""
, [parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.Vim.IpPoolAssociation[]]
$NetworkAssociation
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4SubnetAddress = ''
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Netmask = '255.255.255.0'
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Gateway = ''
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Range
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$IPv4DNS = @("")
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv4DHCP = $false
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv4IpPoolEnabled = $false
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6SubnetAddress = ''
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Netmask = "ffff:ffff:ffff:ffff:ffff:ffff::"
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Gateway = ""
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Range
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$IPv6DNS = @()
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv6DHCP = $false
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv6IpPoolEnabled = $false
)
Process
{
$pool = New-Object VMware.Vim.IpPool
$pool.Name = $Name
$pool.Ipv4Config = New-Object VMware.Vim.IpPoolIpPoolConfigInfo
$pool.Ipv4Config.SubnetAddress = $IPv4SubnetAddress
$pool.Ipv4Config.Netmask = $IPv4Netmask
$pool.Ipv4Config.Gateway = $IPv4Gateway
$pool.Ipv4Config.Dns = $IPv4DNS
$pool.Ipv4Config.DhcpServerAvailable = $IPv4DHCP
$pool.Ipv4Config.IpPoolEnabled = $IPv4IpPoolEnabled
$pool.Ipv6Config = New-Object VMware.Vim.IpPoolIpPoolConfigInfo
if ($IPv4Range)
{
$pool.Ipv4Config.Range = $IPv4Range
}
$pool.Ipv6Config.SubnetAddress = $IPv6SubnetAddress
$pool.Ipv6Config.Netmask = $IPv6Netmask
$pool.Ipv6Config.Gateway = $IPv6Gateway
$pool.Ipv6Config.Dns = $IPv6DNS
$pool.Ipv6Config.DhcpServerAvailable = $IPv6DHCP
$pool.Ipv6Config.IpPoolEnabled = $IPv6IpPoolEnabled
if ($IPv6Range)
{
$pool.Ipv6Config.Range = $IPv6Range
}
$pool.DnsDomain = $DnsDomain
$pool.DnsSearchPath = $DNSSearchPath
$pool.HostPrefix = $HostPrefix
$pool.HttpProxy = $HttpProxy
if ($NetworkAssociation)
{
$pool.NetworkAssociation = $NetworkAssociation
}
$IpPoolManager = Get-View 'IpPoolManager-IpPoolManager'
$IpPoolManager.CreateIpPool($DataCenter.Id, $pool) | Out-Null
if ($?)
{
Get-IPPool
-Datacenter $DataCenter -Name $Name
}
Else
{
Write-Warning "Failed to create IP Pool, check vCenter tasks"
}
}
}
Using the New-IPPool
function, create a new IP pool to be allocated to the vApp. For instance, the code in Listing 10-17 creates such a pool.
Listing 10-17: Creating a new IP pool
Get-Datacenter 'DC1' |
New-IPPool
-Name '10.10.10.0' `
-IPv4SubnetAddress '10.10.10.0' `
-IPv4Gateway '10.10.10.1' `
-IPv4Netmask '255.255.255.0' `
-IPv4Range '10.10.10.11#244' `
-IPv4DNS '10.10.10.5','10.10.10.6' `
-DnsDomain 'vSphere.local' `
-DNSSearchPath 'prod.vSphere.local','dev.vSphere.local' `
-IPv4IpPoolEnabled $true
With the new IP pool created, the next step is to associate a virtual network with the new IP pool. To assist in this task, Listing 10-18 contains the Get-
NetworkAssociation
function. This function retrieves the information needed to perform the network association.
Listing 10-18: The Get-NetworkAssociation
function
<#
.SYNOPSIS
Get networks registered in vCenter
.DESCRIPTION
Get networks registered in vCenter
.PARAMETER Name
Only return networks that match name
.PARAMETER Network
Retrieve the Network Association for a specified network MoRef.
.EXAMPLE
Get-NetworkAssociation -Name 10.10.10.0
#>
function Get-NetworkAssociation {
[CmdletBinding(DefaultParameterSetName='name')]
Param(
[parameter(ParameterSetName='name'
, ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[String]
$Name = "*"
, [parameter(ParameterSetName='MoRef'
, ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.Vim.ManagedObjectReference]
$Network
)
Process
{
if ($PSCmdlet.ParameterSetName -eq 'name')
{
$net = Get-View -ViewType Network -Property Name |
Where-Object {$_.Name -like $Name}
}
else
{
$net = Get-View -Id $Network -Property Name
}
if ($net)
{
foreach ($N in $net)
{
New-Object VMware.Vim.IpPoolAssociation `
-Property @{
Network=$N.MoRef
NetworkName=$N.Name
}
}
}
}
}
Using the Get-NetworkAssociation
function, get the networks that need to be configured for the IP pool (Listing 10-19).
Listing 10-19: Getting networks that need to be configured
Get-VApp App01 |
Get-VM |
Get-NetworkAdapter |
Select-Object -ExpandProperty NetworkName -Unique |
Get-NetworkAssociation
Network NetworkName
------- -----------
DistributedVirtualPortgroup-dvportgroup-109 vDS01-10.10.10.0
In this case, only one network needs to be configured. For that task use the Set-IPPool
function shown in Listing 10-20.
Listing 10-20: The Set-IPPool
function
<#
.SYNOPSIS
Modify an existing IP Pool within vCenter
.DESCRIPTION
Modify an existing IP Pool within vCenter
.PARAMETER Datacenter
Datacenter to create the new IP Pool in.
.PARAMETER Name
Pool name.
.PARAMETER Name
New pool name. Must be unique.
.PARAMETER DnsDomain
DNS Domain. For example, vmware.com. This can be an
empty string if no domain is configured.
.PARAMETER DNSSearchPath
DNS Search Path. For example, eng.vmware.com;vmware.com
.PARAMETER HostPrefix
Prefix for hostnames.
.PARAMETER HttpProxy
The HTTP proxy to use on this network
.PARAMETER NetworkAssociation
The networks that are associated with this IP pool.
Use the Get-Network function to get the objects this
parameter requires.
.PARAMETER IPv4SubnetAddress
Address of the subnet.
.PARAMETER IPv4Netmask
Netmask
.PARAMETER IPv4Gateway
Gateway. This can be an empty string
.PARAMETER IPv4Range
IP range. This is specified as a set of ranges
separated with commas. One range is given by a start
address, a hash (#), and the length of the range.
For example:
192.0.2.235#20 = IPv4 range 192.0.2.235-192.0.2.254
192.0.2.0#24 = IPv4 range 192.0.2.1-192.0.2.254
.PARAMETER IPv4DNS
DNS servers
.PARAMETER IPv4DHCP
Whether a DHCP server is available on this network.
.PARAMETER IPv4IpPoolEnabled
IP addresses can only be allocated from the range if
the IP pool is enabled.
.PARAMETER IPv6SubnetAddress
Address of the subnet.
.PARAMETER IPv6Netmask
Netmask
.PARAMETER IPv6Gateway
Gateway. This can be an empty string
.PARAMETER IPv6Range
IP range. This is specified as a set of ranges
separated with commas. One range is given by a start
address, a hash (#), and the length of the range.
For example:
2001::7334 # 20 = IPv6 range 2001::7334 - 2001::7347
.PARAMETER IPv6DNS
DNS servers
.PARAMETER IPv6DHCP
Whether a DHCP server is available on this network.
.PARAMETER IPv6IpPoolEnabled
IP addresses can only be allocated from the range if
the IP pool is enabled.
#>
function Set-IPPool
{
[CmdletBinding()]
Param(
[parameter(Mandatory=$true
, ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.DatacenterImpl]
$Datacenter
, [parameter(Mandatory=$true
, ValueFromPipelineByPropertyName=$true)]
[String]
$Name
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$NewName
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$DnsDomain
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$DNSSearchPath
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$HostPrefix
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$HttpProxy
, [parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.Vim.IpPoolAssociation[]]
$NetworkAssociation
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4SubnetAddress
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Netmask
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Gateway
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv4Range
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$IPv4DNS
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv4DHCP
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv4IpPoolEnabled
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6SubnetAddress
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Netmask
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Gateway
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String]
$IPv6Range
, [parameter(ValueFromPipelineByPropertyName=$true)]
[String[]]
$IPv6DNS
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv6DHCP
, [parameter(ValueFromPipelineByPropertyName=$true)]
[bool]
$IPv6IpPoolEnabled
)
Process
{
$IPPoolManager = Get-View 'IpPoolManager-IpPoolManager'
$pool = $IPPoolManager.QueryIpPools($Datacenter.Id)|
Where-Object {$_.Name -eq $Name}
Switch ($PSCmdlet.MyInvocation.BoundParameters.Keys)
{
'NewName' {
$pool.Name = $Name
}
'IPv4SubnetAddress' {
$pool.Ipv4Config.SubnetAddress = $IPv4SubnetAddress
}
'IPv4Netmask' {
$pool.Ipv4Config.Netmask = $IPv4Netmask
}
'IPv4Gateway' {
$pool.Ipv4Config.Gateway = $IPv4Gateway
}
'IPv4DNS' {
$pool.Ipv4Config.Dns = $IPv4DNS
}
'IPv4DHCP' {
$pool.Ipv4Config.DhcpServerAvailable = $IPv4DHCP
}
'IPv4IpPoolEnabled' {
$pool.Ipv4Config.IpPoolEnabled = $IPv4IpPoolEnabled
}
'IPv4Range' {
$pool.Ipv4Config.Range = $IPv4Range
}
'IPv6SubnetAddress' {
$pool.Ipv6Config.SubnetAddress = $IPv6SubnetAddress
}
'IPv6Netmask' {
$pool.Ipv6Config.Netmask = $IPv6Netmask
}
'IPv6Gateway' {
$pool.Ipv6Config.Gateway = $IPv6Gateway
}
'IPv6DNS' {
$pool.Ipv6Config.Dns = $IPv6DNS
}
'IPv6DHCP' {
$pool.Ipv6Config.DhcpServerAvailable = $IPv6DHCP
}
'IPv6IpPoolEnabled' {
$pool.Ipv6Config.IpPoolEnabled = $IPv6IpPoolEnabled
}
'IPv6Range' {
$pool.Ipv6Config.Range = $IPv6Range
}
'DnsDomain' {
$pool.DnsDomain = $DnsDomain
}
'DNSSearchPath' {
$pool.DnsSearchPath = $DNSSearchPath
}
'HostPrefix' {
$pool.HostPrefix = $HostPrefix
}
'HttpProxy' {
$pool.HttpProxy = $HttpProxy
}
'NetworkAssociation' {
$pool.NetworkAssociation = $NetworkAssociation
}
}
$IpPoolManager.UpdateIpPool($DataCenter.Id, $pool) | Out-Null
if ($?)
{
Get-IPPool -Datacenter $DataCenter -Name $Name
}
Else
{
Write-Warning "Failed to associate IP Pool, check vCenter tasks"
}
}
}
At this point the vApp is ready to associate the new IP pool with the virtual network that the VM(s) are using, as shown in Listing 10-21.
Listing 10-21: Associating the IP pool to a virtual network
Get-VApp App01 | Get-VM | Get-NetworkAdapter |
Select-Object -ExpandProperty NetworkName -Unique |
Get-NetworkAssociation |
Set-IPPool -Name '10.10.10.0' `
-Datacenter (Get-Datacenter DC1)
With the IP pool configured, vApps can now be configured to use pools for IP assignment. By default, a new vApp does not have any IP protocols or allocation methods enabled, and its IP allocation policy is set to fixedPolicy
. This means that, out of the box, vApp IP allocation is disabled. Enabling IP assignment is a two-step process.
To retrieve the current IP assignment configuration of a vApp, use the Get-vAppIPAssignment
function in Listing 10-22.
Listing 10-22: The Get-vAppIPAssignment
function
<#
.SYNOPSIS
Get the IP assignment for the specified vApp.
.DESCRIPTION
Get the IP assignment for the specified vApp.
.PARAMETER vApp
vApp to retrieve the IP Assignment settings.
.EXAMPLE
Get-vApp | Get-vAppIPAssignment
#>
function Get-vAppIPAssignment {
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VAppImpl]
$vApp
)
Process
{
$vapp.ExtensionData.VAppConfig.IpAssignment | Foreach-Object {
New-Object PSObject -Property @{
'vApp' = $vApp
'IpProtocol' = $_.IpProtocol
'IpAllocationPolicy' = $_.IpAllocationPolicy
'SupportedIpAllocation' = $_.SupportedAllocationScheme
'SupportedIpProtocol' = $_.SupportedIpProtocol
}
}
}
}
Use the Get-vAppIPAssignment
function retrieves the current settings for any given vApp. Out of the box, a new vApp is configured to use static IP assignment, as shown in Listing 10-23.
Listing 10-23: Getting the current IP assignment for App01
Get-VApp App01 | Get-vAppIPAssignment
IpProtocol : IPv4
IpAllocationPolicy : fixedPolicy
SupportedIpAllocation :
SupportedIpProtocol :
vApp : App01
Therefore, to enable this powerful feature, you have to enable IP assignment and specify which protocols will be used. This is broken up into a two-step process in the vSphere Web Client, but the Set-vAppIPAssignment
function (Listing 10-24) enables the configuration of IP assignment in one line of PowerCLI (after the function is defined in the session, of course).
Listing 10-24: The Set-vAppIPAssignment
function
<#
.SYNOPSIS
Set the IP assignment for the specified vApp.
.DESCRIPTION
Set the IP assignment for the specified vApp. These
Settings control how the guest software gets
Configured with IP addresses, including protocol type
(IPv4 or IPv6) and the lifetime of those IP addresses.
.PARAMETER vApp
vApp to modify the IP Assignment settings.
.PARAMETER IpProtocol
Specifies the chosen IP protocol for this deployment.
This must be one of the values in the
SupportedIpAllocation
.PARAMETER IpAllocationPolicy
Specifies how IP allocation should be managed by the VI
Platform. This is typically specified by the deployer.
Valid options are 'dhcpPolicy','transientPolicy', and
'fixedPolicy'
.PARAMETER SupportedIpAllocation
Specifies the IP allocation schemes supported by the
guest software. When updating this field, an array of
the form "" will clear all settings.
Otherwise, the supplied value will overwrite the
current setting.
.PARAMETER SupportedIpProtocol
Specifies the IP protocols supported by the guest
software. When updating this field, an array in the
form "" will clear all settings.
Otherwise, the supplied value will overwrite the
current setting.
#>
function Set-vAppIPAssignment {
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[parameter(ValueFromPipeline=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VAppImpl]
$vApp
, [parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('IPv4','IPv6')]
[string]
$IpProtocol
, [parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('dhcpPolicy',
'transientPolicy',
'fixedPolicy')]
[string]
$IpAllocationPolicy
, [parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('ovfenv','dhcp')]
[string[]]
$SupportedIpAllocation
, [parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('IPv4','IPv6')]
[string[]]
$SupportedIpProtocol
)
Process
{
$spec = New-Object VMware.Vim.VAppConfigSpec
$spec.IpAssignment = $vApp.ExtensionData.VAppConfig.IpAssignment
$msg = "Modifying $($vApp.Name)"
Switch ($PSCmdlet.MyInvocation.BoundParameters.Keys)
{
'IpProtocol'
{
$msg = "{0} IP protocol:{1}" -f $msg, $IpProtocol
$spec.IpAssignment.IpProtocol = $IpProtocol
}
'IpAllocationPolicy'
{
$msg = "{0} IP allocation policy:{1}" -f $msg,
$IpAllocationPolicy
$spec.IpAssignment.IpAllocationPolicy = `
$IpAllocationPolicy
}
'SupportedIpAllocation'
{
$msg = "{0} supported allocation policy:{1}" -f `
$msg, $($SupportedIpAllocation -join ',')
$spec.IpAssignment.SupportedAllocationScheme = `
$SupportedIpAllocation
}
'SupportedIpProtocol'
{
$msg = "{0} supported IP protocol:{1}" -f $msg,
$($SupportedIpProtocol -join ',')
$spec.IpAssignment.SupportedIpProtocol = `
$SupportedIpProtocol
}
}
if ($PSCmdlet.ShouldProcess($vApp.Name,$msg))
{
$vApp.ExtensionData.UpdateVAppConfig($spec)
if ($?)
{
Get-vAppIPAssignment -vApp $vApp
}
Else
{
Write-Warning "Failed to set vApp IP Assignment check vCenter tasks"
}
}
}
}
Listing 10-25 uses the Set-vAppIPAssignment
function to enable both DHCP and IP pools for IPv4 and IPv6 protocols and then set the vApp to obtain its IP from the IP pool using a temporary IPv4 address.
Listing 10-25: Configuring IP assignment for App01
Get-VApp App01 | Set-vAppIPAssignment `
-SupportedIpAllocation ovfenv,DHCP `
-SupportedIpProtocol IPv4,IPv6 `
-IpProtocol IPv4 `
-IpAllocationPolicy transientPolicy
IpProtocol : IPv4
IpAllocationPolicy : transientPolicy
SupportedIpAllocation : {ovfenv, DHCP}
SupportedIpProtocol : {IPv4, IPv6}
vApp : App01
Digging deeper into vApps, there is additional metadata in the form of product information. To get the product information for an existing vApp, the Get-vAppProductInfo
function (Listing 10-26) is provided.
Listing 10-26: The Get-vAppProductInfo
function
<#
.SYNOPSIS
Get the vApp Product Information
.DESCRIPTION
Get the vApp Product Information
.PARAMETER vApp
vApp to retrieve the Product Information for.
.EXAMPLE
Get-VApp | Get-vAppProductInfo
#>
function Get-vAppProductInfo {
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VAppImpl]
$vApp
)
Process
{
$vApp.ExtensionData.VAppConfig.Product |
Select-Object -Property @{
Name='vApp'
Expression={$vApp}
},'Name','Vendor','Version','FullVersion',
'VendorUrl','ProductUrl','AppUrl'
}
}
Of course, when dealing with a brand-new vApp, the product information will likely be blank. The Set-vAppProductInfo
function (Listing 10-27) can be used to supply that information. Note that the Get-vAppProductInfo
function (Listing 10-26) must be loaded into your PowerCLI session before you use the Set-vAppProductInfo
function.
Listing 10-27: The Set-vAppProductInfo
function
<#
.SYNOPSIS
Set the vApp Product Information
.DESCRIPTION
Set the vApp Product Information
Information that describes what product a vApp
contains, e.g., what software that is installed in
the contained virtual machines.
.PARAMETER vApp
vApp to set the product information for.
.PARAMETER Name
Name of the product
.PARAMETER Vendor
Vendor of the product.
.PARAMETER Version
Short version of the product , e.g., 1.0.
.PARAMETER FullVersion
Full-version of the product, e.g., 1.0-build 12323.
.PARAMETER VendorUrl
URL to vendor homepage.
.PARAMETER ProductUrl
URL to product homepage.
.PARAMETER AppUrl
URL to entry-point for application. This is often
specified using a macro, e.g., http://${app.ip}/,
where app.ip is a defined property on the virtual
machine or vApp container.
.EXAMPLE
Get-VApp App01| Set-vAppProductInfo `
-Vendor 'VMware' -Version '4' -FullVersion '4.1'
#>
function Set-vAppProductInfo {
[CmdletBinding()]
Param(
[parameter(ValueFromPipeline=$true
, ValueFromPipelineByPropertyName=$true)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VAppImpl]
$vApp
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$Name
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$Vendor
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$Version
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$FullVersion
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$VendorUrl
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$ProductUrl
, [parameter(ValueFromPipelineByPropertyName=$true)]
[string]
$AppUrl
)
Process
{
$spec = New-Object VMware.Vim.VAppConfigSpec
$spec.Product = New-Object VMware.Vim.VAppProductSpec[] (1)
$spec.Product[0] = New-Object VMware.Vim.VAppProductSpec
$spec.Product[0].Operation = "edit"
$spec.Product[0].Info = New-Object VMware.Vim.VAppProductInfo
$spec.Product[0].Info.Key = `
$vApp.ExtensionData.VAppConfig.Property |
Select-Object -ExpandProperty Key -First 1
$msg = "Modifing Advanced Properties "
Switch ($PSCmdlet.MyInvocation.BoundParameters.Keys)
{
'Name'
{
$spec.Product[0].Info.Name = $Name
$msg = "{0} Name:{1}" -f $msg,$Name
}
'Vendor'
{
$spec.Product[0].Info.Vendor = $Vendor
$msg = "{0} Vendor:{1}" -f $msg,$Vendor
}
'Version'
{
$spec.Product[0].Info.Version = $Version
$msg = "{0} Version:{1}" -f $msg,$Version
}
'FullVersion'
{
$spec.Product[0].Info.FullVersion = $Fullversion
$msg = "{0} Full version:{1}" -f $msg,
$Fullversion
}
'VendorUrl'
{
$spec.Product[0].Info.VendorUrl = $vendorURL
$msg = "{0} vendor URL:{1}" -f $msg,$vendorURL
}
'ProductUrl'
{
$spec.Product[0].Info.ProductUrl = $productUrl
$msg = "{0} product Url:{1}" -f $msg,
$productUrl
}
'AppUrl'
{
$spec.Product[0].Info.AppUrl = $AppUrl
$msg = "{0} App Url:{1}" -f $msg,$AppUrl
}
}
if ($PSCmdlet.ShouldProcess($vApp.Name,$msg))
{
$vApp.ExtensionData.UpdateVAppConfig($spec)
if ($?)
{
Start-Sleep -Milliseconds 500
Get-vAppProductInfo -vApp $vApp
}
Else
{
Write-Warning "Failed to set vApp product info check vCenter tasks"
}
}
}
}
Using the Set-vAppProductInfo
function, you can configure the product information on a custom or home-grown vApp, as seen in Listing 10-28.
Listing 10-28: Configuring product information for App01
Set-vAppProductInfo -Name App01 `
-Vendor Acme -version 1.1 `
-FullVersion 1.1.0.1 `
-VendorURL www.acme.com `
-ProductUrl www.acme.com/go/App01
vApp : App01
Name : App01
Vendor : Acme
Version : 1.1
FullVersion : 1.1.0.1
VendorURL : www.acme.com
ProductUrl : www.acme.com/go/App01
AppUrl :
Although vApps haven’t quite taken over the world as we predicted in the previous edition, they are still extremely powerful and should be part of any vSphere infrastructure—if for nothing else than to control the startup and shutdown order of a complex virtual application. Either way, using the tools provided in this chapter, any administrator can easily automate vApp operations in a repeatable means with PowerCLI.