Chapter 9
Advanced Virtual Machine Features

In this chapter, you will learn to:

  • Interact with the Guest OS
  • Using Linux Native Tools
  • Using Windows Native Tools
  • Using PowerCLI Methods
  • Use vMotion and Storage vMotion
  • Examining vMotion Requirements
  • Moving a Virtual Machine
  • Use and Manage Snapshots
  • Creating and Removing Snapshots
  • Maintaining Snapshots
  • Restricting the Creation of Snapshots

This chapter covers advanced virtual machine features starting with how to interact with the guest operating system using the operating system’s native tools and through the PowerCLI methods. Then, you’ll learn how to automate vMotion and Storage vMotion operations. Last but not least, we’ll show you how to create and maintain snapshots.

Interact with the Guest OS

From time to time, administrators need to interact with a guest operating system (OS). It could be simple, quick stuff like finding the letter of the CD-ROM drive in a Windows guest or discovering whether a specific Microsoft hotfix is installed on your system. Any number of guest OS–specific items may be required. Several options are available when you need to interact with the VM’s guest OS. For instance, OS native tools like Windows Management Instrumentation (WMI), Secure Shell (SSH), or PowerShell Remoting let you interact with the guest, but these techniques might fail due to company system policies or firewall rules. In such situations, these options aren’t reliable. Fear not—VMware PowerCLI contains some cmdlets to help in such instances. Before exploring what PowerCLI has to offer, it may be helpful to briefly cover the guest’s native tools. Table 9-1 explains some pros and cons of various methods available when interacting with the guest OS.

Obviously the PowerCLI method is the only method that doesn’t require a network connection from the PowerCLI management station to the guest and therefore is a uniform method that can be used in almost all cases. The only exception is when you happen to have an unsupported guest OS running that cannot run the VMware Tools package.

Table 9-1: Comparing guest OS methods of interactions

MethodProsCons
Linux SSHCan run anything that can be started from a command line.Returns plain text.
Requires a network connection to the guest.
WMIReturns actual PowerShell objects.
Specifically built to manage system information.
Difficult to learn.
Requires a network connection to the guest.
PowerShell remotingReturns actual PowerShell objects.Requires remote systems to be properly configured.
Requires a network connection to the guest.
PowerCLI Doesn’t require a network connection to the guest.
Can run anything that can be started from a command line.
Returns plain text.
VMware Tools need to be installed in the guest.

There is no silver bullet, although PowerShell Remoting is close. There are natural trade-offs among the various options. Due to parallel development paths and various intellectual property concerns, these tools break down into two camps: Windows and Linux/Unix.

Using Linux Native Tools

When managing Linux systems, Secure Shell (SSH) is the protocol of choice. Because this book focuses on PowerCLI, we assume the scripts will be run on a Windows system. The SSH protocol isn’t a native Windows protocol, so you need to install PuTTY (available for download from www.chiark.greenend.org.uk/~sgtatham/putty/). PuTTY is a Windows SSH client. The PuTTY package also includes Plink.exe (PuTTY Link), a command-line connection tool, which can be called from within PowerShell. After installing PuTTY, you can use the Invoke-SSH function from Listing 6-17 in Chapter 6, “Creating Virtual Machines,” to send commands or scripts using SSH to remote systems.

To understand the trade-offs when using the various tools, consider the following scenario. To prevent a system from running out of disk space, it’s a common practice to monitor its partitions. With VMware Tools installed in the guest OS, the guest OS partition information can be retrieved from the virtual machine object:

$vm = Get-VM CentOS5
$vm.Guest.Disks

CapacityGB      FreeSpaceGB     Path
----------      -----------     ----
11.594          8.240           /
0.096           0.084           /boot

You can also retrieve this information from the guest itself using SSH. To get partition information from a Linux guest, you need to use the shell command df. As mentioned earlier, the Invoke-SSH function from Listing 6-17 in Chapter 6 can be used to retrieve this information:

Invoke-SSH -Computer $vm.Guest.IPAddress[0] `
    -Credential $creds -ScriptText "df"

Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/mapper/VolGroup00-LogVol00
                      12156848   3532644   7996700  31% /
/dev/sda1               101086     13095     82772  14% /boot
tmpfs                  1029380         0   1029380   0% /dev/shm

Notice that the output is similar to the information retrieved through VMware Tools. However, the output is returned as text (not as a PowerShell object this time), so it’s going to be a bit harder to extract data. When only a subset of the data is needed, the Select-String cmdlet can be used. Select-String is similar to the grep command in Unix. For example, to display only information for the boot volume, use this:

Invoke-SSH -Computer $vm.Guest.IPAddress[0] `
    -Credential $creds -ScriptText "df" | Select-String '/boot'

/dev/sda1               101086     13095     82772  14% /boot

The ability to report shell command output is nice, but not very useful. To act on this information, it is necessary to extract the values and store them into an object. This is a simple text-to-object conversion that is part of many PowerShell operations. Listing 9-1 is an example of one such operation using Where-Object and a regular expression to streamline the data-gathering and object-creation functions.

Listing 9-1: Converting text to objects

$searchString = "(S+)s+(d+)s+(d+)s+(d+)s+(d+)%s+(S+)"
$splat = @{
    'Computer' = $vm.Guest.IPAddress[0]
    'Credential' = $creds
    'ScriptText' = "df"
}
Invoke-SSH @splat | Where-Object {$_ -match $searchString} |
  ForEach-Object {
      New-Object PSObject -Property @{
          "FileSystem" = $matches[1]
          "1kBlocks" = [int64]$matches[2]
          "Used" = [int64]$matches[3]
          "Available" = [int64]$matches[4]
          "PercentUsed" = [Decimal]$matches[5]
          "MountPoint" = $matches[6]
      }
  } | Where-Object {$_.Available -lt (50MB/1KB)} |
  ForEach-Object {
    Write-Warning "Free space on volume $($_.MountPoint) is low"
  }

If Listing 9-1 is a bit intimidating, relax. All will all be clear shortly. When using the -match operator in the Where-Object script block, you can use a regular expression. A regular expression is a rule that makes it easy to do advanced pattern recognition and extract data. In this case, Listing 9-1 uses the spaces between data columns to delimit the data. Simple groupings are used to organize the output. This technique is beneficial for many reasons: it is extremely fast and flexible and automatically filters out any lines that don’t match the desired pattern. For a better understanding of the rules used, take a look at Table 9-2. After the data is parsed, it is then organized into a proper object using the New-Object cmdlet. From there, it is a simple PowerShell operation to find any partitions that have less than 50 MB of free space.

Table 9-2: Regular expressions reference sheet

CharacterDefinitionExample
. (dot)Matches any character."pos." matches pos1 or posh but not pos12.
+Preceding item must match one or more times."pos.+" matches posh or poshable but not pos.
*Preceding item must match zero or more times."pos*" matches po and posss but not posh.
()Creates a substring or group."(po)(sh)" matches posh and creates two groups: po and sh.
[]Matches one of any characters enclosed."po[os]h" matches posh and pooh.
sA single whitespace character."possh" matches posh but not posh.
SA single non-whitespace character."poSh" matches posh and po$h but not posh.
wA single word character (alphanumeric, numeric, and underscore)."powh" matches posh or po5h but not po$h.
dA single numeric character."podh" matches po5h but not posh.

Although seemingly daunting at first, using simple regular expressions is critical to getting useful objects out of any text-based tools like SSH. Many online utilities can assist in developing regular expressions, but basic column matching can be done using nothing more than subgroups and the whitespace delimiter, s|S. As an alternative, you can offload more of the parsing work in the command passed over SSH. Whichever method you employ depends on your comfort level. There really isn’t a wrong way to do any of this. Feel free to use any skillset or tools as the need arises.

Using Windows Native Tools

There are two main ways to interact with the Windows guest OS: Windows Management Instrumentation (WMI) and PowerShell Remoting. WMI allows scripting languages like VBScript or Windows PowerShell to manage Microsoft Windows systems, both locally and remotely. WMI is preinstalled in Windows 2000 and newer OSs. PowerShell Remoting is Microsoft’s new way of interacting with remote systems and comes with PowerShell 2.0 or later. The drawbacks to both methods is that they require a network connection not be blocked by a company firewall and that the applicable services be configured and running on the remote system. WMI is an interface designed to manage system information using WMI Query Language (WQL), whereas PowerShell Remoting is more like a remote shell.

WMI

WMI is the Microsoft implementation of Web-Based Enterprise Management (WBEM), which is an industry initiative to develop a standard technology for accessing management information in an enterprise environment. WMI is very powerful but also hard to understand. Many times, finding the right information requires a lot of digging in the enormous collection of WMI classes. An in-depth study of WMI is beyond the scope of this book. A good starting point for learning WMI is the Microsoft MSDN Library. You can access the library from http://msdn.microsoft.com/en-us/library/aa394582(v=VS.85).aspx.

In PowerShell, WMI information is retrieved using the Get-WMIObject cmdlet. To retrieve information from a remote system, use the -Computer parameter. If not specified, the cmdlet is run against the local machine. The -Computer parameter value can be a fully qualified domain name, a NetBIOS name, or an IP address. The next two statements achieve identical results and retrieve a list of services installed on the local system:

Get-WmiObject -Class Win32_Service
Get-WmiObject Win32_Service

For example, to retrieve the drive letter for the CD-ROM drive on a VM named VM001, we would use the Get-WmiObject cmdlet querying the Win32_CDROMDrive WMI class.

Get-WmiObject Win32_CDROMDrive -ComputerName VM001 | Format-List

Caption      : NECVMWar VMware SATA CD00
Drive        : Z:
Manufacturer : (Standard CD-ROM drives)
VolumeName   :

WMI can also be queried using WQL, which is a SQL-like language. WQL queries can be run using the -Query parameter. To retrieve the CD-ROM information again, this time using a WQL query, try this:

Get-WmiObject -Query "Select * From Win32_CDROMDrive" `
    -ComputerName VM001 | Format-List

Caption      : NECVMWar VMware SATA CD00
Drive        : Z:
Manufacturer : (Standard CD-ROM drives)
VolumeName   :

The uses for WMI are almost endless. However, at this point WMI is a fallback, as the primary means to manage Windows is PowerShell.

PowerShell Remoting

PowerShell Remoting is Microsoft’s way of managing remote Windows systems. PowerShell Remoting requires that PowerShell v2.0 or newer and Windows Remote Management (WinRM) be installed and running. Every Microsoft operating system since Windows 7 and Windows 2008 R2 has shipped with WinRM preinstalled.

The required components are included by default, but remote management must be explicitly allowed in all but the latest releases. To enable PowerShell remote sessions on a Windows system, use the Enable-PSRemoting cmdlet. This cmdlet will configure your system, including:

  • Starting or restarting (if already started) the WinRM service
  • Setting the WinRM service type to start automatically
  • Creating a listener to accept requests on any IP address
  • Enabling a firewall exception for WS-Management traffic (for HTTP only)
    Enable-PSRemoting
    
    WinRM Quick Configuration
    Running command "Set-WSManQuickConfig" to enable this machine
    for remote management through WinRM service.
     This includes:
        1. Starting or restarting (if already started) the WinRM
           service
        2. Setting the WinRM service type to auto start
        3. Creating a listener to accept requests on any IP address
        4. Enabling firewall exception for WS-Management traffic
          (for http only).
    
    Do you want to continue?
    [Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend
        [?] Help (default is "Y"): y
    WinRM already is set up to receive requests on this machine.
    WinRM has been updated for remote management.
    Created a WinRM listener on HTTP://* to accept WS-Man requests
    to any IP on this machine.
    WinRM firewall exception enabled.

With the environment configured to receive remote PowerShell sessions, it’s time to put this fantastic technology to use. Remember that the user performing remote script execution must be a member of the Administrators group on the remote computer to be able to run commands.

There are two varieties of PowerShell Remoting: temporary sessions and persistent sessions. Temporary sessions are sessions that are opened to run a command and immediately closed when that command is finished. This kind of session is usually created when the -ComputerName parameter is specified on a cmdlet. For example, the Invoke-Command cmdlet will create a temporary session and retrieve WMI information from the remote guest:

Invoke-Command -ComputerName VM001 -ScriptBlock {
        Get-WmiObject Win32_CDROMDrive} | Format-List

Caption      : NECVMWar VMware SATA CD00
Drive        : Z:
Manufacturer : (Standard CD-ROM drives)
VolumeName   :
PSComputerName : VM001

This is a particularly interesting example, as the Get-WmiObject cmdlet implements its own remoting over WMI. However, by wrapping the Get-WmiObject cmdlet with the Invoke-Command cmdlet, PowerShell uses PowerShell Remoting to remotely execute the Get-WMIObject cmdlet, which in turn executes the WMI query to run locally on the remote machine.

You can also start an interactive session using the Enter-PSSession cmdlet:

PS C:> Enter-PSSession VM001
[VM001]: PS C:>

Notice that the command prompt changed to [VM001]: PS C:> to indicate that the PowerShell session is now working on remote computer VM001. When finished, exit the interactive session by issuing the Exit-PSSession cmdlet.

Let’s take it one step further and use PowerShell Remoting technology in a script. In Listing 9-2, you’ll find a script that reports the status of a service using PowerShell remoting. The optional switches allow the start or restart of a service.

Listing 9-2: Reporting the status of a service using PowerShell Remoting

<#
.SYNOPSIS
  Checks the state of a service using PowerShell remoting
.DESCRIPTION
  This function checks the state of a service using
  PowerShell remoting. The optional restart switch can be used
  to restart a service if it is stopped.
.PARAMETER Computer
  One or more computer names to check the service on
.PARAMETER Service
  One or more service names to check
.PARAMETER Start
  Optional parameter to start a stopped service
.PARAMETER Restart
  Optional parameter to restart a service
.EXAMPLE
  PS> Check-Service -Computer VM001 -Service wuauserv
#>
function Check-Service {
  Param(
    [parameter(Mandatory = $true)]
    [string]$Computer
  ,
    [parameter(Mandatory = $true)]
    [string]$Service
  ,
    [switch]$Start
  ,
    [switch]$Restart
  )
  #establish a persistent connection
  $session = New-PSSession $Computer
  $remoteService = Invoke-Command –Session $session `
      -ScriptBlock {
    param($ServiceName)
    $localService = Get-Service $ServiceName
    $localService
  } -ArgumentList $Service
  if ($Start -and $remoteService.Status -eq "Stopped") {
    Invoke-Command –Session $session -ScriptBlock {
        $localService.Start()
    }
    $remoteService | Add-Member -MemberType NoteProperty `
        -Name Started -Value $True
  }
  if ($Restart) {
    Invoke-Command –Session $session -ScriptBlock {
      $localService.Stop()
      $localService.WaitForStatus("Stopped")
      $localService.Start()
    }
    $remoteService | Add-Member -MemberType NoteProperty `
        -Name Restarted -Value $True
  }
  #close persistent connection
  Remove-PSSession $session
  Write-Output $remoteService
}

Setting up a session each time you run a remote command slows down performance. This can be mitigated by creating a persistent session using the New-PSSession cmdlet. A persistent session can then be specified using the -Session parameter. To close the persistent session, use the Remove-PSSession cmdlet.

As an example, let’s verify that a remote server named VM001 is still receiving Windows updates by reporting the status of the wuauserv service using the function from Listing 9-2.

Check-Service VM001 wuauserv

Status   Name          DisplayName                PSComputerName
------   ----          -----------                --------------
Running  wuauserv      Automatic Updates          VM001

The drawback to the OS native methods is that a network connection is always required. But what about VMs behind a firewall or in completely isolated networks? Luckily, a PowerCLI method is available.

Using PowerCLI Methods

PowerCLI enables scripts to be executed inside a VM using the Invoke-VMScript cmdlet. The only requirement is that the VM must have VMware Tools running. Using this powerful tool, it is possible to interact with the guest OS without worrying about firewalls or services that need to be running. There’s also no need to install PuTTY for interacting with Linux OSs. This is a uniform way of interacting with the guest OS no matter what, as long as VMware Tools is installed and the other prerequisites are met, of course. Using the Invoke-VMScript cmdlet, you can execute any PowerShell or batch scripts on Windows machines and Bash scripts on Linux machines. To run PowerShell scripts, you must make sure PowerShell is installed on the guest.

Because batch and Bash scripts are supported, anything that can be started through a batch or Bash script—for instance, old VBScript scripts or Perl scripts in your Linux guests—can be remotely executed. To accomplish this, copy the VBScript or Perl file to the guest using the Copy-VMGuestFile cmdlet or make the script file accessible over the network by placing it on a file share.

Copy-VMGuestFile -Source C:ScriptsmyScript.vbs `
    -Destination C:Temp -VM VM001 -LocalToGuest `
    -HostCredential $hostCred -GuestCredential $guestCred

$script = 'cscript C:TempmyScript.vbs'

Invoke-VMScript -ScriptText $script -VM VM001 `
    -HostCredential $hostCred -GuestCredential $guestCred `
    -ScriptType Bat

Notice that the contents of the ScriptOutput property returned by the Invoke-VMScript cmdlet is plain text. Using the methods discussed earlier in this chapter, we can easily convert the plain text back into an object. For example, to retrieve the partition information from a Linux VM using Invoke-VMScript cmdlet, use the following:

$output = Invoke-VMScript 'df -P' -VM VM005 `
    -HostCredential $HostCred -GuestCredential $GuestCred `
    -ScriptType "bash"

The $output variable, containing the output text, can be processed using the techniques covered earlier in Listing 9-1.

Another method of converting text to PowerShell objects is to return the information from the guest in a comma-separated values (CSV) format. This can then be piped to a temporary file and you can import that file again using the Import-Csv cmdlet.

Returning to WMI for a moment, Microsoft also provides a command-line interface for WMI called Windows Management Instrumentation Command Line (WMIC). This utility supports a /format parameter to present WMI information in CSV format. For example, take a look at that CD-ROM drive again on VM001:

wmic path Win32_CDROMDrive get "Caption,Drive,Manufacturer" /format:csv

Node,Caption,Drive,Manufacturer
VM001, NECVMWar VMware SATA CD00,Z:,(Standard CD-ROM drives)

Notice that a get parameter was used to retrieve only the specified properties. To retrieve all properties, provide the get parameter without any properties. Combine this new technique with the Invoke-VMScript cmdlet:

$script = "wmic path Win32_CDROMDrive get /format:csv"
$Output = Invoke-VMScript $script -VM VM001 `
    -HostCredential $HostCred -GuestCredential $GuestCred `
    -ScriptType "bat"

At this point PowerShell has the CSV-formatted output as plain text stored in the $Output.ScriptOutput property. With a quick trip through the ConvertFrom-Csv cmdlet, that plain text can be converted into a useful object:

$cdrom = $Output.ScriptOutput.Trim() | ConvertFrom-Csv 

Whenever you are forced to work with text, some additional manipulation may be necessary. In this case, the .Trim() method was used to strip off the nasty empty line returned by wmic at the start of the output; without it, the CSV output would be invalid. CSV headers must start at the first line in a CSV file. The $cdrom variable now contains the desired information in a nice PowerShell object-oriented way:

$cdrom
Node                        : VM001
Availability                : 3
Capabilities                : {3;7}
CapabilityDescriptions      : {Random Access; Supports Removable Media}
Caption                     : NECVMWar VMware SATA CD00
CompressionMethod           : Unknown
ConfigManagerErrorCode      : 0
ConfigManagerUserConfig     : FALSE
CreationClassName           : Win32_CDROMDrive
DefaultBlockSize            :
Description                 : CD-ROM Drive
...
Output truncated, you get the picture.

Although guest manipulation may be viewed as primarily the job of the application administrative team, those lines are rarely maintained. It is almost inevitable that the virtualization team will need to use a combination of the techniques described in this section to perform any number of activities. Which technique is employed will depend on the circumstances of the particular environment and tasking. However, regardless of the situation, PowerCLI has the tools needed to make anything achievable.

Use vMotion and Storage vMotion

vMotion is a feature of VMware vCenter and allows the live migration of virtual machines between vSphere hosts with no perceivable downtime. Storage vMotion, on the other hand, allows the live migration of virtual machine disk files across datastores. Remember that shared storage is required to use either of these vMotion technologies.

The vMotions technology is used primarily when maintenance needs to be performed on a vSphere host. In such a case, vMotion can evacuate all VMs off the vSphere host without impacting the tenant workload. Maintenance might include a memory upgrade of your host, a degraded memory module replacement, or even the complete replacement of the server. Storage vMotion is used for a number of common reasons:

  • The datastore is running out of free disk space.
  • A virtual hard disk(s) format requires conversion to thin provisioned, or vice versa.
  • VMs must be moved to another storage controller.
  • A VM must be moved to a datastore formatted with a different block size.

Regardless of the reason, vMotion and Storage vMotion are extremely powerful tools and are largely responsible for the predominance of vSphere in the modern datacenter.

Examining vMotion Requirements

To use vMotion, the hosts and the VMs that are involved must meet the following requirements:

  • A Gigabit Ethernet network must be available between the hosts.
  • A VMkernel port must be defined and enabled for vMotion on each host.
  • Virtual switches on both hosts must be configured identically. (This requirement can be bypassed by using the advancements in vSphere 6.0.)
  • All port groups to which the VM is attached must exist on both hosts.
  • The processors in both hosts must be compatible.
  • The VM must not be connected to any device that is physically available to only one host. (This includes CD/DVD drives, floppy drives, serial or parallel ports, and disk storage. Common reasons for a failing vMotion are connected CD-ROMs or floppy drives.)
  • The VM must not be connected to an internal-only virtual switch.
  • The VM must not have CPU Affinity set.

Moving a Virtual Machine

Moving a virtual machine using vMotion can be accomplished by using the Move-VM cmdlet. This cmdlet is able to move a VM to another vSphere host using a standard vMotion or to move a VM’s files to another datastore using a Storage vMotion.

vMotion technology is also used by a vCenter Server feature called the Distributed Resource Scheduler (DRS), which evenly distributes workloads across the vSphere hosts in a cluster. If the automation level of DRS is set to Fully Automated, all VMs will be moved to a different host whenever the host is put in Maintenance mode. DRS is only available in the Enterprise and Enterprise Plus editions.

Get-VMHost –Name vSphere01 | Set-VMHost –State Maintenance

If DRS isn’t set to Fully Automated on the cluster, DRS won’t do anything when a host is put into Maintenance mode. If individual virtual machine automation levels are configured, DRS will not migrate VMs that aren’t set to Fully Automated. Where Enterprise or Enterprise Plus edition is not available, the script in Listing 9-3 can be used to employ vMotion to manually evacuate VMs from the vSphere host.

Listing 9-3: Evacuating VMs from the vSphere host

function Evacuate-VMHost {
  Param(
    [parameter(Mandatory = $true,
        ValueFromPipeline=$true,
        ValueFromPipelineByPropertyName=$true)])]
    [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]$VMHost
  ,
    [parameter(Mandatory = $false,
        ValueFromPipelineByPropertyName=$true)]
    [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]$TargetHost
  )
  if (-not $TargetHost) 
  {
    $cluster = Get-Cluster -VMHost $VMHost
    if (-not $cluster) 
    {
      throw "No cluster found"
    }
    $clusterHosts = $cluster | Get-VMHost | 
        Where-Object {$_.Name -ne $VMHost.Name -and `
          $_.ConnectionState -eq “Connected”}
    if (-not $clusterHosts) 
    {
      throw "No valid cluster members found"
    }
  }
  #Evacuate all VMs from host
  foreach ($vm in ($VMHost | Get-VM)) 
  {
    $splat = @{
        'VM'=$VM
        'RunAsync'=$true
        'Confirm'=$false
    }
    if ($TargetHost) 
    {
      $splat.Destination = $TargetHost
    }
    else 
    {
      $splat.Destination = $clusterHosts | Get-Random
    }
    Move-VM @splat | Out-Null
  }

  #Put host into maintenance mode
  $VMHost | Set-VMHost -State "Maintenance" -RunAsync:$true
}

The Move-VM cmdlet can also be used to perform a storage vMotion. To perform a storage vMotion using Move-VM, specify the target datastore using the –Datastore parameter:

Get-VM VM001 | Move-VM –Datastore Datastore02

Notice that using the Move-VM cmdlet moves the complete VM. If only a specific hard disk needs to be relocated, use the Move-HardDisk cmdlet. This is useful when there is a need to use separate datastores for the data and log disks on a SQL Server:

$hd = Get-VM VM001 | Get-HardDisk -Name "Hard disk 2"
$hd | Move-HardDisk –Datastore Datastore02

When using datastore clusters, it may also be necessary to apply affinity rules to the Storage vMotion. Affinity rules define which hard disks should not be collocated on a datastore. Listing 9-4 relocates a virtual machine with two hard disks with an anti-affinity rule.

Listing 9-4: Using Storage vMotion using datastore clusters and affinity rules

$VM = Get-VM -Name 'App01'
$vmdks = Get-Harddisk -VM $VM
$DatastoreCluster = Get-DatastoreCluster -Name 'DatastoreCluster'
$vmdkAntiAffinityRule = New-Object VMware.VimAutomation.ViCore.↵Types.V1.
DatastoreManagement.SdrsVMDiskAntiAffinityRule -ArgumentList $vmdks
Move-VM -VM $VM -Datastore $DatastoreCluster `
    -AdvancedOption $vmdkAntiAffinityRule

Not all moves can be done with PowerCLI cmdlets. For example, if only the VM’s configuration file needs to be relocated, the Move-VM cmdlet lacks the needed parameters. In such an instance, the SDK can be used. Listing 9-5 contains a function that leverages the SDK to move the registered content from one datastore to another. The function gets all VMs with a relation to the source datastore and migrates only the files that are actually stored on the source datastore to the destination datastore. This function is useful when you need to migrate datastores to another storage array or to datastores using a different block size.

Listing 9-5: Moving all VMs from one datastore to another

<#
.SYNOPSIS
  Moves all registered .vmx and .vmdk files to another datastore
.DESCRIPTION
  This function moves all registered vms from the source
  datastore to the target datastore
.PARAMETER Source
  The source datastore 
.PARAMETER Destination
  The target datastore 
.EXAMPLE
  Move-DatastoreContent -Source (Get-Datastore datastore1) `
    -Destination (Get-Datastore datastore2)
#>function Move-DatastoreContent {
  [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
  Param(
    [parameter(Mandatory = $true)]
    [VMware.VimAutomation.ViCore.Impl.V1.DatastoreManagement.↵
DatastoreImpl]$Source
  ,
    [parameter(Mandatory = $true)]
 [VMware.VimAutomation.ViCore.Impl.V1.DatastoreManagement.↵
DatastoreImpl]$Destination
  )
  Process
  {
    foreach ($vm in ($Source | Get-VM)) 
    {
      $configFile = $vm.ExtensionData.Config.Files.VmPathName -match `
          '[(?<ds>S+)]s(?<path>.+)' |
          ForEach-Object {
              New-Object PSObject -Property @{
                'DataStore' = $Matches.ds
                'Path' = $Matches.path
                'VMPathName' = $Matches.0
              }
          }
      if ($configFile.Datastore -eq $Source.Name) 
      {
          $configDatastoreName = $Destination.Name
      }
      else
      {
        $configDatastoreName = $configFile.DataStore
      }
      $spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
      $spec.Datastore = (Get-Datastore $configDatastoreName | 
         Get-View -Property Name).MoRef
      $spec.Disk = foreach ($disk in ($vm | Get-HardDisk))
      {
        $DiskFile = $disk.FileName -match '[(?<ds>S+)]↵s(?<path>.+)' |
          ForEach-Object {
            New-Object PSObject -Property @{
              'DataStore' = $Matches.ds
              'Path' = $Matches.path
              'VMPathName' = $Matches.0
            }
          }
        if ($DiskFile.DataStore -eq $Source.Name) 
        {
          $diskDatastoreName = $Destination.Name
        }
        else
        {
          $diskDatastoreName = $DiskFile.DataStore
        }
        $objDisk = New-Object VMware.Vim.↵VirtualMachineRelocateSpecDiskLocator
        $objDisk.DiskID = $disk.Id.Split('/')[1]
        $objDisk.DataStore = (Get-Datastore $diskDatastoreName | 
            Get-View -Property Name).MoRef
        $objDisk
      }
      if ($PsCmdlet.ShouldProcess($VM, "Move to ↵${diskDatastoreName}"))
      {
        $vm.ExtensionData.RelocateVM_Task($spec, "defaultPriority")
      }
    }
  }
}

Finally, with vSphere 6.0 came a much unexpected but highly useful new capability—the ability to vMotion a virtual machine across vCenter instances! This new capability is a little complex; Listing 9-6 contains a function that simplifies it.

Listing 9-6: Cross vCenter vMotion

function Move-VMCrossVC {
    [CmdletBinding(DefaultParameterSetName='easyNetwork', 
                  SupportsShouldProcess=$true, 
                  PositionalBinding=$false,
                  ConfirmImpact='Medium')]
    Param
    (
        # Name of the VM to be migrated
        [parameter(Mandatory=$true,ValueFromPipelineByProperty↵Name=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$true,ValueFromPipelineByProperty↵Name=$true,
            ParameterSetName='hardNetwork')]
        [Alias("Name")] 
        [string]
        $VMName
    ,
        # Destination resource pool or cluster to relocate vm to
        [parameter(Mandatory=$false,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$false,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [VMware.VimAutomation.ViCore.Types.V1.Inventory.↵VIContainer]
        $Cluster
    ,
        # Destination VMHost to relocate vm to
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]
        $VMHost    
    ,
        # Destination Datastore to relocate vm to
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [VMware.VimAutomation.ViCore.Impl.V1.DatastoreManagement.↵
            DatastoreImpl]
        $Datastore
    ,
        # Destination PortGroup to migrate VM Networking to
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [VMware.VimAutomation.ViCore.Types.V1.Host.Networking.↵
            VirtualPortGroupBase]
        $PortGroup
    ,
        # Hashtable containing key=value pairs of 
        # <VM MAC Address>=<VirtualPortGroupBase>
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [System.Collections.Hashtable]
        $AdvancedNetworkMap
    ,
        # Specifies the destination vCenter Server systems
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$true,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl]
        $DestinationVC
    ,
        # Destination vCenter Server Credentials.
        [parameter(Mandatory=$false,ValueFromPipeline=$true,
            ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$false,ValueFromPipeline=$true,
            ParameterSetName='hardNetwork')]
        [System.Management.Automation.PSCredential]
        $DestinationCredential
    ,
        # Specifies the Source vCenter Server systems default is 
        # to use the current context.
        [parameter(Mandatory=$false,ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$false,ParameterSetName='hardNetwork')]
        [VMware.VimAutomation.ViCore.Impl.V1.VIServerImpl]
        $SourceVC
    ,
        # Indicates that the command returns immediately without 
        # waiting for the task to complete.
        [parameter(Mandatory=$false,ParameterSetName='easyNetwork')]
        [parameter(Mandatory=$false,ParameterSetName='hardNetwork')]
        [switch]
        $RunAsync
    )
  Begin
  {
    function Get-SSLThumbprint {    
      [CmdletBinding()]  
      Param(  
          [parameter(Mandatory = $true, ValueFromPipeline = $true)]  
          [string]$URL,  
          [parameter(Position = 1)]  
          [ValidateRange(1,65535)]  
          [int]$Port = 443,  
          [parameter(Position = 2)]  
          [Net.WebProxy]$Proxy,  
          [parameter(Position = 3)]  
          [int]$Timeout = 15000,  
          [switch]$UseUserContext  
      )  
      $ConnectString = "https://$url`:$port"  
      $WebRequest = [Net.WebRequest]::Create($ConnectString)  
      $WebRequest.Proxy = $Proxy  
      $WebRequest.Credentials = $null  
      $WebRequest.Timeout = $Timeout  
      $WebRequest.AllowAutoRedirect = $true  
      [Net.ServicePointManager]::ServerCertificateValidationCallback = `
          {$true}  
      try {$Response = $WebRequest.GetResponse()}  
      catch {}
      finally { $Response.Close() }  
      if ($WebRequest.ServicePoint.Certificate -ne $null) {  
        $Cert = [Security.Cryptography.X509Certificates.X509Certificate2]↵
        $WebRequest.ServicePoint.Certificate.Handle  
        try {$SAN = ($Cert.Extensions | 
          Where-Object {$_.Oid.Value -eq "2.5.29.17"}).Format(0) -split ", "}  
        catch {$SAN = $null}  
        [System.Security.Cryptography.X509Certificates.X509Certificate2]↵
        $certificate = $WebRequest.ServicePoint.Certificate;  
      } else {  
        Write-Error $Error[0]  
      }
      $ssltumbraw = $certificate.Thumbprint
      $ssltumb = $(for ($i=0;(($i+2) -le $ssltumbraw.Length);$i = $i + 2){
                      $ssltumbraw.Substring($i,2)}) -join ':'  
      return $ssltumb
    }
    # connect to the destination VC
    # Set source VC context
    if ($SourceVC)
    {
        $splat = @{'Server'=$SourceVC}
    }
    else
    {
        $splat = @{'Server'=$global:DefaultVIServer}
    }
  }
  Process
  {
   # Source VM to migrate
   $vm  = Get-View (Get-VM -Name $VMName @splat) `
       -Property Config.Hardware.Device
   
   # determine the destination pool to migrate VM to
   if (-not $Cluster)
   {
       $DestinationID = (Get-ResourcePool -Server $DestinationVC `
           -Name Resources).ExtensionData.MoRef
   }
   else
   {
       switch ($Cluster.GetType().Name)
       {
           "ClusterImpl" {
               $DestinationID = $Cluster.ExtensionData.ResourcePool
           }
           "ResourcePoolImpl" {
               $DestinationID = $Cluster.ExtensionData.MoRef
           }
       }
   }
   if ($DestinationCredential)
   {
     $credential = New-Object VMware.Vim.ServiceLocatorNamePassword `
       -Property @{
         'username' = $DestinationCredential.UserName
         'password' = `
           $DestinationCredential.GetNetworkCredential().Password
       }
     $ServiceLocator = New-Object VMware.Vim.ServiceLocator `
       -Property @{
         'credential' = $credential
         'instanceUuid' = $DestinationVC.InstanceUuid
         'sslThumbprint' = Get-SSLThumbprint -URL $DestinationVC.Name
         'url' = "https://$($DestinationVC.Name)"
       }
   }
   # build the relocation spec
   $spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
   $spec.Datastore = $Datastore.Id
   $spec.Host = $VMHost.Id
   $spec.Pool = $DestinationID
   $spec.Service = $ServiceLocator
   
   # Find Ethernet Device on VM to change VM Networks
   foreach ($device in $vm.Config.Hardware.Device |
       Where-Object {$_ -is [VMware.Vim.VirtualEthernetCard]}) 
   {
     # if using easy then all adapters go to the same network
     # if using hard find the network that matches the MAC address
     if ($AdvancedNetworkMap)
     {
         try
         {
             [VMware.VimAutomation.ViCore.Types.V1.Host.Networking.↵
VirtualPortGroupBase]$destinationNetwork = $AdvancedNetworkMap↵
[$device.MacAddress]
         }
         catch
         {
           Write-Warning "Advanced Network Mapping error: ↵
           $($_.Exception.Message)"
           Write-Warning "Unable to continue with Cross VC vMotion"
           break;
         }
     }
     else
     {
         $destinationNetwork = $PortGroup
     }
   $dev = New-Object VMware.Vim.VirtualDeviceConfigSpec
   $dev.operation = "edit"
   $dev.Device = $device
   # Determine backing type
     if (($device.Backing.GetType().Name -eq `
        'VirtualEthernetCardNetworkBackingInfo' -and 
         $destinationNetwork.GetType().Name -eq `
         'VirtualPortGroupImpl') -or `
         ($device.Backing.GetType().Name -eq `
         'VirtualEthernetCardDistributedVirtualPortBackingInfo' -and 
         $destinationNetwork.GetType().Name -eq 'VmwareVDPortgroupImpl'))
     {
       switch($destinationNetwork.GetType().Name)
       {
             "VirtualPortGroupImpl" {
               $dev.Device.Backing = `
            New-Object VMware.Vim.VirtualEthernetCardNetworkBackingInfo
               $dev.Device.Backing.DeviceName = $destinationNetwork.Name
             }
             "VmwareVDPortgroupImpl" {
              $dvs = Get-View -ViewType VmwareDistributedVirtualSwitch `
                -Server $DestinationVC `
                -Property @("uuid","Summary.PortgroupName") |
                  Where-Object {$_.Summary.PortgroupName -contains `
                    $destinationNetwork.Name }
             $dev.Device.Backing = New-Object VMware.Vim.VirtualEthernetCard↵
DistributedVirtualPortBackingInfo
             $dev.Device.Backing.Port = New-Object VMware.Vim.DistributedVirtual↵
SwitchPortConnection
             $dev.Device.Backing.Port.SwitchUuid = $dvs.Uuid
             $dev.Device.Backing.Port.PortgroupKey = $destinationNetwork.Key
             }
       }
         $spec.DeviceChange += $dev
     }
     else
     {
         Write-Warning "Cross vCenter vMotion does not support ↵
         vMotion between Standard and Distributed Switches"
         break;
     }
   }
   if ($PsCmdlet.ShouldProcess("Cross VC vMotion", 
       "Migrating $VMName to $DestinationVC"))
   {
       # Issue Cross VC-vMotion 
       $task = $vm.RelocateVM_Task($spec,"defaultPriority") 
       $task1 = Get-Task -Id ("Task-$($task.value)") @splat
       if ($RunAsync)
       {
           Write-Output $task1
       }
       else
       {
           $task1 | Wait-Task 
           Get-VM -Name $VMName -Server $DestinationVC
       }
   }
  }
}

Using the function in Listing 9-6, it is possible to nondisruptively relocate a VM to a physically separate vCenter instance. There are a few gotchas. Although the port group can be remapped during the migration, the VM cannot be migrated from a standard switch to a distributed switch, or vice versa. If the source and destination vCenter instances share am SSO domain, the credentials are not needed for the destination vCenter. The following example migrates VM001 from VC1 to VC2:

# Connect to source VC
Connect-VIServer -Server VC1 -Credential $creds
# Connect to destination VC
$vc2 = Connect-VIServer -Server VC2 -Credential $creds -NotDefault
# Destination target cluster or resource pool
$cluster = Get-Cluster -Name Prod01 -Server $vc2
# Destination target VMHost
$VMHost = Get-VMHost -Server $vc2 | Get-Random
# Destination Datastore to copy VM into
$datastore = Get-Datastore -Name Datastore4 -Server $vc2
$PortGroup = Get-VirtualPortGroup -Standard -Server $vc2 -Name VLAN1107
Move-VMCrossVC -VMName VM001 `
-VMHost $VMHost `
-Datastore $datastore`
-PortGroup $PortGroup `
-DestinationVC $VC2 `
-DestinationCredential $creds `
-Cluster $cluster

To accommodate more complex virtual machine configurations, the function also takes a hash table containing a mapping of VM network adapter MAC address to port groups. This allows the migration of VMs that are using both standard and distributed switches. The following example migrates VM002 from VC2 to VC1:

# Connect to source VC
Connect-VIServer -Server VC2 -Credential $creds
# Connect to destination VC
$vc1 = Connect-VIServer -Server VC1 -Credential $creds -NotDefault
# Destination target cluster or resource pool
$cluster = Get-Cluster -Name Prod03 -Server $vc1
# Destination target VMHost
$VMHost = Get-VMHost -Server $vc1 | Get-Random
# Destination Datastore to copy VM into
$datastore = Get-Datastore -Name Datastore0 -Server $vc1
$AdvancedNetworkMap= @{
    '00:50:56:ba:54:2c'=(Get-VirtualPortGroup -Standard -Server $vc1 `
-Name VLAN1107 -VMHost $VMHost)
    '00:50:56:ba:24:9c'=(Get-VDPortgroup -Server $vc1 -Name VLAN2206)
    '00:50:56:ba:d2:26'=(Get-VDPortgroup -Server $vc1 -Name VLAN2206)
}
Move-VMCrossVC -VMName VM002 `
-VMHost $VMHost `
-Datastore $datastore`
-AdvancedNetworkMap $AdvancedNetworkMap `
-DestinationVC $vc1 `
-DestinationCredential $creds `
-Cluster $cluster

This new capability is a massive deal. Cross vCenter vMotion enables an all new level of VM mobility. By providing the operations team with the ability to nondisruptively migrate a VM in between vCenter instances, upgrades and migrations move from difficult to trivial. By combining this new technology with PowerCLI, those migrations can now be completed in a fully automated fashion.

Use and Manage Snapshots

vSphere allows you to create snapshots of virtual machines. This creates a point-in-time “backup” of the VM. Notice that backup is mentioned within quotation marks, because a snapshot is not equivalent to a regular VM backup. Learn more about VM backup solutions in Chapter 11, “Backing Up and Restoring Your Virtual Machines.” The snapshot saves a copy of the complete state of the virtual machine at the time it is taken (although saving the memory state is optional), allowing the VM to quickly revert back to that state when necessary, without the need for restoring backups. Many administrators think about redo files or delta files whenever a snapshot is mentioned. Those thoughts cause confusion when it’s time to permanently delete the snapshot in order to record the changes that were made since the snapshot was taken. A snapshot is much more than a delta VMDK file. So just forget about all the details that vSphere uses under the hood; it’s all about virtual machine states.

A snapshot is a safety net. We recommend that one be created whenever a major modification of any kind is made to a VM. For example, when modifying the virtual machine’s configuration—be it at the VM’s hardware level or a modification of the guest operating system—create a snapshot. Snapshots are often created before installing new software, patching the operating system, or making changes to the operating system’s configuration. If something goes wrong, the snapshot enables the VM to revert back to a known good state. When the change is successfully tested, you can delete the snapshot.

Creating and Removing Snapshots

Creating a snapshot is any easy task in the vSphere client—just a few clicks and the virtual machine has a known good state to revert back to if an issue occurs. But what about doing so on a schedule or creating snapshots for a number of virtual machines? Remember the golden rule of scripting: “If you do something three times or more, then script it!”

Snapshots are created using the New-Snapshot cmdlet. To preserve the VM’s memory state, specify the -Memory parameter, but keep in mind that doing so increases the size of the snapshot state file (VMSN) with the size of the VM’s configured memory for a running virtual machine.

Creating snapshots is about as simple as an action gets in vSphere, and the same is true when working from PowerCLI:

Get-VM VM001 | New-Snapshot -Name "Before Patch" `
    -Description "Installation of patches from 5th Feb." `
    -Memory

A single cmdlet can create a snapshot, but what about multiple VMs? In the vSphere Web Client, each VM would need to be touched individually to create the snapshot. This involves retyping the information for each one. Imagine how long this could take with hundreds of VMs! PowerCLI makes this type of bulk modification trivial:

Get-VM Win* | New-Snapshot -Name "Before Patch" `
    -Description "Installation of patches from 5th Feb." `
    -Memory

To make sure that a guest filesystem is in a consistent state before the snapshot is created, use the -Quiesce parameter. This is particularly useful when making a backup or clone of a running virtual machine; you want to make sure the clone starts without a corrupted filesystem. To use this option, VMware Tools need to be installed on the VM, as they are used to quiesce the guest’s filesystem.

Get-VM VM001 | New-Snapshot -Name "Daily Backup" `
    -Description "Daily snapshot for VM backup" -Quiesce

Once the change to the virtual machine is deemed successful, the snapshot can be removed using the Remove-Snapshot cmdlet:

Get-Snapshot -VM VM001 -Name "Before Patch" | Remove-Snapshot

Or for multiple VMs, just as before, simply modify the Get-VM cmdlet and let the pipeline do the rest:

Get-VM Win* | Get-Snapshot -Name "Before Patch" | `
 Remove-Snapshot

Whenever something goes wrong during a change to the VM, you can revert back to the snapshot. Reverting to a snapshot is done using the Set-VM cmdlet:

$snap = Get-Snapshot -VM VM001 -Name "Before Patch"
Set-VM -VM VM001 -Snapshot $snap -Confirm:$false

With the basics of snapshots established, it’s time to discuss how to maintain snapshots in a larger environment.

Maintaining Snapshots

The ability to create a snapshot is very useful, but it is critical to managing snapshots. Monitor them regularly to detect snapshots that are getting old and/or large. Fail to do so and datastores might run out of free space.

To keep track of snapshots from within the vSphere Web Client, you would need to check each VM to see how many snapshots have been created. This clearly doesn’t scale. Additionally, a simple check for how much space was being used on a datastore by the snapshots could take hours (or days). Other than going through each folder on each datastore, there is no easy way from within the vSphere Web Client. PowerCLI, however, makes this very easy. The following code lists all the snapshots belonging to a VM and includes information about the size of the snapshots and the date they were created:

Get-VM | Get-Snapshot | Select VM, Name, SizeGB, Created

With some extra work, it is possible to narrow the search. Listing 9-7 contains a function that cross-references the task database and identifies the individual who created the snapshot.

Listing 9-7: Find snapshot creator

function Get-SnapshotCreator {
    Param(
        [parameter(Mandatory=$true,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]
        [VMware.VimAutomation.ViCore.Impl.V1.VM.SnapshotImpl]$Snapshot
    )
    Begin
    {
        function Get-SnapshotTree {
          Param($tree, $target)
          $found = $null
          foreach($elem in $tree){
            if ($elem.Snapshot.Value -eq $target.Value)
            {
              $found = $elem
              continue
            }
          }
          if ($found -eq $null -and $elem.ChildSnapshotList -ne $null)
          {
            $found = Get-SnapshotTree $elem.ChildSnapshotList $target
          }
          return $found
        }
    }
    Process
    {
      $guestName = $Snapshot.VM.Name
      $tasknumber = 999 
      $tMgr = Get-View TaskManager
      #Create hash table. Each entry is a create snapshot task
      $report = @{}
      
      $filter = New-Object VMware.Vim.TaskFilterSpec
      $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
      $filter.Time.BeginTime = $Snapshot.Created.AddDays(-5)
      $filter.Time.TimeType = “startedTime”
      
      $collectionImpl = Get-View ($tMgr.CreateCollectorForTasks($filter))
      $collectionImpl.RewindCollector | Out-Null
      $collection = $collectionImpl.ReadNextTasks($tasknumber)
      while($collection -ne $null)
      {
       $collection | 
        ? {$_.DescriptionId -eq "VirtualMachine.createSnapshot"} |
        ? {$_.State -eq "success"}|
        ? {$_.EntityName -eq $guestName} | 
        ForEach-Object {
          $row = New-Object PsObject -Property @{
            'User' = $_.Reason.UserName
          }
        
          $vm = Get-View $_.Entity
          if ($vm -ne $null)
          {
            $snapshottree = Get-SnapshotTree -target $_.Result `
                -tree $vm.Snapshot.RootSnapshotList 
            if ($snapshottree -ne $null)
            {
                $key = "{0}&{1}" -f $_.EntityName, 
                    $snapshottree.CreateTime.ToFileTimeUtc()
                $report[$key] = $row
            }
          }
       }
       $collection = $collectionImpl.ReadNextTasks($tasknumber)
    }
    $collectionImpl.DestroyCollector()
    # Get the guest’s snapshots and add the user
    foreach ($snap in $snapshot) 
    {
      $key = "{0}&{1}" -f $snap.vm.Name, $snap.Created.ToFileTimeUtc()
      if ($report.ContainsKey($key))
      {
          $snap | Add-Member -MemberType NoteProperty -Name Creator `
              -Value $report[$key].User -PassThru
      }
    }
  }
}

Using a combination of the techniques that have been covered so far, it is fairly simple to quickly find any VM with a snapshot older than two weeks and identify the creator for follow-up:

Get-VM |
    Get-Snapshot |
    Where {$_.Created -lt [datetime]::Now.AddDays(-14)}|
    Get-SnapshotCreator |
    Format-Table VM, Name, Created, Creator -Auto


VM   Name         Created              Creator
--   ----         -------              -------
AD01 Before Patch 2/23/2015 2:03:19 AM VSPHERE.LOCALAdministrator

Restricting the Creation of Snapshots

If snapshot management is proving too difficult, another option is to restrict the creation of snapshots in the environment. As discussed earlier, if multiple snapshots are taken or if they are left on the virtual machines for a long time, they can cause performance or disk space issues.

One of the lesser known configuration capabilities is the ability to configure a static maximum number of snapshots on a per-VM basis. This is possible thanks to an undocumented configuration setting that you can make to the VM advanced configuration or VMX file, as shown in Figure 9-1.

c09f001.tif

Figure 9-1: Example VM advanced configuration

This setting was originally exposed by William Lam of virtuallyGhetto (www.virtuallyghetto.com) and can be used not only to restrict the use of snapshots, but also to set a numeric value on the number of snapshots that can be created per virtual machine.

To do this on multiple virtual machines, the obvious answer is to script it. Fortunately, PowerCLI provides a simple method for doing so:

Get-VM VM001 | New-AdvancedSetting -Name "snapshot.maxSnapshots" -Value 1
..................Content has been hidden....................

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