Capítulo 7: Trabajo con WMI

WMI y CIM

Windows PowerShell se incluye de forma predeterminada con cmdlets para trabajar con otras tecnologías, como Instrumental de administración de Windows (WMI). Los cmdlets de WMI están en desuso y no están disponibles en PowerShell 6 y versiones posteriores, pero se tratan aquí, ya que pueden encontrarse en scripts anteriores que se ejecutan en Windows PowerShell. Para el nuevo desarrollo, use los cmdlets CIM en su lugar.

Hay varios cmdlets nativos de WMI que existen en PowerShell sin necesidad de instalar ningún software ni módulo adicional. Get-Command se puede usar para determinar qué cmdlets WMI existen en Windows PowerShell. Los siguientes resultados corresponden a mi equipo del entorno de laboratorio de Windows 10 con la versión 5.1 de PowerShell. Sus resultados pueden diferir en función de la versión de PowerShell que esté ejecutando.

Get-Command -Noun WMI*
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Get-WmiObject                                      3.1.0.0    Microsof...
Cmdlet          Invoke-WmiMethod                                   3.1.0.0    Microsof...
Cmdlet          Register-WmiEvent                                  3.1.0.0    Microsof...
Cmdlet          Remove-WmiObject                                   3.1.0.0    Microsof...
Cmdlet          Set-WmiInstance                                    3.1.0.0    Microsof...

Los cmdlets de Modelo de información común (CIM) se incorporaron en la versión 3.0 de PowerShell. Los cmdlets de CIM están diseñados para usarse en equipos Windows y no Windows.

Todos los cmdlets de CIM están incluidos en un módulo. Para obtener una lista de los cmdlets de CIM, use Get-Command con el parámetro Module, como se muestra en el ejemplo siguiente.

Get-Command -Module CimCmdlets
CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Export-BinaryMiLog                                 1.0.0.0    CimCmdlets
Cmdlet          Get-CimAssociatedInstance                          1.0.0.0    CimCmdlets
Cmdlet          Get-CimClass                                       1.0.0.0    CimCmdlets
Cmdlet          Get-CimInstance                                    1.0.0.0    CimCmdlets
Cmdlet          Get-CimSession                                     1.0.0.0    CimCmdlets
Cmdlet          Import-BinaryMiLog                                 1.0.0.0    CimCmdlets
Cmdlet          Invoke-CimMethod                                   1.0.0.0    CimCmdlets
Cmdlet          New-CimInstance                                    1.0.0.0    CimCmdlets
Cmdlet          New-CimSession                                     1.0.0.0    CimCmdlets
Cmdlet          New-CimSessionOption                               1.0.0.0    CimCmdlets
Cmdlet          Register-CimIndicationEvent                        1.0.0.0    CimCmdlets
Cmdlet          Remove-CimInstance                                 1.0.0.0    CimCmdlets
Cmdlet          Remove-CimSession                                  1.0.0.0    CimCmdlets
Cmdlet          Set-CimInstance                                    1.0.0.0    CimCmdlets

Los cmdlets de CIM siguen permitiendo trabajar con WMI, así que no se desconcierte si alguien afirma "Cuando consulto WMI con los cmdlets de CIM de PowerShell..."

Como he mencionado anteriormente, WMI es una tecnología independiente de PowerShell y simplemente se están usando los cmdlets de CIM para acceder a WMI. Puede encontrar un VBScript antiguo que usa lenguaje de consulta de WMI (WQL) para consultar WMI como en el ejemplo siguiente.

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colBIOS = objWMIService.ExecQuery _
    ("Select * from Win32_BIOS")

For each objBIOS in colBIOS
    Wscript.Echo "Manufacturer: " & objBIOS.Manufacturer
    Wscript.Echo "Name: " & objBIOS.Name
    Wscript.Echo "Serial Number: " & objBIOS.SerialNumber
    Wscript.Echo "SMBIOS Version: " & objBIOS.SMBIOSBIOSVersion
    Wscript.Echo "Version: " & objBIOS.Version
Next

Puede tomar la consulta de WQL de ese VBScript y usarla con el cmdlet Get-CimInstance sin ninguna modificación.

Get-CimInstance -Query 'Select * from Win32_BIOS'
SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 3810-1995-1654-4615-2295-2755-89
Version           : VRTUAL - 4001628

Así no es como yo normalmente consulto WMI con PowerShell, pero funciona y permite migrar fácilmente los VBScript existentes a PowerShell. Al empezar a escribir un comando único de una línea para consultar WMI, yo uso la sintaxis siguiente.

Get-CimInstance -ClassName Win32_BIOS
SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 3810-1995-1654-4615-2295-2755-89
Version           : VRTUAL - 4001628

Si solo quiero el número de serie, puedo canalizar la salida a Select-Object y especificar únicamente la propiedad SerialNumber.

Get-CimInstance -ClassName Win32_BIOS | Select-Object -Property SerialNumber
SerialNumber
------------
3810-1995-1654-4615-2295-2755-89

De forma predeterminada, hay varias propiedades que se recuperan en segundo plano que nunca se usan. Es posible que no importe mucho al consultar WMI en el equipo local. Pero una vez que se empieza a consultar equipos remotos, no solo se trata de tiempo de procesamiento adicional para devolver esa información, sino también de información innecesaria adicional que se debe extraer en la red. Get-CimInstance tiene un parámetro Property que limita la información que se recupera. Esto hace que la consulta a WMI sea más eficaz.

Get-CimInstance -ClassName Win32_BIOS -Property SerialNumber |
Select-Object -Property SerialNumber
SerialNumber
------------
3810-1995-1654-4615-2295-2755-89

Los resultados anteriores han devuelto un objeto. Para devolver una cadena simple, use el parámetro ExpandProperty.

Get-CimInstance -ClassName Win32_BIOS -Property SerialNumber |
Select-Object -ExpandProperty SerialNumber
3810-1995-1654-4615-2295-2755-89

También puede usar el estilo de puntos de sintaxis para devolver una cadena simple. Así se elimina la necesidad de canalizar a Select-Object.

(Get-CimInstance -ClassName Win32_BIOS -Property SerialNumber).SerialNumber
3810-1995-1654-4615-2295-2755-89

Consulta a equipos remotos con los cmdlets de CIM

Sigo ejecutando PowerShell como administrador local que es usuario de dominio. Al intentar consultar información de un equipo remoto mediante el cmdlet Get-CimInstance, aparece un mensaje de error de acceso denegado.

Get-CimInstance -ComputerName dc01 -ClassName Win32_BIOS
Get-CimInstance : Access is denied.
At line:1 char:1
+ Get-CimInstance -ComputerName dc01 -ClassName Win32_BIOS
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (root\cimv2:Win32_BIOS:String) [Get-CimI
   nstance], CimException
    + FullyQualifiedErrorId : HRESULT 0x80070005,Microsoft.Management.Infrastructure.Cim
   Cmdlets.GetCimInstanceCommand
    + PSComputerName        : dc01

Muchas personas tienen problemas de seguridad con PowerShell, pero la verdad es que se tienen exactamente los mismos permisos en PowerShell que en la GUI. Ni más ni menos. El problema del ejemplo anterior es que el usuario que ejecuta PowerShell no tiene derechos para consultar información de WMI desde el servidor DC01. Podría volver a iniciar PowerShell como administrador de dominio, ya que Get-CimInstance no tiene un parámetro Credential. Pero confíe en mí, no es una buena idea porque, a continuación, cualquier cosa que ejecute desde PowerShell se ejecutaría como administrador de dominio. Esto podría ser peligroso desde el punto de vista de la seguridad en función de la situación.

Con el principio de privilegios mínimos, elevo a mi cuenta de administrador de dominio por comando mediante el parámetro Credential, si un comando tiene uno. Get-CimInstance no tiene un parámetro Credential, por lo que la solución de este escenario es crear un elemento CimSession primero. Luego uso CimSession en lugar de un nombre de equipo para consultar WMI en el equipo remoto.

$CimSession = New-CimSession -ComputerName dc01 -Credential (Get-Credential)
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential

La sesión de CIM se ha almacenado en una variable de nombre $CimSession. Observe que también he especificado el cmdlet Get-Credential entre paréntesis para que se ejecute primero y me pida credenciales alternativas antes de crear la nueva sesión. Más adelante en este capítulo muestro otra forma más eficaz de especificar credenciales alternativas, pero es importante entender este concepto básico antes de complicarlo más.

La sesión de CIM creada en el ejemplo anterior se puede usar ahora con el cmdlet Get-CimInstance para consultar la información de BIOS desde WMI en el equipo remoto.

Get-CimInstance -CimSession $CimSession -ClassName Win32_BIOS
SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 0986-6980-3916-0512-6608-8243-13
Version           : VRTUAL - 4001628
PSComputerName    : dc01

El uso de sesiones de CIM en lugar de simplemente especificar un nombre de equipo ofrece varias ventajas adicionales. Al ejecutar varias consultas en el mismo equipo, el uso de una sesión de CIM es más eficaz que el uso del nombre de equipo para cada consulta. Al crear una sesión de CIM, solo se configura la conexión una vez. Luego, varias consultas usan esa misma sesión para recuperar información. El uso del nombre de equipo requiere los cmdlets para configurar y desmantelar la conexión con cada consulta individual.

El cmdlet Get-CimInstance usa el protocolo WSMan de forma predeterminada, lo que significa que el equipo remoto necesita la versión 3.0 o superior de PowerShell para conectarse. En realidad, no es la versión de PowerShell lo que importa, sino la versión de la pila. La versión de la pila se puede determinar mediante el cmdlet Test-WSMan. Debe ser la versión 3.0. Esa es la versión que se encuentra con PowerShell versión 3.0 y superior.

Test-WSMan -ComputerName dc01
wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor   : Microsoft Corporation
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 3.0

Los cmdlets de WMI anteriores usan el protocolo DCOM, que es compatible con versiones anteriores de Windows. Pero DCOM normalmente resulta bloqueado por el firewall en las versiones más recientes de Windows. El cmdlet New-CimSessionOption permite crear una conexión de protocolo DCOM para su uso con New-CimSession. Esto permite usar el cmdlet Get-CimInstance para comunicarse con versiones de Windows anteriores a Windows Server 2000. Esto también significa que no se necesita PowerShell en el equipo remoto cuando se usa el cmdlet Get-CimInstance con un elemento CimSession configurado para usar el protocolo DCOM.

Cree la opción del protocolo DCOM con el cmdlet New-CimSessionOption y almacénela en una variable.

$DCOM = New-CimSessionOption -Protocol Dcom

Por eficacia, puede almacenar el administrador de dominio o las credenciales con privilegios elevados en una variable, de modo que no tenga que escribirlos constantemente para cada comando.

$Cred = Get-Credential
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential

Yo tengo un servidor denominado SQL03 que ejecuta Windows Server 2008 (no R2). Es el sistema operativo Windows Server más reciente sin PowerShell instalado de forma predeterminada.

Cree un elemento CimSession para SQL03 mediante el protocolo DCOM.

$CimSession = New-CimSession -ComputerName sql03 -SessionOption $DCOM -Credential $Cred

En el comando anterior, observe que esta vez he especificado la variable denominada $Cred como valor del parámetro Credential en lugar de volver a escribirlos manualmente.

La salida de la consulta es la misma independientemente del protocolo subyacente que se use.

Get-CimInstance -CimSession $CimSession -ClassName Win32_BIOS
SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 7237-7483-8873-8926-7271-5004-86
Version           : VRTUAL - 4001628
PSComputerName    : sql03

El cmdlet Get-CimSession se usa para ver qué elementos CimSession hay conectados actualmente y qué protocolos están usando.

Get-CimSession
Id           : 1
Name         : CimSession1
InstanceId   : 80742787-e38e-41b1-a7d7-fa1369cf1402
ComputerName : dc01
Protocol     : WSMAN

Id           : 2
Name         : CimSession2
InstanceId   : 8fcabd81-43cf-4682-bd53-ccce1e24aecb
ComputerName : sql03
Protocol     : DCOM

Recupere y almacene los dos elementos CimSession creados previamente en una variable denominada $CimSession.

$CimSession = Get-CimSession

Consulte ambos equipos con un comando, uno con el protocolo WSMan y el otro con DCOM.

Get-CimInstance -CimSession $CimSession -ClassName Win32_BIOS
SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 0986-6980-3916-0512-6608-8243-13
Version           : VRTUAL - 4001628
PSComputerName    : dc01

SMBIOSBIOSVersion : 090006
Manufacturer      : American Megatrends Inc.
Name              : Intel(R) Xeon(R) CPU E3-1505M v5 @ 2.80GHz
SerialNumber      : 7237-7483-8873-8926-7271-5004-86
Version           : VRTUAL - 4001628
PSComputerName    : sql03

He escrito muchas entradas de blog sobre los cmdlets de WMI y CIM. Una de las más útiles trata de una función que he creado para determinar automáticamente si se debe usar WSMan o DCOM y configurar la sesión de CIM automáticamente sin tener que averiguar cuál de ellos de forma manual. Esa entrada de blog se titula Función de PowerShell para crear CimSessions en equipos remotos con reserva para DCOM.

Cuando haya terminado con las sesiones de CIM, debe quitarlas con el cmdlet Remove-CimSession. Para quitar todas las sesiones de CIM, simplemente canalice Get-CimSession a Remove-CimSession.

Get-CimSession | Remove-CimSession

Resumen

En este capítulo ha aprendido a usar PowerShell para trabajar con WMI en equipos locales y remotos. También ha aprendido a usar los cmdlets de CIM para trabajar con equipos remotos con el protocolo WSMan o DCOM.

Revisar

  1. ¿Cuál es la diferencia entre los cmdlets de WMI y CIM?
  2. De forma predeterminada, ¿qué protocolo usa el cmdlet Get-CimInstance?
  3. ¿Cuáles son algunas de las ventajas que aporta el uso de una sesión de CIM en lugar de especificar un nombre de equipo con Get-CimInstance?
  4. ¿Cómo se especifica un protocolo alternativo distinto al predeterminado para su uso con Get-CimInstance?
  5. ¿Cómo se cierran o quitan las sesiones de CIM?