Chapter 6
Creating Virtual Machines

In this chapter, you will learn to:

  • Use the New-VM Cmdlet
  • Creating a New Virtual Machine
  • Cloning a Virtual Machine
  • Deploying from a Template
  • Registering a Virtual Machine
  • Perform a Mass Deployment
  • Preparing for Mass Deployment
  • Running the Deployment Synchronous or Asynchronous
  • Postbuild Configuration and Validation
  • Maintain VMware Tools
  • Windows Silent Install
  • Linux Silent Install
  • Updating VMware Tools
  • Automatically Updating VMware Tools

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.

Use the New-VM Cmdlet

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

ParameterDefault value
DiskGB4
DiskStorageFormatThick
GuestIdwinXPProGuest
MemoryMB256
NumCpu1
Version11

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.

Creating a New Virtual Machine

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:

http://pubs.vmware.com/vsphere-60/index.jsp#com.vmware.wssdk.apiref.doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html

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.

c06f001.tif

Figure 6-1: Get-VMGuestId piped to Out-Gridview

Now, what about a more complex VM—say a SQL Server? To create a Windows Server 2012 R2 virtual machine with the following specifications:

  • 4 vCPUs
  • 8 GB of RAM
  • A thin-provisioned 40 GB OS hard drive
  • 100 GB thick and 10 GB thick hard drive
  • Two c10e000e network adapters on the Public and VLAN100 port groups

…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.

Cloning a Virtual Machine

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.

Deploying from a Template

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.

Registering a Virtual Machine

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.

Perform a Mass Deployment

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!

Preparing for Mass Deployment

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.

c06f002.tif

Figure 6-2: MassVM.csv

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.

Running the Deployment Synchronous or Asynchronous

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.

Postbuild Configuration and Validation

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.

Maintain VMware Tools

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.

Windows Silent Install

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.

Linux Silent Install

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.

Updating VMware Tools

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

Automatically Updating VMware Tools

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)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset