In this chapter, you will learn to:
Once the virtualization environment is up and running, there comes a time when a virtual machine needs to be reconfigured. Perhaps performance is lacking and an additional vCPU or more memory is needed. Or a disk might be running into its capacity limit and needs to be extended. All of these tasks and some other reconfiguration tasks are covered in this chapter.
This section will focus on changing virtual machine (VM) hardware configurations like memory, vCPU, network adapters, and virtual disks.
It’s a story as old as virtualization itself. A VM is pushed into production with 1,024 MB of RAM, and you quickly notice that performance is lacking due to memory resource exhaustion. The fix is simple: increase the amount of memory on the VM. To do so, use the Set-VM
cmdlet. This cmdlet accepts a -MemoryMB
parameter, which allows modification of the VM’s memory size:
Set-VM VM001 -MemoryMB 2048 -Confirm:$false
Like everything else in vSphere, the ability to modify a VM is based on the hardware version rules and the VM configuration. The VM in question must either be powered off or support memory hot plug. If the VM doesn’t support memory hot plug and the VM can only be shut down during maintenance hours, it might be advantageous to schedule the upgrade process.
Listing 8-1 automates the complete process. First, the VM is gracefully shut down, and after the memory is upgraded, the VM is powered on again. Using this simple script, it is possible to schedule the upgrade during maintenance hours when the impact on end users is minimal.
Listing 8-1: Changing virtual machine memory offline
Get-VM VM001 | Stop-VMGuest -Confirm:$false
While ((Get-VM VM001).PowerState -ne "PoweredOff"){
Sleep -Seconds 2
}
Get-VM VM001 | Set-VM -MemoryMB 2048 -Confirm:$false
Get-VM VM001 | Start-VM -Confirm:$false
To guarantee memory entitlement, a memory reservation must be configured for the VM. Memory reservations guarantee that the reserved memory will always be backed by physical machine memory, even during times of memory contention. This is often configured to ensure some level of performance for a VM. Memory reservations also determine the size of the VM’s swap file on the vSphere host, as the swap file’s size is equal to the VM’s configured memory minus its reservation.
There are also times when it is necessary to artificially cap a VM to prevent it from using too much machine memory. This is accomplished by configuring a memory limit. Memory limits force any memory requirements above the limit to be provisioned from the VM’s swap file on the host. Memory provisioned from the swap file doesn’t perform very well and should only be used in situations where extreme memory pressure is degrading the performance of all VMs.
All elements of VM resource configuration are configurable with the Get-VMResourceConfiguration
and Set-VMResourceConfiguration
cmdlets.
To set a memory reservation, use the -MemReservationMB
parameter. The value of this parameter ranges from 0 to the value of the memory limit or, if no limit is set, to the amount of configured memory. To set a memory limit, use the -MemLimitMB
parameter. The value for this parameter ranges from the value of the memory reservation to the maximum amount of configured memory. Figure 8-1 is a graphical representation of these parameter ranges.
To set the memory reservation to 1,024 MB and the memory limit to 1,536 MB on your virtual machine, use the following:
Get-VM VM001 | Get-VMResourceConfiguration | '
Set-VMResourceConfiguration -MemReservationMB 1024 '
–MemLimitMB 1536
To reset the memory limit on a VM (set it to Unlimited), use the following:
Get-VM VM001 | Get-VMResourceConfiguration | '
Set-VMResourceConfiguration –MemLimitMB $null
The third mechanism to configure memory resources is shares. Shares come into play when there’s memory contention on a host. When memory contention is encountered, shares determine the VM’s entitlement to machine memory for the amount of memory between the reservation and the limit (see Figure 8-1). To change the number of memory shares, use the -MemSharesLevel
parameter. Possible enumeration values are Custom, High, Low, and Normal. Because this parameter accepts an enumerator value, it is also possible to specify an index value from 0 to 3, where an index value of 0 means Custom, 1 means High, 2 means Low, and 3 means Normal. Table 8-1 contains an overview of the available memory shares levels. It is rare to need to fine-tune the memory shares beyond the factory presets (High, Low, and Normal), but if you have to, set the share level to Custom and specify the number of shares with the -NumMemShares
parameter.
Table 8-1: Memory shares
Shares level | Shares/MB | Index |
Custom | 1–1,000,000 | 0 |
High | 20 | 1 |
Low | 5 | 2 |
Normal | 10 | 3 |
To set the memory share level to a custom value of 1,500, use the following:
Get-VM VM001 | Get-VMResourceConfiguration | '
Set-VMResourceConfiguration -MemSharesLevel "Custom" '
-NumMemShares 1500
After addressing the performance issues by adding additional memory, it wouldn’t be surprising to find that the VM now has increased CPU utilization. To ensure that performance stays at an acceptable level, you can allocate additional CPU to the VM. (Be sure to confirm with the application vendor that the application is multithreaded and can benefit from an extra CPU.) To change the number of vCPUs assigned to a VM, use the Set-VM
cmdlet again as with changing the amount of memory. This time, however, use the -NumCpu
parameter to specify the new number of vCPUs. To add a vCPU to a VM named VM001
, use the following:
Set-VM VM001 –NumCpu 2 –Confirm:$false
Again, remember that the VM needs to support CPU hot plug or be powered off. Listing 8-2 contains a function based on the idea of the code in Listing 8-1 that automates the process of changing the number of vCPUs or amount of memory when a VM doesn’t support hot plug. This kind of meta-scripting is common in PowerShell, and it’s one of the reasons the language is so popular with virtualization administrators.
Listing 8-2: Changing VM memory and vCPU offline
function Set-VMOffline
{
<#
.SYNOPSIS
Changes the vCPU and memory configuration of the
virtual machine Offline
.DESCRIPTION
This function changes the vCPU and memory configuration of
the virtual machine Offline
.NOTES
Source: Automating vSphere Administration
Authors: Luc Dekens, Arnim van Lieshout, Jonathan Medd,
Alan Renouf, Glenn Sizemore, Brian Graf,
Andrew Sullivan.PARAMETER VM
Specify the virtual machine
.PARAMETER MemoryMB
Specify the memory size in MB
.PARAMETER NumCpu
Specify the number of virtual CPUs
.PARAMETER TimeOut
Specify the number of seconds to wait for the vm to shut down
gracefully. Default timeout is 300 seconds
.PARAMETER Force
Switch parameter to forcibly shutdown the virtual machine
after timeout
.EXAMPLE
PS> Get-VM VM001 | Set-VMOffline -memoryMB 4096 -numCpu 2 '
-timeOut 60
#>
Param (
[parameter(ValueFromPipeline = $true, Mandatory = $true,
HelpMessage = "Enter a vm entity")]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]$VM,
[int64]$memoryMB,
[int32]$numCpu,
[Int32]$timeOut = 300,
[switch]$force)
Process {
if ($memoryMB -or $numCpu)
{
if ((Get-VM $VM).PowerState -eq "PoweredOn")
{
$powerState = "On"
Stop-VMGuest $VM -Confirm:$false | Out-Null
}
$startTime = Get-Date
while (((Get-VM $VM).PowerState -eq "PoweredOn") -and
(((Get-Date) - $startTime).totalseconds -lt $timeOut))
{
Start-Sleep -Seconds 2
}
if ((Get-VM $VM).PowerState -eq "PoweredOff" -or $force)
{
if ((Get-VM $VM).PowerState -eq "PoweredOn")
{
Write-Warning "The shutdown guest operation timed out"
Write-Warning "Forcing shutdown"
Stop-VM $VM -Confirm:$false | Out-Null
}
$Splat = @{'VM'=$VM;'Confirm'=$False}
if ($memoryMB)
{
$Splat['MemoryMB'] = $memoryMB
}
if ($numCpu)
{
$Splat['NumCpu'] = $numCpu
}
Set-VM @Splat | Out-Null
if ($powerState -eq "On")
{
Start-VM $VM
}
}
else
{
Write-Error "The shutdown guest operation timed out"
}
}
else
{
Write-Error "No value for -memoryMB or -numCpu supplied"
}
}
}
As with memory resources, CPU resources can be influenced by tuning the CPU resource entitlement of a VM with reservations, limits, and shares. Reservations and limits are specified in megahertz (MHz). Reserves are used to guarantee a minimal level of performance for an important production application and to reserve a certain number of MHz for a particular VM. The reserved MHz will always be available to that application. On the other hand, it may be necessary to prevent that badly behaving legacy application from hogging valuable CPU resources by configuring a CPU limit. To change vCPU reservations, limits, and shares, use the Get-VMResourceConfiguration
and Set-VMResourceConfiguration
cmdlets as described in the section “Changing Memory Resources.” To set a vCPU reservation, use the -CpuReservationMhz
parameter. To set a vCPU limit, use the -CpuLimitMhz
parameter.
Use the following to set a vCPU reservation of 4,000 MHz on a VM named VM001
:
Get-VM VM001 | Get-VMResourceConfiguration | '
Set-VMResourceConfiguration -CpuReservationMhz 4000
To change the number of vCPU shares, use the -CpuSharesLevel
parameter. Possible enumeration values are Custom, High, Low, and Normal. Because this parameter accepts an enumerator value, you can also specify an index value from 0 to 3, where index value 0 means Custom, 1 means High, 2 means Low, and 3 means Normal. Table 8-2 contains an overview of the available CPU shares levels. To fine-tune the shares level beyond the factory presets (High, Low, and Normal), set the share level to Custom and specify the number of shares with the -NumCpuShares
parameter.
Table 8-2: CPU shares
Shares level | Shares/vCPU | Index |
Custom | 1–1,000,000 | 0 |
High | 2,000 | 1 |
Low | 500 | 2 |
Normal | 1,000 | 3 |
To set a vCPU share level to High, use the following:
Get-VM VM001 | Get-VMResourceConfiguration | '
Set-VMResourceConfiguration -CpuSharesLevel "High"
Resource limits, both CPU and memory, are real performance killers. Memory limits cause the affected VM to start ballooning and swapping in almost all cases. How to generate a report containing all VMs that have CPU and memory limits configured is covered in Chapter 15, “Reporting and Auditing.”
With the primary resources of CPU and memory entitlement configured, it’s time to move on to the VM’s networking hardware.
Networking design requirements are one of the most fluid elements of a virtual infrastructure. Often network adapters must be added and reassigned almost on demand. Additional network adapters can be added to an existing VM with the New-NetworkAdapter
cmdlet. This cmdlet has undergone several iterations as the ability to manage distributed virtual switches was added to PowerCLI. To maintain backward compatibility, the old parameters have been maintained, but when writing code today the ConnectToPortgroup
parameter set should be used because it’s fully compatible with every network technology in vSphere. When using this parameter set, keep in mind the two mandatory parameters:
-VM
parameter specifies the virtual machine(s) you want to modify. -Portgroup
parameter specifies the port group that the network adapter will be connected to.Port group names are case sensitive.
By default a newly created network adapter isn’t connected to the network at startup unless the -StartConnected
switch is applied. The adapter type can be specified using the -Type
parameter. As of this writing, valid types included 1000, c10e000e, Flexible, Vmxnet, and EnhancedVmxnet. If this parameter isn’t specified, the type will be set as recommended by VMware for the guest OS you set.
To create a second network adapter for VM001
and connect that network adapter to a port group named VLAN100
, use the following:
New-NetworkAdapter -VM VM001 '
-PortGroup (Get-VDPortgroup -Name VLAN100) -StartConnected
Name Type NetworkName MacAddress WakeOnLan
Enabled
---- ---- ----------- ---------- ---------
Network adapter 2 c10e000e VLAN100 00:50:56:be:0f:7d False
Though no longer common due to the rise of virtualization, it is still possible for an application license to be based on the Media Access Control (MAC) address of the server’s network card. In such a case, it is possible to assign a static MAC address to the network adapter. Using a static MAC address prevents the MAC address from being changed during the application’s life cycle. Whatever the reason for adding a static MAC address to a VM, setting a static MAC address is accomplished with the -MacAddress
parameter. Remember that the specified MAC address must be in the valid VMware range for static MAC addresses of 00:50:56:00:00:00–00:50:56:3F:FF:FF. Use the following to create a new network adapter with a static MAC address:
New-NetworkAdapter -VM VM001 '
-PortGroup (Get-VirtualPortGroup -Standard -Name Public -VMHost ESX01) '
-MacAddress 00:50:56:03:15:10
Once the new network adapter has been added, it is possible to change its settings using the Set-NetworkAdapter
cmdlet. Perhaps there is a need to change the MAC address to the static MAC address used to create the production license for your application. Use the code that follows; just substitute your static address for the one used in the -MacAddress
parameter:
Get-VM VM001 | Get-NetworkAdapter -Name "Network adapter 2" |
Set-NetworkAdapter -MacAddress 00:50:56:03:15:10 -Confirm:$false
As a security best practice, it’s recommended that all unused hardware be removed from your systems. (You can learn more about hardening your VM in Chapter 13, “Hardening the vSphere Environment.”) Suppose your backup application has been upgraded and now uses storage array–based snapshots to create full-image backups. The extra network adapters connected to the now redundant backup network will no longer be used and need to be removed. Removing an adapter is done using the Remove-NetworkAdapter
cmdlet. This cmdlet accepts only one parameter: -NetworkAdapter
. The parameter specifies the NetworkAdapter
object to be removed. You can retrieve NetworkAdapter
objects using the Get-NetworkAdapter
cmdlet.
Remember, removing a network adapter from a powered-on virtual machine is supported only on virtual hardware version 7 or later—and then only if the guest OS supports hot removal. Be sure to power off the VM before running this code if you are working with an earlier virtual hardware version:
Get-VM VM001 | Get-NetworkAdapter | ?{$_.NetworkName -eq '
"BACKUPVLAN10"} | Remove-NetworkAdapter -Confirm:$false
Once a network adapter is created, there’s not much that can be changed. But there may come a time when you need to move your VM to another network. You can accomplish this with the Set-NetworkAdapter
cmdlet by using the -PortGroup
parameter. For example, to move VM001
from VM_VLAN101
to VM_VLAN202
use the following:
Get-VM VM001 |
Get-NetworkAdapter |
Where-Object NetworkName -eq VLAN100 |
Set-NetworkAdapter -confirm:$false '
-Portgroup (Get-VirtualPortGroup -Name Public -Standard -VMHost ESX01)
That’s really simple, isn’t it? Well, switching networks probably also means the VM will need to be assigned a different IP address, subnet mask, and/or gateway. None of that was handled in the code that moved the VM from VLAN100
port group to the Public port group, so the virtual machine probably won’t have network connectivity anymore. To change the virtual machine’s IP address, you can use the Invoke-VMScript
cmdlet. Listing 8-3 uses the new PowerShell cmdlets in Windows Server 2012 to change the IP address inside the guest operating system for VM001
.
Listing 8-3: Changing guest IP address with Invoke-VMScript
$HostCredential = Get-Credential
$GuestCredential = Get-Credential
$ScriptText = 'Get-NetIPAddress |
Where-Object IPAddress -eq 192.168.2.37 |
Remove-NetIPAddress -Confirm:$false -PassThru |
New-NetIPAddress -IPAddress 10.10.2.5 '
-PrefixLength 22 '
-DefaultGateway 10.10.2.1'
Invoke-VMScript -HostCredential $hostCreds '
-GuestCredential $GuestCredential '
-ScriptText $ScriptText '
-VM VM001
If there is one universal truth, it is that there is no such thing as enough storage. No matter how much disk space is assigned to a VM, it will eventually need more. As a best practice, always try to right-size VM disks from the start and determine a reasonable amount of free space. There is no need to wastefully overallocate—more can be provisioned online. However, reducing allocated storage is not a trivial task.
To add a new hard disk to a VM, use the New-HardDisk
cmdlet. The -CapacityGB
parameter allows you to specify the size of the new virtual disk.
Specify the virtual machine to which the new virtual disk will be added by using the -VM
parameter, or pass the virtual machine object (retrieved using the Get-VM
cmdlet) on the pipeline, as demonstrated in the next example. To create a new virtual disk with a size of 10 GB and assign it to VM001, use the following:
Get-VM VM001 | New-HardDisk -CapacityGB 10
CapacityGB Persistence Filename
---------- ----------- --------
10.000 Persistent [Datastore1] VM001/VM001_1.vmdk
In some cases, it will be necessary to store the new hard disk on a different datastore. To specify a datastore that is different from the default (the datastore where the virtual machine’s configuration file [VMX] is stored), use the -Datastore
parameter:
New-HardDisk -VM VM001 -CapacityGB 10 -Datastore Datastore2
CapacityGB Persistence Filename
---------- ----------- --------
10.000 Persistent [Datastore2] VM001/VM001.vmdk
When configuring a new hard disk, it is also possible to specify how changes are written to the disk. Table 8-3 lists the options available.
Table 8-3: Virtual disk modes
Mode | Description |
Persistent | The default mode and the only one that allows snapshots. Changes are immediately and permanently written to the disk. |
IndependentPersistent | The disk is not affected by snapshots. Changes are immediately and permanently written to the disk. |
IndependentNonPersistent | Changes to this disk are discarded when you power off the VM or revert to a snapshot. |
Use the -Persistence
parameter to make the disk Persistent
, IndependentPersistent
, or IndependentNonPersistent
. In addition to write behavior, the allocation format can be specified. For example, use the following to specify that the new disk be deployed using thin provisioning with the -ThinProvisioned
switch:
Get-VM VM001 | New-HardDisk -CapacityGB 10 '
-Persistence IndependentPersistent -StorageFormat Thin
Besides creating new virtual disks, the New-HardDisk
cmdlet can be used to attach an existing virtual disk. The path to this disk must be specified using the -DiskPath
parameter:
New-HardDisk -VM VM001 -DiskPath "[Datastore2] OtherVM/OtherVM.vmdk"
What about adding a raw device mapping (RDM)? No problem. Simply specify the disk type using the -DiskType
parameter and pass the disk’s location (or console device name) to the -DeviceName
parameter. Valid disk types for RDM disks are rawVirtual
or rawPhysical
. The disk’s location can be retrieved using the Get-ScsiLun
cmdlet. Here’s the code and a typical return:
Get-VMHost ESX01 | Get-ScsiLun -LunType disk | Select ConsoleDeviceName
ConsoleDeviceName
-----------------
/vmfs/devices/disks/naa.6006048c8d27a37983d99994cfddcea5
/vmfs/devices/disks/naa.6006048c877401f6aaa26f591bf602d6
/vmfs/devices/disks/mpx.vmhba1:C0:T0:L0
Assuming that the disk with identifier naa.6006048c8d27a37983d99994cfddcea5
is the RDM disk that needs to be assigned to the VM, the following code would attach that base logical unit number (LUN) as an RDM:
New-HardDisk -VM VM001 -DiskType rawVirtual -DeviceName '
/vmfs/devices/disks/naa.6006048c8d27a37983d99994cfddcea5
CapacityGB Persistence Filename
---------- ----------- --------
50.000 Persistent [Datastore1] VM001/VM001_1.vmdk
Sometimes it may be necessary to remove a disk from a VM. Removal is accomplished with the Remove-HardDisk
cmdlet. This cmdlet requires only one parameter: -HardDisk
. The -HardDisk
parameter specifies the hard disk(s) to be removed.
To remove all hard disks from a VM named VM001, use the following:
Get-VM VM001 | Get-HardDisk | Remove-HardDisk -Confirm:$false
This may not be very useful. Chances are a specific disk needs to be removed. For example, to remove all RDM disks from a virtual machine, use the following:
Get-HardDisk -VM VM001 -DiskType rawVirtual,rawPhysical | '
Remove-HardDisk -Confirm:$false
To remove just one specific disk, specify the details to filter out that disk using a unique property like Name
, Id
, or Filename
. Assuming a VM hard disk named Hard Disk 2
needed to be removed from virtual machine VM001
:
Get-HardDisk -VM VM001 -Name "Hard disk 2" | Remove-HardDisk -Confirm:$false
Notice that the Remove-HardDisk
cmdlet only removes the specified hard disk from the virtual machine. It doesn’t delete the actual virtual disk file from the datastore. To permanently delete the files from the datastore itself and not simply remove the hard disk from the virtual machine, add the -DeletePermanently
switch:
Get-HardDisk -VM VM001 -Name "Hard disk 19" | '
Remove-HardDisk -Confirm:$false -DeletePermanently
The hardest part of removing a virtual hard disk can be finding the right virtual hard disk. It starts simply enough. For a virtual machine that uses only two virtual disks, chances are it’s straightforward. But what about removing a disk from a virtual machine running multiple Microsoft SQL Server instances that happens to have 26 disks?
To make matters worse, the SQL administrator probably said, “Remove the K: drive” or “Remove Windows disk 21.”
What then? How does one determine which virtual disk to remove? The same problem arises when extending disks, but first things first.
Listing 8-4 contains a function to assist in identifying the right virtual disk. The Get-VMDiskMapping
function can be used on hardware level 4 and up. Using mixed SCSI adapter types is not supported on hardware level 4, however.
Listing 8-4: Determining which virtual disk corresponds to which Windows disk
function Get-VMDiskMapping {
<#
.SYNOPSIS
Creates a report to match Windows disk numbers and their
virtual disk counterparts.
.DESCRIPTION
This function creates an overview of the virtual machine's
virtual disks and their Windows counterparts.
.NOTES
Source: Automating vSphere Administration
Authors: Luc Dekens, Arnim van Lieshout, Jonathan Medd,
Alan Renouf, Glenn Sizemore, Brian Graf,
Andrew Sullivan.PARAMETER VM
Specify the virtual machine to report on.
.PARAMETER HostCredential
Specify a PSCredential object containing the credentials you
want to use for authenticating with the host.
.PARAMETER GuestCredential
Specify a PSCredential object containing the credentials you
want to use for authenticating with the VM guest OS.
.EXAMPLE
PS> Get-VM VM001 | Get-VMDiskMapping
.EXAMPLE
PS> Get-VM VM001 | Get-VMDiskMapping -hostCredential $hostCred
-guestCredential $guestCred | Out-GridView
#>
Param (
[parameter(ValueFromPipeline = $true, Mandatory = $true,
HelpMessage = "Enter a vm entity")]
[VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]$VM,
[parameter(Mandatory = $false,
HelpMessage = "Enter a PSCredential object for the host")]
[System.Management.Automation.PSCredential]$hostCredential,
[parameter(Mandatory = $true,
HelpMessage = "Enter a PSCredential object for the guest")]
[System.Management.Automation.PSCredential]$guestCredential)
#Create vbs scriptfile
$FileName = [System.IO.Path]::GetTempFileName()
'Set objReg = GetObject("winmgmts:{impersonationLevel=
impersonate}!\.
ootdefault:StdRegProv")' > $filename
'Set objWMI = GetObject("winmgmts:{impersonationLevel=
impersonate}!\.
ootcimv2")' >> $filename
'Set colPCISlotNumber = CreateObject("Scripting.Dictionary")'
>> $filename
'objReg.EnumKey &H80000002,"SYSTEMCurrentControlSetEnum
PCI", colHardwareId' >> $filename
'For Each HardwareId In colHardwareId' >> $filename
' objReg.EnumKey &H80000002,"SYSTEMCurrentControlSetEnum
PCI" & HardwareId, colControllerId' >> $filename
' For Each ControllerId In colControllerId' >> $filename
' objReg.GetDWORDValue &H80000002,"SYSTEM
CurrentControlSetEnumPCI" & HardwareId & "" &
ControllerId, "UINumber", dwUINumber' >> $filename
' colPCISlotNumber.Add "PCI" & UCase(HardwareId) & "" &
UCase(ControllerId), dwUINumber' >> $filename
' Next' >> $filename
'Next' >> $filename
'Set colDiskDrive = objWMI.ExecQuery("Select * from Win32_
DiskDrive")' >> $filename
'Set colSCSIControllerDevice = objWMI.ExecQuery("Select *
from Win32_SCSIControllerDevice")' >> $filename
'WScript.Echo "DiskPNPDeviceId,Index,SCSIPort,SCSITargetId,
Size,CtrlPNPDeviceId,CtrlPCISlotNumber"' >> $filename
'For Each Disk in colDiskDrive' >> $filename
' For Each item in colSCSIControllerDevice' >> $filename
' If Replace(Split(item.Dependent,chr(34))(1),"\","") =
Disk.PNPDeviceId Then' >> $filename
' CtrlPNPDeviceId = UCase(Replace(Split(item.Antecedent,
chr(34))(1),"\",""))' >> $filename
' Exit For' >> $filename
' End If' >> $filename
' Next' >> $filename
' WScript.Echo Disk.PNPDeviceId & "," & Disk.Index & "," &
Disk.SCSIPort & "," & Disk.SCSITargetId & "," & Disk.Size &
"," & CtrlPNPDeviceId & "," & colPCISlotNumber.Item(
CtrlPNPDeviceId)' >> $filename
'Next' >> $filename
#Determine location to copy script to
$temp = Invoke-VMScript "echo %temp%" -vm $VM '
-HostCredential $hostCredential -GuestCredential '
$guestCredential -ScriptType "bat"
$destFileName = $temp.Trim("'r'n") + "guestScsiInfo.vbs"
Copy-VMGuestFile -Source $FileName -Destination '
$destFileName -VM $VM -LocalToGuest -HostCredential '
$hostCredential -GuestCredential $guestCredential
Remove-Item $FileName
#Get Windows disk info
$error.Clear()
$Out = (Invoke-VMScript '
"cscript /nologo $destFileName && del $destFileName" '
-vm $VM -HostCredential $hostCredential '
-GuestCredential $guestCredential '
-ScriptType "bat").ScriptOutput
if (!$error -and $Out)
{
$WinDisks = $Out | ConvertFrom-Csv
#Determine SCSIPort offset
$portOffset = ($WinDisks | Where-Object {$_.SCSIPort} | '
Measure-Object -Property SCSIPort -Minimum).Minimum
#All entries that don't match any known pciSlotNumber are
#attached to scsi0. Change these entries to the pciSlotnumber
#of scsi0
$scsi0pciSlotNumber = ($VM.Extensiondata.Config.ExtraConfig |
Where-Object{$_.key -like "scsi0.pciSlotNumber"}).value
$scsiPciSlotNumbers= @()
$VM.Extensiondata.Config.ExtraConfig |
Where-Object {$_.key -like "scsi?.pciSlotNumber"} |
ForEach-Object{
$scsiPciSlotNumbers += $_.value
}
$WinDisks | Foreach-Object {
if ($scsiPciSlotNumbers -notcontains $_.CtrlPCISlotNumber)
{
$_.CtrlPCISlotNumber = ($VM.ExtensionData.Config.Extraconfig |
Where-Object{$_.key -like "scsi0.pciSlotNumber"}).value
}
}
#Create DiskMapping table
foreach ($VirtualSCSIController in ($VM.Extensiondata.Config.Hardware.Device
| Where-Object {$_.DeviceInfo.Label -match "SCSI Controller"}))
{
foreach ($VirtualDiskDevice in ($VM.Extensiondata.Config.Hardware.Device |
Where-Object {$_.ControllerKey -eq $VirtualSCSIController.Key}))
{
$VirtualDisk = New-Object PSObject -Property @{
VMSCSIController = $VirtualSCSIController.DeviceInfo.Label
VMDiskName = $VirtualDiskDevice.DeviceInfo.Label
SCSI_Id = "{0} : {1}" -f $VirtualSCSIController.BusNumber,
$VirtualDiskDevice.UnitNumber
VMDiskFile = $VirtualDiskDevice.Backing.FileName
VMDiskSizeGB = $VirtualDiskDevice.CapacityInKB * 1KB / 1GB
RawDeviceName = $VirtualDiskDevice.Backing.DeviceName
LunUuid = $VirtualDiskDevice.Backing.LunUuid
WindowsDisk = ""
WindowsDiskSizeGB = 0
}
#Match disks
if ([int]$vm.version.ToString().Replace("v","") -lt 7)
{
# For hardware v4 match disks based on controller's SCSIPort an
# disk's SCSITargetId.
# Not supported with mixed scsi adapter types.
$DiskMatch = $WinDisks | Where-Object {($_.SCSIPort – $portOffset) '
-eq $VirtualSCSIController.BusNumber -and '
$_.SCSITargetID -eq $VirtualDiskDevice.UnitNumber}
}
else
{
# For hardware v7+ match disks based on controller's pciSlotNumber
# and disk's SCSITargetId
$DiskMatch = $WinDisks | Where-Object {$_.CtrlPCISlotNumber -eq '
($VM.Extensiondata.Config.Extraconfig | Where-Object {$_.key -match '
"scsi$($VirtualSCSIController.BusNumber).pcislotnumber"}).value '
-and $_.SCSITargetID -eq $VirtualDiskDevice.UnitNumber} }
if ($DiskMatch)
{
$VirtualDisk.WindowsDisk = "Disk $($DiskMatch.Index)"
$VirtualDisk.WindowsDiskSizeGB = $DiskMatch.Size / 1GB
}
else
{
Write-Warning "No matching Windows disk found for SCSI id ↵
$($virtualDisk.SCSI_Id)"
}
$VirtualDisk
}
}
}
else
{
Write-Error "Error Retrieving Windows disk info from guest"
}
}
The output of the Get-VMDiskMapping
function from Listing 8-4 is best viewed in table format using the Format-Table
cmdlet, or even better, using the Out-GridView
cmdlet. Figure 8-2 shows a sample output.
$map = Get-VM App04 | Get-VMDiskMapping -guestCredential $guestCred
$map | Out-GridView
Given enough time, almost any VM will eventually run out of disk space. To extend that disk use the Set-HardDisk
cmdlet. The -CapacityGB
parameter specifies the new size of the virtual disk. For example, to extend virtual disk Hard disk 2
of virtual machine VM001
to 10 GB, use the following:
Get-VM VM001 | Get-HardDisk -Name "Hard disk 2" |
Set-HardDisk -CapacityGB 10
Extending the virtual disk is straightforward and easy to achieve. But extending the virtual disk is only half the work. Chances are the disk was extended because the guest’s partition was reaching its capacity. Therefore, it is also necessary to extend the guest’s partition to use the newly added capacity. But don’t worry; the Set-HardDisk
cmdlet also supports extending the guest’s partition.
To extend the guest’s partition, the Set-HardDisk
cmdlet calls the Invoke-VMScript
cmdlet in the background and uses a script that runs inside the VM in the context of the specified guest user to extend the guest’s partition. These scripts are located in the Scripts
folder in the PowerCLI installation directory. To specify the guest’s partition, use the -Partition
parameter. If a partition isn’t specified using this parameter, the last partition on the hard disk is expanded. It is possible to specify a Windows partition, by specifying the drive letter without the colon. There are some restrictions, however:
Extending the guest’s partition is currently only supported on Windows XP SP3 or newer, and ext3 partitions on Linux RedHat Enterprise 5.
To extend the Windows partition on the previously extended Hard disk 2
on virtual machine VM001
to 100GB and expand the guest partition, use the following:
Get-VM VM001 | Get-HardDisk -Name "Hard disk 2" |
Set-HardDisk -CapacityGB 100 '
-HostCredential $hostCredential '
-GuestCredential $guestCredential '
-ResizeGuestPartition -Partition E
When the partition being expanded is the system disk, additional restrictions apply:
On Windows, the helper virtual machine can be specified using the -HelperVM
parameter. When a helper virtual machine is used, all VMs associated with the disk and the helper virtual machine must be powered off before expanding the disk. When you resize more than one disk using a helper virtual machine, the disks are resized one at a time, causing the helper machine to power on and off for each virtual disk. This process might slow down the cmdlet’s performance.
For example, to expand the system disk on VM001
, where the system partition happens to be Hard disk 1
and VM002
will be used as the helper virtual machine, both VM001
and VM002
need to be powered off before extending the disk. Notice that the guest logon credentials used in Listing 8-5 are the guest logon credentials for the helper virtual machine.
Listing 8-5: Automated system partition expansion
Get-VM VM001,VM002 | Shutdown-VMGuest -Confirm:$false
While ((Get-VM VM001).PowerState -eq "PoweredOn"){
Sleep -Seconds 2
}
While ((Get-VM VM002).PowerState -eq "PoweredOn"){
Sleep -Seconds 2
}
Get-VM VM001 | Get-HardDisk -Name "Hard disk 1"} | '
Set-HardDisk -CapacityGB 60 '
-HostCredential $hostCredential '
-GuestCredential $guestCredential '
-HelperVM (Get-VM VM002)
Get-VM VM001,VM002 | Start-VM -Confirm:$false
Sit back, relax, and enjoy the show. This is really cool stuff!
All the steps involved in expanding the system partition using a helper virtual machine (normally done by hand) are now fully automated. The following tasks are performed automatically for you:
When it’s time to install software from a CD or ISO image, a CD drive will need to be added to the VM if it doesn’t have one already. A new CD drive can be added to your virtual machine using the New-CDDrive
cmdlet. You can connect the CD drive to an ISO image file:
Get-VM VM001 | New-CDDrive -IsoPath '
"[Datastore01] ISO/gparted-live.iso" -StartConnected
or direct to the host’s CD drive:
Get-VM VM001 | New-CDDrive -HostDevice '
"/vmfs/devices/cdrom/mpx.vmhba32:C0:T0:L0"
For security purposes, it is a good practice to remove all virtual hardware from your VM that is not used. For example, VMs rarely contain a floppy drive anymore, as it’s very unlikely that one will ever be needed. But there may be a need for a temporary floppy drive for a legacy application. To connect a floppy drive, use the New-FloppyDrive
cmdlet. To connect a floppy image file to our VM001, use the following:
Get-VM VM001 | New-FloppyDrive -FloppyImagePath '
"[Datastore01] ISO/floppy.flp"
A new floppy image can be created by using the -NewFloppyImagePath
parameter and specifying a datastore path to the new floppy image:
Get-VM VM001 | New-FloppyDrive -NewFloppyImagePath '
"[Datastore01] ISO/myNewFloppy.flp"
Beginning with vSphere 4, it became possible to add VMDirectPath
I/O pass-through devices, also referred to as PCI pass-through devices. The other type of pass-through device available is the SCSI pass-through device. You can connect up to six pass-through devices to a virtual machine by using the Add-PassthroughDevice
cmdlet. Remember that before PowerCLI can connect a PCI pass-through device to your virtual machine, the device must be enabled for pass-through on the ESX host first:
$deviceList = Get-VMHost ESX01 | Get-PassthroughDevice
Get-VM VM001 | Add-PassthroughDevice '
-PassthroughDevice $deviceList[0]
When a new virtual disk is created and disk type is not specified, the disk type is thick by default for any VMFS datastore. A thick virtual disk preallocates all the space during the creation of the disk. You can also create a thin virtual disk. A thin virtual disk does not preallocate the space. The blocks of a thin virtual disk are allocated on demand when first written to. You create a thin-provisioned disk by specifying the -StorageFormat Thin
parameter on the New-HardDisk
cmdlet:
Get-VM VM001 | New-HardDisk -CapacityGB 10 -StorageFormat Thin
In a VMware environment, every VM needs to have its own boot and data volumes. The initial size of the boot volume is dictated by the size of the operating system and applications. The size of the data volume is dictated by the size of the existing application data. Typically, there’s also an amount of free space added to each partition to accommodate future patches, service packs, upgrades, and application data growth. Traditionally data volumes are designed to take into account additional data for the upcoming year or even years, because disks used to be fixed in an era when servers were installed on direct attached disks. Nowadays, servers store their files on storage arrays, which are more flexible in size, and presented disks (or logical units) can easily be extended.
But still, servers are many, many times designed the old-school way. This leads to lots of free unutilized space on your expensive storage arrays. Using thin provisioning, you can reclaim this wasted space and increase your storage capacity utilization. This boost in capacity utilization (typically 20–30 percent) also saves an equal percentage of your annual storage costs, because the same servers and applications are stored on less space.
Unused space can be reclaimed by converting the virtual disk to thin. You can accomplish this by using vMotion or by converting the disk in place.
Converting a thick virtual disk to a thin-provisioned virtual disk, or vice versa, can be accomplished by moving the disk to another datastore using storage vMotion. Because storage vMotion creates a new copy of the hard disk on the destination volume, it can specify a different disk type for the copy on the destination volume. This is accomplished using the Move-HardDisk
cmdlet and specifying the new datastore with the -Datastore
parameter and the new disk type with the -DiskType
parameter. The following code uses Storage vMotion to move all virtual disks on virtual machine VM001 to datastore Datastore02 and converts them to thin disks on the fly:
Get-VM VM001 | Get-HardDisk | Move-HardDisk '
-Datastore Datastore02 -StorageFormat Thin
To convert a single hard disk on a VM, simply specify the -Name
parameter. The following code uses Storage vMotion to move only Hard disk 2
on virtual machine VM001
and converts it to thin provisioning on the fly:
Get-VM VM001 | Get-HardDisk -Name "Hard disk 2" | '
Move-HardDisk –Datastore Datastore02 -StorageFormat Thin
If faced with only one datastore and unable to utilize storage vMotion, you can convert a virtual disk in place. Do so by copying the virtual disk and then reconfiguring the virtual machine to use the new copy. This method has some drawbacks:
Copy-HardDisk
cmdlet to work, the disk must be connected directly to an ESXi host.Listing 8-6 contains a function that creates a thin-provisioned cloned copy of your thick virtual disk.
Listing 8-6: Converting thick to thin in place using a disk copy
function Set-ThinDisk {
Param (
[parameter(ValueFromPipeline = $true, Mandatory = $true)]
[VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.HardDisk]$hardDisk
,
[Parameter(Mandatory = $true, ParameterSetName = "cred")]
[System.Management.Automation.PSCredential]$credential
,
[Parameter(ParameterSetName = "user")]
[ValidateNotNullOrEmpty()]
[string]$user = "root"
,
[Parameter(Mandatory = $true, ParameterSetName = "user")]
[string]$password
,
[switch]$replace
)
process {
if ($hardDisk.Parent.PowerState -eq "PoweredOff") {
if ($hardDisk.StorageFormat -ne "Thin") {
if ($credential) {
$esxHost = Connect-VIServer '
-Server $hardDisk.Parent.host.name '
-Credential $credential -NotDefault
}
else {
$esxHost = Connect-VIServer -Server '
$hardDisk.Parent.host.name -User $user '
-Password $password -NotDefault
}
$thinFile = $hardDisk.Filename.Replace("/","/thin_")
$datastore = '
$hardDisk.Filename.split('[')[1].split(']')[0]
$esxHardDisk = Get-HardDisk -server $esxHost '
-Datastore $datastore '
-DatastorePath $hardDisk.Filename
Copy-HardDisk -HardDisk $esxHardDisk '
-DestinationPath $thinFile '
-DestinationStorageFormat "thin" | Out-Null
Disconnect-VIServer $esxHost -Confirm:$false
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$spec.deviceChange = New-Object '
VMware.Vim.VirtualDeviceConfigSpec[] (2)
$spec.deviceChange[0] = New-Object '
VMware.Vim.VirtualDeviceConfigSpec
$spec.deviceChange[0].operation = "remove"
if ($replace) {
$spec.deviceChange[0].fileOperation = "destroy"
}
$spec.deviceChange[0].device = $hardDisk.ExtensionData
$spec.deviceChange[1] = New-Object '
VMware.Vim.VirtualDeviceConfigSpec
$spec.deviceChange[1].operation = "add"
$spec.deviceChange[1].device = New-Object '
VMware.Vim.VirtualDisk
$spec.deviceChange[1].device.key = -100
$spec.deviceChange[1].device.backing = New-Object '
VMware.Vim.VirtualDiskFlatVer2BackingInfo
$spec.deviceChange[1].device.backing.fileName = '
$thinFile
$spec.deviceChange[1].device.backing.diskMode = '
"persistent"
$spec.deviceChange[1].device.backing.thinProvisioned = '
$true
$spec.deviceChange[1].device.controllerKey = '
$hardDisk.ExtensionData.ControllerKey
$spec.deviceChange[1].device.unitNumber = '
$hardDisk.ExtensionData.UnitNumber
$vm = Get-View -Id $hardDisk.ParentID -Property Name
$vm.ReconfigVM_Task($spec) | Out-Null
}
else {
Write-Error "Virtual disk already thin provisioned"
}
}
else {
Write-Error "Virtual machine must be powered off"
}
}
}