En este articulo voy a realizar un ejemplo de caso práctico de uso de PowerCLI Get-View, con una solución a uno de sus principales inconvenientes: solo obtenemos las claves y no los nombres.
Es parte de la serie de artículos sobre Get-VM y Get-View:
- GET-VM PARTE I: LO BASICO
- GET-VM PARTE II: RENDIMIENTO
- GET-VIEW PARTE I: LO NO TAN BASICO
- GET-VIEW PARTE II: CASO PRÁCTICO
- GET-VIEW PARTE III: RENDIMIENTO
- GET-VM VS GET-VIEW: CONCLUSIONES
¿Y cual es mejor manera de resolver los nombres de los objetos?
Caso Practico
Vamos con un ejemplo. Quiero obtener un listado con todas las máquinas de vCenter con los siguientes campos:
Campo | Propiedad Get-View | Descripción |
Clave | Moref | Clave de la maquina en vCenter. Siempre retorna, y en este caso ademas da error si lo especificamos explicitamente |
Nombre | Name | Nombre de la maquina |
Estado | Runtime.PowerState | Si la maquina esta encendida o apagada. |
Tools | Guest.ToolsStatus | El estado de las tools |
CPU | Config.Hardware.NumCPU | Numero de cores asignados. |
RAM | Config.Hardware.MemoryMB | RAM asignada, en MB. |
Espacio | Summary.Storage.Committed | Espacio consumido en bytes. |
Notas | Config.Annotation | Las notas de la maquinas |
Folder | Parent | El parent de una maquina es su folder |
Host | Runtime.Host | Host ESXI donde está corriendo la máquina. |
Cluster | ??? | Este dato no lo tenemos. Es el atributo Parent del Host. Es decir deberemos obtener el host, y una vez que lo tengamos pedir el parent del host. |
Solución 1: SubConsultas
Para poder resolver los nombres podriamos lanzar una consulta (cmdlet) por cada clave.
Primero genero un array con las propiedades que quiero obtener:
$propiedades = @( "Name", "Runtime.PowerState", "Guest.ToolsStatus", "Config.Hardware.NumCPU", "Config.Hardware.MemoryMB", "Summary.Storage.Committed", "Config.Annotation", "Parent", "Runtime.Host" )
Y ahora ejecuto Get-View seleccionando solo los campos para obtener un Custom Object con las propiedades definidas.
Recordar que Get-View retorna todos los atributos, aunque estén vacíos. Por eso tenemos que seleccionarlos.
$vms = get-view -ViewType VirtualMachine -Property $propiedades| Select-Object @{n="Nombre"; e={$_.Name}}, @{n="Clave";e={$_.Moref}}, @{n="Estado"; e={$_.Runtime.PowerState}}, @{n="Tools"; e={$_.guest.ToolsStatus}}, @{n="CPU"; e={$_.Config.Hardware.NumCPU}}, @{n="RAM"; e={$_.Config.Hardware.MemoryMB}}, @{n="Espacio"; e={$_.Summary.Storage.Committed}}, @{n="Notas"; e={$_.Config.Annotation}}, @{n='Folder';e={(get-view -Id $_.Parent -property name).Name}}, @{n='Host';e={(get-view -Id $_.Runtime.Host -property parent).Name}}, @{n='Cluster';e={get-view -Id (get-view -Id $_.Runtime.Host -property parent).Parent -Property Name}} $vms
Para resolver Folder, Host he usado subconsultas Get-View con el Id, consultas que son muy rápidas: Entre 20 y 50ms.
(get-view -Id $_.Parent -property name).Name (get-view -Id $_.Runtime.Host -property parent).Name
En el caso del cluster he tenido que usar una subconsulta (parent del host) de la subconsulta (host de la maquina). Un poco lio si. También podría haberlo separado en dos sentencias por claridad, tal que así:
$hostParentId = (get-view -Id $_.Runtime.Host -property parent).Parent; get-view -Id $hostParentId -Property Name
Podría haber usado también get-Folder, get-VMHost y get-Cluster respectivamente, pero me ha parecido mas apropiado seguir usando Get-View como ejemplo adicional.

Resultado = 270 ms por maquina
El problema de esta solución, es que por cada máquina va a ejecutar 5 consultas Get-View. La principal más cuatro subconsultas.
Si este ejemplo ha tardado 270ms y tengo 6000 máquinas = 1620 segundos -> 27 minutos.
Esta solución no es nada eficiente!
En general, para todo tipo de scripts en los que procesamos muchos objetos, tenemos que minimizar todo lo posible el uso de subconsultas, porque pasamos de unos pocos milisegundos a muchos minutos.
¿Y cómo resuelvo entonces el nombre del cluster o del host?
Solución 2: UpdateViewData
Para esta solución voy a usar el evento UpdateViewData() de las vistas, con el que podemos pedir otros datos de cualquier otro id que contenga nuestra vista y lo guardara en una propiedad llamada LinkedView.
UpdateViewData admite varios propiedades y de vistas diferentes en la misma sentencia. El nombre de el/los campo/s a pedir será:
Ruta de la propiedad en la vista actual + Nombre de la propiedad del objeto que queremos traer.
Y la ruta del LinkedView:
Ruta de la propiedad + LinkedView + Propiedad del objeto traido.
Mejor vemos unos ejemplos que es mucho mas facil de entender:
Quiero pedir los nombres de host de un cluster y donde lo guarda el LinkedView:
$clusters = get-view -ViewType ClusterComputeResource $clusters.UpdateViewData("Host.name") $clusters.LinkedView.Host.Name
En este otro ejemplo quiero el nombre de las maquinas que alberga el host y donde ha guardado el LinkedView
$clusters.UpdateViewData("Host.VM.Name") $clusters.LinkedView.Host.LinkedView.VM.Name
Ahora que he explicado como utilizar UpdateViewData(), vamos con la solución al caso practico:
Primero obtengo la vista con las propiedades seleccionados:
$vms = get-view -ViewType VirtualMachine -Property $propiedades

Resultado = 6,3 segundos para traer la vista con nuestras propiedades.
Ahora ejecuto el UpdateViewData():
$vms.UpdateViewData("Parent.Name", "Runtime.Host.Name", "Runtime.Host.Parent.Name")

Resultado = 114 segundos
Y para terminar, saco el listado con los LinkedView de las nombres de Folder, Host y Cluster:
$vms | Select-Object @{n="Nombre"; e={$_.Name}}, @{n="Clave"; e={$_.Moref}}, @{n="Estado"; e={$_.Runtime.PowerState}}, @{n="Tools"; e={$_.guest.ToolsStatus}}, @{n="CPU"; e={$_.Config.Hardware.NumCPU}}, @{n="RAM"; e={$_.Config.Hardware.MemoryMB}}, @{n="Espacio"; e={$_.Summary.Storage.Committed}}, @{n="Notas"; e={$_.Config.Annotation}}, @{n="Folder"; e={$_.LinkedView.Parent.Name}}, @{n="Host"; e={$_.Runtime.LinkedView.Host.Name}}, @{n="Cluster"; e={$_.Runtime.LinkedView.Host.LinkedView.Parent.Name}}

En 2 minutos exactos hemos obtenido todas las propiedades que necesitamos para las 6000 maquinas.
Es una solución muchísimo mas rápida que las subconsultas y muy versátil para cualquier dato. Lógicamente, cuantos mas datos de otras vistas se pidan con el evento UpdateViewData, mas tardara.
Solución 3: HashTables
Para esta solución voy a usar uno de los tipos de objetos más útiles de PowerShell: hashtables.
En PowerShell un hashtable es un array asociativo, también llamado diccionario, en la que tenemos un conjunto de pares Key = Value. Permite direccionar un elemento directamente con su Key, sin tener que recorrer todo su contenido, por lo que es prácticamente inmediato.
Para saber más sobre este tipo de array os recomiendo la lectura de este articulo imprescindible: Powershell: Everything you wanted to know about hashtables de Kevin Marquette
Veamos cómo hacerlo: Voy a obtener con Get-View TODOS los nombres de TODOS los Folders, Host y Cluster y cargar su contenido en diferentes HashTables.
Empiezo con los folders: Genero un hashtable con el nombre como Valor y su MoRef como Key. El resultado será algo así:
$hashFolder = @{} get-view -ViewType Folder -Property Name | %{ $hash.Add($_.moref.value, @{"Name"=$_.name}) }

Resultado: 200 ms para 758 objetos.
Hago lo mismo con TODOS los host ESXI de vCenter. En este caso además obtengo también su Parent, que contiene el MoRef del cluster:
$hashHost = @{} get-view -ViewType HostSystem -Property Name, Parent | %{ $hashHost.Add($_.moref.value, @{"Name"=$_.name; "Parent"=$_.parent.value}) }

Resultado: 130 ms para 111 host.
Y ahora lo mismo con los clusters:
$hashCluster = @{} get-view -ViewType ClusterComputeResource -Property Name | %{ $hashCluster.Add($_.moref.value, @{"Name"=$_.name}) }

Resultado: 90 ms para 12 clusters.
Ahora que tengo toda la información que necesitamos en los hashtables, solo tenemos que obtener el valor usando la clave (key). Por ejemplo:
$hashFolder["group-v536545"] $hashCluster["domain-c105337"] $hashHost["host-331167"]
Y como parece que me va a ser muy útil en el futuro, creo una función genérica y «universal» que como parámetro le pasamos el tipo de vista:
function Get-HashView { param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][String]$Tipo ) $hash = @{} get-view -ViewType $Tipo -Property Name, Parent | %{ $hash.Add($_.moref.value, @{"Name"=$_.name; "Parent"=$_.parent.value} )} return $hash }
Y que luego podemos usar así:
$hashFolders = Get-HashView -Tipo Folder $hashHosts = Get-HashView -Tipo HostSystem $hashClusters = Get-HashView -Tipo ClusterComputeResource

Resultado: 442 ms para cargar los 3 hashtables.
Y ahora vuelvo a ejecutar el Get-View de máquinas, pero esta vez usaremos los hashtables cargados:
$vms = get-view -ViewType VirtualMachine -Property $propiedades | Select-Object @{n="Nombre"; e={$_.Name}}, @{n="Clave";e={$_.Moref}}, @{n="Estado"; e={$_.Runtime.PowerState}}, @{n="Tools"; e={$_.guest.ToolsStatus}}, @{n="CPU"; e={$_.Config.Hardware.NumCPU}}, @{n="RAM"; e={[math]::round(($_.Config.Hardware.MemoryMB) / 1KB)}}, @{n="Espacio"; e={[math]::round(($_.Summary.Storage.Committed) / 1GB)}}, @{n="Notas"; e={$_.Config.Annotation}}, @{n='Folder';e={$hashFolders[$_.Parent.Value].name}}, @{n='Host';e={$hashHosts[$_.Runtime.Host.Value].name}}, @{n='Cluster';e={$hashClusters[$hashHosts[$_.Runtime.Host.Value].parent].name}}

Resultado: 7 segundos para obtener 5892 máquinas.
Eureka!!! Todas las propiedades que necesito de TODAS las maquinas en apenas unos segundos!
Pongo todo el código junto para que quede mas claro:
function Get-HashView { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [String]$Tipo ) $hash = @{} get-view -ViewType $Tipo -Property Name, Parent | %{ $hash.Add($_.moref.value, @{"Name"=$_.name; "Parent"=$_.parent.value} )} return $hash } $hashFolders = Get-HashView -Tipo Folder $hashHosts = Get-HashView -Tipo HostSystem $hashClusters = Get-HashView -Tipo ClusterComputeResource $propiedades = @( "Name", "Runtime.PowerState", "Guest.ToolsStatus", "Config.Hardware.NumCPU", "Config.Hardware.MemoryMB", "Summary.Storage.Committed", "Config.Annotation", "Parent", "Runtime.Host" ) $vms = get-view -ViewType VirtualMachine -Property $propiedades | Select-Object @{n="Nombre"; e={$_.Name}}, @{n="Clave";e={$_.Moref}}, @{n="Estado"; e={$_.Runtime.PowerState}}, @{n="Tools"; e={$_.guest.ToolsStatus}}, @{n="CPU"; e={$_.Config.Hardware.NumCPU}}, @{n="RAM"; e={[math]::round(($_.Config.Hardware.MemoryMB) / 1KB)}}, @{n="Espacio"; e={[math]::round(($_.Summary.Storage.Committed) / 1GB)}}, @{n="Notas"; e={$_.Config.Annotation}}, @{n='Folder';e={$hashFolders[$_.Parent.Value].name}}, @{n='Host';e={$hashHosts[$_.Runtime.Host.Value].name}}, @{n='Cluster';e={$hashClusters[$hashHosts[$_.Runtime.Host.Value].parent].name}} $vms.count $vms |Out-GridView
Como hemos visto, con esta ultima solución podremos resolver el nombre de cualquier MoRef con apenas unos milisegundos de penalización, y superar así una traba habitual de Get-View, pero con las ventajas de su mejor virtud: En unos segundos tengo el informe completo!
Habrá casos en la que no sea practica esta solución y es mejor usar el evento UpdateViewData. Y en otros muchos, quizás lo mejor sea una combinación de ambas soluciones. Lo importante es conocer las opciones de las que disponemos y saber utilizarlas en cada momento.
En el siguiente articulo continuare con el ultimo articulo de Get-View con el Rendimiento en diferentes escenarios y pruebas.
Cualquier duda o pregunta, responderé encantando en los comentarios.