Showing posts with label Get-View. Show all posts
Showing posts with label Get-View. Show all posts

06 December 2014

Get VMs' Current EVC Mode via PowerCLI

EVC Mode
Need to report on what is the current EVC mode in which some VMs are running?  We recently did, and out popped a bit of PowerShell/PowerCLI code to do it, of course. As part of some migrations and retirements, we were moving VMs from clusters at one EVC mode (lower) to clusters with higher- or no EVC mode, but were not scheduling power-cycles for the VMs at migration time, so as to keep smaller the scope of work for that event.  Rather than have to keep track of "here are the X number of VMs that are at EVC mode [low]", which would need power-cycled at some later date to bring them up to the EVC mode of their new cluster, PowerCLI is there for us.
It is pretty straightforward, but is handy for identifying those VMs that are, say, at a different EVC mode than is configured on the cluster.  So, the code:
function Get-VMEVCMode {
    <#  .Description
        Code to get VMs' EVC mode and that of the cluster in which the VMs reside.  May 2014, vNugglets.com
        .Example
        Get-VMEVCMode -Cluster myCluster | ?{$_.VMEVCMode -ne $_.ClusterEVCMode}
        Get all VMs in given clusters and return, for each, an object with the VM's- and its cluster's EVC mode, if any
        .Outputs
        PSCustomObject
    #>
    param(
        ## Cluster name pattern (regex) to use for getting the clusters whose VMs to get
        [string]$Cluster_str = ".+"
    )

    process {
        ## get the matching cluster View objects
        Get-View -ViewType ClusterComputeResource -Property Name,Summary -Filter @{"Name" = $Cluster_str} | Foreach-Object {
            $viewThisCluster = $_
            ## get the VMs Views in this cluster
            Get-View -ViewType VirtualMachine -Property Name,Runtime.PowerState,Summary.Runtime.MinRequiredEVCModeKey -SearchRoot $viewThisCluster.MoRef | Foreach-Object {
                ## create new PSObject with some nice info
                New-Object -Type PSObject -Property ([ordered]@{
                    Name = $_.Name
                    PowerState = $_.Runtime.PowerState
                    VMEVCMode = $_.Summary.Runtime.MinRequiredEVCModeKey
                    ClusterEVCMode = $viewThisCluster.Summary.CurrentEVCModeKey
                    ClusterName = $viewThisCluster.Name
                })
            } ## end foreach-object
        } ## end foreach-object
    } ## end process
} ## end function

And, some examples: Get EVC mode info for all of the VMs in this cluster:
PS vN:\> Get-VMEVCMode -Cluster myCluster
Name                   PowerState   VMEVCMode        ClusterEVCMode   ClusterName
----                   ----------   ---------        --------------   -----------
somesvr01.dom.com       poweredOn   intel-westmere   intel-westmere   OurCluster
somesvrlm1.dom.com      poweredOn   intel-penryn     intel-westmere   OurCluster
somesvrbs03.dom.com    poweredOff                    intel-westmere   OurCluster
somesvrxp.dom.com       poweredOn   intel-penryn     intel-westmere   OurCluster
...


For all of the VMs in this cluster where their EVC mode does not match the config'd cluster EVC mode, return their info:
PS vN:\> Get-VMEVCMode -Cluster myCluster | ?{($_.VMEVCMode -ne $_.ClusterEVCMode) -and ($_.PowerState -eq "poweredOn")}
Name                   PowerState   VMEVCMode      ClusterEVCMode   ClusterName
----                   ----------   ---------      --------------   -----------
somesvrlm1.dom.com      poweredOn   intel-penryn   intel-westmere   OurCluster
somesvrp83.dom.com      poweredOn   intel-penryn   intel-westmere   OurCluster
somesvra.dom.com        poweredOn   intel-merom    intel-westmere   OurCluster
...


Note:  The function does expect that you are using at least PowerShell v3, which surely everyone is well beyond by now, anyway.  But, if not, the "[ordered]" cast should be the only thing one would need to take out in order to use in older PowerShell versions.

Straightforward, and uses everyone's favorite PowerCLI cmdlet, Get-View, so you know that it is FaF!  Enjoy.

17 July 2014

Get VMHost FC HBA WWN Info Most Quickly with PowerCLI

VMHba WWN Info
People know about getting VMHost HBA info via PowerCLI -- there are plenty of posts about doing so, generally via Get-VMHostHba.  Since that works great, there is barely a reason to write something else to do the same thing, save one reason that is near and dear to our vNugglets hearts:  speed.  We made this a couple of years ago, have tweaked it a few times, and now it seems like time to post it (like, maybe it will be helpful in an upcoming post, or something?  So exciting).

This function uses everybody's favorite PowerCLI cmdlet Get-View.  The speed reward:  well worth it.  For example:
Technique
Time, 28 hosts
Time, 270 hosts
Get-VMHostHba
61.9s
720s
Get-VMHostHBAWWN
1.32s
14.1s
improvement:
46x
51x

More than 45 times faster?  I'm in.  Now for the function, complete with examples in comment-based help:  (double-click anywhere in the code to Select All)
function Get-VMHostHBAWWN {
<#    .Description
    Get the Port- and Node WWN(s) for HBA(s) in host(s). Feb 2012, vNugglets
    .Example
    Get-VMHostHBAWWN -VMHost myhost0.dom.com
    VMHostName       DeviceName  HBAPortWWN               HBANodeWWN               HBAStatus
    ----------       ----------  ----------               ----------               ---------
    myhost0.dom.com  vmhba1      10:00:00:00:00:00:00:ca  20:00:00:00:00:00:00:ca  online
    myhost0.dom.com  vmhba2      10:00:00:00:00:00:00:83  20:00:00:00:00:00:00:83  online
    Get the HBA WWNs for hosts whose name match the pattern myhost0.dom.com
    .Example
    Get-VMHostHBAWWN -VMHost ^my.+
    VMHostName       DeviceName  HBAPortWWN               HBANodeWWN               HBAStatus
    ----------       ----------  ----------               ----------               ---------
    myhost0.dom.com  vmhba1      10:00:00:00:00:00:00:ca  20:00:00:00:00:00:00:ca  online
    myhost0.dom.com  vmhba2      10:00:00:00:00:00:00:83  20:00:00:00:00:00:00:83  online
    mytest1.dom.com  vmhba1      10:00:00:00:00:00:00:da  20:00:00:00:00:00:00:da  online
    mytest0.dom.com  vmhba2      10:00:00:00:00:00:00:93  20:00:00:00:00:00:00:93  online
    Get the HBA WWNs for hosts whose name match the pattern ^my.+
    .Example
    Get-Cluster mycluster | Get-VMHostHBAWWN
    ...
    Get the HBA WWNs for hosts in the cluster "mycluster"
    .Outputs
    PSCustomObject
#>
[CmdletBinding()]param(
    ## Name pattern of the host for which to get HBA info (accepts regex patterns)
    [parameter(Mandatory=$true,ParameterSetName="SearchByHostName")][string]$VMHostName_str,
    ## Name pattern of the cluster for whose hosts to get HBA info (accepts regex patterns)
    [parameter(Mandatory=$true,ParameterSetName="SearchByCluster",ValueFromPipelineByPropertyName)][Alias("Name")][string]$ClusterName_str
) ## end param
Begin {
    ## helper function for formatting WWN as hex string with colon-separators
    function _Format-AsHexWWNString {
        param([parameter(Mandatory=$true)][long]$WWN_long)
        (("{0:x}" -f $WWN_long) -split "(\w{2})" | ?{$_ -ne ""}) -join ":"
    } ## end function
}

Process {
    ## params for the Get-View expression for getting the View objects
    $hshGetViewParams = @{
        ViewType = "HostSystem"
        Property = "Name", "Config.StorageDevice.HostBusAdapter"
    } ## end hashtable
    Switch ($PSCmdlet.ParameterSetName) {
        ## if host name pattern was provided, filter on it in the Get-View expression
        "SearchByHostName" {$hshGetViewParams["Filter"] = @{"Name" = $VMHostName_str}; break;} ## end case
        ## if cluster name pattern was provided, set it as the search root for the Get-View expression
        "SearchByCluster" {$hshGetViewParams["SearchRoot"] = (Get-Cluster $ClusterName_str).Id; break;}
    } ## end switch

    Get-View @hshGetViewParams | Foreach-Object {
        $viewHost = $_
        $viewHost.Config.StorageDevice.HostBusAdapter | Where-Object {$_ -is [VMware.Vim.HostFibreChannelHba]} | Foreach-Object {
            New-Object -TypeName PSObject -Property ([ordered]@{
                VMHostName = $viewHost.Name
                DeviceName = $_.Device
                HBAPortWWN = _Format-AsHexWWNString -WWN $_.PortWorldWideName
                HBANodeWWN = _Format-AsHexWWNString -WWN $_.NodeWorldWideName
                HBAStatus = $_.Status
            }) ## end new-object
        } ## end foreach-object
    } ## end foreach-object
} ## end process
} ## end function

Not terribly fancy, but something to get it done more quickly (FaF, in fact).  And, handy for other things down the road.  Like, oh, using the WWN info when creating some initiator groups on your XtremIO array?  What a good idea.

Oh, and if you have not yet seen/used parameter splatting, this provides a decent example of that, too -- the hashtable of parameters used on line 54.  Enjoy!

29 December 2013

Get VM Disks and RDMs via PowerCLI

Need to get info about a VM's hard disks?  Like, "regular" virtual disks and RDMs, in a handy and fast manner?  We need to, occasionally, and so wrote a function to do so (a while ago -- just finally posting it).  It gets things like the hard disk name, the SCSI ID, the storage device display name, the disk size, the SCSI canonical name, and [optionally] the full datastore path for the disk files.  Here again, not the first bit of code around to retrieve such things, but a version that is written to do things most quickly.

The script:
<# .Description
    Function to get a VM's hard disk and RDM info
    Originally from Sep 2011, updated Dec 2013 -- vNugglets.com
    .Example
    Get-VMDiskAndRDM -vmName someVM -ShowVMDKDatastorePath | ft -a
    VMName HardDiskName ScsiId DeviceDisplayName SizeGB ScsiCanonicalName                    VMDKDStorePath
    ------ ------------ ------ ----------------- ------ -----------------                    --------------
    someVM Hard disk 1  0:0                          50                                      [dstore0] someVM/someVM.vmdk
    someVM Hard disk 2  1:0    someVM-/log_dir       20 naa.60000945618415615641111111111111 [dstore0] someVM/someVM_1.vmdk
    Get the disks (including RDMs) for "someVM", and include the datastore path for each VMDK
#>
function Get-VMDiskAndRDM {
    param(
        ## name pattern of the VM guest for which to get info
        [parameter(Mandatory=$true)][string]$vmName_str = "myVM",
        ## switch to specify that VMDK's datastore path should also be returned
        [switch]$ShowVMDKDatastorePath_sw
    )

    ## the cool, FaF way (using .NET View objects)
    ## get the VM object(s)
    $arrVMViewsForStorageInfo = Get-View -Viewtype VirtualMachine -Property Name, Config.Hardware.Device, Runtime.Host -Filter @{"Name" = "$vmName_str"}
    if (($arrVMViewsForStorageInfo | Measure-Object).Count -eq 0) {Write-Warning "No VirtualMachine objects found matching name pattern '$vmName_str'"; exit} ## end if

    $arrVMViewsForStorageInfo | %{
        $viewVMForStorageInfo = $_
        ## get the view of the host on which the VM currently resides
        $viewHostWithStorage = Get-View -Id $viewVMForStorageInfo.Runtime.Host -Property Config.StorageDevice.ScsiLun

        $viewVMForStorageInfo.Config.Hardware.Device | ?{$_ -is [VMware.Vim.VirtualDisk]} | %{
            $hdThisDisk = $_
            $oScsiLun = $viewHostWithStorage.Config.StorageDevice.ScsiLun | ?{$_.UUID -eq $hdThisDisk.Backing.LunUuid}
            ## the properties to return in new object
            $hshThisVMProperties = @{
                VMName = $viewVMForStorageInfo.Name
                ## the disk's "name", like "Hard disk 1"
                HardDiskName = $hdThisDisk.DeviceInfo.Label
                ## get device's SCSI controller and Unit numbers (1:0, 1:3, etc)
                ScsiId = &{$strControllerKey = $_.ControllerKey.ToString(); "{0}`:{1}" -f $strControllerKey[$strControllerKey.Length - 1], $_.Unitnumber}
                DeviceDisplayName = $oScsiLun.DisplayName
                SizeGB = [Math]::Round($_.CapacityInKB / 1MB, 0)
                ScsiCanonicalName = $oScsiLun.CanonicalName
            } ## end hsh
            ## the array of items to select for output
            $arrPropertiesToSelect = "VMName,HardDiskName,ScsiId,DeviceDisplayName,SizeGB,ScsiCanonicalName".Split(",")
            ## add property for VMDKDStorePath if desired
            if ($ShowVMDKDatastorePath_sw -eq $true) {$hshThisVMProperties["VMDKDStorePath"] = $hdThisDisk.Backing.Filename; $arrPropertiesToSelect += "VMDKDStorePath"}
            New-Object -Type PSObject -Property $hshThisVMProperties | Select $arrPropertiesToSelect
        } ## end foreach-object
    } ## end foreach-object
} ## end function

Some example usage:
PS vN:\> Get-VMDiskAndRDM -vmName myVM01 -ShowVMDKDatastorePath | ft -a

VMName HardDiskName ScsiId DeviceDisplayName SizeGB ScsiCanonicalName                    VMDKDStorePath
------ ------------ ------ ----------------- ------ -----------------                    --------------
myVM01 Hard disk 1  0:0                          50                                      [dstore0] myVM01/myVM01.vmdk
myVM01 Hard disk 2  1:0    myVM01-/data001      660 naa.60000946665554443331111111111111 [dstore0] myVM01/myVM01_1.vmdk


The disks with no values for the DeviceDisplayName or ScsiCanonicalName properties are the "regular" virtual disks, and the others are RDMs.  And, this VM has hard disks on two separate SCSI controllers.

Note:  the vmName parameter is used as a regular expression when getting the .NET View object of the given VM.  As such, one can use a pattern, and can get info on multiple VMs that share the same name pattern.

And, that is that:  some juicy disk/RDM info for VMs, and on the quick!  Thanks, Get-View, for keeping things FaF!  Enjoy

BTW:  related post, Get VM by RDM with PowerCLI:  how to get the VM that is using a particular SAN storage device as an RDM.

24 December 2013

Get VM By RDM with PowerCLI

Sometimes one needs to find a VM by RDM.  This is not a new thought, and there are plenty of spots on the internet that give ways to achieve this.  When this need arose for us a couple of years ago, we found no spot that gave a way to achieve this quickly.  So, with vNugglets' focus on speed, I put together a function to do just that:  quickly find the VM that is using a given SAN device as an RDM.

Then, just recently, a VMware Communities forum post made me realize that we had not yet shared this function with the world.  So, here we go:
<#    .Description
    Function to find what VM (if any) is using a LUN as an RDM, based on the LUN's SCSI canonical name. Assumes that the best practice of all hosts in a cluster seeing the same LUNs is followed.
    vNugglets, originally from Nov 2011
    .Example
    Get-VMWithGivenRDM -CanonicalName naa.60000970000192602222333031314444 -Cluster myCluster0 | ft -a
    Find a VM using the given LUN as an RDM, formatting output in auto-sized table.  Output would be something like:
    VMName            VMDiskName     DeviceDisplayName   CanonicalName
    ------            ----------     -----------------   -------------
    myvm033.dom.com   Hard disk 10   myvm033-data3FS     naa.60000970000192602222333031314444
    .Outputs
    Zero or more PSObjects with info about the VM and its RDM
#>
function Get-VMWithGivenRDM {
    param(
        ## Canonical name of the LUN in question
        [parameter(Mandatory=$true)][string[]]$CanonicalNameOfRDMToFind_arr,
        ## Cluster whose hosts see this LUN
        [parameter(Mandatory=$true)][string]$ClusterName_str
    ) ## end param

    ## get the View object of the cluster in question
    $viewCluster = Get-View -ViewType ClusterComputeResource -Property Name -Filter @{"Name" = "^$([RegEx]::escape($ClusterName_str))$"}
    ## get the View of a host in the given cluster (presumably all hosts in the cluster see the same storage)
    $viewHostInGivenCluster = Get-View -ViewType HostSystem -Property Name -SearchRoot $viewCluster.MoRef | Get-Random
    ## get the Config.StorageDevice.ScsiLun property of the host (retrieved _after_ getting the View object for speed, as this property is only retrieved for this object, not all hosts' View objects)
    $viewHostInGivenCluster.UpdateViewData("Config.StorageDevice.ScsiLun")

    ## if matching device(s) found, store some info for later use
    $arrMatchingDisk = &{
    ## get the View objects for all VMs in the given cluster
    Get-View -ViewType VirtualMachine -Property Name, Config.Hardware.Device -SearchRoot $viewCluster.MoRef | %{$viewThisVM = $_
        ## for all of the RDM devices on this VM, see if the canonical name matches the canonical name in question
        $viewThisVM.Config.Hardware.Device | ?{($_ -is [VMware.Vim.VirtualDisk]) -and ("physicalMode","virtualMode" -contains $_.Backing.CompatibilityMode)} | %{
            $hdThisDisk = $_
            $lunScsiLunOfThisDisk = $viewHostInGivenCluster.Config.StorageDevice.ScsiLun | ?{$_.UUID -eq $hdThisDisk.Backing.LunUuid}
            ## if the canonical names match, create a new PSObject with some info about the VirtualDisk and the VM using it
            if ($CanonicalNameOfRDMToFind_arr -contains $lunScsiLunOfThisDisk.CanonicalName) {
                New-Object -TypeName PSObject -Property @{
                    VMName = $viewThisVM.Name
                    VMDiskName = $hdThisDisk.DeviceInfo.Label
                    CanonicalName = $lunScsiLunOfThisDisk.CanonicalName
                    DeviceDisplayName = $lunScsiLunOfThisDisk.DisplayName
                } ## end new-object
            } ## end if
        } ## end foreach-object
    } ## end foreach-object
    } ## end scriptblock

    ## if a matching device was found, output its info
    if ($arrMatchingDisk) {$arrMatchingDisk | Select VMName, VMDiskName, DeviceDisplayName, CanonicalName}
    ## else, say so
    else {Write-Verbose -Verbose "Booo. No matching disk device with canonical name in '$CanonicalNameOfRDMToFind_arr' found attached to a VM as an RDM in cluster '$ClusterName_str'"}
} ## end fn
Some example usage:
PS vN:\> Get-VMWithGivenRDM -CanonicalName naa.60000123412342602222333031314444 -Cluster myMegaCluster

VMName            VMDiskName     DeviceDisplayName   CanonicalName
------            ----------     -----------------   -------------
myvm002.dom.com   Hard disk 13   myvm002-dataFS_7    naa.60000123412342602222333031314444
...

The point of having a Cluster param is to focus the scope of the search (one generally knows to what set of hosts a particular LUN has been presented).

And, some thoughts on how to make this function even better:
  • take cluster input from pipeline
  • add ability to check entire vCenter inventory, but keep things fast by first narrowing scope to only the VMHosts that see given device, then check those hosts' clusters for a VM with RDM using said device (removes need for "Cluster" param)
  • maybe:  add other info to output object, if there are other things that would be useful/valuable
So, how does this do -- FaF for you, too?  Yeah, just as we thought!

21 November 2013

Get VMs by Virtual Port Group with PowerCLI

How to find out what VMs use a particular virtual port group, and quickly?  Well, we have just the thing.  And, it also helps to illustrate the practical use of LinkedViews and the UpdateViewData() method of .NET View objects (about which we posted a while back in Even Faster PowerCLI Code with Get-View, UpdateViewData() and LinkedViews), along with the kind of speed increase that said method brings.

So, the function:
<#  .Description
    Function to get info about what VMs are on a virtual portgroup.  vNugglets, Oct 2013
    Highlights the use of the UpdateViewData() method of a .NET View object
    .Outputs
    PSObject
#>
function Get-VMOnNetworkPortGroup {
    param(
        ## name of network to get; regex pattern
        [parameter(Mandatory=$true)][string]$NetworkName_str
    ) ## end param

    ## get the .NET View objects for the network port groups whose label match the given name
    $arrNetworkViews = Get-View -ViewType Network -Property Name -Filter @{"Name" = $NetworkName_str}
    if (($arrNetworkViews | Measure-Object).Count -eq 0) {Write-Warning "No networks found matching name '$NetworkName_str'"; exit}

    ## get the networks' VMs' names, along with the name of the corresponding VMHost and cluster
    $arrNetworkViews | %{$_.UpdateViewData("Vm.Name","Vm.Runtime.Host.Name","Vm.Runtime.Host.Parent.Name")}
    ## create a new object for each VM on this network
    $arrNetworkViews | %{
        $viewNetwk = $_
        $viewNetwk.LinkedView.Vm | %{
            New-Object -TypeName PSObject -Property @{
                VMName = $_.Name
                NetworkName = $viewNetwk.Name
                VMHost = $_.Runtime.LinkedView.Host.Name
                Cluster = $_.Runtime.LinkedView.Host.LinkedView.Parent.Name
            } | Select-Object VMName,NetworkName,VMHost,Cluster
        } ## end foreach-object
    } ## end foreach-object
} ## end fn
And, some sample usage:
PS vN:\> Get-VMOnNetworkPortGroup -NetworkName "223|237"

VMName        NetworkName   VMHost           Cluster
------        -----------   ------           -------
dubuntu0      223.Prod      esxi01.dom.com   Cluster0
vma5          223.Prod      esxi02.dom.com   Cluster0
vcsa02        223.Prod      esxi02.dom.com   Cluster0
tmpl_test1    237.Dev       esxi12.dom.com   Cluster1
...
This illustrates the use of a regular expression pattern to match virtual networks whose names contain "223" or "237".

And, on the topic of speed increases:
## using standard cmdlets:
PS vN:\> Measure-Command {Get-VirtualPortGroup -Name SomeNetworkName | Get-VM | select name,@{n="VMHostName"; e={$_.VMHost.name}},@{n="ClusterName"; e={$_.VMHost.Parent.Name}}}
...
TotalSeconds      : 25.4870345


## using vNugglets function:
PS vN:\> Measure-Command {Get-VMOnNetworkPortGroup -NetworkName SomeNetworkName}
...
TotalSeconds      : 0.9676149      ## big winner!

So, while similar results can be had with native PowerCLI cmdlets, leveraging this function definitely comes in handy, especially when you are as impatient as we can be here at vNugglets.  Enjoy!

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!

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?