13 October 2013

Get VM by IP or MAC address via PowerCLI

I had need to get VMs based on IP, but, not particular IPs, just IPs in a certain subnet. Previously here on vNugglets we had a post about how to Find VM by NIC MAC Address. I have since added this logic to a function, and added the ability to get a VM by its IP address. With my recent need, I added another piece that allows for the IP address "wildcarding". The function, Get-VMByAddress, with its three ways to find a VM by IP- or MAC address:
function Get-VMByAddress {
    <#  .Description
        Find all VMs w/ a NIC w/ the given MAC address or IP address (by IP address relies on info returned from VMware Tools in the guest, so those must be installed).  Includes FindByIPWildcard, so that one can find VMs that approximate IP, like "10.0.0.*"
        .Example
        Get-VMByAddress -MAC 00:50:56:00:00:02
        VMName        MacAddress
        ------        ----------
        dev0-server   00:50:56:00:00:02,00:50:56:00:00:04

        Get VMs with given MAC address, return VM name and its MAC addresses
        .Example
        Get-VMByAddress -IP 10.37.31.120
        VMName         IPAddr
        ------         ------
        dev0-server2   192.168.133.1,192.168.253.1,10.37.31.120,fe80::...

        Get VMs with given IP as reported by Tools, return VM name and its IP addresses
        .Example
        Get-VMByAddress -AddressWildcard 10.0.0.*
        VMName   IPAddr
        ------   ------
        someVM0  10.0.0.119,fe80::...
        someVM2  10.0.0.138,fe80::...
        ...

        Get VMs matching the given wildcarded IP address
    #>

    [CmdletBinding(DefaultParametersetName="FindByMac")]
    param (
        ## MAC address in question, if finding VM by MAC; expects address in format "00:50:56:83:00:69"
        [parameter(Mandatory=$true,ParameterSetName="FindByMac")][string]$MacToFind_str,
        ## IP address in question, if finding VM by IP
        [parameter(Mandatory=$true,ParameterSetName="FindByIP")][ValidateScript({[bool][System.Net.IPAddress]::Parse($_)})][string]$IpToFind_str,
        ## wildcard string IP address (standard wildcards like "10.0.0.*"), if finding VM by approximate IP
        [parameter(Mandatory=$true,ParameterSetName="FindByIPWildcard")][string]$AddressWildcard_str
    ) ## end param


    Process {
        Switch ($PsCmdlet.ParameterSetName) {
            "FindByMac" {
                ## return the some info for the VM(s) with the NIC w/ the given MAC
                Get-View -Viewtype VirtualMachine -Property Name, Config.Hardware.Device | Where-Object {$_.Config.Hardware.Device | Where-Object {($_ -is [VMware.Vim.VirtualEthernetCard]) -and ($_.MacAddress -eq $MacToFind_str)}} | select @{n="VMName"; e={$_.Name}},@{n="MacAddress"; e={($_.Config.Hardware.Device | Where-Object {$_ -is [VMware.Vim.VirtualEthernetCard]} | %{$_.MacAddress} | sort) -join ","}}
                break;
                } ## end case
            {"FindByIp","FindByIPWildcard" -contains $_} {
                ## scriptblock to use for the Where clause in finding VMs
                $sblkFindByIP_WhereStatement = if ($PsCmdlet.ParameterSetName -eq "FindByIPWildcard") {{$_.IpAddress | Where-Object {$_ -like $AddressWildcard_str}}} else {{$_.IpAddress -contains $IpToFind_str}}
                ## return the .Net View object(s) for the VM(s) with the NIC(s) w/ the given IP
                Get-View -Viewtype VirtualMachine -Property Name, Guest.Net | Where-Object {$_.Guest.Net | Where-Object $sblkFindByIP_WhereStatement} | Select @{n="VMName"; e={$_.Name}}, @{n="IPAddr"; e={($_.Guest.Net | %{$_.IpAddress} | sort) -join ","}}
            } ## end case
        } ## end switch
    } ## end process
} ## end function

A few examples of using the function to find VMs by various addresses (as shown in the comment-based help, too):
PS vN:\> Get-VMByAddress -MAC 00:50:56:00:00:02
VMName        MacAddress
------        ----------
dev0-server   00:50:56:00:00:02,00:50:56:00:00:04


PS vN:\> Get-VMByAddress -IP 10.37.31.120
VMName         IPAddr
------         ------
dev0-server2   192.168.133.1,192.168.253.1,10.37.31.120,fe80::...


PS vN:\> Get-VMByAddress -AddressWildcard 10.0.0.*
VMName    IPAddr
------    ------
someVM0   10.0.0.119,fe80::...
someVM2   10.0.0.138,fe80::...
...

There are several places on the web that talk about getting VMs' IPs, getting their MAC addresses, exporting them, and so on, but this is for getting VMs based on the given address. Note: Since IP address info in vCenter is gotten from guest OSes via VMware Tools, this function is going to rely on that info. And, since the function uses everyone's favorite cmdlet, Get-View, and the calls with this cmdlet are properly tuned, you know that it is going to be FaF!

20 August 2013

Copy VIRole via PowerCLI

I had the occasion of needing to copy some VIRoles from one vCenter to another.  There are export/import scripts out there for exporting VIRoles to txt files, and then importing them elsewhere.  But, so as to help simplify the copying of VIRoles, I decided to make a Copy-VIRole function that does the copy straight-away.

It is pretty straight forward:  it creates a VIRole of the given name in a destination vCenter, based on the name of a VIRole and source vCenter.  This does involve the additional step of getting the VIPrivileges (handled in the function via Get-VIPrivilege) from the destination vCenter -- the PrivilegeList itself did not suffice.  The function definition:
function Copy-VIRole {
<#    .Description
    Copy a role to another role, either in same vCenter or to a different vCenter
    This assumes that connections to source/destination vCenter(s) are already established.  If role of given name already exists in destination vCenter, will stop.
    Author:  vNugglets.com -- Jul 2013
    .Example
    Copy-VIRole -SrcRoleName SysAdm -DestRoleName SysAdm_copyTest -SrcVCName vcenter.com -DestVCName labvcenter.com
    .Outputs
    VMware.VimAutomation.ViCore.Impl.V1.PermissionManagement.RoleImpl if role is created/updated, String in Warning stream and nothing in standard out otherwise
#>
    param(
        ## source role name
        [parameter(Mandatory=$true)][string]$SrcRoleName_str,
        ## destination role name
        [parameter(Mandatory=$true)]$DestRoleName_str,
        ## source vCenter connection name
        [parameter(Mandatory=$true)]$SrcVCName_str,
        ## destination vCenter connection name
        [parameter(Mandatory=$true)]$DestVCName_str,
        ## WhatIf switch
        [switch]$WhatIf_sw
    ) ## end param

    ## get the VIRole from the source vCenter
    $oSrcVIRole = Get-VIRole -Server $SrcVCName_str -Name $SrcRoleName_str -ErrorAction:SilentlyContinue
    ## if the role does not exist in the source vCenter
    if ($null -eq $oSrcVIRole) {Write-Warning "VIRole '$DestRoleName_str' does not exist in source vCenter '$SrcVCName_str'. No source VIRole from which to copy. Exiting"; exit}
    ## see if there is VIRole by the given name in the destination vCenter
    $oDestVIRole = Get-VIRole -Server $DestVCName_str -Name $DestRoleName_str -ErrorAction:SilentlyContinue

    ## if the role already exists in the destination vCenter
    if ($null -ne $oDestVIRole) {Write-Warning "VIRole '$DestRoleName_str' already exists in destination vCenter '$DestVCName_str'. Exiting"; exit}
    ## else, create the role
    else {
        $strNewVIRoleExpr = 'New-VIRole -Server $DestVCName_str -Name $DestRoleName_str -Privilege (Get-VIPrivilege -Server $DestVCName_str -Id $oSrcVIRole.PrivilegeList){0}' -f $(if ($WhatIf_sw) {" -WhatIf"})
        Invoke-Expression $strNewVIRoleExpr
    } ## end else
} ## end function

An example of using the function to copy a role from one vCenter to another, with a new role name:
PS vN:\> Copy-VIRole -SrcRoleName MyRole0 -SrcVCName myvcenter.dom.com -DestRoleName MyNewRole -DestVCName vcenter2.dom.com
Name            IsSystem
----            --------
MyNewRole       False

PS vN:\> Get-VIRole MyNewRole -server vcenter2*
Name            IsSystem
----            --------
MyNewRole       False

One can also use this function to clone a VIRole in the same vCenter -- just use the same vCenter for the Source and Destination vCenter parameters.

Note:  this function expects/requires that the PowerCLI session already has a connection to each of the vCenter(s) (one or two) involved in the operation.

For another way of copying a VIRole (between separate vCenters, particularly), see Grzegorz's post at http://psvmware.wordpress.com/2012/07/19/clone-roles-between-two-virtual-center-servers/.

Enjoy.

06 January 2013

Determine VMs' Uplink vmnic on vSwitch with PowerCLI

Ever have the need to determine which uplink vmnic on which vSwitch a VM is using?  We have, too.  And, we have relied on traditional hardware level info in the past to get such info:  get with the network team and have them tell us on what physical switch port they see the MAC address of the given VM, and then correlate that with the VMHost's vmnic.  Straight forward, fast, easy, and self-service.  Not.

The standard ways described elsewhere on the web are to use esxtop on a VMHost or resxtop from somewhere with the vCLI installed (press "n" once in [r]esxtop).  But, wouldn't it be nice if there was a way without having to enable SSH on a host, or SSH'ing to a vMA appliance (if you do not already have the vCLI installed).  Maybe, say, with PowerCLI, which everybody has installed (right?).  Oh, wait -- there is:  Get-EsxTop.

Here is a function that uses Get-EsxTop to get, for VMs on a VMHost, their vSwitch and vmnic uplink information:
<#  .Description
Function to retrieve Netports' (portgroup ports') client, uplink info, vSwitch, etc. info.  Includes things like VMKernel ports and Management uplinks.
    Author:  vNugglets.com -- Nov 2012
    .Outputs
    PSObject
#>
function Get-VNUplinkNic {
    param(
        ## the VMHost DNS name whose VMs' uplink info to get (not VMHost object name -- so, do not use wildcards)
        [parameter(Mandatory=$true)][string]$VMHostToCheck_str,
        ## PSCredential to use for connecting to VMHost; will prompt for credentials if not passed in here
        [System.Management.Automation.PSCredential]$CredentialForVMHost_cred
    ) ## end param

    $strThisVMHostName = $VMHostToCheck_str

    ## check if VMHost name given is responsive on the network; if not, exit
    if (-not (Test-Connection -Quiet -Count 3 -ComputerName $strThisVMHostName)) {
        Write-Warning "VMHost '$strThisVMHostName' not responding on network -- not proceeding"; exit}

    ## set/get the credential to use for connecting to the VMHost (get if not already passed as param)
    $credToUseForVMHost = if ($CredentialForVMHost_cred) {$CredentialForVMHost_cred} else 
        {$host.ui.PromptForCredential("Need credentials to connect to VMHost", "Please enter credentials for '$strThisVMHostName'", $null, $null)}

    ## connect to the given VIServer (VMHost, here)
    $oVIServer = Connect-VIServer $strThisVMHostName -Credential $credToUseForVMHost

    ## if connecting to VMHost failed, write warning and exit
    if (-not $oVIServer) {Write-Warning "Did not connect to VMHost '$strThisVMHostName' -- not proceeding"; exit}

    ## array with PortID to vSwitch info, for determining vSwitch name from PortID
    ## get vSwitch ("PortsetName") and PortID info, not grouped
    $arrNetPortEntries = (Get-EsxTop -Server $strThisVMHostName -TopologyInfo NetPort).Entries

    ## calculated property for vSwitch name
    $hshVSwitchInfo = @{n="vSwitch"; e={$oNetportCounterValue = $_; ($arrNetPortEntries | ?{$_.PortId -eq $oNetportCounterValue.PortId}).PortsetName}}

    ## get the VM, uplink NIC, vSwitch, and VMHost info
    Get-EsxTop -Server $strThisVMHostName -CounterName NetPort | select @{n="VMName"; e={$_.ClientName}},TeamUplink,$hshVSwitchInfo,@{n="VMHostName"; e={$_.Server.Name}}

    Disconnect-VIServer $strThisVMHostName -Confirm:$false
} ## end function

The comments should pretty much explain what is happening, if it is not already clear.  Once you have defined the function, you can use it like:

PS vNuggs:\> Get-VNUplinkNic -VMHostToCheck somehost

Need credentials to connect to VMHost
Please enter credentials for 'somehost'
User: root
Password for user root: *************

VMName         TeamUplink   vSwitch     VMHostName
----------     ----------   -------     ----------
Management     n/a          vSwitch0    somehost
vmk1           vmnic0       vSwitch0    somehost
vmk0           vmnic0       vSwitch0    somehost
dev0-vm0       vmnic0       vSwitch0    somehost
dev0-vm12323   vmnic5       vSwitch0    somehost
dev0-vm2       vmnic5       vSwitch0    somehost
dev0-vm32237   vmnic4       vSwitch1    somehost
dev0-vm9       vmnic8       vSwitch1    somehost
...

Pretty handy.  And, it does not meet up with the same limitation that you might face with [r]esxtop -- that you can only get uplink info for the top X number of VMs on a host, where X is the number of rows available for display in your console session.  So, you can get the uplink info for all VMs on the host.  Then, you can sort/filter as needed -- VMs on a given uplink is a nice to know, for example.

There you go:  another tasty vNugglet.

Postscript:  After writing this function, and in the process of composing this post, I found a VMware Communities thread (http://communities.vmware.com/thread/341604) that does also provide a portion of this goodness, but that thread did not include the cool sauce for getting the vSwitch info -- so, at least that part is new -- yeah-hooey.

16 December 2012

Evacuating VMs and Templates From Datastores

In many environments, ours included, it is not always possible or desired to store all of a VM's virtual disks on the same datastore.  For example, the storage performance may not be ideal if the VM has high I/O requirements, you may have a policy to keep database logs on separate datastores from the actual data files, or you might just have different tiers of storage for your OS disks vs. your data disks.  Whatever the rationale, it is somewhat troublesome if you need to evacuate the datastore for some reason, such as moving to a new storage array.  Why is this?  Consider the following scenario:



Let's assume DATASTORE1 is the datastore that needs to be evacuated in this example.  Initially, you may consider a simple Move-VM command such as this:

Get-Datastore DATASTORE1 | Get-VM | Move-VM -Datastore DATASTORE4

The problem with this command is that it will attempt to move all four VMs to DATASTORE4 but will fail because there isn't enough free space.  At first glance, you may be thinking "What are you talking about?  The VMs on DATASTORE1 only add up to 400GB and there is 500GB free on DATASTORE4!"  Well, the problem with the Move-VM cmdlet is that it will consolidate all the config files and virtual disks that make up a VM at the destination datastore.  If you take a closer look at the diagram, the total capacity needed to host all four VMs is actually 1.4TB.  So Move-VM is not an option here unless we get a larger datastore, but remember, there was a valid reason for splitting the virtual disks across multiple datastores, so we really don't want to lose the VMs' layout on this migration.

To the VMware (VMTN) Communities I went to begin researching this and big surprise, LucD had the answer for someone.  However, this solution had a few issues for us:

1) It does not move any templates that may be located on the datastore you are evacuating.  Surely you use templates, right?

2) It moves the VM's config files even if they are not on the datastore you are evacuating.  Not a huge deal, but some people like to keep the config files on the same datastore as the "OS disk" and this could break that.  For example, if we wanted to evacuate DATASTORE2 instead, VM2 would have not only its 500GB disk moved to the new datastore, but it would also grab the config files from DATASTORE1 as well, which might lead to some confusion since they now reside with the second virtual disk instead of the first (OS disk).

3) If the VM only has its config files stored on the datastore you are evacuating, then we've got a larger problem.  Let's go back to our original example.  We want to migrate the VMs on DATASTORE1 to DATASTORE4, but keep their existing layout so it all fits.  With the script from the Community, it'll work great except on VM3 (and sort of VM4, see above).  With VM3, only the config files are stored there, but because the script sets the "datastore" property of the VirtualMachineRelocateSpec object regardless of where the config files reside, it'll not only move those files, but also all virtual disks associated with that VM because the "disk" property is optional.  Think of it kind of like a "default datastore" to use for migrating the VM and its virtual disks so you don't need to bother specifying the destination datastore on each disk if you desire.  If you do the math on that, it'll move 400GB of virtual disks just for VM3.  That brings the total for all VMs on DATASTORE1 to 800GB, which again, will not fit on DATASTORE4.

So the fix for #3, of course, is to specify the destination datastore for each and every disk--either the new destination datastore if they need moved or the current datastore if they need to stay put.  This is done through the VirtualMachineRelocateSpec object's "disk" property, which is itself an array of VirtualMachineRelocateSpecDiskLocator objects.  Without further adieu, the final code looks like this:

<#
    .Description
    Script to evacuate virtual disks and/or VM config files from a given datastore; does not move the entire VM and all its disks if they reside elsewhere. Created 12-Dec-2012 by vNugglets.com.
    .Example
    EvacuateDatastore.ps1 -SourceDatastore datastoreToEvac -DestDatastore destinationDatastore

    Move virtual disks and/or VM config files (if any) from source datastore to the destination datastore
#>

## Params for source and destination datastore
param(
    ## The name of the source datastore (the one to evacuate).  Required.
    [parameter(Mandatory=$true)][string]$SourceDatastore_str,
    ## The name of the destination datastore.  Required.
    [parameter(Mandatory=$true)][string]$DestDatastore_str
) ## end parameter


## Set proper variable names from the supplied parameters
$strSrcDatastore = $SourceDatastore_str
$strDestDatastore = $DestDatastore_str

## Get the .NET view of the source datastore
$viewSrcDatastore = Get-View -ViewType Datastore -Property Name -Filter @{"Name" = "^${strSrcDatastore}$"}
## Get the linked view that contains the list of VMs on the source datastore
$viewSrcDatastore.UpdateViewData("Vm.Config.Files.VmPathName", "Vm.Config.Hardware.Device", "Vm.Config.Template", "Vm.Runtime.Host", "Vm.Name")
## Get the .NET view of the destination datastore
$viewDestDatastore = Get-View -ViewType Datastore -Property Name -Filter @{"Name" = "^${strDestDatastore}$"}
## Create a VirtualMachineMovePriority object for the RelocateVM task; 0 = defaultPriority, 1 = highPriority, 2 = lowPriority (per http://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.VirtualMachine.MovePriority.html)
$specVMMovePriority = New-Object VMware.Vim.VirtualMachineMovePriority -Property @{"value__" = 1}
## Create empty arrays to track templates and VMs
$arrVMList = $arrTemplateList = @()

## For each VM managed object, sort into separate arrays based on whether it is a VM or a template
$viewSrcDatastore.LinkedView.Vm | % {
    ## If object is a template, add to template array
    if ($_.Config.Template -eq "True") {$arrTemplateList += $_}
    ## Else, add it to the VM array
    else {$arrVMList += $_}
}

## For each VM object, initiate the RelocateVM_Task() method; for each template object, initiate the RelocateVM() method
$arrVMList, $arrTemplateList | %{$_} | %{
    $viewVMToMove = $_
    ## Create a VirtualMachineRelocateSpec object for the RelocateVM task
    $specVMRelocate = New-Object Vmware.Vim.VirtualMachineRelocateSpec
    ## Create an array containing all the virtual disks for the current VM/template
    $arrVirtualDisks = $viewVMToMove.Config.Hardware.Device | ?{$_ -is [VMware.Vim.VirtualDisk]}
    ## If the VM/template's config files reside on the source datastore, set this to the destination datastore (if not specified, the config files are not moved)
    if ($viewVMToMove.Config.Files.VmPathName.Split("]")[0].Trim("[") -eq $strSrcDatastore) {
        $specVMRelocate.Datastore = $viewDestDatastore.MoRef
    } ## end if

    ## For each VirtualDisk for this VM/template, make a VirtualMachineRelocateSpecDiskLocator object (to move disks that are on the source datastore, and leave other disks on their current datastore)
    ## But first, make sure the VM/template actually has any disks
    if ($arrVirtualDisks) {
        foreach($oVirtualDisk in $arrVirtualDisks) {
            $oVMReloSpecDiskLocator = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator -Property @{
                ## If this virtual disk's filename matches the source datastore name, set the VMReloSpecDiskLocator Datastore property to the destination datastore's MoRef, else, set this property to the virtual disk's current datastore MoRef
                DataStore = if ($oVirtualDisk.Backing.Filename -match $strSrcDatastore) {$viewDestDatastore.MoRef} else {$oVirtualDisk.Backing.Datastore}
                DiskID = $oVirtualDisk.Key
            } ## end new-object
            $specVMRelocate.disk += $oVMReloSpecDiskLocator
        } ## end foreach
    } ## end if

    ## Determine if template or VM, then perform necessary relocation steps
    if ($viewVMToMove.Config.Template -eq "True") {
        ## Gather necessary objects to mark template as a VM (VMHost where template currently resides and default, root resource pool of the cluster)
        $viewTemplateVMHost = Get-View -Id $_.Runtime.Host -Property Parent
        $viewTemplateResPool = Get-View -ViewType ResourcePool -Property Name -SearchRoot $viewTemplateVMHost.Parent -Filter @{"Name" = "^Resources$"}
        ## Mark the template as a VM
        $_.MarkAsVirtualMachine($viewTemplateResPool.MoRef, $viewTemplateVMHost.MoRef)
        ## Relocate the template synchronously (i.e. one at a time)
        $viewVMToMove.RelocateVM($specVMRelocate, $specVMMovePriority)
        ## Convert VM back to template
        $viewVMToMove.MarkAsTemplate()
    }
    else {
        ## Initiate the RelocateVM task (asynchronously)
        $viewVMToMove.RelocateVM_Task($specVMRelocate, $specVMMovePriority)
    }
} ## end foreach-object

To use the script, you'll need to provide the source and destination datastore as parameters as such:

PS vNuggs:\> .\EvacuateDatastore.ps1 -SourceDatastore DATASTORE1 -DestDatastore DATASTORE4

Many of the comments in the code are self-explanatory, but here are some additional details around the more important/complex parts:

Lines 35-40: In this section, we determine whether the objects from the datastore's linked view are standard VM managed objects or whether they are actually a template and put them into separate arrays.

Line 43: This is where we pipe the two arrays into a ForEach-Object loop to start the process of gathering the necessary data to relocate the VM/template to the new datastore.  We chose to begin with the VMs first because we eventually want to kick them off as vCenter tasks so that we can then focus on the templates.

Line 50: This is the part that combats problem #2 above.  Here, we parse the datastore name out of the fully-qualified path to the VM/template's config file and if it matches the source datastore, then that's the only time we set the "datastore" property of the VirtualMachineRelocateSpec object to the destination datastore as that's the only time we want to move the config files.

Lines 56-65: Now the VirtualMachineRelocateSpecDiskLocator object is populated accordingly--if the disk is on the source datastore, then its "datastore" property is set to the destination datastore so it gets moved, otherwise it is set to its current datastore so it does not get moved.  Line 63 adds it to the "disk" property of the VirtualMachineRelocateSpec object and everything repeats for the next disk if more exist.

Lines 68-78: On line 68 we have to check if the current VM object is really a VM or a template, even though we already did this earlier.  This is better than having to duplicate all the code from lines 43-65, however.  Then lines 70-73 are how we deal with problem #1 from above.  Since we determined this object was a template, we must convert it to a VM in order to relocate it to a different datastore.  It is a shame VMware still doesn't allow us to move templates for some reason.  Finally, line 75 calls the RelocateVM() method of the VM managed object and begins the relocation.  It is important to note that this occurs in a synchronous manner (i.e. one template at a time).  As you'll see in the next section, we don't do this if it is a VM object, but we really have no choice when dealing with templates because we need to wait for the relocation to complete before we can convert the VM back to a template.  This occurs on line 77.

Lines 79-82: In this section, namely line 81, we call the RelocateVM_Task() method this time since this object is a VM, not a template.  This way we kick off "RelocateVM" tasks asynchronously and let vCenter decide how many it can handle at once, which as of vSphere 5.0 and 5.1 is eight per datastore and two per host.  In addition, we piped the VM objects into the loop first so that we could kick them all off and then focus on the templates one by one.  But, if you are concerned that your storage array won't be able to handle the load of multiple storage migrations, simply change line 81 to look like line 75 and it'll behave synchronously like the templates do.

If you've read this far, you may be wondering what use this sort of script has in the world of SDRS (Storage DRS).  Yes, SDRS has features like datastore maintenance mode that would prevent the need for some of this type of work, but not everyone has SDRS available to them; either because they haven't found the time to implement it yet, haven't upgraded to vSphere 5.x yet, or just don't own the Enterprise Plus edition, so this script is likely to be of use to many people still. We hope so.

02 October 2012

VMHost Logical Drive Info from CIM Provider with PowerCLI

Joe at filippello.com was grabbing some Logical Volume info for local disks on VMware hosts he was building in his post "Frustrated with fast booting hardware?".  He realized that, rather than going to the RAID controller setup and viewing the logical volume info, the CIM provider is nice enough to provide that info, and vCenter displays it on the Hardware Status tab for the host.

We wanted to get that info for lots of hosts instead of one at a time in the vSphere client, so I checked into the properties of the HostSystem. The CIM provider tells about the logical volumes.  vSphere presents this in the Runtime.HealthSystemRuntime.HardwareStatusInfo.StorageStatusInfo property.  For the HP hosts I used, this property also holds info about each individual local disk, the array controller(s), and their battery.  So, if you are looking for such info, this is a good property to explore.  But, back to the topic at hand.

The function:
<# .Description
    Get logical volume info for HP VMHosts from StorageStatusInfo of their managed objects. Depends on CIM provider being installed and in good health (responsive and whatnot), presumably
    Author:  vNugglets.com -- Aug 2012
#>
function Get-VNVMHostLogicalVolumeInfo {
    param(
        ## name of VMHost to check; if none, queries all hosts
        [string]$VMHostName_str
    ) ## end param

    ## make the Get-View expression to invoke
    $strGetViewExpr = 'Get-View -ViewType HostSystem -Property Name,Runtime.HealthSystemRuntime.HardwareStatusInfo.StorageStatusInfo'
    if ($VMHostName_str) {$strGetViewExpr += " -Filter @{'Name' = '$VMHostName_str'}"}
    Invoke-Expression $strGetViewExpr | Select name,
        @{n="logicalVol"; 
        e={($_.Runtime.HealthSystemRuntime.HardwareStatusInfo.StorageStatusInfo | `
            ?{$_.Name -like "Logical*"} | %{$_.Name}) -join ", "}}
} ## end function

Some example usage:
## for one host
PS vNuggs:> Get-VNVMHostLogicalVolumeInfo.ps1 -VMHostName devhost0323.mydomain.com

Name                        logicalVol
----                        ----------
devhost0323.mydomain.com    Logical Volume 1 on HPSA1 : RAID 1 : 136GB : Disk 1,2,3


## for all hosts, call with no parameter
PS vNuggs:> Get-VNVMHostLogicalVolumeInfo.ps1

Name                        logicalVol
----                        ----------
devhost0323.mydomain.com    Logical Volume 1 on HPSA1 : RAID 1 : 136GB : Disk 1,2,3
devhost4323.mydomain.com    Logical Volume 1 on HPSA1 : RAID 1 : 136GB : Disk 1,2,3
devhost1.mydomain.com       Logical Volume 1 on HPSA1 : RAID 1 : 136GB : Disk 1,2,3
devhost27.our.domain.com    Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost2.our.domain.com     Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost743.our.domain.com   Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost31111.our.domain.com Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost21.our.domain.com    Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost431.our.domain.com   Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost3.mydomain.com       Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost30.mydomain.com      Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2
devhost.mydomain.com        Logical Volume 1 on HPSA1 : RAID 1 : 68GB : Disk 1,2, Logical Volume 2 on HPSA1 : RAID 1 : 68GB : Disk 3,4
devhost122.mydomain.com     Logical Volume 1 on HPSA1 : RAID 1 : 136GB : Disk 1,2
...

And, an example of using some of the other info returned by the CIM provider:  if you were to change the ?{$_.Name -like "Logical*"} in the above function (line 17) to ?{$_.Name -like "Disk*"}, you could get just the local disks' info (instead of info on the logical volumes):
...
Disk 1 on HPSA1 : Port 1I Box 1 Bay 1 : 136GB : Data Disk, Disk 2 on HPSA1 : Port 1I Box 1 Bay 2 : 136GB : Data Disk, Disk 3 on HPSA1 : Port 1I Box 1 Bay 3 : 136GB : Spare Disk
...
Enjoy.

28 August 2012

Even Faster PowerCLI Code with Get-View, UpdateViewData() and LinkedViews

We are always looking for speed, speed, speed in our PowerShell / PowerCLI scripts.  When properly used, the Get-View cmdlet is known to be far faster than using other PowerCLI cmdlets for getting info (such as Get-VM, Get-VMHost, Get-Datastore, etc.).  It can take quite a bit longer to write a script using Get-View versus using other cmdlets, but the speed payoff is usually well worth the additional upfront time investement.  VMware has provided a way to speed things up even more.

A while back (Aug 2011), the official PowerCLI blog had a post about how to "Optimize the performance of PowerCLI's views".  The technique is centered around using the UpdateViewData() method of a "view" of a managed object (".NET View object") to retrieve nested views of managed objects.

The UpdateViewData() method had already been able to selectively update properties of the given managed object view, but one would have to use Get-View again to retrieve views of nested managed objects that are properties of that parent managed object.  "So, what?"  So, speed!

Example 0:  Get guest IP info for VMs using given VM network

Say you wanted to get the IPs configured on VMs that are connected to particular VM networks.  This example goes about doing so by getting said network(s), getting the VMs connected to them, and then getting guest IP info.

You could use the following standard way using multiple Get-View calls:
## the network name pattern to use for getting vitual networks
$strNetworkNamePattern = "MyVMNetwork1711"
## get the virtual networks whose name match this pattern
$arrNetworkViews = Get-View -ViewType Network -Property Name,VM -Filter @{"Name" = $strNetworkNamePattern}

## get the VM managed objects with a couple of properties
$arrVMsUsingThis = $arrNetworkViews | ?{$_.Vm -ne $null} | %{Get-View -Property Name,Guest.Net $_.Vm}

## get info about IPs used on this/these network(s)
$arrVMsUsingThis | %{
   $strVMName = $_.Name
   ## for each Guest.Net where the network name is like the given network name
   $_.Guest.Net | ?{$_.Network -like "*$strNetworkNamePattern*"} | %{
       ## get IPs
       $_.IpAddress | %{
           New-Object -TypeName PSObject -Property @{
               VMName = $strVMName
               IPAddr = $_
           } ## end new-object
       } ## end %
   } ## end %
} ## end %

Or, using the UpdateViewData() method, the code would be:
## the network name pattern to use for getting vitual networks
$strNetworkNamePattern = "MyVMNetwork1711"
## get the virtual networks whose name match this pattern
$arrNetworkViews = Get-View -ViewType Network -Property Name -Filter @{"Name" = $strNetworkNamePattern}

## get a couple of properties for the VMs using this network
$arrNetworkViews | %{$_.UpdateViewData("VM.Name","VM.Guest.Net")}

## get info about IPs used on this/these network(s)
$arrNetworkViews | %{$_.LinkedView.Vm} | %{
   $strVMName = $_.Name
   ## for each Guest.Net where the network name is like the given network name
   $_.Guest.Net | ?{$_.Network -like "*$strNetworkNamePattern*"} | %{
       ## get IPs
       $_.IpAddress | %{
           New-Object -TypeName PSObject -Property @{
               VMName = $strVMName
               IPAddr = $_
           } ## end new-object
       } ## end %
   } ## end %
} ## end %
The only slight differences in the code in the two ways:
  • Line 04 in each:  the first way gets an additional property, "VM", for later use
  • Line 07 in each:  the first way uses and additional Get-View call, whereas the second way uses the UpdateViewData() method
  • Line 10 in each:  both access an array of VMs connected to the given VM network, but getting to those VM arrays is a touch different (the "LinkedView" property in the second way is due to having used the UpdateViewData() method)
This runs reasonably quickly both ways when just getting information for VMs on one VM network.  But, to really illustrate the potential speed increases, I tested the main difference (line 07) with not just one or two networks, but with about 30 networks and with about 160 networks.  The results are impressive.  Again, this was measuring just the central point of each way:  the amount of time for the code on line 07 in each way above to get the VM info from vCenter (first via an additional Get-View, and then via UpdateViewData()).

Get-View vs. UpdateViewData() -- Speed testing results in a couple of different environment sizes:
Method usedNum. networksNum. netwk connectionsTime to run (sec)Total Get-View calls
additional Get-View call3343295.334
UpdateViewData()334327.751
additional Get-View call1593705811.4160
UpdateViewData()159370539.611

Mhmm.  7.75 seconds vs. 95.3 seconds in a small-ish environment -- more than twelve (12) times faster for UpdateViewData().  And 40 seconds vs. 811 seconds in a larger environment -- more than twenty (20) times faster!

Example 1:  Get VMHosts' FirewallSystems to refresh firewall info

Another example.  AC recently had needed to refresh the VMHost firewall info on hosts.  This stemmed from a bug that AC ran into in which the host firewall info is gathered during the host boot process before the firewall services are fully initiated.

Next is the overall code for each way, and then some speed comparisons of each method.  Note, we originally wrote this to only refresh firewall info for VMHosts in one cluster at a time, but to have a bit larger set of operations so as to better illustrate the speed differences, the following code performs the actions for all clusters, one cluster at a time.

Traditional way, using multiple Get-View calls:
## get the Cluster managed objects
Get-View -ViewType ClusterComputeResource -Property Name,Host | %{
   ## get all of this cluster's HostSystem managed objects
   $arrHostViews = Get-View $_.Host -Property ConfigManager.FirewallSystem
   ## for each host in the given cluster, get the FirewallSystem managed object
   foreach ($viewHost in $arrHostViews) {
       $viewFirewallSystem = Get-View $viewHost.ConfigManager.FirewallSystem -Property FirewallInfo.DefaultPolicy
       ## call RefreshFirewall() method for this host's FirewallSystem
       $viewFirewallSystem.RefreshFirewall()
   } ## end %
} ## end %

And, the UpdateViewData() way:
## get the Cluster managed objects
$arrClusterViews = Get-View -ViewType ClusterComputeResource -Property Name
## update the .NET View data with a select FirewallSystem sub-property
$arrClusterViews | %{$_.UpdateViewData("Host.ConfigManager.FirewallSystem.FirewallInfo.DefaultPolicy")}
## for each host in the given cluster, call RefreshFirewall() method
$arrClusterViews | %{$_.LinkedView.Host} | % {$_.ConfigManager.LinkedView.FirewallSystem.RefreshFirewall()}

The differences in code between the two ways:
  • Lines 04-07 in 1st way vs. Line 04 in 2nd way:  The first way uses the standard way of additionally calling Get-View for each cluster to get the HostSystems, and then again for each HostSystem to get the FirewallSystem objects.  The second way uses one UpdateViewData() call to make the FirewallSystem managed objects available from the original array of cluster managed objects.
To see the speed differences for the main info-gathering portion of each way, I measured these two pieces mentioned above:  lines 04-07 for the first way, and line 04 for the second way.  UpdateViewData() wins.

Get-View vs. UpdateViewData() -- Speed testing results in a couple of different environment sizes:
Method usedNum. clustersNum. HostSystemsTime to run (sec)Total Get-View calls
additional Get-View5219.4727
UpdateViewData()5211.391
additional Get-View 3619170.85228
UpdateViewData()361918.201


The speed gain comes, in part, from the sheer number of calls being made that require a round trip request to the given vCenter server(s).  To get additional managed object info using Get-View, there is a Get-View call involved for every [group of] managed object references (MORefs).  Conversely, there is but one (1) call to communicate with vCenter for all "child" managed objects involved when UpdateViewData() is used.

This is not the most obvious route at first, but eventually you can see how to use the method to update managed object view data, and then access the nested managed objects' views using "LinkedView" objects that then reside "in" the parent managed object as properties.

The reward for successfully employing the technique:  speed.  The PowerCLI blog post reports a 100x speed increase in the given example versus using multiple Get-View calls to get nested managed object views.  100x -- that is speeding up a script that takes five (5) minutes to take only three (3) seconds -- FaF!  This speed increase varies based on how many Get-View calls are saved/avoided, what they are retrieving, etc, but that kind of potential speed gain demands attention.

For some further info:  I used this technique in a VMware communities thread about VM portgroup inventory (yes -- December 2011; it has taken me a while to get this post together).  It shows other examples of using the resulting LinkedView properties.  And, there is good info in the PowerCLI lab guide from the 2011 VMworld labs.  The PowerCLI Blog post about PowerCLI Lab at VMWorld 2011 links to the lab manual PDF.  See pages 91-93.

How much faster is your script about to be?

27 August 2012

Power Saving, PSODs, ESXi 5, and Intel E7 CPUs

Strangely enough, I have not found much on the Internet about this particular issue:


Why is that strange?  Primarily because I have a hard time believing we are the only ones using ESXi 5.0 U1 on HP DL580 G7 servers, which appears to be one of the combinations where you would have this issue.  And actually, the problem is apparently more widespread than that.  Rumor is that it affects any server with the Intel Xeon E7 or 75xx CPU in it.  By now you are surely wanting more details.  Read on...

At a client site, we started receiving new HP DL580 G7 servers.  The first use was for VDI and in that environment we weren't (and still aren't) running ESXi 5 yet.  Those happened to have the Intel X7560 CPUs.  Let's ignore that for now.  Later we started deploying new HP DL580 G7 servers with the Intel E7-L8867 in the server environment, which was already running ESXi 5.0 U1.  This is when the problems began.  At seemingly random intervals, we'd receive the PSOD shown at the top of this post.  Time for an SR to VMware.

In short, new power management features (in the form of CPU throttling as I understand it) were added with ESXi 5 and VMware attempts to utilize them by default.  In our case, we have our BIOS set to a mode that is supposed to not allow the OS (ESXi) any control over the CPU settings, but it does not seem to matter in this case.  Per VMware, even in that scenario, SMIs (System Management Interupts) are still generated and the CPUs are not responding to them in a timely fashion (hence the "didn't have a heartbeat" messages in the PSOD message) and that's when VMware triggers the PSOD.

So at this point I felt like VMware was pointing the finger at HP because they said their internal PR only listed HP DL580 G7s as the affected servers.  Later on it seemed HP was pointing the finger back at VMware.  I, of course, had a support case open with both vendors and at one point HP released a new BIOS update that was rumored to address the problem.  The release notes read:

Problems Fixed:
Addressed a processor issue with Intel Xeon 7500-series Processors and Intel Xeon E7-series Processors that may result in unpredictable system behavior including application level errors, system hangs, Windows blue screens, Linux kernel panics, or a VMware ESX Purple Screen of Death (PSoD).  This issue is not unique to HP ProLiant servers and could impact any system using affected processors.  This revision of the System ROM contains an updated version of Intel's microcode for affected processors that addresses this issue.  The fix for this issue does not impact performance.  Due to the potential severity of the issue addressed in this revision of the System ROM, this System ROM upgrade is considered a critical fix.  HP strongly recommends an immediate update to firmware revisions with required critical fixes.

At this point I was very excited thinking that someone is actually addressing the issue.  Unfortunately, I received a PSOD on one of the servers in less than 24 hours after applying this BIOS update.  Maybe the issue with the Intel CPUs the release notes speak of was something entirely different.

From the beginning, both VMware and HP had a workaround I could apply, but as our servers were not in production yet, I wanted to spend the extra time troubleshooting the issue in hopes for a proper, long-term fix.  But, fast-forward to the end, VMware eventually started getting reports of other vendors' servers being affected--also with the Intel Xeon E7 CPU.

So the bottom line is, VMware has decided that they are going to just disable the power management features that are causing this issue as of ESXi 5.0 U2 when it is released.  Supposedly the code is being re-written for ESXi 6.0 and they are confident the issue will be resolved.  In addition, I was told they aren't even completely sure it will only affect the aforementioned Intel Xeon E7 / 75xx CPUs, so we've made the decision to disable the power management feature on all of our servers.

The setting to disable is an advanced VMkernel boot setting that can be found under the "Software > Advanced Settings > VMkernel > Boot" section and is named "VMkernel.Boot.usePCC" (PCC apparently stands for Power Collaborative Control).  Simply uncheck that option if using the vSphere Client to make the change.  VMware claims this requires a reboot to take effect, though I've seen no indication that this is the case.  Of course, vNugglets.com uses PowerCLI to make the change at build time with the following one-liner:

Set-VMHostAdvancedConfiguration -VMHost <ESXi host name> -Name "VMkernel.Boot.usePCC" -Value $false

However you prefer to do it, if you use Host Profiles, you'll need to keep one more thing in mind.  By default the Host Profile setting "Power system > CPU Policy" is set to "Balanced" if created from a default build of ESXi 5, but if you were to make the above change and then you created the Host Profile from the host, it would be set to "User must explicily choose the policy option" (and yes, that's a VMware typo, not mine).  If you are going back and retroactively changing all your hosts to disable PCC you may find your hosts are out of compliance with the attached Host Profile as it is expecting whatever power management policy you had previously specified and that feature is essentially disabled now.  As a matter of fact, if you tried to apply the Host Profile with a "Power system > CPU Policy" set against a host with PCC disabled, you'd see an error message like so:


To get around this, you'll need to either update the Host Profile from the modified reference host, or simply edit the Host Profile and change the "Power system > CPU Policy" value to "User must explicily choose the policy option" as hinted at earlier.  Then you should be able to re-apply the Host Profile to the affected hosts and it'll disable the option for you, or you can just script it with PowerCLI and verify the hosts are now compliant with your freshly updated Host Profile.

Hopefully this blog post helps someone else that may run into this particular issue with the Intel E7 CPU on ESXi 5.0 U1.