In this chapter, you will learn to:
So, the vSphere environment is ready. The hosts are loaded, storage is provisioned, and vCenter Server is ready to go. Now it’s time to create some virtual machines. This chapter focuses on creating virtual machines, starting with basic background on the PowerCLI New-VM
cmdlet. You’ll discover the various methods of creating new virtual machines. Then we’ll take a deep dive into the vSphere API, and you’ll learn the dark art of using advanced methods and techniques. Finally, you’ll learn how to use custom attributes as well as VMware Tools installation techniques.
The New-VM
cmdlet is used to add, create, clone, or register a new virtual machine (VM). It has grown up over the past six versions of PowerCLI and is quite powerful. It is not, however, complete, and there are still some things that cannot be done natively within PowerCLI. A good analogy for using PowerCLI cmdlets is that it’s like speaking a foreign language. For the most part, the words are the same, but they’re often said in a different order. If the vSphere web console is your native language, then be prepared to do things out of order.
The New-VM
cmdlet provides a high-level abstraction over the whole VM creation process. As a result of this abstraction, New-VM
makes use of some defaults. Unless explicitly told otherwise, the New-VM
cmdlet will always create a VM with the specifications listed in Table 6-1.
Table 6-1: PowerCLI New-VM
default values
Parameter | Default value |
DiskGB | 4 |
DiskStorageFormat | Thick |
GuestId | winXPProGuest |
MemoryMB | 256 |
NumCpu | 1 |
Version | 11 |
Based on VMware vSphere PowerCLI 6.0 build 2442775
When a cmdlet can perform one or more functions, PowerShell groups the parameters for each function into a parameter set. The New-VM
cmdlet includes four parameter sets, or four separate types of operations:
NewVM
Template
RegisterVm
CloneVm
Each parameter set is a grouping of parameters that perform a specific function. Unfortunately, mixing and matching parameters from different parameter sets is not allowed. For clarity, and because they perform four separate functions, we will cover each parameter set independently.
This raises a question: How does PowerCLI choose which parameter set to use? Each parameter set has at least one unique parameter. The default parameter set is assumed until PowerCLI finds that one unique parameter that differentiates the current application from the configured default. In the case of the New-VM
cmdlet, the NewVM
parameter set is assumed until a parameter used elsewhere is found.
Regardless of the parameter set, there are two mandatory parameters: Name
and VMHost
. When connected directly to a vSphere host, PowerCLI assumes the VMHost
parameter. That means that when connected directly to a vSphere host, creating a new VM is as easy as New-VM -Name VM01
.
Although this makes for a cool demo, it’s not very useful in the real world. Nevertheless, it is useful to understand why that works. PowerCLI inferred both the VMHost
and the parameter set. It then took the one remaining mandatory parameter, Name
, and created a new VM using the defaults outlined in Table 6-1. The point here is because PowerCLI and its underlying engine, PowerShell, go to great lengths to “help,” 99.9 percent of the time the guess is correct. Keep in mind that without explicit instructions, PowerCLI will always use those defaults.
Amazingly, PowerCLI is not often used to create new virtual machines. Somehow, the community at large missed the huge advantage PowerCLI offers in this arena. If an organization makes use of enterprise provisioning systems, such as Microsoft’s System Center Configuration Manager (SCCM) or HP’s Business Service Automation (HP-BSA), you can benefit from automating your new VM creation. Even if you’re using a basic OS provisioning tool like Windows Deployment Services, Symantec Ghost, Linux Kickstart, or Solaris JumpStart, there is still benefit. Automation ensures that the base virtual hardware is exactly the same every single time!
How can you create a virtual machine? Well, generally the way you solve any problem in PowerCLI is by starting with a statement or question—for example, “I need to create an REHL6 VM with a thin-provisioned 10 GB hard drive, 1 GB of RAM. I need to mount an ISO in datastore0, and the network should be on VLAN 22.” From there, you parse it out into cmdlets, like those in Listing 6-1.
Listing 6-1: Cmdlets to create a new VM
New-VM -Name RHEL6_01 `
-DiskMB 10240 `
-DiskStorageFormat thin `
-MemoryMB 1024 `
-GuestId rhel6_64Guest `
-Portgroup (Get-VDPortgroup -Name VLAN22) `
-CD |
Get-CDDrive |
Set-CDDrive -IsoPath "[datastore0] ↵/rhel-server-6.5-x86_64-boot.iso" `
-StartConnected:$true `
-Confirm:$False
Notice how that one liner required the use of several cmdlets. The conventional wisdom would be that all aspects of creating a new VM would be performed with the New-VM
cmdlet. This is not the case with PowerCLI, nor with PowerShell in general. Every cmdlet is responsible for only one small task or function. Therefore, more complex operations require that multiple cmdlets be chained into a pipeline to complete the whole task. For example, the New-VM
cmdlet creates only the bare hardware. Everything beyond that—like connecting a CD—must be done after the VM has been created.
For the most part, this is straightforward. However, sometimes a parameter may require a value that is not as clear. For example, consider the rather cryptic -GuestId
parameter. You can always check the vSphere API documentation—visit this site:
Chances are, it will be more convenient to keep this data in PowerShell where it can be manipulated. Listing 6-2 contains a function that does just that. The Get-VMGuestID
function queries vCenter Server for supported operating systems and corresponding guest IDs.
Listing 6-2: Querying vCenter Server for operating systems and guest IDs
<#
.SYNOPSIS
Query VMHost for a list of the supported operating ↵ systems, and their
Guest IDs.
.DESCRIPTION
Query VMHost for a list of the supported operating ↵ systems, and their
Guest IDs.
.PARAMETER VMHost
VMHost to query for the list of Guest IDs
.PARAMETER Version
Virtual Machine Hardware version, if not supplied the ↵ default for that
host will be returned. e.g. ESX3.5 = 4, vSphere = 7
.EXAMPLE
Get-VMGuestId -VMHost vSphere1
.EXAMPLE
Get-VMGuestId -VMHost vSphere1 | Where {$_.family -eq ↵ 'windowsGuest'}
#>
function Get-VMGuestId {
[CmdletBinding()]
Param(
[parameter(Mandatory=$true
, ValueFromPipeline=$true
)]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]
$VMHost
,
[int]
$Version
)
Process
{
$HostSystem = Get-View -VIObject $VMHost -Property Parent
$compResource = Get-View -Id $HostSystem.Parent `
-Property EnvironmentBrowser
$EnvironmentBrowser = `
Get-View -Id $compResource.EnvironmentBrowser
$VMConfigOptionDescriptors = `
$EnvironmentBrowser.QueryConfigOptionDescriptor()
if ($Version)
{
$Key = $VMConfigOptionDescriptors |
Where-Object {$_.key -match "$($Version)$"} |
Select-Object -ExpandProperty Key
}
else
{
$Key = $VMConfigOptionDescriptors |
Where-Object {$_.DefaultConfigOption} |
Select-Object -ExpandProperty Key
}
$EnvironmentBrowser.QueryConfigOption($Key, ↵$HostSystem.MoRef) |
Select-Object -ExpandProperty GuestOSDescriptor |
Select-Object @{
Name='GuestId'
Expression={$_.Id}
},
@{
Name='GuestFamily'
Expression={$_.Family}
},
@{
Name='FullName'
Expression={$_.FullName}
}
}
}
When run, the Get-VMGuestId
function returns a collection of all the supported Guest
objects. From there, PowerShell can be used to filter the collection down with Where-Object
and send it to a file, printer, or CSV—the world is your oyster at that point. The easiest method is to pipe the output to Out-GridView
. The Out-GridView
cmdlet was built for situations like this. It enables quick filtering, sorting, and analyzing of large datasets, as shown in Figure 6-1.
Now, what about a more complex VM—say a SQL Server? To create a Windows Server 2012 R2 virtual machine with the following specifications:
…you must place the new VM within the SQL resource pool on Cluster1
. Listing 6-3 shows how to accomplish this task.
Listing 6-3: Creating a complex virtual machine
$Cluster = Get-Cluster -Name 'Cluster1'
$ResourcePool = Get-ResourcePool -Name 'SQL' -Location $Cluster
$VM = New-VM -Name 'SQL01' `
-NumCpu 4 `
-MemoryGB 8 `
-DiskStorageFormat 'Thin' `
-DiskGB 40 `
-GuestId 'windows8Server64Guest' `
-VMHost (Get-VMHost -Location $Cluster -State Connected | ↵Get-Random) `
-ResourcePool $ResourcePool `
#Add additional c10e000e Network Adapters
New-NetworkAdapter -Portgroup (Get-VDPortgroup -Name VLAN22) `
-StartConnected `
-Type 'c10e000e' `
-VM $VM
#Add additional hard drives
New-HardDisk -CapacityGB 100 -VM $VM
New-HardDisk -CapacityGB 10 -VM $VM
Listing 6-3 is slightly more complicated than the previous listing, but it’s still easy to comprehend.
There are many reasons to clone a virtual machine. Perhaps the most popular is to create a test instance of a production VM. Clones can serve as a form of backup. We’ve even seen clones implemented as an inexpensive DR solution. Whatever your reason, PowerCLI can accommodate you.
For example, here’s how to clone the SQL Server recently deployed to the Test
cluster and name it SQL01_Clone
:
# Get source VM Object
$SourceVM = Get-VM -Name SQL01
#Get a connected host within the Test cluster
$VMHost = Get-Cluster Test | Get-VMHost -State Connected | Get-Random
#Clone SQL01 to SQL01_Clone
$VM = New-VM -Name SQL01_Clone -VM $SourceVM -VMHost $VMHost
That’s as basic as a clone operation can get; it is both good and bad. An optimist would say that they have an exact copy of SQL01
. A pessimist would say that they have an exact copy of SQL01
. The message here is to be extra careful with this new virtual machine. Simply powering it on can cause conflicts with the production SQL01
virtual machine. This can be overcome by customizing the clone with an OSCustomization
task.
Let’s do just that with the SQL virtual machine we just created. SQL01
houses a critical database, and we want to be able to work on an upgrade procedure. To accomplish this task, let’s start by cloning SQL01
, this time applying an OSCustomization
specification named SQL_TEST
. Doing so will prepare the virtual machine with Sysprep and change the hostname and IP information all in one shot. To be successful, the virtual machines network also needs to be changed to one that supports Dynamic Host Configuration Protocol (DHCP).
# Get source VM Object
$SourceVM = Get-VM -Name 'SQL01'
#Get a host within the Test cluster
$VMHost = Get-Cluster 'Test' | Get-VMHost -State Connected | ↵Get-Random
# Get the OS CustomizationSpec
$OSCustomizationSpec = Get-OSCustomizationSpec -Name 'SQL_TEST'
# Clone SQL01 to SQL01_clone
$VM = New-VM -Name 'SQL01_Clone' `
-VM $SourceVM `
-VMHost $VMHost `
-OSCustomizationSpec $OSCustomizationSpec
# Change the Network
Get-NetworkAdapter -VM $VM |
Set-NetworkAdapter -Portgroup (Get-VDPortgroup -Name ↵VLAN100)`
-Confirm:$false
# PowerOn our clone, triggering the CustomizationSpec
Start-VM -VM $VM
Clones when matched with customization specs are limited only by your imagination. They aren’t used as heavily as templates, but they are an incredibly powerful tool when put to the right use.
Templates are the preferred means of deploying virtual machines en masse. Using templates is also the easiest of the three. Listing 6-4 deploys a virtual machine using a template.
Listing 6-4: Deploying a virtual machine from a template
# Get source Template
$Template = Get-Template -Name 'RHEL6.5'
# Get a host within the development cluster
$VMHost = Get-Cluster 'dev01' | Get-VMHost -State Connected | ↵Get-Random
# Deploy our new VM
New-VM -Template $Template -Name 'RHEL_01' -VMHost $VMHost
Given the right template, the code in Listing 6-4 can produce a usable zero-touch virtual machine. This would require all of the preparation to be done in the template ahead of time. Often, templates are paired with customization specs to perform the postconfiguration tasks. The code in Listing 6-5 deploys a virtual machine with customization specs.
Listing 6-5: Deploying a VM using a template and customization specs
# Get source Template
$Template = Get-Template -Name 'RHEL6.5'
# Get a host within the development cluster
$VMHost = Get-Cluster 'dev01' | Get-VMHost -State Connected | ↵Get-Random
# Get the OS CustomizationSpec
$Spec = Get-OSCustomizationSpec -Name 'RHEL6.5'
# Deploy our new VM
New-VM -Template $Template `
-Name 'RHEL_01' `
-VMHost $VMHost `
-OSCustomizationSpec $Spec
Although the code in Listing 6-5 is complete, it does leave some things to chance. To put it all together and fully automate the virtual machine deployment, some preflight requirements need to be verified. The code contained in Listing 6-6 confirms that the target datastore has sufficient free space before deployment starts.
Listing 6-6: Deploying using a template, customization specs, and checks for sufficient free space
# Get source Template
$Template = Get-Template -Name 'RHEL6.5'
# Get the OS CustomizationSpec
$OSCustomizationSpec = Get-OSCustomizationSpec -Name 'RHEL6.5'
# Get a host within the development cluster
$VMHost = Get-Cluster 'dev01' | Get-VMHost -State Connected | ↵Get-Random
# Determine the capacity requirements of this VM
$CapacityKB = Get-HardDisk -Template $Template |
Select-Object -ExpandProperty CapacityKB |
Measure-Object -Sum |
Select-Object -ExpandProperty Sum
# Find a datastore with enough room
$Datastore = Get-Datastore -VMHost $VMHost |
?{($_.FreeSpaceMB * 1mb) -gt (($CapacityKB * 1kb) * 1.1 )} |
Select-Object -First 1
# Deploy our Virtual Machine
$VM = New-VM -Name 'RHEL_01' `
-Template $Template `
-VMHost $VMHost `
-Datastore $Datastore `
-OSCustomizationSpec $OSCustomizationSpec
Templates can do as much or as little as required. See Chapter 7, “Using Templates and Customization Specifications,” for more information on template creation and maintenance.
The fourth and final way to add a new virtual machine to vCenter Server or vSphere is to register an existing virtual machine. To do so, the full path to the VM configuration file must be known:
New-VM -Name CentOS07 -VMHost $VMHost `
-VMFilePath '<Full path to the VM configuration File>.vmx'
Now, that’s all well and good if the full path is known, but what do you do if the VM is removed from inventory and the full path is not known? To assist in these situations, we wrote a simple script that you can use to search datastore(s) looking for any file that matches a pattern (Listing 6-7).
Listing 6-7: Searching a datastore for any file matching a pattern
<#
.SYNOPSIS
Search Datastore for any file that matched
the specified pattern.
.DESCRIPTION
Search Datastore for any file that matched
the specified pattern.
.PARAMETER Pattern
Pattern to search for
.PARAMETER Datastore
Datastore Object to search
.EXAMPLE
Search-DataStore -Pattern *.vmx -Datastore `
(Get-Datastore Datastore1)
.EXAMPLE
Get-Datastore | Search-Datastore *.vmdk
#>
function Search-Datastore {
[CmdletBinding()]
Param(
[parameter(Mandatory=$True
, HelpMessage="Pattern to search for"
)]
[string]
$Pattern
,
[parameter(Mandatory=$True
, HelpMessage="Datastore Object to search"
, ValueFromPipeline=$True
, ValueFromPipeLineByPropertyName=$True
)]
[VMware.VimAutomation.ViCore.Impl.V1.↵DatastoreManagement.DatastoreImpl]
$Datastore
)
Process
{
$DSObject = Get-View -VIObject $Datastore -Property `
Name, Browser, Parent
$DSBrowser = Get-View -Id $DSObject.Browser -Property ↵Datastore
$DSPath = "[{0}]" -f $DSObject.Name
$Spec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$Spec.MatchPattern = $pattern
$TaskMoRef = $DSBrowser.SearchDatastoreSubFolders_ ↵Task($DSPath, $Spec)
$Task = Get-View -Id $TaskMoRef -Property Info
while ("running","queued" -contains $Task.Info.State)
{
$Task.UpdateViewData("Info.State")
Start-Sleep -Milliseconds 500
$Task.UpdateViewData("Info.Result")
$Task.Info.Result |
Where-Object {$_.FolderPath -match `
"[(?<DS>[^]]+)]s(?<Folder>.+)"} |
Select-Object -ExpandProperty File |
Select-Object @{
Name='Datastore'
Expression={$DSObject.Name}
},
@{
Name='Path'
Expression={
"[{0}] {1}{2}" -f $Matches.DS, $Matches.Folder,
$_.Path
}
}
}
}
When used appropriately, Search-Datastore
enables you to find the exact path of a file. For example, say a virtual machine named App04
was accidentally removed from inventory. The old way of finding the path would be to fire up the datastore browser or just perform a simple search with PowerCLI:
Get-Datastore | Search-Datastore -Pattern App04.vmx
Datastore Path
--------- ----
datastore1 [datastore1] App04/App04.vmx
Of course, the entire purpose for getting this information in PowerCLI is to enable the VM to be registered quickly and easily:
Get-Datastore | Search-Datastore -Pattern App04.vmx |
ForEach-Object {
New-VM -Name App04 -VMHost $VMHost `
-VMFilePath $_.Path
}
Name PowerState Num CPUs MemoryGB
---- -----------------------------
App04 PoweredOff 1 2.000
Using this technique, it also is possible to re-register any virtual machines that have been removed from inventory but not deleted from disk. This strategy is slightly more complicated, but it still involves just a couple lines of code (Listing 6-8).
Listing 6-8: Re-registering virtual machines
# Get every VM registered in vCenter
$RegisteredVMs = Get-VM |
Select-Object -ExpandProperty ExtensionData |
Select-Object -ExpandProperty Summary |
Select-Object -ExpandProperty Config |
Select-Object -ExpandProperty VmPathName
# Now find every .vmx on every datastore.
# If it's not part of vCenter then add it back in.
Get-Datastore |
Search-Datastore -Pattern *.vmx |
Where-Object { $RegisteredVMs -notcontains $_.path } |
Where-Object {$_.Path -match "(?<Name>w+).vmx$"} |
ForEach-Object {
$VMHost = Get-Datastore -Name $_.Datastore |
Get-VMHost -State Connected |
Get-Random
New-VM -Name $matches.Name `
-VMHost $VMHost `
-VMFilePath $_.Path
}
At this point, we can all agree that the New-VM
cmdlet is very powerful and for the most part can accommodate all your virtual machine creation needs. We urge you to use the built-in supported cmdlets even if doing so requires more work than using the SDK. It is infinitely easier for a novice to read and understand a snippet that is written with the supported cmdlets than the SDK. However, if you find yourself in a situation where the cmdlets just can’t quite meet your requirements, remember there is always the SDK.
Shortly after realizing the power, space, and cooling savings that virtualization brings to the table, organizations usually notice the speed of deployment. This discovery isn’t all roses, though, as it brings with it the need to pump out virtual machines many times faster than is feasible in the physical world. Fear not! As you’ll soon learn, with PowerCLI no project is too big!
Regardless of whether you are planning to use a third-party provisioning tool (such as SCCM) or you have established a gold master image, PowerCLI enables you to effortlessly scale to any size deployment. The real question is, how unique is each virtual machine? If you’re deploying 1,000 blank Linux virtual machines to load from Kickstart, it’s one line (see Listing 6-9)!
Listing 6-9: Mass-deploying blank virtual machines
1..1000 |
Foreach-Object {
New-VM -Name ("RHEL6_{0}" -f $_ ) `
-VMHost (Get-VMHost -State Connected | Get-Random) `
-DiskGB 10 `
-DiskStorageFormat thin `
-MemoryGB 1 `
-GuestId rhel6_64Guest `
-Portgroup (Get-VDPortgroup -Name VLAN22) `
-CD
}
As you can see, to scale out the deployment, we simply wrapped the New-VM
cmdlet within a basic foreach
loop. This will, of course, run the New-VM
cmdlet 1,000 times, deploying the desired VM. Five hundred Windows desktops deployed from a template? It’s just as simple, as shown in Listing 6-10.
Listing 6-10: Mass-deploying from a template
$template = "WIN8.1"
$datastore = Get-Datastore -Name "Datastore1"
$OSCustomizationSpec = Get-OSCustomizationSpec WIN8.1
$VMHost = Get-Cluster PROD_01 | Get-VMHost -State Connected | Get-Random
1..500 |
Foreach-Object {
New-VM -Name WIN_$_ `
-Template $template `
-Host $VMhost `
-Datastore $datastore `
-OSCustomizationSpec $OSCustomizationSpec
}
So, what about mass deployments that aren’t so cookie cutter? The sky is the limit here, but the simplest method is to use a CSV. First, establish the unique information for each system. In this case, we’ll use CPU, memory, hard disk, and network adapter. Simply create a CSV text file; the first row should contain the label of each column, as shown in Figure 6-2.
To use this information and simplify the deployment process, use a PowerShell cmdlet that will read in the CSV file and create an object (Listing 6-11).
Listing 6-11: Importing a CSV and creating an object
$datastore = Get-Datastore -Name "Datastore1"
$VMHost = Get-Cluster PROD_01 | Get-VMHost -State Connected | Get-Random
Import-Csv .massVM.CSV |
Foreach-Object {
New-VM -Name $_.Name `
-Host $VMhost `
-Datastore $datastore `
-NumCpu $_.CPU `
-MemoryGB $_.Memory `
-DiskGB $_.HardDisk `
-Portgroup (Get-VDPortgroup -Name $_.Nic)
}
Notice how the overall flow doesn’t change. Again, the only limiting factor is imagination. That said, it is always a good idea to only incorporate as much (or as little) as required.
By default, the New-VM
cmdlet monitors the progress of any provisioning operation and updates a progress bar along the way. Though useful, the progress bar can decommission a PowerCLI prompt for minutes or hours depending on the job. It is especially frustrating when writing scripts to deploy a large number of VMs. For instance, let’s deploy four new virtual machines from a template. We want to balance the load, so we’ll manage the datastore placement. Our target environment has two datastores with plenty of free space (see Listing 6-12).
Listing 6-12: Synchronously deploying four virtual machines
$Datastores = Get-Cluster -Name 'Cluster1'|
Get-VMHost |
Get-Datastore
$i=1
while ($i -le 4)
{
foreach ($Datastore in $Datastores)
{
New-VM -Name "VM0$i" `
-Host ($Datastore | Get-VMHost -State Connected | ↵ Get-Random) `
-Datastore $datastore
$i++
}
}
At first glance, there appears to be nothing wrong with the script in Listing 6-12, but that pesky progress bar will effectively force our four virtual machines to be deployed serially! To overcome this, apply the -RunAsync
switch, as shown in Listing 6-13. This switch directs the New-VM
cmdlet
to return the task object rather than monitor the built progress. You can achieve parity with the synchronous VM deployment by saving these task objects in a variable. You can then pass the objects to the Wait-Task
cmdlet.
Listing 6-13: Asynchronously deploying new virtual machines
$Datastores = Get-Cluster -Name '
Cluster1' |
Get-VMHost |
Get-Datastore
$i=1
$Task = while ($i -le 4)
{
foreach ($Datastore in $Datastores)
{
if ($i -le 4)
{
New-VM -Name "VM0$i" `
-Host ($Datastore | Get-VMHost -State Connected | Get-Random) `
-Datastore $datastore `
-RunAsync
}
$i++
}
}
Wait-Task -Task $Task
By intelligently using the -RunAsync
switch, it is possible to maximize the infrastructure and minimize the time needed by driving vSphere to its full potential. There can be negative effects, however, especially when cloning a VM or deploying from a template. These are fairly high I/O operations, and running too many concurrently can bring any storage solution to its knees.
The decision to run code synchronously or asynchronously has more to do with the code for the VM creation. We recommend that, whenever possible, all operations run synchronously because this provides a safety net. The PowerShell pipeline, with a little help from the PowerCLI team, limits the potential for mistakes. However, when speed is paramount, asynchronous deployment is there. Just be mindful of the whole process and pay special attention to the execution order at all times.
Thus far, we have been focused solely on the creation of virtual machines. Now, we are going to shift gears a bit and focus on managing a VM after it has been deployed—particularly, the postbuild configuration. Of course, templates should be used to do the lion’s share of the work. The reason is to achieve parity between a virtual machine deployed from PowerCLI and one deployed from the GUI. As they both use the same sources, they produce identical VMs. Given that, there are some things that cannot be automated via postconfiguration scripts. Sometimes there is a need to use a larger build process—in those circumstances, Invoke-VMScript
is an ace in the hole.
Invoke-VMScript
uses the GuestOperationsManager
core API to communicate directly with the VMware Tools service running in the VM. Security conscious, fear not: this channel is heavily protected and is not an unattended backdoor. Instead, it’s more like a guarded, VIP-only entrance. To use this protected pipeline, admin credentials are needed for both the VM host as well as the guest OS. Once authenticated, Invoke-VMScript
calls the execution engine of your choice and executes the script text passed to it.
Think Secure Shell (SSH) or PowerShell remoting without the need for a network connection! Even if the VM is off the network, you can still programmatically script against the guest OS, assuming it is possible to communicate to the VMHost directly from PowerCLI.
In practice, Invoke-VMScript
can be used for simple verification of postbuild steps. For example, if an organization has a requirement that the CD drive always be mapped to X, you can invoke a VM script to verify that this setting is correct on all your server machines. Listing 6-14 is an excerpt from a postbuild verification script that you could use to perform quality assurance on a new VM.
Listing 6-14: Postbuild verification script
$GuestCreds = Get-Credential
$HostCreds = Get-Credential
$DrvLetter = Invoke-VMScript -VM $VM `
-GuestCredential $GuestCreds `
-HostCredential $HostCreds `
-ScriptType PowerShell `
-ScriptText @'
Get-Volume | ?{ $_.DriveType -eq 'CD-ROM'}|
Select-Object -ExpandProperty DriveLetter
'@
if ($DrvLetter.ScriptOutput.Split() -notcontains "X")
{
Write-warning "$VM CD-Drive out of compliance"
}
With this very simple yet powerful cmdlet, virtually any aspect of a systems configuration can be validated. Although it takes a little more work up front, think of these checks as unit tests for the production environment—simple little scripts that ensure that every system goes into production in the desired state.
VMware Tools should be considered mandatory for all virtual machines. The reason for this is simple: VMware Tools expose critical information required to safely host a VM within a dynamic datacenter. Without the DNS name, IP, guest OS, and heartbeat information, decisions can impact a guest adversely. For this reason, VMware Tools are a key piece of every service-level agreement (SLA) or office-level agreement (OLA) written.
Unfortunately, keeping these tools installed and up to date can, at times, be a full-time job. To help alleviate some of that burden, we’ve created a few examples to assist your efforts.
Any Windows administrator would agree that administering Windows remotely is a moving target. With that in mind, we present the following example that should work in most environments. It’s impossible to address every concern with Group Policy Objects (GPOs), firewalls, and so on. There are a thousand things that could break a remote install. However, once a procedure has been established in a given environment, that procedure is then predictable.
Disclaimer aside, it’s time to remotely manage a Windows machine. There are two primary tools: PowerShell and WMI. PowerShell remoting is the more modern and capable weapon of choice, but it’s not always configured. These instances are disappearing every day, and the collective Windows administrative ecosystem will truly celebrate the day when the last of the legacy infrastructure is put to pasture and PowerShell can be assumed. Although that day is indeed in sight, it is not today, and WMI is still Old Faithful. Listing 6-15 assumes that WMI is available through the firewall on a Windows Server 2008 R2 virtual machine and that the VM’s guest name is the same as the VM object’s name.
Listing 6-15: Windows silent VMware Tools install
$GuestCred = Get-Credential Administrator
$VM = Get-VM 'Win2k8R2'
# Mount VMware Tools media
Mount-Tools -VM $VM
# Find the drive letter of the mounted media
$DrvLetter = Get-WmiObject -Class 'Win32_CDROMDrive' `
-ComputerName $VM.Name `
-Credential $GuestCred |
Where-Object {$_.VolumeName -match "VMware Tools"} |
Select-Object -ExpandProperty Drive
#Build our cmd line
$cmd = "$($DrvLetter)setup.exe /S /v`"/qn REBOOT=ReallySuppress ↵ADDLOCAL=ALL`""
# spawn a new process on the remote VM, and execute setup
$go = Invoke-WMIMethod -path Win32_Process `
-Name Create `
-Credential $GuestCred `
-ComputerName $VM.Name `
-ArgumentList $cmd
if ($go.ReturnValue -ne 0)
{
Write-Warning "Installer returned code $($go.ReturnValue)! ↵Unmounting media"
Dismount-Tools -VM $VM
}
else
{
Write-Verbose "Tool installation successfully
triggered on `
$($VM.Name) media will be ejected upon completion."
}
To recap, the script in Listing 6-15 mounted the VMware Tools ISO file, connected via WMI, discovered the drive letter of the VMware Tools ISO file, and finally spawned a new process that launches setup.
Unfortunately, the Linux disclaimer is even worse; needless to say, it will be different depending on the distribution and revision. At a minimum, it is safe to assume an SSH Server has been installed and configured to allow remote authentication. This will also vary widely depending on the Linux distribution, but generally a bash script that can independently install VMware Tools is required. From there, you can use the Invoke-SSH
function to remotely execute the script. For example, to install VMware Tools on a Red Hat 5 server, you could use the shell script presented in Listing 6-16.
Listing 6-16: silent Linux VMware Toolsinstall
#!/bin/bash
echo -n "Executing preflight checks "
# make sure we are root
if [ `id -u` -ne 0 ]; then
echo "You must be root to install tools!"
exit 1;
fi
# make sure we are in RHEL, CentOS or some reasonable facsimile
if [ ! -s /etc/redhat-release ]; then
echo "You must be using RHEL or CentOS for this script to work!"
exit 1;
fi
echo "[ OK ]"
echo -n "Mounting Media "
# check for the presence of a directory to mount the CD to
if [ ! -d /media/cdrom ]; then
mkdir -p /media/cdrom
fi
# mount the cdrom, if necessary...this is rudimentary
if [ `mount | grep -c iso9660` -eq 0 ]; then
mount -o loop /dev/cdrom /media/cdrom
fi
# make sure the cdrom that is mounted is VMware Tools
MOUNT=`mount | grep iso9660 | awk '{ print $3 }'`
if [ `ls -l $MOUNT/VMwareTools* | wc -l` -ne 1 ]; then
# there are no tools here
echo "No tools found on CD-ROM!"
exit 1;
fi
echo "[ OK ]"
echo -n "Installing VMware Tools "
# extract the installer to a temporary location
tar xzf $MOUNT/VMwareTools*.tar.gz -C /var/tmp
# install the tools, accepting defaults, capture output to a file
(/var/tmp/vmware-tools-distrib/vmware-install.pl--default )>
~/vmware-tools_install.log
# remove the unpackaging directory
rm -rf /var/tmp/vmware-tools-distrib
echo "[ OK ]"
echo -n "Restarting Network:"
# the vmxnet kernel module may need to be loaded/reloaded...
service network stop
rmmod pcnet32
rmmod vmxnet
modprobe vmxnet
service network start
# or just reboot after tools install
# shutdown -r now
When run, the shell script in Listing 6-16 installs VMware Tools. Now, all that is needed is a method to deliver the script. This is where Invoke-SSH
comes in (Listing 6-17); it is a PowerShell function that wraps plink.exe
to provide a PowerCLI-friendly SSH interface.
Listing 6-17: The Invoke-SSH
function
function Invoke-SSH {
<#
.SYNOPSIS
Execute a command via SSH on a remote system.
.DESCRIPTION
Execute a command via SSH on a remote system.
.PARAMETER Computer
Computer to execute script/command against.
.PARAMETER Credential
PSCredential to use for remote authentication
.PARAMETER Username
Username to use for remote authentication
.PARAMETER Password
Password to use for remote authentication
.PARAMETER FilePath
Path to a script to execute on the remote machine
.PARAMETER ScriptText
ScriptText to execute on the remote system
.EXAMPLE
Invoke-SSH -Credential $Creds `
-Computer 10.1.1.2 `
-FilePath .installtools.sh
.EXAMPLE
Invoke-SSH -Credential $Creds `
-Computer $VM.name `
-ScriptText 'rpm -qa' |
Select-String ssh
#>
[CmdletBinding(DefaultParameterSetName='Command')]
Param(
[parameter(Mandatory=$True
, ValueFromPipeline=$True
, ValueFromPipelineByPropertyName=$True
, HelpMessage='ip or hostname of remote computer'
, ParameterSetName='Script'
)]
[parameter(Mandatory=$True
, ValueFromPipeline=$True
, ValueFromPipelineByPropertyName=$True
, HelpMessage='ip or hostname of remote computer'
, ParameterSetName='Command'
)]
[string]
$Computer
,
[parameter(Mandatory=$False
, ValueFromPipeline=$True
, ParameterSetName='Script'
)]
[parameter(Mandatory=$False
, ValueFromPipeline=$True
, ParameterSetName='Command'
)]
[System.Management.Automation.PSCredential]
$Credential
,
[parameter(ParameterSetName='Script')]
[parameter(ParameterSetName='Command')]
[string]
$Username
,
[parameter(ParameterSetName='Script')]
[parameter(ParameterSetName='Command')]
[AllowEmptyString()]
[string]
$Password
,
[parameter(Mandatory=$True
, ParameterSetName='Script'
, ValueFromPipelineByPropertyName=$True
, HelpMessage='Path to shell script'
)]
[ValidateScript({Test-Path $_})]
[Alias("PSPath","FullName")]
[string]
$FilePath
,
[parameter(Mandatory=$True
, ParameterSetName='Command'
, ValueFromRemainingArguments=$True
, HelpMessage='Command to execute'
)]
[string]
$ScriptText
)
Begin
{
$PLink = "${env:ProgramFiles(x86)}PuTTYplink.exe","plink.exe" |
Get-Command -EA SilentlyContinue |
Select-Object -First 1 -ExpandProperty Definition
if (-not $PLink)
{
throw "PLink could not be found, please install putty!"
exit 1;
}
if ($Credential)
{
$Cred = $Credential.GetNetworkCredential()
$Username = $Cred.UserName
$Password = $Cred.Password
}
}
Process
{
switch ($PSCmdlet.ParameterSetName)
{
"Script"
{
& $Plink -l $Username -pw $Password $Computer -m $FilePath
}
"Command"
{
& $Plink -l $Username -pw $Password $Computer $ScriptText
}
}
}
}
Now to bring it all together, combine Listing 6-16 and Listing 6-17 to remotely install VMware Tools to a Community Enterprise Operating System (CentOS
) 5 server using the code in Listing 6-18.
Listing 6-18: Remotely installing Linux VMware Tools
$VM = Get-VM CentOS5
Mount-Tools -VM $VM
Invoke-SSH -Username root `
-Password 'Pa$$word' `
-Computer 10.10.10.63 `
-FilePath .InstallTools.sh
Dismount-Tools -VM $VM
Admittedly, most of the heavy lifting is done in the shell script, but by integrating the installation in PowerCLI, this solution can be used wherever, whenever it’s needed.
Prior to PowerCLI 4.0 Update 1, updating VMware Tools programmatically was a big deal. With Update 1, the PowerCLI team shipped an Update-Tools
cmdlet with a -NoReboot
switch that actually worked! Although it is possible to delay the restart of a VM after you update or install VMware Tools, there are some side effects, such as excessive CPU usage or intermittent network connectivity. In short, while this approach is not recommended, as administrators of several large VI farms, we understand! It’s not always possible to reboot a guest, nor is it reliable to count on someone else to remember to update VMware Tools. It is very handy to have VMware Tools installed just waiting for the reboot.
In fact it’s so easy, it’s not uncommon to update smaller Windows environments in one shot (see Listing 6-19).
Listing 6-19: Installing VMware Tools en masse
Get-View -ViewType "VirtualMachine" `
-Property Guest,Name `
-Filter @{
"Guest.GuestFamily"="windowsGuest";
"Guest.ToolsStatus"="ToolsOld";
"Guest.GuestState"="^running$"
} |
Get-VIObjectByVIView |
Update-Tools -NoReboot
Linux virtual machines are a bit trickier. The Update-Tools
cmdlet will attempt an update, but if it fails it will simply mount the ISO and rely on the administrator to complete the install. However, the remote installation script found in Listing 6-16 can be reused to perform the update. Additionally, now that VMware Tools are installed on the system, the Invoke-VMScript
cmdlet can be used to perform the update! (See Listing 6-20.)
Listing 6-20: Updating VMware Tools for a Linux guest
$cmd = Get-Content .installTools.sh | Out-String
Mount-Tools -VM $VM
Invoke-VMScript -VM $VM `
-GuestCredential $guestCreds `
-HostCredential $hostCreds `
-ScriptText $cmd
Dismount-Tools -VM $VM
Of course there is always the option to simply allow VMware Tools to automatically update upon the next power cycle. This is not an option for all, as some organizations require strict change control over all installed software. However, if you are able, the simplest method is to set the tools upgrade policy to Upgrade At Power Cycle, as we did in Listing 6-21.
Listing 6-21: Set the tools upgrade policy to Upgrade At Power Cycle
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.tools = New-Object VMware.Vim.ToolsConfigInfo
$spec.tools.toolsUpgradePolicy = "upgradeAtPowerCycle"
$VM = Get-VM App04
$VM.ExtensionData.ReconfigVM($spec)